NSImage to NSBitmapImageRep

12.7k views Asked by At

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

3

There are 3 answers

3
mrwalker On BEST ANSWER

You could try this method adapted from Mike Ash's "Obtaining and Interpreting Image Data" blog post:

- (NSBitmapImageRep *)bitmapImageRepresentation {
  int width = [self size].width;
  int height = [self size].height;

  if(width < 1 || height < 1)
      return nil;

  NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
                           initWithBitmapDataPlanes: NULL
                           pixelsWide: width
                           pixelsHigh: height
                           bitsPerSample: 8
                           samplesPerPixel: 4
                           hasAlpha: YES
                           isPlanar: NO
                           colorSpaceName: NSDeviceRGBColorSpace
                           bytesPerRow: width * 4
                           bitsPerPixel: 32];

  NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep: rep];
  [NSGraphicsContext saveGraphicsState];
  [NSGraphicsContext setCurrentContext: ctx];  
  [self drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositeCopy fraction: 1.0];
  [ctx flushGraphics];
  [NSGraphicsContext restoreGraphicsState];

  return [rep autorelease];
}
3
Heinrich Giesen On

@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.

0
valvoline On

Maybe late to the party, but this is the Swift 3 version from the @mrwalker response:

extension NSImage {
    func bitmapImageRepresentation(colorSpaceName: String) -> NSBitmapImageRep? {
        let width = self.size.width
        let height = self.size.height

        if width < 1 || height < 1 {
            return nil
        }

        if let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(width), pixelsHigh: Int(height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: colorSpaceName, bytesPerRow: Int(width) * 4, bitsPerPixel: 32)
        {
            let ctx = NSGraphicsContext.init(bitmapImageRep: rep)
            NSGraphicsContext.saveGraphicsState()
            NSGraphicsContext.setCurrent(ctx)
            self.draw(at: NSZeroPoint, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
            ctx?.flushGraphics()
            NSGraphicsContext.restoreGraphicsState()
            return rep
        }
        return nil
    }
}