2009-07-17 7 views

risposta

4

Niente di così semplice. La soluzione più semplice è probabilmente quella di creare un CGContext in memoria con un formato pixel noto, disegnare l'immagine in quel contesto, quindi leggere/modificare i pixel nel buffer di formato noto.

Non credo che CG supporti uno spazio colore con un canale di saturazione separato, quindi dovrai convertire da RGB a HSV o HSL o eseguire i calcoli direttamente nello spazio RGB.


Un modo per fare il calcolo direttamente in RGB potrebbe essere qualcosa di simile:

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 

Ciò sposterà i canali che sono lontano dalla media circa il 10% più lontano. Questo è quasi come aumentare la saturazione, anche se darà cambiamenti di tonalità per alcuni colori.

9

Iniziando con una vista basata su Template applicazione, creare una nuova sottoclasse di UIView in questo modo:

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

Ora, nel metodo viewDidLoad del controller della vista, mettere la visualizzazione sullo schermo e impostare è la saturazione in questo modo:

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

modificare la saturazione in questo modo:

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

Ob vivamente se vuoi usarlo al di fuori di un singolo metodo, dovresti fare dv un ivar del controller della vista. Spero che questo ti aiuti.

+1

Perché non basta chiamare [setNeedsDisplay auto] nella setter per desaturazione? –

+0

Il mio cervello ha evitato di scavalcare il setter sintetizzato per qualche motivo sconosciuto ma hai ragione. Cambierò la risposta – willc2

+3

Quindi questo diminuisce la saturazione, come si _increase_ saturazione? – Shizam

5

Ecco una implementazione dell'attacco di Bessey (inserire questo codice in una categoria UIImage). Non è veloce e sposta decisamente i colori, ma funziona in un certo senso.

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

Qualcuno ha qualche idea su come migliorare questo senza eseguire una conversione HSV completa? O meglio ancora, una vera implementazione per:

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

C'è un filtro CoreImage per questo. CIColorControls Basta impostare inputSaturation a < 1.0 per desaturare o> 1,0 per aumentare la saturazione ...

es. Ecco un metodo che ho aggiunto in una categoria su UIImage per desaturare un'immagine.

-(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. Penso che questo potenzialmente perde il 'cgImage', anche se l'inserimento di' CFAutorelease (cgImage); 'prima che il' return' dovrebbe risolvere il problema. Puoi anche usare 'kCIInputImageKey' e' kCIInputSaturationKey' invece di '@" inputImage "' e '@" inputSaturation "', e '@ 0.0f' invece di' [NSNumber numberWithFloat: 0.0f] '. – Isaac

+1

Grazie a @Isaac, hai aggiornato con i tuoi suggerimenti. –

+0

Mike, ti suggerisco di usare 'UIImage * image = [UIImage imageWithCGImage: cgImage scale: self.scale orientation: self.imageOrientation];' poiché garantisce che la scala e l'orientamento siano mantenuti dall'originale, il che è particolarmente importante quando si ha a che fare con dispositivi retina. – devios1

2

Ho appena testato metodo Mike Pollard s' e che sia corretto.

Ecco la rapida 3 versione

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