NSImage to NSBitmapImageRep

hockeyman picture hockeyman · Oct 15, 2012 · Viewed 11.1k times · Source

How to convert NSImage to NSBitmapImageRep? I have code:

- (NSBitmapImageRep *)bitmapImageRepresentation
{
    NSBitmapImageRep *ret = (NSBitmapImageRep *)[self representations];

    if(![ret isKindOfClass:[NSBitmapImageRep class]])
    {
        ret = nil;
        for(NSBitmapImageRep *rep in [self representations])
            if([rep isKindOfClass:[NSBitmapImageRep class]])
            {
                ret = rep;
                break;
            }
    }

    if(ret == nil)
    {
        NSSize size = [self size];

        size_t width         = size.width;
        size_t height        = size.height;
        size_t bitsPerComp   = 32;
        size_t bytesPerPixel = (bitsPerComp / CHAR_BIT) * 4;
        size_t bytesPerRow   = bytesPerPixel * width;
        size_t totalBytes    = height * bytesPerRow;

        NSMutableData *data = [NSMutableData dataWithBytesNoCopy:calloc(totalBytes, 1) length:totalBytes freeWhenDone:YES];

        CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

        CGContextRef ctx = CGBitmapContextCreate([data mutableBytes], width, height, bitsPerComp, bytesPerRow, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), kCGBitmapFloatComponents | kCGImageAlphaPremultipliedLast);

        if(ctx != NULL)
        {
            [NSGraphicsContext saveGraphicsState];
            [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[self isFlipped]]];

            [self drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];

            [NSGraphicsContext restoreGraphicsState];

            CGImageRef img = CGBitmapContextCreateImage(ctx);

            ret = [[NSBitmapImageRep alloc] initWithCGImage:img];
            [self addRepresentation:ret];

            CFRelease(img);
            CFRelease(space);

            CGContextRelease(ctx);
        }
    }


    return ret;
}

It works, but it causes memory leaks. At least when I am using it with ARC. Using initWithData:[nsimagename TIFFRepresentation] it not working correctly. Some images`s representations are not good. I think it depends on format and colorspace of image. Is there any other ways to achieve that?


Result with mrwalker suggested solution:

Original image:
enter image description here

Converted to bitmapimagerep and back to image 1 time:
enter image description here

Converted to bitmapimagerep and back to image 3 times:
enter image description here

As you see image gets darker every time after converting to NSBitmapImageRep

Answer

Heinrich Giesen picture Heinrich Giesen · Oct 16, 2012

@Julius: Your code is way too complicated and contains several bugs. I will make a correction for only the first lines:

- (NSBitmapImageRep *)bitmapImageRepresentation
{
   for( NSImageRep *rep in [self representations] )
      if( [rep isKindOfClass:[NSBitmapImageRep class]] ) return rep;
   return nil;
}

This will extract the first NSBitmapImageRep if it is member in the representations or will return nil, if there is no NSBitmapImageRep. I'll give you another solution which will always work whatever NSImageReps are in the representations: NSBitmapImageRep, NSPDFImageRep or NSCGImageSnapshotRep or ...

- (NSBitmapImageRep *)bitmapImageRepresentation
{
   CGImageRef CGImage = [self CGImageForProposedRect:nil context:nil hints:nil];
   return [[[NSBitmapImageRep alloc] initWithCGImage:CGImage] autorelease];
}

Or to avoid subclassing of NSImage you may write:

NSImage *img = [[[NSImage alloc] initWithContentsOfFile:filename] autorelease];
CGImageRef CGImage = [img CGImageForProposedRect:nil context:nil hints:nil];
NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithCGImage:CGImage] autorelease];

This will only return a single NSBitmapImageRep which maybe not good enough if the image contains more than one representation (e.g. a lot of NSBitmapImageReps from TIFF files). But adding some code is straightforward.