16

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

hockeyman
  • 1,141
  • 6
  • 27
  • 57
  • Hockeyman, do you still have the full solution to your question? I am looking for a way to identify pixels colors without too much process. Maybe NSBitmapImageRep could help me out somehow. Thanks. – RickON Jan 05 '14 at 10:18
  • You are doing some colorspace conversion and colorspace conversion is usually not lossless as it often involves floating point calculations that later on need to be rounded back to integer values. Over the time you get more and more rounding errors. Your code is way too complicated though, just see here: https://stackoverflow.com/a/17510651/15809 – Mecki Aug 20 '18 at 16:27

3 Answers3

16

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

aleclarson
  • 18,087
  • 14
  • 64
  • 91
Heinrich Giesen
  • 1,765
  • 1
  • 11
  • 8
  • 1
    As an additional suggestion, you might want to replace the second `[self representations]` with `ret` in the iteration, because right now `ret` is unused. – Brad Larson Oct 26 '12 at 19:24
  • Note that if the image has been created from scratch rather than loaded from a file, it may not have ANY image representations. – Ash Aug 16 '20 at 08:37
  • Thanks for this! That first segment doesn't work for me without the cast: `if( [rep isKindOfClass:[NSBitmapImageRep class]] ) return (NSBitmapImageRep *)rep;` – idbrii Feb 19 '21 at 20:28
14

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];
}
mrwalker
  • 1,883
  • 21
  • 23
3

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
    }
}
valvoline
  • 7,737
  • 3
  • 47
  • 52