转自:https://www.jianshu.com/p/619ef8423895
假设这样一个场景:一张图片中有一朵白花,我们想要把它变成红花;或者一张图片中有一段黑色的文字,我们想要把它变成红色,应该怎么做?
想要实现这个需求,就需要从像素尺度上对图片进行修改,将指定区域内的像素的色值改为我们需要的颜色。但是,如何从这张图上找到那段文字或者那朵花,并不在本文的讨论范围内,那是OCR和机器学期的事ㄟ( ▔, ▔ )ㄏ。
假设我们要把一张有一段黑色文字的图片中的文字修改为红色:
要实现这个需求,我们应该怎么做?
-
创建一个画布,并将原始图片平铺在画布上
-
遍历图片上的像素,找到目标区域内的黑色文字的像素,将它改为红色
-
输出修改后的图片,并清理内存
我们需要哪些信息才足够实现这个功能?
-
一个Rect:需要修改这张图片上哪个区域的像素
-
需要被修改的色值区域:需要把哪个色值范围内的像素修改为目标颜色
-
目标颜色:需要将符合上述两点的像素修改为什么颜色
在贴代码之前,先讲一些废话:
-
图片的分辨率代表着它的像素个数,比如上图的分辨率为1054 * 316,那么它的像素个数就是 1054 * 316 = 333064;
-
图片的宽度代表这张图一共有多少列像素,高度代表一共有多少行像素;即宽度代表列数,高度代表行数
-
在像素尺度上,图片中元素边缘的颜色并不如我们肉眼看到的那样。比如上图中的文字是纯黑色的,但是如果你放大放大再放大,会发现文字边缘的颜色其实是灰色的(这也是上面为什么说需要一个色值区域的原因);
-
图片转为2进制的数据时,每个像素为最小单元,从左上角开始,到右下角结束,从左到右从上到下排列像素,但它并不是二维,而是一维的。
-
alpha通道:一个像素的色值是由RGBA四个值确定的。如果不含alpha通道的话,则是由RGB三个值确定,而A则一直是0xFF,即RGBX(X代表不含alpha通道,X一直为0xFF)
下面就是实现这个功能的核心代码了,这里是作为UIImage的一个category方法实现的:
解释一下前两个参数的含义:
想象一个数轴,最左边是黑色(RGBX:0x000000FF),最右边是白色(0xFFFFFFFF),
nearBlackColor是靠近左边边界的色值,nearWhiteColor是靠近右边边界的色值,
它们中间则是需要被修改的色值范围
- (UIImage *)translatePixelColorByTargetNearBlackColorRGBA:(UInt32)nearBlackRGBA
nearWhiteColorRGBA:(UInt32)nearWhiteRGBA
transColorRGBA:(UInt32)transRGBA
inRect:(CGRect)rect {
// 第一步:判断传入的rect是否在图片的bounds内
CGRect canvas = CGRectMake(0, 0, self.size.width, self.size.height);
if (!CGRectContainsRect(canvas, rect)) {
if (CGRectIntersectsRect(canvas, rect)) {
rect = CGRectIntersection(canvas, rect); // 取交集
} else {
return self;
UIImage *transImage = nil;
int imageWidth = self.size.width;
int imageHeight = self.size.height;
// 第二步:创建色彩空间、画布上下文,并将图片以bitmap(不含alpha通道)的方式画在画布上。
size_t bytesPerRow = imageWidth * 4;
uint32_t *rgbImageBuf = (uint32_t *)malloc(bytesPerRow * imageHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), self.CGImage);
// 第三步:遍历并修改像素
uint32_t *pCurPtr = rgbImageBuf;
pCurPtr += (long)(rect.origin.y*imageWidth); // 将指针移动到初始行的起始位置
// 空间复杂度:O(rect.size.width * rect.size.height)
for (int i = rect.origin.y; i < CGRectGetMaxY(rect); i++) { // row
pCurPtr += (long)rect.origin.x; // 将指针移动到当前行的起始列
for (int j = rect.origin.x; j < CGRectGetMaxX(rect); j++, pCurPtr++) { // column
if (*pCurPtr < nearBlackRGBA || *pCurPtr > nearWhiteRGBA) { continue; }
// 将图片转成想要的颜色
uint8_t *ptr = (uint8_t *)pCurPtr;
ptr[3] = (transRGBA >> 24) & 0xFF; // R
ptr[2] = (transRGBA >> 16) & 0xFF; // G
ptr[1] = (transRGBA >> 8) & 0xFF; // B
pCurPtr += (long)(imageWidth - CGRectGetMaxX(rect)); // 将指针移动到下一行的起始列
// 第四步:输出图片
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, providerReleaseDataCallback);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,
kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider,
NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
transImage = [UIImage imageWithCGImage:imageRef];
// end:清理空间
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return transImage ? : self;
void providerReleaseDataCallback (void *info, const void *data, size_t size) {
free((void*)data);
怎么调用呢?
[image translatePixelColorByTargetNearBlackColorRGBA:0x000000FF nearWhiteColorRGBA:0x323232FF transColorRGBA:0xFF0000FF inRect:rect];
看起来有些麻烦是吗?色值要写那么长,而且既然是以不含alpha通道的方式实现的,那么alpha值便没有意义,所以我们还可以再封装几个方法以便使用起来更方便:
- (UIImage *)translatePixelColorByTargetNearBlackColor:(UIColor *)nearBlackColor
nearWhiteColor:(UIColor *)nearWhiteColor
transColor:(UIColor *)transColor {
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
return [self translatePixelColorByTargetNearBlackColor:nearBlackColor nearWhiteColor:nearWhiteColor transColor:transColor inRect:rect];
- (UIImage *)translatePixelColorByTargetNearBlackColor:(UIColor *)nearBlackColor
nearWhiteColor:(UIColor *)nearWhiteColor
transColor:(UIColor *)transColor
inRect:(CGRect)rect {
// UIColor 转 RGBA
UInt32 nearBlackRGBA = nearBlackColor.RGBA;
UInt32 nearWhiteRGBA = nearWhiteColor.RGBA;
UInt32 transRGBA = transColor.RGBA;
return [self translatePixelColorByTargetNearBlackColorRGBA:nearBlackRGBA nearWhiteColorRGBA:nearWhiteRGBA transColorRGBA:transRGBA inRect:rect];
- (UIImage *)translatePixelColorByTargetNearBlackColorHex:(UInt32)nearBlackRGB
nearWhiteColorHex:(UInt32)nearWhiteRGB
transColorHex:(UInt32)transRGB {
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
return [self translatePixelColorByTargetNearBlackColorHex:nearBlackRGB nearWhiteColorHex:nearWhiteRGB transColorHex:transRGB inRect:rect];
- (UIImage *)translatePixelColorByTargetNearBlackColorHex:(UInt32)nearBlackRGB
nearWhiteColorHex:(UInt32)nearWhiteRGB
transColorHex:(UInt32)transRGB
inRect:(CGRect)rect {
// RGB 转 RGBA
UInt32 nearBlackRGBA = (nearBlackRGB << 8) + 0xFF;
UInt32 nearWhiteRGBA = (nearWhiteRGB << 8) + 0xFF;
UInt32 transRGBA = (transRGB << 8) + 0xFF;
return [self translatePixelColorByTargetNearBlackColorRGBA:nearBlackRGBA nearWhiteColorRGBA:nearWhiteRGBA transColorRGBA:transRGBA inRect:rect];
另外,这是上面使用到的UIColor转RGBA的方法,它是作为UIColor的category方法实现的:
- (UInt32)RGBA {
CGFloat red = 0;
CGFloat green = 0;
CGFloat blue = 0;
CGFloat alpha = 0;
BOOL succ = [self getRed:&red green:&green blue:&blue alpha:&alpha];
UInt32 r = round(red*255);
UInt32 g = round(green*255);
UInt32 b = round(blue*255);
UInt32 a = round(alpha*255);
r = (r << 24);
g = (g << 16);
b = (b << 8);
UInt32 rgba = r + g + b + a;
return succ ? rgba : 0x00000000;
如果上述正好能符合你目前遇到的问题,而你又急于验证能否解决问题的话,把上面的代码copy一下就可以了。如果你既想知其然,又想知其所以然,那么我们继续。
上述核心代码中分四步实现了修改图片像素色值,其中第一、二、四没有什么可说的,都是固定代码。
但第三步的算法我认为有必要解释一下,所以有了下面这些内容。
当然,如果你已经从代码中看明白了,那么我可以负责任的告诉你,本文已经结束啦~!
如果你觉得有些懵哔,那太好了!我又可以继续讲(zhuang)解(bi)了!那么,来嘛客官,咱们继续~
首先先来看下面一张图:
像素矩阵示例
前面已经说过了,我们采用不含alpha通道的方式实现。那么一个像素就是由RGBX四个值确定,其中X是无效的。这是上图中“Pixel”想要表示的含义。
“Image Raw Data”想要表示的是,图片在转为2进制后,像素在其中是怎样排列的。其中的数字表示的是像素在整张图片中的索引。前面也说过,是由一个二维的图片像素矩阵(就是上图最后那个4*4的“Image Pixel Matrix”)从左到右从上到下转换成的一维队列。
可以看出,在二维的图片上,我们需要修改的区域是连续的一块,但是在转化为二进制的数据中,它们则是断续的。
我把上面的那段代码再贴一下,以便对照解释:
// 第三步:遍历并修改像素
uint32_t *pCurPtr = rgbImageBuf;
pCurPtr += (long)(rect.origin.y*imageWidth); // 将指针移动到初始行的起始位置
// 空间复杂度:O(rect.size.width * rect.size.height)
for (int i = rect.origin.y; i < CGRectGetMaxY(rect); i++) { // row
pCurPtr += (long)rect.origin.x; // 将指针移动到当前行的起始列
for (int j = rect.origin.x; j < CGRectGetMaxX(rect); j++, pCurPtr++) { // column
if (*pCurPtr < nearBlackRGBA || *pCurPtr > nearWhiteRGBA) { continue; }
// 将图片转成想要的颜色
uint8_t *ptr = (uint8_t *)pCurPtr;
ptr[3] = (transRGBA >> 24) & 0xFF; // R
ptr[2] = (transRGBA >> 16) & 0xFF; // G
ptr[1] = (transRGBA >> 8) & 0xFF; // B
pCurPtr += (long)(imageWidth - CGRectGetMaxX(rect)); // 将指针移动到下一行的起始列
所以按上图所示,整张图片的bounds为(0, 0, 4, 4),我们需要修改rect(1, 1, 2, 2)内的像素色值。下面所讲要学会自动脑补二维图片转换一维二进制数据,凡是指出坐标的都是二维图片,而说指针的都是在说一维的二进制数据中某个像素的指针。
- 我们的空间复杂度为O(rect.size.width * rect.size.height),所以遍历时第一层的for循环遍历次数为rect.size.width(即2),而i是从rect.origin.y(即1)开始的;第二层for循环的遍历次数为rect.size.height(也是2),而j是从rect.origin.x(即1)开始的。总之,我们是从point(1, 1)位置开始遍历的。
- 首先需要将指针移动到初始行的起始列:
pCurPtr += (long)(rect.origin.y*imageWidth);
,即像素4的所在的位置。目的是为了跳过目标区域上方的无关行。 - 只跳过了上面的无关行还不够,我们还需要跳过左边的无关列,即
pCurPtr += (long)rect.origin.x;
,这时候指针指到了像素5的位置(就是步骤1中所说point(1, 1)的位置),然后我们就可以开始真正的遍历了。 - 在遍历完这一行的目标区域后,指针指到了像素7的位置;然后还需要跳过右边的无关列:
pCurPtr += (long)(imageWidth - CGRectGetMaxX(rect));
,这时候指针指到了像素8的位置。此时这一行已经完全遍历结束,跳到了下一行的起始位置,又回到了步骤3的状态(只是row+1了) - 然后重复执行3、4步骤,直到
i >= CGRectGetMaxY(rect)
结束
至此,这个算法解释完毕~
唉~,这一块我也是想破头该怎么描述,可是写出来发现还是不太理想。。。
我只能祈祷我太低估读者的水平,其实大家都是能直接看懂代码的,根本不需要我解释ㄟ( ▔, ▔ )ㄏ。
如果大家看完之后还是有不理解的地方;还有一些我没详细解释的地方,如果有不理解的,都欢迎在留言区讨论。
本人作为写文章的新手,如果有错误的地方,也欢迎大家在留言区指正!
最后,这里是Demo地址
转自:https://www.jianshu.com/p/619ef8423895 假设这样一个场景:一张图片中有一朵白花,我们想要把它变成红花;或者一张图片中有一段黑色的文字,我们想要把它变成红色,应该怎么做?想要实现这个需求,就需要从像素尺度上对图片进行修改,将指定区域内的像素的色值改为我们需要的颜色。但是,如何从这张图上找到那段文字或者那朵花,并不在本文的讨论范围内,那是OCR和机...
实际项目场景:去除图片的纯白色背景图,获得一张透明底图片用于拼图功能
介绍两种途径的三种处理方式(不知道为啥想起了孔乙己),具体性能鶸并未对比,如果有大佬能告知,不胜感激。
Core Image Core Graphics/Quarz 2D Core Image
Core Image是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度,色泽,或者曝光。 它利用GPU(或者CPU)来非常快速、甚至实时地处理图像数据和视频的帧。并且隐藏了底层图形处理的所有细节,通过提供的API就能简单的使用了,无须关心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不需要
self.view.backgroundColor = [UIColor blackColor];
MyButton *button = [[MyButton alloc] initWithFrame:CGRectMake(55, 55, 100, 50)];
[button s
1.滤镜链
在一个复合滤镜中,多种滤镜效果处理时,通常都是图片 -> 设置顶点/纹理坐标 -> 滤镜效果处理 -> 帧缓冲区 -> 新的纹理 -> 滤镜效果处理 -> 帧缓冲区 -> 新的纹理。
此过程就是滤镜链。
思考:那如何将滤镜处理完之后存到帧缓冲区,并生成纹理的呢?
2.滤镜处理 -> 帧缓冲区 -> 新的纹理 -> 相册
2.1 步骤
根据屏幕上的显示,重新获取顶点坐标和纹理坐标,并生成新的纹理(将纹理加载到帧缓冲区中)
- 根据屏幕
在 IOS 开发中,有时候需要获取图像中某个像素点的颜色,返回 UIColor 值。网上收集资料,参考各种方案,最后总结如下:- (UIColor *)colorAtPixel:(CGPoint)point {
// 如果点超出图像范围,则退出
if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.size.width, self.
有时候,我们通常会有修改图片底色,或者让底色透明,也或者颜色交换的类似的需求,特别是我们做界面或类似渲染等功能时可能需求更特殊一些。
我们可能清楚像素如何修改,但是使用常用的mspaint又不太好修改,这时可能我们想写个程序来修改,不过用qt/mfc等写还是太重型了,还需要打开vs,编译运行调试,比较麻烦。
用js就比较简单了,打开chrome浏览器的开发者工具的console窗口,代码写进来就可
在做项目的时候碰到了一个需求,把app的主题颜色改变成另外一种颜色,由于没有给出新的素材图片,需要把现有图片的颜色改变一下。下面是改变图片颜色的方法(一般对于单一颜色的图片)
imageWithRenderingMode:
UIImage有一个方法imageWithRenderingMode:,通过它设置图片的渲染模式,再通过设置tintcolor,就可以改变图片颜色。其中参数部分UIIMageR...
2. 你的图片太大,超过了服务器能够处理的大小限制。
3. 服务器端出了问题,导致无法正常接收上传的图片。
4. 你的 uniapp 客户端代码存在 bug,导致无法正常上传图片。
如果你不确定是哪个原因造成的上传失败,建议你尝试以下步骤来解决这个问题:
1. 确保你的网络环境良好。
2. 尝试将图片的大小压缩到合理的范围内再上传。
3. 尝试在不同的时间上传图片,如果总是在同一时间上传失败,可能是服务器端出了问题。
4. 检查你的 uniapp 客户端代码,确保没有任何 bug 影响了图片上传的过程。