NSImage to NSBitmapImageRep
Asked Answered
D

3

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

Darrickdarrill answered 15/10, 2012 at 13:38 Comment(2)
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.Babara
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://mcmap.net/q/245495/-how-to-save-png-file-from-nsimage-retina-issuesByandby
H
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];
}
Hunkers answered 15/10, 2012 at 14:18 Comment(3)
Well this method works but not correctly. It returns representation with darker image. I'll add example to my question.Darrickdarrill
Are the results better if you use NSDeviceRGBColorSpace instead of NSCalibratedRGBColorSpace?Hunkers
You're welcome. Updated my answer to use NSDeviceRGBColorSpace too.Hunkers
B
17

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

Bypass answered 16/10, 2012 at 9:20 Comment(3)
As an additional suggestion, you might want to replace the second [self representations] with ret in the iteration, because right now ret is unused.Brote
Note that if the image has been created from scratch rather than loaded from a file, it may not have ANY image representations.Chromaticity
Thanks for this! That first segment doesn't work for me without the cast: if( [rep isKindOfClass:[NSBitmapImageRep class]] ) return (NSBitmapImageRep *)rep;Efrenefron
H
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];
}
Hunkers answered 15/10, 2012 at 14:18 Comment(3)
Well this method works but not correctly. It returns representation with darker image. I'll add example to my question.Darrickdarrill
Are the results better if you use NSDeviceRGBColorSpace instead of NSCalibratedRGBColorSpace?Hunkers
You're welcome. Updated my answer to use NSDeviceRGBColorSpace too.Hunkers
P
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
    }
}
Phane answered 16/5, 2017 at 19:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.