吃晚饭的时候, 朋友发了一组图片过来, 让我抠个图; 对, 没听错, 知道我是程序猿, 所以让我抠个图;
抠图这个说法, UI设计师们都接受不了; 身为程序猿当然要更高雅一些, 能敲代码的就绝不抠图;
好了, 进入正题; 其实分析一下, 需求也比较简单, 就是把图片中的白色或者接近白色的背景换为透明, 黑色签名露出来; 下面是图片处理前:
首先说一下返回原始图片文字, 代码如下:
//返回原始图像(将图片的背景置为透明)
- (UIImage *)returnOrginImage:(UIImage *)sourceImage
{
//分配内存
const int imageWidth = sourceImage.size.width;
const int imageHeight = sourceImage.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
//创建context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), sourceImage.CGImage);
//遍历像素
int pixelNum = imageWidth * imageHeight;
uint32_t* pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++){
//接近白色
//将像素点转成子节数组来表示---RGBA
//ptr[0]:透明度,ptr[1]:R,ptr[2]:G,ptr[3]:B
//分别取出RGB值后。进行判断需不需要设成透明
uint8_t* ptr = (uint8_t*)pCurPtr;
if (ptr[1] > 140 && ptr[2] > 140 && ptr[3] > 140) {
//当RGB值都大于140则比较接近白色的都将透明度设为0
//demo中的图片有点灰, 所以设置了140, 可以根据需要自行设置
ptr[0] = 0;
}
}
//将内存转成image
CGDataProviderRef dataProvider =CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, nil);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight,8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast |kCGBitmapByteOrder32Little, dataProvider,NULL, true,kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
UIImage *resultUIImage = [UIImage imageWithCGImage:imageRef];
//释放
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return resultUIImage;
}
参数bytesPerRow代表被渲染内存区域中每行所占字节, 其中4代表每个像素点RGBA所占的字节, 之后乘以imageHeight
得到所占内存空间;colorspace
用于被渲染内存区域的颜色;最关键的代码就是将RGB值为白色或趋近与白色的透明度设为0;我这里取了140是因为这张图片背景有点灰, 大家可以自行设置;
效果如下:
看得出文字有点模糊, 这是因为处理过程是对每一个像素直接设置为透明, 图片并不是只有黑白两个颜色, 所以文字周边的区域就会变暗了, 再加上原图给的也不是很清晰, 那应该怎么处理呢?
这里涉及到灰度图, 代码如下:
//返回灰度图像
-(UIImage*)returnGrayImage:(UIImage*)sourceImage{
int width = sourceImage.size.width;
int height = sourceImage.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate (nil,width,height,8,0,colorSpace,kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
if (context == NULL) {
return nil;
}
CGContextDrawImage(context,CGRectMake(0, 0, width, height), sourceImage.CGImage);
UIImage *grayImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
CGContextRelease(context);
return grayImage;
}
效果图:
说到灰度图, 就不得不提一个很著名的彩色转灰度的心理学公式:
Gray = R*0.299 + G*0.587 + B*0.114
算法处理如下:
a. 首先将彩色图像转换为灰度图像
b. 计算灰度图像的算术平均值M
c. 以M为阈值,完成对灰度图二值化( 大于阈值M,像素点赋值为白色,否则赋值为黑
色)
上面的代码还看不出来, 应该是方法内部做了处理, 咱们来看下面的:
for (size_t i = 0; i < height; i++) {
for (size_t j = 0; j < width; j++){
// 设置每个像素的rgba值
size_t pixelIndex = i * width * 4 + j * 4;
unsigned char red = data[pixelIndex];
unsigned char green = data[pixelIndex + 1];
unsigned char blue = data[pixelIndex + 2];
//取灰度值
unsigned char gray = 0.299 * red + 0.587 * green + 0.114 * blue;
data[pixelIndex] = gray; // r
data[pixelIndex + 1] = gray; // g
data[pixelIndex + 2] = gray; // b
}
}
附:
维基灰度算法
处理成灰度图后基本只剩下黑白两种颜色; 为了寻求最优解, 我们再把屏幕中白色变透明, 黑色加深, 就得到所谓的高保真效果了;
代码如下:
//返回高保真图像(只返回黑白两种颜色)
- (UIImage *)returnHightQualityImage:(UIImage *)sourceImage{
NSData *imageData = UIImagePNGRepresentation(sourceImage);
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
unsigned char *data = calloc(width * height * 4, sizeof(unsigned char)); // 取图片首地址
size_t bitsPerComponent = 8; // r g b a 每个component bits数目
size_t bytesPerRow = width * 4; // 一张图片每行字节数目 (每个像素点包含r g b a 四个字节)
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); // 创建rgb颜色空间
CGContextRef context = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
for (size_t i = 0; i < height; i++)
{
for (size_t j = 0; j < width; j++)
{
// 设置每个像素的rgba值
size_t pixelIndex = i * width * 4 + j * 4;
unsigned char red = data[pixelIndex];
unsigned char green = data[pixelIndex + 1];
unsigned char blue = data[pixelIndex + 2];
//取灰度值
unsigned char gray = 0.299 * red + 0.587 * green + 0.114 * blue;
gray = gray > 150 ? 255 : 0;
data[pixelIndex] = gray; // r
data[pixelIndex + 1] = gray; // g
data[pixelIndex + 2] = gray; // b
data[pixelIndex + 3] = gray == 255 ? 0 : 255; // a (255 代表透明)
}
}
CGImageRef newImage = CGBitmapContextCreateImage(context);
UIImage *image = [[UIImage alloc] initWithCGImage:newImage];
return image;
}
最终效果图:
最后附上github地址;
demo下载
扩展
用 C 语言画光--Milo Yip