首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >支持新文件格式的超级NSImageRep (pgm)

支持新文件格式的超级NSImageRep (pgm)
EN

Stack Overflow用户
提问于 2013-01-11 02:01:37
回答 2查看 712关注 0票数 1

大家好!这是我在这里的第一篇文章。

我在目标方面很新,所以也许我的问题不是很难,但这对我来说是个问题。我在网上搜索过却找不到任何有用的线索.我正在用Xcode中的Objective编写一个程序。我需要读取和显示一个pgm文件(每个像素有两个字节的P5)。为此,我尝试将NSImageRep子类化,但我不知道如何为该文件创建适当的位图以及如何绘制它。下面是到目前为止我的代码:

标题:

代码语言:javascript
复制
@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

和执行情况:

代码语言:javascript
复制
#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;
}

有趣的是,我的canInitWithData:方法从未被调用过。你能告诉我怎么聪明地读位图吗?我认为我需要使用initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bytesPerRow:bitsPerPixel:,但我不知道如何使用它。如何从我的NSData对象创建智能创建(无符号char **)平面?我需要它吗?

我可以使用NSDeviceWhiteColorSpace,它有白色和阿尔法成分(我不需要alpha)

代码语言:javascript
复制
[self setColorSpaceName:NSDeviceWhiteColorSpace];

最复杂的部分-我完全不知道如何实现绘图方法。有暗示或暗示吗?

提前谢谢你的帮助。

编辑:

好的。现在,我已经按照NSGod指南实现了:

代码语言:javascript
复制
@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;
}

如您所见,我留下了在0-255之间扩展值的部分,因为我的像素的值在0-65535之间。

但不起作用。当我从面板中选择一个pgm文件时,什么都不会发生。贝娄是我的openPanel代码:

代码语言:javascript
复制
- (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
    }];
}

此外,当我用代码// NSLog(@"Bits per pixel: %ld",[pgmImage bitsPerPixel]); // BUG HERE!取消注释行时,只为了检查我的Xcode冻结了一会儿,EXC_BAD_ACCESS就进入了:

代码语言:javascript
复制
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=...)

有什么帮助吗?我不知道怎么回事.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-01-11 08:25:46

其实比你想象的要容易得多。

首先,我建议将CTPWTPGMImageRep作为NSBitmapImageRep的子类,而不是NSImageRep。这将解决“最困难”的问题,因为不需要实现自定义draw方法,因为NSBitmapImageRep已经知道如何绘制自己。(在OSX10.5及更高版本中,NSBitmapImageRep基本上是CoreGraphics CGImageRefs的直接包装器)。

我不太熟悉PGM格式,但基本上您要做的是以最接近的目标格式创建一个与源格式相匹配的图像表示。为了使用一个具体的例子,我们将从维基百科获取PGM例FEEP图像

代码语言:javascript
复制
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

能够描述示例图像中的值的最接近的本地图像是一幅单通道灰度图像,每像素8位,没有alpha。然后,计划创建一个具有指定设置的CGImageRef,然后使用NSBitmapImageRepinitWithCGImage:方法初始化自定义子类。

首先,我将重写以下两种方法,使它们都依赖于单数+imageRepWithData:

代码语言:javascript
复制
+ (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;
}

对我来说,在正确加载图像之前,有必要实现+imageRepsWithData:方法来调用奇异方法。

然后,我将奇异的+imageRepWithData:方法修改如下:

代码语言:javascript
复制
+ (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;
}

正如您所看到的,我们需要循环遍历原始图像中的字节,并创建这些字节的第二个副本,其整个扩展范围在0到255之间。

要使用这个映像代表,您可以像下面这样调用它(一定要使用NSImageinitWithData:方法):

代码语言:javascript
复制
// 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];

关于+imageUnfilteredFileTypes方法的另一个注意事项:文件名扩展不区分大小写,因此不需要同时指定小写和大写@"PGM",您只需做小写操作:

代码语言:javascript
复制
+ (NSArray *)imageUnfilteredFileTypes {
    static NSArray *types = nil;
    if (!types) types = [[NSArray alloc] initWithObjects:@"pgm", nil];
    return types;
}
票数 2
EN

Stack Overflow用户

发布于 2013-01-11 05:00:33

简单地将像素数据读入内存并从像素数据创建NSBitmapImageRep可能更容易,而不是试图为.pgm文件创建图像代表。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14270496

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档