2009-07-17 21 views

Respuesta

4

Nada tan sencillo. La solución más fácil es, probablemente, crear un CGContext en memoria con un formato de píxel conocido, dibujar la imagen en ese contexto y luego leer/modificar los píxeles en el búfer de formato conocido.

No creo que CG admita un espacio de color con un canal de saturación separado, por lo que deberá convertir de RGB a HSV o HSL, o hacer los cálculos directamente en el espacio RGB.


Una forma de hacer el cálculo directamente en RGB podría ser algo como esto:

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 

Esto moverá los canales que están lejos de la media un 10% más lejos. Esto es casi como aumentar la saturación, aunque dará cambios de tono para algunos colores.

9

A partir de una plantilla de aplicación basada en Ver, crear una nueva subclase de UIView así:

// 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 

Ahora en el método viewDidLoad de su controlador de vista, poner la vista en la pantalla y ajustar es la saturación de la siguiente manera:

- (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 
} 

cambiar la saturación de la siguiente manera:

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

Ob viously si desea utilizarlo fuera de un único método, debe hacer dv un ivar del controlador de vista. Espero que esto ayude.

+1

¿Por qué no llaman [auto setNeedsDisplay] en la incubadora para la desaturación? –

+0

Mi cerebro rehuyó anular el setter sintetizado por algún motivo desconocido, pero tiene razón. Cambiaré la respuesta. – willc2

+3

Entonces, esto reduce la saturación, ¿cómo aumentaría _ la saturación? – Shizam

5

Aquí hay una implementación del truco de Bessey (ponga este código en una categoría UIImage). No es rápido y definitivamente cambia de color, pero funciona de alguna manera.

+ (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]; 
} 

¿Alguien tiene alguna idea sobre cómo mejorar esto sin hacer una conversión de HSV completa? O mejor aún, una verdadera implementación para:

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

Hay un filtro CoreImage para esto. CIColorControls Simplemente configure inputSaturation en < 1.0 para desaturar o> 1.0 para aumentar la saturación ...

por ejemplo. Aquí hay un método que agregué en una categoría en UIImage para desaturar una imagen.

-(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; 
} 
+1

+1. Creo que esto potencialmente filtra el 'cgImage', aunque insertar' CFAutorelease (cgImage); 'antes del' return' debería arreglar eso. También puede usar 'kCIInputImageKey' y' kCIInputSaturationKey' en lugar de '@" inputImage "' y '@" inputSaturation "', y '@ 0.0f' en lugar de' [NSNumber numberWithFloat: 0.0f] '. – Isaac

+1

Gracias @Isaac, se han actualizado con sus sugerencias. –

+0

Mike, sugiero que use 'UIImage * image = [UIImage imageWithCGImage: cgImage scale: self.scale orientation: self.imageOrientation];' ya que asegura que la escala y la orientación se mantienen desde el original, lo cual es especialmente importante cuando se trata de dispositivos retina. – devios1

2

que he acabo de probar método Mike Pollard 's y es correcta.

Aquí es el rápida 3 versión

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