如何更改 UIImage 的饱和度?

问题描述 投票:0回答:7

我有一个 UIImage,想将其饱和度调整大约 +10%。是否有可用于此目的的标准方法或函数?

iphone cocoa-touch uikit core-graphics
7个回答
17
投票

为此有一个 CoreImage 过滤器。

CIColorControls
只需将
inputSaturation
设置为 < 1.0 to desaturate or > 1.0 即可增加饱和度...

例如。这是我在

UIImage
的类别中添加的一种方法,用于降低图像的饱和度。

-(UIImage*) imageDesaturated {
    CIContext *context = [CIContext contextWithOptions:nil];
    CIImage *ciimage = [CIImage imageWithCGImage:self.CGImage];
    CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"];
    [filter setValue:ciimage forKey:kCIInputImageKey];
    [filter setValue:@0.0f forKey:kCIInputSaturationKey];
    CIImage *result = [filter valueForKey:kCIOutputImageKey];
    CGImageRef cgImage = [context createCGImage:result fromRect:[result extent]];
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    return image;
}

9
投票

从基于视图的应用程序模板开始,创建 UIView 的新子类,如下所示:

// header file
@interface DesatView : UIView {
     UIImage *image;
     float saturation;
}
@property (nonatomic, retain) UIImage *image;
@property (nonatomic) float desaturation;
@end  

// implementation file
#import "DesatView.h"
@implementation DesatView
@synthesize image, desaturation;

-(void)setSaturation:(float)sat;
    {
        saturation = sat;
        [self setNeedsDisplay];
    }

- (id)initWithFrame:(CGRect)frame {
     if (self = [super initWithFrame:frame]) {
          self.backgroundColor = [UIColor clearColor]; // else background is black
          desaturation = 0.0; // default is no effect
     }
     return self;
}

- (void)drawRect:(CGRect)rect {
     CGContextRef context = UIGraphicsGetCurrentContext();
     CGContextSaveGState(context);

     CGContextTranslateCTM(context, 0.0, self.bounds.size.height); // flip image right side up
     CGContextScaleCTM(context, 1.0, -1.0);

     CGContextDrawImage(context, rect, self.image.CGImage);
     CGContextSetBlendMode(context, kCGBlendModeSaturation);
     CGContextClipToMask(context, self.bounds, image.CGImage); // restricts drawing to within alpha channel
     CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, desaturation);
     CGContextFillRect(context, rect);

     CGContextRestoreGState(context); // restore state to reset blend mode
}
@end

现在在视图控制器的 viewDidLoad 方法中,将视图放在屏幕上并设置其饱和度,如下所示:

- (void)viewDidLoad {
    [super viewDidLoad];  

    DesatView *dv = [[DesatView alloc] initWithFrame:CGRectZero];
    dv.image = [UIImage imageNamed:@"someImage.png"];
    dv.frame = CGRectMake(0, 0, dv.image.size.width, dv.image.size.height);
    dv.center = CGPointMake(160, 240); // put it mid-screen
    dv.desaturation = 0.2; // desaturate by 20%,

    [self.view addSubview:dv];  // put it on screen   
}  

像这样改变饱和度:

 dv.saturation = 0.8; // desaturate by 80%  

显然,如果你想在单个方法之外使用它,你应该将 dv 设置为视图控制器的 ivar。希望这有帮助。


9
投票

斯威夫特5

extension UIImage {
    
    func withSaturationAdjustment(byVal: CGFloat) -> UIImage {
        guard let cgImage = self.cgImage else { return self }
        guard let filter = CIFilter(name: "CIColorControls") else { return self }
        filter.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
        filter.setValue(byVal, forKey: kCIInputSaturationKey)
        guard let result = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return self }
        guard let newCgImage = CIContext(options: nil).createCGImage(result, from: result.extent) else { return self }
        return UIImage(cgImage: newCgImage, scale: UIScreen.main.scale, orientation: imageOrientation)
    }

}

5
投票

这是 Bessey 的 hack 的实现(将此代码放在 UIImage 类别中)。它速度不快,而且肯定会改变色调,但它确实有效。

+ (CGFloat) clamp:(CGFloat)pixel
{
      if(pixel > 255) return 255;
      else if(pixel < 0) return 0;
      return pixel;
}

- (UIImage*) saturation:(CGFloat)s
{
      CGImageRef inImage = self.CGImage;
      CFDataRef ref = CGDataProviderCopyData(CGImageGetDataProvider(inImage)); 
      UInt8 * buf = (UInt8 *) CFDataGetBytePtr(ref); 
      int length = CFDataGetLength(ref);

      for(int i=0; i<length; i+=4)
      {
            int r = buf[i];
            int g = buf[i+1];
            int b = buf[i+2];

            CGFloat avg = (r + g + b) / 3.0;
            buf[i] = [UIImage clamp:(r - avg) * s + avg];
            buf[i+1] = [UIImage clamp:(g - avg) * s + avg];
            buf[i+2] = [UIImage clamp:(b - avg) * s + avg];
      }

      CGContextRef ctx = CGBitmapContextCreate(buf,
                              CGImageGetWidth(inImage), 
                              CGImageGetHeight(inImage), 
                              CGImageGetBitsPerComponent(inImage),
                              CGImageGetBytesPerRow(inImage), 
                              CGImageGetColorSpace(inImage),
                              CGImageGetAlphaInfo(inImage));

      CGImageRef img = CGBitmapContextCreateImage(ctx);
      CFRelease(ref);
      CGContextRelease(ctx);
      return [UIImage imageWithCGImage:img];
}

有人对如何在不进行完整 HSV 转换的情况下改进这一点有任何想法吗?或者更好的是,真正的实现:

- (UIImage*) imageWithHueOffset:(CGFloat)h saturation:(CGFloat)s value:(CGFloat)v

4
投票

没有什么那么简单。最简单的解决方案可能是使用已知的像素格式创建一个内存中的 CGContext,将图像绘制到该上下文中,然后读取/修改已知格式缓冲区中的像素。

我认为 CG 不支持具有单独饱和通道的颜色空间,因此您必须从 RGB 转换为 HSV 或 HSL,或者直接在 RGB 空间中进行计算。


直接在 RGB 中进行计算的一种方法可能是这样的:

average = (R + G + B) / 3;
red_delta = (R - average) * 11 / 10;
green_delta = (G - average) * 11 / 10;
blue_delta = (B - average) * 11 / 10;

R = average + red_delta;
G = average + green_delta;
B = average + blue_delta;
// clip R,G,B to be in 0-255 range

这将使偏离均值的通道进一步移动约 10%。这“几乎”就像增加饱和度一样,尽管它会给某些颜色带来色调变化。


2
投票
Mike Pollard

的方法,它是正确的。 这是

swift 3

版本 let ciimage = CIImage.init(cgImage: self.givenImage.cgImage) let filter = CIFilter.init(name: "CIColorControls") filter?.setValue(ciimage, forKey: kCIInputImageKey) filter?.setValue(0.0, forKey: kCIInputSaturationKey) let result = filter?.value(forKey: kCIOutputImageKey) as! CIImage let cgimage = CIContext.init(options: nil).createCGImage(result, from: result.extent) let image = UIImage.init(cgImage: cgimage!)



0
投票

extension UIImage { /// Returns desaturated image. func desaturated() -> UIImage? { let filter = CIFilter.colorControls() filter.inputImage = CIImage(image: self) filter.saturation = 0 // 0 means no saturation guard let outputCIImage = filter.outputImage else { return nil } return UIImage(ciImage: outputCIImage) } }

	
© www.soinside.com 2019 - 2024. All rights reserved.