NSImage в NSBitmapImageRep

Как преобразовать NSImage в NSBitmapImageRep? У меня есть код:

- (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;
}

Это работает, но вызывает утечку памяти. По крайней мере, когда я использую его с ARC. Использование initWithData:[nsimagename TIFFRepresentation] не работает правильно. Представления некоторых изображений не очень хороши. Я думаю, это зависит от формата и цветового пространства изображения. Есть ли другие способы добиться этого?


Результат с предложенным mrwalker решением:

Исходное изображение:
введите здесь описание изображения

Преобразование в растровое изображение и обратно в изображение 1 раз:
введите здесь описание изображения

Преобразование в растровое изображение и обратно в изображение 3 раза:
введите здесь описание изображения

Как вы видите, изображение становится темнее каждый раз после преобразования в NSBitmapImageRep.


person hockeyman    schedule 15.10.2012    source источник
comment
Хоккеймен, у тебя еще есть полное решение твоего вопроса? Я ищу способ определить цвета пикселей без особого процесса. Возможно, NSBitmapImageRep мог бы мне как-то помочь. Спасибо.   -  person RickON    schedule 05.01.2014
comment
Вы выполняете некоторое преобразование цветового пространства, и преобразование цветового пространства обычно не происходит без потерь, поскольку оно часто включает вычисления с плавающей запятой, которые позже необходимо округлить до целых значений. Со временем вы получаете все больше и больше ошибок округления. Ваш код слишком сложен, просто посмотрите здесь: stackoverflow.com/a/17510651/15809   -  person Mecki    schedule 20.08.2018


Ответы (3)


Вы можете попробовать этот метод, адаптированный из Майка Эша " Получение и интерпретация данных изображения»:

- (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];
}
person mrwalker    schedule 15.10.2012
comment
Ну этот метод работает, но не правильно. Он возвращает представление с более темным изображением. Я добавлю пример к моему вопросу. - person hockeyman; 15.10.2012
comment
Будут ли результаты лучше, если вы используете NSDeviceRGBColorSpace вместо NSCalibratedRGBColorSpace? - person mrwalker; 15.10.2012
comment
Пожалуйста. Обновлен мой ответ, чтобы использовать NSDeviceRGBColorSpace. - person mrwalker; 16.10.2012

@Julius: Ваш код слишком сложен и содержит несколько ошибок. Сделаю поправку только для первых строк:

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

Это извлечет первый NSBitmapImageRep, если он входит в представления, или вернет nil, если NSBitmapImageRep нет. Я дам вам другое решение, которое всегда будет работать независимо от того, какие NSImageReps есть в представлениях: NSBitmapImageRep, NSPDFImageRep или NSCGImageSnapshotRep или...

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

Или, чтобы избежать подкласса NSImage, вы можете написать:

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

Это вернет только один NSBitmapImageRep, что может быть недостаточно, если изображение содержит более одного представления (например, много NSBitmapImageRep из файлов TIFF). Но добавить код несложно.

person Heinrich Giesen    schedule 16.10.2012
comment
В качестве дополнительного предложения вы можете заменить второй [self representations] на ret в итерации, потому что сейчас ret не используется. - person Brad Larson; 26.10.2012
comment
Обратите внимание, что если изображение было создано с нуля, а не загружено из файла, оно может не иметь НИКАКИХ изображений. - person Ash; 16.08.2020
comment
Спасибо за это! Этот первый сегмент не работает для меня без актерского состава: if( [rep isKindOfClass:[NSBitmapImageRep class]] ) return (NSBitmapImageRep *)rep; - person idbrii; 19.02.2021

Возможно, поздно на вечеринку, но это версия Swift 3 из ответа @mrwalker:

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
    }
}
person valvoline    schedule 16.05.2017