0

I am trying to use the C interface of CoreGraphics & CoreFoundation to save a buffer of 32-bit RGBA data (as a void*) to a PNG file. When I try to finialize the CGImageDestinationRef, the following error message is printed to the console:

libpng error: No IDATs written into file

As far as I can tell, the CGImageRef I'm adding to the CGImageDestinationRef is valid.

Relavent Code:

void saveImage(const char* szImage, void* data, size_t dataSize, size_t width, size_t height)
{
    CFStringRef name = CFStringCreateWithCString(NULL, szImage, kCFStringEncodingASCII);
    CFURLRef texture_url = CFURLCreateWithFileSystemPath(
                                                     NULL,
                                                     name,
                                                     kCFURLPOSIXPathStyle,
                                                     false);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, data, dataSize, NULL);
    CGImageRef image = CGImageCreate(width, height, 8, 32, 32 * width, colorSpace,
                                 kCGImageAlphaLast | kCGBitmapByteOrderDefault, dataProvider,
                                 NULL, FALSE, kCGRenderingIntentDefault);

    // From Image I/O Programming Guide, "Working with Image Destinations"
    float compression = 1.0; // Lossless compression if available.
    int orientation = 4; // Origin is at bottom, left.
    CFStringRef myKeys[3];
    CFTypeRef   myValues[3];
    CFDictionaryRef myOptions = NULL;
    myKeys[0] = kCGImagePropertyOrientation;
    myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
    myKeys[1] = kCGImagePropertyHasAlpha;
    myValues[1] = kCFBooleanTrue;
    myKeys[2] = kCGImageDestinationLossyCompressionQuality;
    myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
    myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
                               &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    CFStringRef type = CFStringCreateWithCString(NULL, "public.png", kCFStringEncodingASCII);
    CGImageDestinationRef dest = CGImageDestinationCreateWithURL(texture_url, type, 1, myOptions);

    CGImageDestinationAddImage(dest, image, NULL);

    if (!CGImageDestinationFinalize(dest))
    {
        // ERROR!
    }

    CFRelease(image);
    CFRelease(colorSpace);
    CFRelease(dataProvider);
    CFRelease(dest);
    CFRelease(texture_url);
}

This post is similar, except I'm not using the Objective C interface: Saving a 32 bit RGBA buffer into a .png file (Cocoa OSX)

Community
  • 1
  • 1
Chris
  • 452
  • 3
  • 14

2 Answers2

0

There are numerous issues with your code.

Here it is rewritten how I would do it:

void saveImage(const char* szImage, void* data, size_t dataSize, size_t width, size_t height)
{
    CFStringRef name = CFStringCreateWithCString(NULL, szImage, kCFStringEncodingUTF8);
    CFURLRef texture_url = CFURLCreateWithFileSystemPath(
                                                     NULL,
                                                     name,
                                                     kCFURLPOSIXPathStyle,
                                                     false);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, data,
                                                                dataSize, NULL);
    CGImageRef image = CGImageCreate(width, height, 8, 32, 32 * width, colorSpace,
                                 kCGImageAlphaLast | kCGBitmapByteOrderDefault,
                          dataProvider, NULL, FALSE, kCGRenderingIntentDefault);

    // From Image I/O Programming Guide, "Working with Image Destinations"
    float compression = 1.0; // Lossless compression if available.
    int orientation = 4; // Origin is at bottom, left.
    CFStringRef myKeys[3];
    CFTypeRef   myValues[3];
    CFDictionaryRef myOptions = NULL;
    myKeys[0] = kCGImagePropertyOrientation;
    myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
    myKeys[1] = kCGImagePropertyHasAlpha;
    myValues[1] = kCFBooleanTrue;
    myKeys[2] = kCGImageDestinationLossyCompressionQuality;
    myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
    myOptions = CFDictionaryCreate(NULL, (const void **)myKeys,
                    (const void **)myValues, 3, &kCFTypeDictionaryKeyCallBacks,
                                          &kCFTypeDictionaryValueCallBacks);

    CGImageDestinationRef dest = 
          CGImageDestinationCreateWithURL(texture_url, kUTTypePNG, 1, NULL);

    CGImageDestinationAddImage(dest, image, NULL);
    CGImageDestinationSetProperties(dest, myOptions);

    if (!CGImageDestinationFinalize(dest))
    {
        // ERROR!
    }

}

First, never use ASCII when dealing with file system paths, use UTF8. Second, you were constructing a dictionary to be used to set the properties of the image, but you were using it with the wrong function. The documentation for CGImageDestinationCreateWithURL() says the following:

CGImageDestinationCreateWithURL

Creates an image destination that writes to a location specified by a URL.

CGImageDestinationRef CGImageDestinationCreateWithURL (
   CFURLRef url,
   CFStringRef type,
   size_t count,
   CFDictionaryRef options
);

Parameters options - Reserved for future use. Pass NULL.

You were trying to pass a dictionary of properties when you were supposed to pass NULL. (Also, you can simply use the kUTTypePNG Uniform Type Identifier string constant instead of re-creating it). First call CGImageDestinationCreateWithURL(), then call CGImageDestinationAddImage() to add the image, then call CGImageDestinationSetProperties() and pass in the dictionary of properties you created.

[UPDATE]: If after these changes you're still having libpng error: No IDATs written into file issues, try the following: First, make sure that dataProvider is non-NULL-- in other words, make sure the CGDataProviderCreateWithData() function succeeded. Second, if dataProvider is valid, perhaps try changing the options from kCGImageAlphaLast | kCGBitmapByteOrderDefault to simply kCGImageAlphaPremultipliedLast and see if it succeeds.

NSGod
  • 22,699
  • 3
  • 58
  • 66
  • Thanks for the pointers. I am still searching for this UTCoreTypes.h file that has the kUTTypePNG constant defined. The documentation says it's in MobileCoreServices.framework, but I do not have that framework in my MacOSX10.8 SDK. The underlying issue still exists, though: I still get `libpng error: No IDATs written into file` after applying your fixes for the options dictionary. – Chris Aug 09 '13 at 22:05
  • @Chris: MobileCoreServices is for iOS, which doesn't really have a full blown CoreServices framework like OS X does. In OS X, UTCoreTypes.h will be part of the `LaunchServices.framework`, which is a subframework of the `CoreServices.framework` umbrella framework. Updated the answer as well (or will shortly). – NSGod Aug 10 '13 at 18:33
  • Ugh, didn't see your own answer before writing mine. – NSGod Aug 10 '13 at 18:38
  • Using `UTF8String` for file system paths isn't a good idea, especially if it has non-ASCII text. Instead, you should use `fileSystemRepresentation` to get the proper path to send to POSIX file commands. – MaddTheSane Sep 02 '15 at 19:20
0

Answering my own questions:

  1. In addition to the issues pointed out by NSGod, the IDAT issue was an invalid parameter to CGImageCreate(): parameter 5 is bytesPerRow, not bitsPerRow. So 32 * width was incorrect; 4 * width is correct.

  2. Despite what this page of the official documentation lists, UTCoreTypes.h is located in the CoreServices.framework for MacOSX, not MobileCoreServices.framework.

Chris
  • 452
  • 3
  • 14