Domanda

Hi everyone! It is my first post here.

I am quite new in objective-c so maybe my question is not so hard but It is a problem for me. I've searched the web and didn't find any useful hints for me... I am writing a program in Objective-c in Xcode. I need to read and display a pgm file (P5 with two bytes per pixel). To do that i'm trying to subclass NSImageRep but I don't know how to create a proper bitmap for this file and how to draw it. Below is my code so far:

header:

@interface CTPWTPGMImageRep : NSImageRep

@property (readonly)NSInteger width;
@property (readonly)NSInteger height;
@property (readonly)NSInteger maxValue;

+ (void)load;
+ (NSArray *)imageUnfilteredTypes;
+ (NSArray *)imageUnfilteredFileTypes;
+ (BOOL)canInitWithData:(NSData *)data;
+ (id)imageRepWithContentsOfFile:(NSString*)file;
+ (id)imageRepWithData:(NSData*)pgmData;

- (id)initWithData:(NSData *)data;
- (BOOL)draw;

@end

and implementation:

#import "CTPWTPGMImageRep.h"

@implementation CTPWTPGMImageRep

@synthesize width;
@synthesize height;
@synthesize maxValue;

#pragma mark - class methods
+(void) load
{
    NSLog(@"Called 'load' method for CTPWTPGMImageRep");
    [NSImageRep registerImageRepClass:[CTPWTPGMImageRep class]];
}

+ (NSArray *)imageUnfilteredTypes
{    
    // This is a UTI
    NSLog(@"imageUnfilteredTypes called");
    static NSArray *types = nil;
    if (!types) {
        types = [[NSArray alloc] initWithObjects:@"public.unix-executable", @"public.data", @"public.item", @"public.executable", nil];
    }

    return types;
}

+ (NSArray *)imageUnfilteredFileTypes
{
    // This is a filename suffix
    NSLog(@"imageUnfilteredFileTypes called");

    static NSArray *types = nil;
    if (!types)
        types = [[NSArray alloc] initWithObjects:@"pgm", @"PGM", nil];
    return types;
}

+ (BOOL)canInitWithData:(NSData *)data;
{
    // FIX IT
    NSLog(@"canInitWithData called");
    if ([data length] >= 2) // First two bytes for magic number magic number
    {
        NSString *magicNumber = @"P5";
        const unsigned char *mNum = (const unsigned char *)[magicNumber UTF8String];
        unsigned char aBuffer[2];
        [data getBytes:aBuffer length:2];
        if(memcmp(mNum, aBuffer, 2) == 0)
        {
            NSLog(@"canInitWithData: YES");
            return YES;
        }
    }
    NSLog(@"canInitWithData: NO");

    // end
    return NO;
}

+ (id)imageRepWithContentsOfFile:(NSString*)file {
    NSLog(@"imageRepWithContentsOfFile called");
    NSData* data = [NSData dataWithContentsOfFile:file];
    if (data)
        return [CTPWTPGMImageRep imageRepWithData:data];
    return nil;
 }

+ (id)imageRepWithData:(NSData*)pgmData {
    NSLog(@"imageRepWithData called");
    return [[self alloc] initWithData:pgmData];
}


#pragma mark - instance methods

- (id)initWithData:(NSData *)data;
{
    NSLog(@"initWithData called");
    self = [super init];

    if (!self)
    {
        return nil;
    }

    if ([data length] >= 2) {
        NSString *magicNumberP5 = @"P5";
        const unsigned char *mnP5 = (const unsigned char *)[magicNumberP5 UTF8String];
        unsigned char headerBuffer[20];
        [data getBytes:headerBuffer length:2];

        if(memcmp(mnP5, headerBuffer, 2) == 0)
        {
            NSArray *pgmParameters = [self calculatePgmParameters:data beginingByte:3];
            width = [[pgmParameters objectAtIndex:0] integerValue];
            height = [[pgmParameters objectAtIndex:1] integerValue];
            maxValue = [[pgmParameters objectAtIndex:2] integerValue];

            if (width <= 0 || height <= 0)
            {
                NSLog(@"Invalid image size: Both width and height must be > 0");
                return nil;
            }

            [self setPixelsWide:width];
            [self setPixelsHigh:height];
            [self setSize:NSMakeSize(width, height)];
            [self setColorSpaceName:NSDeviceWhiteColorSpace];
            [self setBitsPerSample:16];
            [self setAlpha:NO];
            [self setOpaque:NO];

            //What to do here?
            //CTPWTPGMImageRep *imageRep = 
              [NSBitmapImageRep alloc] initWithBitmapDataPlanes:];

            //if (imageRep) {
            /* code to populate the pixel map */
            //}
        }
        else
        {
            NSLog(@"It is not supported pgm file format.");
        }
    }
    return self;
  //return imageRep;
}

