旋转、改变颜色并从 NSImage 获取 RGB565 数据

Posted

技术标签:

【中文标题】旋转、改变颜色并从 NSImage 获取 RGB565 数据【英文标题】:Rotate, Change Colors and Get RGB565 data from NSImage 【发布时间】:2013-04-22 03:50:05 【问题描述】:

我发现自己有几个 NSImage 对象需要旋转 90 度,将一种颜色的像素的颜色更改为另一种颜色,然后将 RGB565 数据表示为 NSData 对象.

我在 Accelerate 框架中找到了 vImageConvert_ARGB8888toRGB565 函数,所以它应该能够进行 RGB565 输出。

我在 *** 上找到了一些 UIImage 旋转,但我无法将它们转换为 NSImage,因为我似乎必须使用 NSGraphicsContext 而不是 CGContextRef?

理想情况下,我希望这些在 NSImage 类别中,这样我就可以调用了。

NSImage *rotated = [inputImage rotateByDegrees:90];
NSImage *colored = [rotated changeColorFrom:[NSColor redColor] toColor:[NSColor blackColor]];
NSData *rgb565 = [colored rgb565Data];

我只是不知道从哪里开始,因为图像处理对我来说是新的。

感谢我能得到的任何帮助。

编辑 (22/04/2013)

我已经设法将这段代码拼凑在一起以生成 RGB565 数据,它会颠倒生成它并带有一些小伪像,我认为第一个是由于使用了不同的坐标系,第二个可能是因为我来自 PNG到 BMP。我将使用 BMP 和非透明 PNG 进行更多测试。

- (NSData *)RGB565Data

    CGContextRef cgctx = CreateARGBBitmapContext(self.CGImage);
    if (cgctx == NULL)
        return nil;

    size_t w = CGImageGetWidth(self.CGImage);
    size_t h = CGImageGetHeight(self.CGImage);
    CGRect rect = 0,0,w,h;
    CGContextDrawImage(cgctx, rect, self.CGImage);

    void *data = CGBitmapContextGetData (cgctx);
    CGContextRelease(cgctx);

    if (!data)
        return nil;

    vImage_Buffer src;
    src.data = data;
    src.width = w;
    src.height = h;
    src.rowBytes = (w * 4);

    void* destData = malloc((w * 2) * h);

    vImage_Buffer dst;
    dst.data = destData;
    dst.width = w;
    dst.height = h;
    dst.rowBytes = (w * 2);

    vImageConvert_ARGB8888toRGB565(&src, &dst, 0);

    size_t dataSize = 2 * w * h; // RGB565 = 2 5-bit components and 1 6-bit (16 bits/2 bytes)
    NSData *RGB565Data = [NSData dataWithBytes:dst.data length:dataSize];

    free(destData);

    return RGB565Data;


- (CGImageRef)CGImage

    return [self CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil];


