iOS开发-修改图片的HSB(HSV)值

个人博客地址:hxhxt.cn

HSB是什么?

HSB等于HSV,是一种圆形的颜色空间,H代表色调,S代表饱和度,B(V)代表明度。

修改HSV的值

修改图片的HSB值来改变图片整体颜色效果,解决方案如下:

  1. 遍历图片所有像素,获取每个像素的RGB值
  2. 将RGB值转换为HSB值来做修改
  3. 将HSB值转换为RGB值,赋值
// Category方法
// 3个入参均为偏移量,传0-360之间的正整数,默认传0。
- (UIImage *)changeImageWithHueOffset:(CGFloat)hueOffset saturationOffset:(CGFloat)saturationOffset brightnessOffset:(CGFloat)brightnessOffset {
    CGImageRef image = self.CGImage;
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
    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), image);
    for (size_t i = 0; i < height; i++) {
        for (size_t j = 0; j < width; j++) {
            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 a = data[pixelIndex + 3];
            if (a == 0) { // 不处理透明通道
                continue;
            }
            // 修改颜色
            // RGB转HSV
            UIColor *color = [[UIColor alloc] initWithRed:red green:green blue:blue alpha:a];
            CGFloat hue;
            CGFloat saturation;
            CGFloat brightness;
            CGFloat alpha;
            BOOL success = [color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
            if (success) {
                // 修改HSV
                CGFloat (^block)(CGFloat, CGFloat) = ^(CGFloat value, CGFloat offset) {
                    if (offset) {
                        value = value * 360 + offset;
                        if (value > 360) {
                            value = value - 360;
                        }
                        value = value / 360;
                    }
                    return value;
                };
                hue = block(hue, hueOffset);
                saturation = block(saturation, saturationOffset);
                brightness = block(brightness, brightnessOffset);
                
                // HSV转RGB
                color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
                CGFloat red;
                CGFloat green;
                CGFloat blue;
                BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha];
                if (success) {
                    data[pixelIndex] = red;
                    data[pixelIndex + 1] = green;
                    data[pixelIndex + 2] = blue;
                    continue;
                }
            }
            data[pixelIndex] = red;
            data[pixelIndex + 1] = green;
            data[pixelIndex + 2] = blue;
        }
    }
    image = CGBitmapContextCreateImage(context);
    return [UIImage imageWithCGImage:image];
}

注意此处3个入参均为偏移量,传0-360之间的正整数,默认传0,通常只需要修改hue的值就可以达到整体换色的目的了。

因为HSV是圆形的颜色空间,所以可以理解为什么是X/360。
此方案可稍作修改即可实现修改像素的RGB值或者其他颜色值。


如果不要求修改HSB,用drawInRect来合成图层就可以简单的实现更换颜色,在这里也做个记录。

- (UIImage *)imageWithTintColor:(UIColor *)tintColor blendMode:(CGBlendMode)blendMode {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
    [tintColor setFill];
    CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
    UIRectFill(bounds);
    
    // 绘图-合成图片使用kCGBlendModeMultiply
    [self drawInRect:bounds blendMode:blendMode alpha:1.0f];
    
    // 去除透明通道
    if (blendMode != kCGBlendModeDestinationIn) {
        [self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];
    }
    
    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return tintedImage;
}

这里使用了两次drawInRect,因为不同的blendMode有着不同的效果,第二次的blendMode使用了kCGBlendModeDestinationIn是为了保留原图的透明图层,我们只需要在第一次使用kCGBlendModeMultiply就可以达到混合图层的目的。还有很多其他的blendMode可以自行去测试显示效果。