- (BOOL)draw
{
    NSLog(@"draw method1 called");
    return NO;
}

It is interesting that my canInitWithData: method is never called. Can you give me a hint how to smart read the Bitmap? I think that I need to use initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bytesPerRow:bitsPerPixel: but I don't know how to use it. How to create smart create (unsigned char **)planes from my NSData object? Do I need it?

May I use NSDeviceWhiteColorSpace which has white and alpha components (I don't need alpha)

[self setColorSpaceName:NSDeviceWhiteColorSpace];

and the most dificult part - I've completely no idea how to implement draw method. Any sugestions or hints?

Thank you in advance for your help.

EDIT:

Ok. Now I have implementation according to NSGod directions:

@implementation CTPWTPGMImageRep

//@synthesize width;
//@synthesize height;

#pragma mark - class methods

+(void) load
{
    NSLog(@"Called 'load' method for CTPWTPGMImageRep");
    [NSBitmapImageRep registerImageRepClass:[CTPWTPGMImageRep class]];
}

+ (NSArray *)imageUnfilteredTypes
{
    // This is a UTI
    NSLog(@"imageUnfilteredTypes called");
    static NSArray *types = nil;
    if (!types) {
        types = [[NSArray alloc] initWithObjects:@"public.unix-executable", @"public.data", @"public.item", @"public.executable", nil];
    }

    return types;
}

+ (NSArray *)imageUnfilteredFileTypes
{
    // This is a filename suffix
    NSLog(@"imageUnfilteredFileTypes called");

    static NSArray *types = nil;
    if (!types)
        types = [[NSArray alloc] initWithObjects:@"pgm", nil];
    return types;
}

+ (NSArray *)imageRepsWithData:(NSData *)data {
    NSLog(@"imageRepsWithData called");
    id imageRep = [[self class] imageRepWithData:data];
    return [NSArray arrayWithObject:imageRep];
}

- (id)initWithData:(NSData *)data {
    NSLog(@"initWithData called");
    CTPWTPGMImageRep *imageRep = [[self class] imageRepWithData:data];

    if (imageRep == nil) {
        return nil;
    }
    return self;
}

#pragma mark - instance methods

+ (id)imageRepWithData:(NSData *)data {
    NSLog(@"imageRepWithData called");
    if (data.length < 2) return nil;
    NSString *magicNumberP5 = @"P5";
    const unsigned char *mnP5 = (const unsigned char *)[magicNumberP5 UTF8String];
    unsigned char headerBuffer[2];
    [data getBytes:headerBuffer length:2];

    if (memcmp(mnP5, headerBuffer, 2) != 0) {
        NSLog(@"It is not supported pgm file format.");
        return nil;
    }
    NSArray *pgmParameters = [self calculatePgmParameters:data beginingByte:3];

    NSInteger width = [[pgmParameters objectAtIndex:0] integerValue];     // width in pixels
    NSInteger height = [[pgmParameters objectAtIndex:1] integerValue];    // height in pixels

    NSUInteger imageLength = width * height * 2;                          // two bytes per pixel

    // imageData contains bytes of Bitmap only. Without header

    NSData *imageData = [data subdataWithRange:
                         NSMakeRange(data.length - imageLength, imageLength)];

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)CFBridgingRetain(imageData));
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); // kCGColorSpaceGenericGrayGamma2_2
    CGImageRef imageRef = CGImageCreate(width,
                                        height,
                                        16,
                                        16,
                                        width * 2,
                                        colorSpace,
                                        kCGImageAlphaNone,
                                        provider,
                                        NULL,
                                        false,
                                        kCGRenderingIntentDefault);
    CGColorSpaceRelease(colorSpace);
    CGDataProviderRelease(provider);

    if (imageRef == NULL) {
        NSLog(@"CGImageCreate() failed!");
    }
    CTPWTPGMImageRep *imageRep = [[CTPWTPGMImageRep alloc] initWithCGImage:imageRef];
    return imageRep;
}

