I'm trying to write a PNG file with indexed colours and alpha values (RGBA) using CGImageDestination. In terms of the PNG specification that is an image with color type 3 and a tRNS chunk. I'm getting a matrix of indices and an attached palette from a separate part of the program.
I've successfully managed to write indexed images without alpha values (i.e. only opaque colours). If I add alpha values as per documentation the produced images no longer use color type 3 (palette), but become full RGBA images (color type 6). In the actual PNG file the alpha values are essentially attached to the palette entry. There doesn't seem to be a way to create an explicit RGBA colour space.
I could use libpng or other PNG libraries directly to achieve the desired encoding. However, I'd prefer to only use CGImage and friends if possible.
TL;DR: How do I produce an indexed PNG file with alpha values using CGImage/CGImageDestination?
A very similar question was already asked two years ago (Create and write paletted RGBA PNG using NSImage), but it talked about Cocoa and didn't receive a solution.
My test code follows. If USE_ALPHA
is defined at compile time, the produced image uses alpha values. Otherwise it doesn't.
/* Compilation command (tested with Xcode 4.6.3 on Mac OS X 10.8.4):
*
* clang++ -std=c++11 -stdlib=libc++ -framework ApplicationServices -DUSE_ALPHA -o demo demo.cpp
*/
#include <utility>
#include <vector>
#include <array>
#include <ApplicationServices/ApplicationServices.h>
/* R, G, B */
const std::vector<std::array<std::uint8_t, 3>> colortable = {
{ 0xF0, 0x10, 0x10 },
{ 0x20, 0xF0, 0x20 },
{ 0x30, 0x30, 0xF0 },
{ 0xF0, 0xF0, 0x40 },
{ 0x50, 0xF0, 0xF0 },
};
/* Colour index, alpha */
const std::vector<std::uint8_t> data = {
3, 0x30,
2, 0x20,
1, 0x10,
0, 0x00,
4, 0x40,
3, 0x30,
2, 0x20,
1, 0x10,
};
void safeCfRelease(const ::CFTypeRef ref) {
if (ref != nullptr) {
::CFRelease(ref);
}
}
int main() {
const std::shared_ptr<const ::__CFURL>
url(::CFURLCreateWithFileSystemPath(nullptr, CFSTR("out.png"),
::kCFURLPOSIXPathStyle, false),
safeCfRelease);
const std::shared_ptr<::CGDataProvider>
prov_data(::CGDataProviderCreateWithData(nullptr, &data[0], data.size(), nullptr),
safeCfRelease);
const std::shared_ptr<::CGColorSpace>
gray(::CGColorSpaceCreateDeviceGray(), safeCfRelease),
rgb(::CGColorSpaceCreateDeviceRGB(), safeCfRelease),
indexed(::CGColorSpaceCreateIndexed(rgb.get(), colortable.size(),
reinterpret_cast<const unsigned char *>(&colortable[0])),
safeCfRelease);
const auto bitmap_info =
#ifdef USE_ALPHA
::kCGImageAlphaLast |
#else
::kCGImageAlphaNoneSkipLast |
#endif
::kCGBitmapByteOrderDefault;
const std::shared_ptr<::CGImage>
image(::CGImageCreate(/* width */ 4,
/* height */ 2,
/* bitsPerComponent */ 8,
/* bitsPerPixel */ 16,
/* bytesPerRow */ 8,
indexed.get(),
bitmap_info,
prov_data.get(), nullptr, false,
::kCGRenderingIntentDefault),
safeCfRelease);
const std::shared_ptr<::CGImageDestination>
dest(::CGImageDestinationCreateWithURL(url.get(), ::kUTTypePNG, 1, nullptr),
safeCfRelease);
::CGImageDestinationAddImage(dest.get(), image.get(), nullptr);
::CGImageDestinationFinalize(dest.get());
}
Description given by file
for opaque colours (this is also given for an image with the tRNS chunk produced using ImageMagick):
PNG image data, 4 x 2, 8-bit colormap, non-interlaced
Description with alpha values using CGImageDestination:
PNG image data, 4 x 2, 8-bit/color RGBA, non-interlaced