Вопрос по iphone, core-graphics, uikit, cocoa-touch – Как я могу изменить насыщенность UIImage?

16

У меня есть UIImage и я хочу изменить его насыщенность примерно на + 10%. Существуют ли стандартные методы или функции, которые можно использовать для этого?

Ваш Ответ

5   ответов
16

Для этого есть фильтр CoreImage.CIColorControls Просто установитеinputSaturation & lt; 1,0 для обесцвечивания или & gt; 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;
}
Хотя это работает, на самом деле требуется много обработки! Удостоверьтесь, чтобы сделать это на отправленной нити.
Спасибо @Isaac, обновил ваши предложения.
+1. Я думаю, что это потенциально утечкаcgImageхоть вставляяCFAutorelease(cgImage); передreturn должен это исправить. Вы также можете использоватьkCIInputImageKey а такжеkCIInputSaturationKey вместо@"inputImage" а также@"inputSaturation", а также@0.0f вместо[NSNumber numberWithFloat:0.0f].
Майк, я предлагаю тебе использоватьUIImage *image = [UIImage imageWithCGImage:cgImage scale:self.scale orientation:self.imageOrientation]; так как он гарантирует, что масштаб и ориентация сохраняются от оригинала, что особенно важно при работе с устройствами сетчатки.
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 контроллера представления. Надеюсь это поможет.

Мой мозг уклонялся от переопределения синтезированного сеттера по неизвестной причине, но вы правы. Я изменю ответ.
Почему бы просто не вызвать [self setNeedsDisplay] в сеттере для десатурации?
Мне бы очень хотелось увидеть код, который также увеличил насыщенность.
Так что это уменьшает насыщенность, как бы выincrease Насыщение?
5

Вот реализация взлома Бесси (поместите этот код в категорию 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% дальше. Этоalmost как увеличение насыщенности, хотя это даст сдвиги оттенков для некоторых цветов.

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!)

Похожие вопросы