as you can see I left the part with extending values between 0-255 because my pixels has values between 0-65535.

But It doesn't work. When I am choosing a pgm file from panel nothing happens. Bellow is my openPanel code:

- (IBAction)showOpenPanel:(id)sender
{
    NSLog(@"showPanel method called");

    __block NSOpenPanel *panel = [NSOpenPanel openPanel];
    [panel setAllowedFileTypes:[NSImage imageFileTypes]];
    [panel beginSheetModalForWindow:[pgmImageView window] completionHandler:^ (NSInteger result) {
        if (result == NSOKButton) {
            CTPWTPGMImageRep *pgmImage = [[CTPWTPGMImageRep alloc] initWithData:[NSData dataWithContentsOfURL:[panel URL]]];

            // NSLog(@"Bits per pixel: %ld",[pgmImage bitsPerPixel]); // BUG HERE!

            NSImage *image = [[NSImage alloc] init];
            [image addRepresentation:pgmImage];
            [pgmImageView setImage:image];
        }
        panel = nil; // prevent strong ref cycle
    }];
}

Moreover, when i uncomment line with code // NSLog(@"Bits per pixel: %ld",[pgmImage bitsPerPixel]); // BUG HERE! just for check my Xcode freezes for a moment and I get EXC_BAD_ACCESS in:

