Как мне создать 8-битный PNG с прозрачностью из NSBitmapImageRep? - PullRequest
6 голосов
/ 03 марта 2010

У меня есть 32-битный NSBitmapImageRep, у которого есть альфа-канал с практически 1-битными значениями (пиксели либо включены, либо выключены).

Я хочу сохранить это растровое изображение в 8-битном файле PNG с прозрачностью. Если я использую -representationUsingType:properties: метод NSBitmapImageRep и передаю NSPNGFileType, создается 32-битный PNG, а это не то, что мне нужно.

Я знаю, что 8-битные PNG-файлы можно читать, они открываются в Preview без проблем, но возможно ли записать этот тип PNG-файла с помощью любых встроенных API-интерфейсов Mac OS X? Я счастлив, что при необходимости перейду в Core Image или QuickTime. Беглый осмотр документов CGImage не выявил ничего очевидного.

EDIT: Я начал вознаграждение по этому вопросу, если кто-то может предоставить рабочий исходный код, который принимает 32-битный NSBitmapImageRep и пишет 256-цветный PNG с 1-битной прозрачностью, он ваш.

Ответы [ 5 ]

2 голосов
/ 03 марта 2010

Как насчет pnglib?Это действительно легкий и простой в использовании.

1 голос
/ 10 марта 2010

pngnq (и new pngquant , что обеспечивает более высокое качество) имеет лицензию в стиле BSD, так что вы можете просто включить ее в свою программу. Нет необходимости порождать как отдельное задание.

1 голос
/ 03 марта 2010

Отличным справочником для работы с API нижнего уровня является Программирование с помощью Quartz

Часть приведенного ниже кода основана на примерах из этой книги.

Примечание. Это не проверенный код, предназначенный только для отправной точки ....

- (NSBitmapImageRep*)convertImageRep:(NSBitmapImageRep*)startingImage{

    CGImageRef anImage = [startingImage CGImage];

    CGContextRef    bitmapContext;
    CGRect ctxRect;
    size_t  bytesPerRow, width, height;

    width = CGImageGetWidth(anImage);
    height = CGImageGetHeight(anImage);
    ctxRect = CGRectMake(0.0, 0.0, width, height);
    bytesPerRow = (width * 4 + 63) & ~63;
    bitmapData = calloc(bytesPerRow * height, 1);
    bitmapContext = createRGBBitmapContext(width, height, TRUE);
    CGContextDrawImage (bitmapContext, ctxRect, anImage);

    //Now extract the image from the context
    CGImageRef      bitmapImage = nil;
    bitmapImage = CGBitmapContextCreateImage(bitmapContext);
    if(!bitmapImage){
        fprintf(stderr, "Couldn't create the image!\n");
        return nil;
    }

    NSBitmapImageRep *newImage = [[NSBitmapImageRep alloc] initWithCGImage:bitmapImage];
    return newImage;
}

Функция создания контекста:

CGContextRef createRGBBitmapContext(size_t width, size_t height, Boolean needsTransparentBitmap)
{
    CGContextRef context;
    size_t bytesPerRow;
    unsigned char *rasterData;

    //minimum bytes per row is 4 bytes per sample * number of samples
    bytesPerRow = width*4;
    //round up to nearest multiple of 16.
    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);

    int bitsPerComponent = 2;  // to get 256 colors (2xRGBA)

    //use function 'calloc' so memory is initialized to 0.
    rasterData = calloc(1, bytesPerRow * height);
    if(rasterData == NULL){
        fprintf(stderr, "Couldn't allocate the needed amount of memory!\n");
        return NULL;
    }

    // uses the generic calibrated RGB color space.
    context = CGBitmapContextCreate(rasterData, width, height, bitsPerComponent, bytesPerRow,
                                    CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB),
                                    (needsTransparentBitmap ? kCGImageAlphaPremultipliedFirst :
                                     kCGImageAlphaNoneSkipFirst)
                                    );
    if(context == NULL){
        free(rasterData);
        fprintf(stderr, "Couldn't create the context!\n");
        return NULL;
    }

    //Either clear the rect or paint with opaque white,
    if(needsTransparentBitmap){
        CGContextClearRect(context, CGRectMake(0, 0, width, height));
    }else{
        CGContextSaveGState(context);
        CGContextSetFillColorWithColor(context, getRGBOpaqueWhiteColor());
        CGContextFillRect(context, CGRectMake(0, 0, width, height));
        CGContextRestoreGState(context);
    }
    return context;
}

Использование будет:

NSBitmapImageRep *startingImage;  // assumed to be previously set.
NSBitmapImageRep *endingImageRep = [self convertImageRep:startingImage];
// Write out as data
NSData *outputData = [endingImageRep representationUsingType:NSPNGFileType properties:nil];
// somePath is set elsewhere
[outputData writeToFile:somePath atomically:YES];
0 голосов
/ 03 марта 2010

CGImageDestination - ваш человек для низкоуровневой записи изображений, но я не знаю, поддерживает ли он эту конкретную способность.

0 голосов
/ 03 марта 2010

Можно попробовать создать NSBitmapImageRep с 8 битами, а затем скопировать в него данные.

На самом деле это будет много работы, так как вам придется самостоятельно вычислять таблицу индексов цвета.

...