iOS制作电子签章

1,629 阅读8分钟

原文链接:http://www.jianshu.com/p/8a951ceec54f

吃晚饭的时候, 朋友发了一组图片过来, 让我抠个图; 对, 没听错, 知道我是程序猿, 所以让我抠个图;

抠图这个说法, 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

效果图