CGContextRef CreateARGBBitmapContext (CGImageRef inImage)

    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;


    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
    bitmapBytesPerRow   = (int)(pixelsWide * 4);
    bitmapByteCount     = (int)(bitmapBytesPerRow * pixelsHigh);

    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
        return nil;

    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    
        CGColorSpaceRelease( colorSpace );
        return nil;
    
    context = CGBitmapContextCreate (bitmapData,
                                 pixelsWide,
                                 pixelsHigh,
                                 8,
                                 bitmapBytesPerRow,
                                 colorSpace,
                                 kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    
    CGColorSpaceRelease( colorSpace );

    return context;

【问题讨论】:

您通常根本不需要直接使用 NSGraphicsContext。您需要做一些事情,例如 gstate 或获取 CGContext,但 AppKit 的绘图方法只是自己获取当前上下文,您甚至可以从整块布料创建图像,而无需自己创建上下文。 “将一种颜色的像素颜色更改为另一种颜色”到底是什么意思? @PeterHosey 我的意思是,我想将图像中具有某个 RGB 值的所有像素更改为另一个 RGB 值。例如。所有红色像素都变成绿色。 棘手。颜色循环通常假设一个索引颜色空间(使用转换表将一些颜色转换为其他颜色)。您可能需要编写自定义过滤器。 【参考方案1】:

在大多数情况下,您需要使用 Core Image。

您可以使用 CIAffineTransform 过滤器进行旋转。这需要一个 NSAffineTransform 对象。您之前可能已经使用过该课程。 (您可以使用 NSImage 本身进行旋转,但使用 Core Image 更容易,而且您可能需要在下一步使用它。)

我不知道您所说的“将一种颜色的像素颜色更改为另一种颜色”是什么意思;这可能意味着很多不同的事情。不过,有可能有一个过滤器。

我也不知道您为什么特别需要 565 数据,但假设您确实需要它,那么您将涉及该功能是正确的。使用CIContext's lowest-level rendering method to get 8-bit-per-component ARGB output,然后使用 vImage 函数将其转换为 565 RGB。

【讨论】:

谢谢彼得。我正在编写的程序需要接收各种图像文件(由用户选择),即 PNG、BMP、GIF,更改“背景”颜色,旋转它,然后将其保存为 RGB565。该程序准备这些图像以在嵌入式图形平台上使用(因此使用 RGB565)。【参考方案2】:

我已经设法通过使用 NSBitmapImageRep 获得了我想要的东西(通过一些技巧来访问它)。如果有人知道这样做的更好方法,请分享。

- (NSBitmapImageRep)bitmap 方法是我的 hack。 NSImage 开始时只有一个 NSBitmapImageRep,但是在旋转方法之后添加了一个 CIImageRep,它优先于破坏颜色代码的 NSBitmapImageRep(因为 NSImage 呈现不着色的 CIImageRep)。

BitmapImage.m(NSImage 的子类)

CGContextRef CreateARGBBitmapContext (CGImageRef inImage)

    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;


    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
    bitmapBytesPerRow   = (int)(pixelsWide * 4);
    bitmapByteCount     = (int)(bitmapBytesPerRow * pixelsHigh);

    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
        return nil;

    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    
        CGColorSpaceRelease( colorSpace );
        return nil;
    
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    
    CGColorSpaceRelease( colorSpace );

    return context;


- (NSData *)RGB565Data

    CGContextRef cgctx = CreateARGBBitmapContext(self.CGImage);
    if (cgctx == NULL)
        return nil;

    size_t w = CGImageGetWidth(self.CGImage);
    size_t h = CGImageGetHeight(self.CGImage);
    CGRect rect = 0,0,w,h;
    CGContextDrawImage(cgctx, rect, self.CGImage);

    void *data = CGBitmapContextGetData (cgctx);
    CGContextRelease(cgctx);

    if (!data)
        return nil;

    vImage_Buffer src;
    src.data = data;
    src.width = w;
    src.height = h;
    src.rowBytes = (w * 4);

    void* destData = malloc((w * 2) * h);

    vImage_Buffer dst;
    dst.data = destData;
    dst.width = w;
    dst.height = h;
    dst.rowBytes = (w * 2);

    vImageConvert_ARGB8888toRGB565(&src, &dst, 0);

    size_t dataSize = 2 * w * h; // RGB565 = 2 5-bit components and 1 6-bit (16 bits/2 bytes)
    NSData *RGB565Data = [NSData dataWithBytes:dst.data length:dataSize];

    free(destData);

    return RGB565Data;


- (NSBitmapImageRep*)bitmap

    NSBitmapImageRep *bitmap = nil;

    NSMutableArray *repsToRemove = [NSMutableArray array];

    // Iterate through the representations that back the NSImage
    for (NSImageRep *rep in self.representations)
    
        // If the representation is a bitmap
        if ([rep isKindOfClass:[NSBitmapImageRep class]])
        
            bitmap = [(NSBitmapImageRep*)rep retain];
            break;
        
        else
        
            [repsToRemove addObject:rep];
        
    

    // If no bitmap representation was found, we create one (this shouldn't occur)
    if (bitmap == nil)
    
        bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:self.CGImage] retain];
        [self addRepresentation:bitmap];
    

    for (NSImageRep *rep2 in repsToRemove)
    
        [self removeRepresentation:rep2];
    

    return [bitmap autorelease];