AppKit`__75-[NSBitmapImageRep _withoutChangingBackingPerformBlockUsingBackingCGImage:]_block_invoke_0:
0x7fff8b4823e8:  pushq  %rbp
0x7fff8b4823e9:  movq   %rsp, %rbp
0x7fff8b4823ec:  pushq  %r15
0x7fff8b4823ee:  pushq  %r14
0x7fff8b4823f0:  pushq  %r13
0x7fff8b4823f2:  pushq  %r12
0x7fff8b4823f4:  pushq  %rbx
0x7fff8b4823f5:  subq   $312, %rsp
0x7fff8b4823fc:  movq   %rsi, %rbx
0x7fff8b4823ff:  movq   %rdi, %r15
0x7fff8b482402:  movq   10625679(%rip), %rax
0x7fff8b482409:  movq   (%rax), %rax
0x7fff8b48240c:  movq   %rax, -48(%rbp)
0x7fff8b482410:  movq   %rbx, %rdi
0x7fff8b482413:  callq  0x7fff8b383148            ; BIRBackingType  //EXC_BAD_ACCESS (code=2, adress=...)

any help??? I have no idea what is wrong...

È stato utile?

Soluzione

It's actually much easier than you're thinking.

First of all, I would recommend making CTPWTPGMImageRep a subclass of NSBitmapImageRep rather than NSImageRep. That will take care of the "hardest" problem, since there will be no need to implement a custom draw method, as NSBitmapImageReps already know how to draw themselves. (In OS X 10.5 and later, NSBitmapImageRep is basically a direct wrapper around CoreGraphics CGImageRefs).

I'm not that familiar with the PGM format, but what you'll basically be doing is creating a representation of the image in the closest destination format that matches the source format. To use a specific example, we'll take the PGM example FEEP image from Wikipedia.

P2
# Shows the word "FEEP" (example from Netpbm main page on PGM)
24 7
15
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

enter image description here

The closest native image to be able to portray the values in the example image would be a single channel grayscale image, 8 bits per pixel, no alpha. The plan then is to create a CGImageRef with the specified settings, and then use NSBitmapImageRep's initWithCGImage: method to initialize the custom subclass.

I would first override the following two methods to both rely on the singular +imageRepWithData::

+ (NSArray *)imageRepsWithData:(NSData *)data {
    id imageRep = [[self class] imageRepWithData:data];
    return [NSArray arrayWithObject:imageRep];
}

- (id)initWithData:(NSData *)data {
    CTPWTPGMImageRep *imageRep = [[self class] imageRepWithData:data];
    if (imageRep == nil) {
        [self release];
        return nil;
    }
    self = [imageRep retain];
    return self;
}

For me, it was necessary to implement the +imageRepsWithData: method to call the singular method before the image was able to be loaded properly.

I would then change the singular +imageRepWithData: method be as follows:

+ (id)imageRepWithData:(NSData *)data {
    if (data.length < 2) return nil;
    NSString *magicNumberP5 = @"P5";
    const unsigned char *mnP5 = (const unsigned char *)[magicNumberP5 UTF8String];
    unsigned char headerBuffer[20];
    [data getBytes:headerBuffer length:2];

    if (memcmp(mnP5, headerBuffer, 2) != 0) {
        NSLog(@"It is not supported pgm file format.");
        return nil;
    }
    NSArray *pgmParameters = [self calculatePgmParameters:data beginingByte:3];

    NSUInteger width = [[pgmParameters objectAtIndex:0] integerValue];
    NSUInteger height = [[pgmParameters objectAtIndex:1] integerValue];
    NSUInteger maxValue = [[pgmParameters objectAtIndex:2] integerValue];

    NSUInteger imageLength = width * height * 1;

    NSData *imageData = [data subdataWithRange:
                        NSMakeRange(data.length - imageLength, imageLength)];
    const UInt8 *imageDataBytes = [imageData bytes];
    UInt8 *expandedImageDataBytes = malloc(imageLength);

    for (NSUInteger i = 0; i < imageLength; i++) {
        expandedImageDataBytes[i] = 255 * (imageDataBytes[i] / (CGFloat)maxValue);
    }

    NSData *expandedImageData = [NSData dataWithBytes:expandedImageDataBytes
                                               length:imageLength];
    free(expandedImageDataBytes);
    CGDataProviderRef provider = CGDataProviderCreateWithCFData(
                                          (CFDataRef)expandedImageData);
    CGColorSpaceRef colorSpace =
             CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2);
    CGImageRef imageRef = CGImageCreate(width,
                                        height,
                                        8,
                                        8,
                                        width * 1,
                                        colorSpace,
                                        kCGImageAlphaNone,
                                        provider,
                                        NULL,
                                        false,
                                        kCGRenderingIntentDefault);
    CGColorSpaceRelease(colorSpace);
    CGDataProviderRelease(provider);
    if (imageRef == NULL) {
        NSLog(@"CGImageCreate() failed!");
    }
    CTPWTPGMImageRep *imageRep = [[[CTPWTPGMImageRep alloc]
                                initWithCGImage:imageRef] autorelease];
    CGImageRelease(imageRef);
    return imageRep;
}

As you can see, we need to loop through the bytes in the original image and create a second copy of these bytes with the full expanded range between 0 and 255.

To make use of this image rep, you can call it like the following (being sure to use NSImage's initWithData: method):

// if it hasn't been done already:
[NSImageRep registerImageRepClass:[CTPWTPGMImageRep class]];

NSString *path = [[NSBundle mainBundle] pathForResource:@"feep" ofType:@"pgm"];
NSData *data = [NSData dataWithContentsOfFile:path];
NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
[self.imageView setImage:image];

One more note about your +imageUnfilteredFileTypes method: filename extensions are case insensitive, so there's no need to specify both lowercase and uppercase @"PGM", you can just do lowercase:

+ (NSArray *)imageUnfilteredFileTypes {
    static NSArray *types = nil;
    if (!types) types = [[NSArray alloc] initWithObjects:@"pgm", nil];
    return types;
}

Altri suggerimenti

It might be easier to simply read the pixel data into memory and just create an NSBitmapImageRep from the pixel data instead of trying to create an image rep specifically for .pgm files.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top