macoscocoasizensimageicns

Creating an ICNS programmatically: "Unsupported Image Size"


I'm trying to create an ICNS (including a 1024x1024 image) programmatically. Currently I'm creating an NSImage, then I create CGImageRef objects with the appropriate resolution, finally I'm adding them to an icon by using CGImageDestinationAddImage(). Peter Hosey has helped me create '@2x' images already, but the sizes of the images don't wanna be set.

This is the code (still a bit messy, sourcefile represents the path to the image):

NSSize sizes[10];
sizes[0] = NSMakeSize(1024,1024);
sizes[1] = NSMakeSize(512,512);
sizes[2] = NSMakeSize(512,512);
sizes[3] = NSMakeSize(256,256);
sizes[4] = NSMakeSize(256,256);
sizes[5] = NSMakeSize(128,128);
sizes[6] = NSMakeSize(64,64);
sizes[7] = NSMakeSize(32,32);
sizes[8] = NSMakeSize(32,32);
sizes[9] = NSMakeSize(16,16);
int count = 0;
for (int i=0 ; i<10 ; i++) {
    if ([[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"Size%i",i+1]]) count++;
}
NSURL *fileURL = [NSURL fileURLWithPath:aPath];

// Create icns
CGImageDestinationRef dr = CGImageDestinationCreateWithURL((CFURLRef)fileURL, kUTTypeAppleICNS , count, NULL);
NSImage *img = [[NSImage alloc] initWithContentsOfFile:sourcefile];
for (int i=0 ; i<10 ; i++) {
    if ([[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"Size%i",i+1]]) {

        // Create dictionary
        BOOL is2X = true;
        if (i == 1 || i == 3 || i == 5 || i == 7 || i == 9) is2X = false;
        int dpi = 144, size = (int)(sizes[i].width/2);
        if (!is2X) {dpi = 72;size = sizes[i].width;}
        [img setSize:NSMakeSize(size,size)];
        for (NSImageRep *rep in [img representations])[rep setSize:NSMakeSize(size,size)];
        const void *keys[2] = {kCGImagePropertyDPIWidth, kCGImagePropertyDPIHeight};
        const void *values[2] = {CFNumberCreate(0, kCFNumberSInt32Type, &dpi), CFNumberCreate(0, kCFNumberSInt32Type, &dpi)};
        CFDictionaryRef imgprops = CFDictionaryCreate(NULL, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

        // Add image
        NSRect prect = NSMakeRect(0,0,size,size);
        CGImageRef i1 = [img CGImageForProposedRect:&prect context:nil hints:nil];
        CGImageDestinationAddImage(dr, i1, imgprops);
    }
}
CGImageDestinationFinalize(dr);
CFRelease(dr);

size is the width or height that the current image should be. dpi is 144 if we're making an '@2x' image, otherwise it's 72. These values have been checked with NSLog.

The images in the resulting ICNS file are all the same size as the input image. If the size of the input image is 1024x1024, ImageIO complains:

ImageIO: _CGImagePluginWriteICNS unsupported image size (1024 x 1024) - scaling factor: 1

The above error is displayed every time the dpi is 72 and the size is 1024x1024.

I need to know how to set the size of the CGImage that is to be added to the ICNS file.


EDIT: I logged the images:

2012-12-31 12:48:51.281 Eicon[912:680f] |NSImage 0x101b4caf0 Size={512, 512} Reps=(

"NSBitmapImageRep 0x10380b900 Size={512, 512} ColorSpace=(not yet loaded) BPS=8 BPP=(not yet loaded) Pixels=1024x1024 Alpha=YES

Planar=NO Format=(not yet loaded) CurrentBacking=nil (faulting) CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.058 Eicon[912:680f] |NSImage 0x101b4caf0 Size={512, 512} Reps=(

"NSBitmapImageRep 0x10380b900 Size={512, 512} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO

Format=2 CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.111 Eicon[912:680f] |NSImage 0x101b4caf0 Size={256, 256} Reps=(

"NSBitmapImageRep 0x10380b900 Size={256, 256} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO

Format=2 CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.238 Eicon[912:680f] |NSImage 0x101b4caf0 Size={256, 256} Reps=(

"NSBitmapImageRep 0x10380b900 Size={256, 256} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO

Format=2 CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.309 Eicon[912:680f] |NSImage 0x101b4caf0 Size={128, 128} Reps=(

"NSBitmapImageRep 0x10380b900 Size={128, 128} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO

Format=2 CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.409 Eicon[912:680f] |NSImage 0x101b4caf0 Size={128, 128} Reps=(

"NSBitmapImageRep 0x10380b900 Size={128, 128} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO

Format=2 CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.534 Eicon[912:680f] |NSImage 0x101b4caf0 Size={32, 32} Reps=(

"NSBitmapImageRep 0x10380b900 Size={32, 32} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO Format=2

CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.616 Eicon[912:680f] |NSImage 0x101b4caf0 Size={32, 32} Reps=(

"NSBitmapImageRep 0x10380b900 Size={32, 32} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO Format=2

CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.729 Eicon[912:680f] |NSImage 0x101b4caf0 Size={16, 16} Reps=(

"NSBitmapImageRep 0x10380b900 Size={16, 16} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO Format=2

CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|

2012-12-31 12:48:52.864 Eicon[912:680f] |NSImage 0x101b4caf0 Size={16, 16} Reps=(

"NSBitmapImageRep 0x10380b900 Size={16, 16} ColorSpace=Generic RGB colorspace BPS=8 BPP=32 Pixels=1024x1024 Alpha=YES Planar=NO Format=2

CurrentBacking=|CGImageRef: 0x101c14630| CGImageSource=0x10380ae70"

)|


Solution

  • The error message is correct. You're putting in images of a size that is not supported by the IconFamily format. Specifically, from your output:

    2012-12-26 13:48:57.682 Eicon[1131:1b0f] |NSImage 0x1025233b0 Size={11.52, 11.52} Reps=( "NSBitmapImageRep 0x10421fc30 Size={11.52, 11.52} ColorSpace=(not yet loaded) BPS=8 BPP=(not yet loaded) Pixels=1024x1024 Alpha=NO Planar=NO Format=(not yet loaded) CurrentBacking=nil (faulting) CGImageSource=0x104221170"

    11.52 points is not a valid size for any element of an IconFamily. You need to find out why this image and rep have that size.

    A couple of other things:

    1. As I told you on that other answer, you don't need to change the pixel size of the representation. Leave the pixel size alone. Set the size (point size) of the rep and image (preferably to something valid).
    2. The -[NSImage initWithSize:] documentation says:

      It is permissible to initialize the receiver by passing a size of (0.0, 0.0); however, the receiver’s size must be set to a non-zero value before the NSImage object is used or an exception will be raised.

      You are not setting either object's size, which is what you need to do. (I'm surprised you're not getting an exception about this like the documentation promises.)

    As I mentioned on your other question, there is no 1024-point element anymore; the correct specification for a 1024-by-1024-pixel element is as 512 points @ 2x. That's a size (of both image and rep) of (NSSize){ 512.0, 512.0 } (points), with the rep being 1024 pixelsWide and 1024 pixelsHigh.


    Looks like I was missing one key ingredient before. Here it is.

    The CGImage that you give to the CGImageDestination doesn't have a point size associated with it—only NSImages and NSImageReps have that. The CGImage only has a pixel size; nothing to indicate the image's physical size or resolution.

    To tell the CGImageDestination whether a given CGImage is meant to be @ 1x or @ 2x, you need to create a dictionary that gives the image's DPI:

    NSDictionary *imageProps1x = @{
        (__bridge NSString *)kCGImagePropertyDPIWidth: @72.0,
        (__bridge NSString *)kCGImagePropertyDPIHeight: @72.0,
    };
    NSDictionary *imageProps2x = @{
        (__bridge NSString *)kCGImagePropertyDPIWidth: @144.0,
        (__bridge NSString *)kCGImagePropertyDPIHeight: @144.0,
    };
    

    Pass the correct dictionary as the last argument to CGImageDestinationAddImage.