- (NSColor*)colorAtX:(NSInteger)x y:(NSInteger)y

    return [self.bitmap colorAtX:x y:y];


- (void)setColor:(NSColor*)color atX:(NSInteger)x y:(NSInteger)y

    [self.bitmap setColor:color atX:x y:y];

NSImage+Extra.m(NSImage 类别)

- (CGImageRef)CGImage

    return [self CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil];

用法

- (IBAction)load:(id)sender

    NSOpenPanel* openDlg = [NSOpenPanel openPanel];

    [openDlg setCanChooseFiles:YES];

    [openDlg setCanChooseDirectories:YES];

    if ( [openDlg runModalForDirectory:nil file:nil] == NSOKButton )
    
        NSArray* files = [openDlg filenames];

        for( int i = 0; i < [files count]; i++ )
        
            NSString* fileName = [files objectAtIndex:i];
            BitmapImage *image = [[BitmapImage alloc] initWithContentsOfFile:fileName];

            imageView.image = image;
        
    


- (IBAction)colorize:(id)sender

    float width = imageView.image.size.width;
    float height = imageView.image.size.height;

    BitmapImage *img = (BitmapImage*)imageView.image;

    NSColor *newColor = [img colorAtX:1 y:1];

    for (int x = 0; x <= width; x++)
    
        for (int y = 0; y <= height; y++)
        
            if ([img colorAtX:x y:y] == newColor)
            
                [img setColor:[NSColor redColor] atX:x y:y];
            
        
    
    [imageView setNeedsDisplay:YES];


- (IBAction)rotate:(id)sender

    BitmapImage *img = (BitmapImage*)imageView.image;
    BitmapImage *newImg = [img rotate90DegreesClockwise:NO];
    imageView.image = newImg;

编辑(2013 年 4 月 24 日)

我更改了以下代码:

- (RGBColor)colorAtX:(NSInteger)x y:(NSInteger)y

    NSUInteger components[4];
    [self.bitmap getPixel:components atX:x y:y];
    //NSLog(@"R: %ld, G:%ld, B:%ld", components[0], components[1], components[2]);

    RGBColor color = components[0], components[1], components[2];

    return color;


- (BOOL)color:(RGBColor)a isEqualToColor:(RGBColor)b

    return ((a.red == b.red) && (a.green == b.green) && (a.blue == b.blue));


- (void)setColor:(RGBColor)color atX:(NSUInteger)x y:(NSUInteger)y

    NSUInteger components[4] = (NSUInteger)color.red, (NSUInteger)color.green, (NSUInteger)color.blue, 255;

    //NSLog(@"R: %ld, G: %ld, B: %ld", components[0], components[1], components[2]);

    [self.bitmap setPixel:components atX:x y:y];


- (IBAction)colorize:(id)sender

    float width = imageView.image.size.width;
    float height = imageView.image.size.height;

    BitmapImage *img = (BitmapImage*)imageView.image;

    RGBColor oldColor = [img colorAtX:0 y:0];
    RGBColor newColor;// = 255, 0, 0;
    newColor.red = 255;
    newColor.green = 0;
    newColor.blue = 0;

    for (int x = 0; x <= width; x++)
    
        for (int y = 0; y <= height; y++)
        
            if ([img color:[img colorAtX:x y:y] isEqualToColor:oldColor])
            
                [img setColor:newColor atX:x y:y];
            
        
    
    [imageView setNeedsDisplay:YES];

但现在它在第一次调用 colorize 方法时将像素变为红色,然后变为蓝色。

编辑 2 (24/04/2013)

下面的代码修复了它。这是因为旋转代码为 NSBitmapImageRep 添加了一个 alpha 通道。

- (RGBColor)colorAtX:(NSInteger)x y:(NSInteger)y

    if (self.bitmap.hasAlpha)
    
        NSUInteger components[4];
        [self.bitmap getPixel:components atX:x y:y];
        RGBColor color = components[1], components[2], components[3];
        return color;
    
    else
    
        NSUInteger components[3];
        [self.bitmap getPixel:components atX:x y:y];
        RGBColor color = components[0], components[1], components[2];
        return color;
    


- (void)setColor:(RGBColor)color atX:(NSUInteger)x y:(NSUInteger)y

    if (self.bitmap.hasAlpha)
    
        NSUInteger components[4] = 255, (NSUInteger)color.red, (NSUInteger)color.green, (NSUInteger)color.blue;
        [self.bitmap setPixel:components atX:x y:y];
    
    else
    
        NSUInteger components[3] = color.red, color.green, color.blue;
        [self.bitmap setPixel:components atX:x y:y];
    

【讨论】:

colorAtX:y: 非常昂贵——创建所有这些 NSColor 并不便宜。如果== 在这里实际上不起作用,我也不会感到惊讶。你最好编写一个 Core Image 过滤器内核来执行此操作,或者至少直接访问缓冲区中的像素。 谢谢彼得。关于从哪里开始的任何建议?我应该能够使用返回 NSInteger 的 getPixel:x:y: 方法,然后提取 4 个通道(R、G、B、A)我不应该吗?然后以同样的方式使用 setPixel:x:y:。我看过基于 Apple 的“CromaKey”示例编写 CoreImage 过滤器,但不知道从哪里开始。 @PeterHosey 我已经编辑了上面的代码以使用直接访问像素值,这样就不会创建 NSImage 对象。 (我看不到我使用的小图像的性能有任何提高,但我可以看到这会如何导致更大的图像出现问题,并且总体上会使用更多的内存)。【参考方案3】:

好的,我决定花一天时间研究 Peter 关于使用 CoreImage 的建议。 我之前做过一些研究,认为这太难了,但经过一整天的研究,我终于找到了我需要做的事情,令人惊讶的是,这再简单不过了。

早些时候,我决定 Apple ChromaKey Core Image 示例将是一个很好的起点,但示例代码由于 3 维颜色立方体而吓坏了我。在观看了 Core Image 上的 WWDC 2012 视频并在 github (https://github.com/vhbit/ColorCubeSample) 上找到了一些示例代码后,我决定加入并尝试一下。

这里是工作代码的重要部分,我还没有包含 RGB565Data 方法,因为我还没有编写它,但是使用 Peter 建议的方法应该很容易:

CIImage+Extras.h

- (NSImage*) NSImage;
- (CIImage*) imageRotated90DegreesClockwise:(BOOL)clockwise;
- (CIImage*) imageWithChromaColor:(NSColor*)chromaColor BackgroundColor:(NSColor*)backColor;
- (NSColor*) colorAtX:(NSUInteger)x y:(NSUInteger)y;

CIImage+Extras.m

- (NSImage*) NSImage

    CGContextRef cg = [[NSGraphicsContext currentContext] graphicsPort];
    CIContext *context = [CIContext contextWithCGContext:cg options:nil];
    CGImageRef cgImage = [context createCGImage:self fromRect:self.extent];

    NSImage *image = [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];

    return [image autorelease];


- (CIImage*) imageRotated90DegreesClockwise:(BOOL)clockwise

    CIImage *im = self;
    CIFilter *f = [CIFilter filterWithName:@"CIAffineTransform"];
    NSAffineTransform *t = [NSAffineTransform transform];
    [t rotateByDegrees:clockwise ? -90 : 90];
    [f setValue:t forKey:@"inputTransform"];
    [f setValue:im forKey:@"inputImage"];
    im = [f valueForKey:@"outputImage"];

    CGRect extent = [im extent];
    f = [CIFilter filterWithName:@"CIAffineTransform"];
    t = [NSAffineTransform transform];
    [t translateXBy:-extent.origin.x
                yBy:-extent.origin.y];
    [f setValue:t forKey:@"inputTransform"];
    [f setValue:im forKey:@"inputImage"];
    im = [f valueForKey:@"outputImage"];

    return im;


- (CIImage*) imageWithChromaColor:(NSColor*)chromaColor BackgroundColor:(NSColor*)backColor

    CIImage *im = self;

    CIColor *backCIColor = [[CIColor alloc] initWithColor:backColor];
    CIImage *backImage = [CIImage imageWithColor:backCIColor];
    backImage = [backImage imageByCroppingToRect:self.extent];
    [backCIColor release];
    float chroma[3];

    chroma[0] = chromaColor.redComponent;
    chroma[1] = chromaColor.greenComponent;
    chroma[2] = chromaColor.blueComponent;

    // Allocate memory
    const unsigned int size = 64;
    const unsigned int cubeDataSize = size * size * size * sizeof (float) * 4;
    float *cubeData = (float *)malloc (cubeDataSize);
    float rgb[3];//, *c = cubeData;

    // Populate cube with a simple gradient going from 0 to 1
    size_t offset = 0;
    for (int z = 0; z < size; z++)
        rgb[2] = ((double)z)/(size-1); // Blue value
        for (int y = 0; y < size; y++)
            rgb[1] = ((double)y)/(size-1); // Green value
            for (int x = 0; x < size; x ++)
                rgb[0] = ((double)x)/(size-1); // Red value
                float alpha = ((rgb[0] == chroma[0]) && (rgb[1] == chroma[1]) && (rgb[2] == chroma[2])) ? 0.0 : 1.0;

                cubeData[offset]   = rgb[0] * alpha;
                cubeData[offset+1] = rgb[1] * alpha;
                cubeData[offset+2] = rgb[2] * alpha;
                cubeData[offset+3] = alpha;

                offset += 4;
            
        
    

    // Create memory with the cube data
    NSData *data = [NSData dataWithBytesNoCopy:cubeData
                                        length:cubeDataSize
                                  freeWhenDone:YES];
    CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"];
    [colorCube setValue:[NSNumber numberWithInt:size] forKey:@"inputCubeDimension"];
    // Set data for cube
    [colorCube setValue:data forKey:@"inputCubeData"];

    [colorCube setValue:im forKey:@"inputImage"];
    im = [colorCube valueForKey:@"outputImage"];

    CIFilter *sourceOver = [CIFilter filterWithName:@"CISourceOverCompositing"];
    [sourceOver setValue:im forKey:@"inputImage"];
    [sourceOver setValue:backImage forKey:@"inputBackgroundImage"];

    im = [sourceOver valueForKey:@"outputImage"];

    return im;


- (NSColor*)colorAtX:(NSUInteger)x y:(NSUInteger)y

    NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithCIImage:self];
    NSColor *color = [bitmap colorAtX:x y:y];
    [bitmap release];
    return color;

【讨论】:

出于效率考虑,我将从构建 z/y/x 循环的多维数据集中移除 alpha 的计算。考虑一下:内部循环将被命中 262,144 次,但 alpha = ... ? 0.0 : 1.0; 只有 一次 会产生 0.0。相反,在循环完成后设置该特定元素。 其他需要考虑的事项:您使用星标的文件的 RGB 值将是 0 到 255。立方体的索引是 0 到 64。仅适用于可被 85 整除的 R、G 和 B 值文件颜色是否会完全落在索引颜色上。所有其他值都将被插值,包括 alpha。取颜色(2,2,2)。从颜色转换为索引,它会非常接近 (.5,.5,.5)。 CIColorCube 将获取索引 (0,0,0)、(1,0,0)、... (1,1,1) 的 8 个立方体值,并在它们之间进行插值。如果只为 (0,0,0) 设置立方体值,则生成的 alpha 大约为 0.875。

以上是关于旋转、改变颜色并从 NSImage 获取 RGB565 数据的主要内容,如果未能解决你的问题,请参考以下文章

SVG 在 Safari 10 中旋转时改变颜色

任何 NSImage 的棕褐色版本

从像素数组问题中获取 NSImage (Swift)

如何从 NSImage/UIImage 获取 AVFrame(ffmpeg)

如何使用 glut 和 openGL 改变颜色?

从CGImage获取像素格式