-1

I am building a Cocoa application to help me organize my zillions of digital pictures. It is intended to keep a very lean database of information to allow me to find the images I want, then I can go to the storage where I keep the actual images (burned to DVDs or CDs). I can browse the images and set ratings, tags, etc. I then store this data on my hard disk with a database smaller than a couple dozen of the pictures themselves. In my database I would like to store a thumbnail image (which will make it about 10x larger). I have played around with several techniques and using JPEG-2000 format would be the smallest, but it does not seem to display properly?

My code to create the thumbnails is:

    // This code 'downsamples' my very large digital pictures
        // Thumbnail size
        int pixelsWide = 72;
        int pixelsHigh = 72;
        CGContextRef    myBitmapContext = NULL;
        CGColorSpaceRef colorSpace;
        void        *bitmapData;
        int     bitmapByteCount;
        int     bitmapBytesPerRow;

        bitmapBytesPerRow   = (pixelsWide * 4);
        bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);

        colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
        bitmapData = calloc( 1, bitmapByteCount );
        if( bitmapData == NULL ) {
            NSLog( @"Memory not allocated!" );
        } else {
            myBitmapContext = CGBitmapContextCreate( bitmapData,
                         pixelsWide,
                         pixelsHigh,
                         8,      // bits per component
                         bitmapBytesPerRow,
                         colorSpace,
                         kCGImageAlphaPremultipliedLast );
            if( myBitmapContext == NULL ) {
                free (bitmapData);
                NSLog( @"myBitmapContext not created!" );
            }
        }
        CGColorSpaceRelease( colorSpace );

        // This gets any zooming or rotating that I've done to the master image
        CGAffineTransform imageTransformMatrix = [self generateImageTransformMatrixForRect: NSMakeRect( 0.0, 0.0, (CGFloat) pixelsWide, (CGFloat) pixelsHigh )];

        CGContextConcatCTM( myBitmapContext, imageTransformMatrix );
        // imageRef is a CGImageRef to the master image to create the thumbnail of
        double imgWidth  = (double) CGImageGetWidth( imageRef );
        double imgHeight = (double) CGImageGetHeight( imageRef );
        NSRect dr = NSMakeRect( 0, 0, imgWidth, imgHeight );
        CGContextDrawImage( myBitmapContext, dr, imageRef );
    // This code pulls the downsampled image back in to save to the database
        thumbnailImageRef = CGBitmapContextCreateImage( myBitmapContext );
        bitmapData = CGBitmapContextGetData( myBitmapContext ); 
        CGContextRelease( myBitmapContext );
        if( bitmapData ) free( bitmapData );

        NSDictionary *JPGopts = [NSDictionary dictionaryWithObjectsAndKeys: NSImageCompressionFactor, [NSNumber numberWithFloat: 0.0], nil];
        NSBitmapImageRep *thumbnailBitmap = [[NSBitmapImageRep alloc] initWithCGImage: thumbnailImageRef];
        NSData *thumbnailDataJPG = [thumbnailBitmap representationUsingType: NSJPEGFileType properties: JPGopts];
        [thumbnailDataJPG writeToFile: @"/tmp/thumb.jpg" atomically: NO];
        NSData *thumbnailDataPNG = [thumbnailBitmap representationUsingType: NSPNGFileType properties: nil];
        [thumbnailDataPNG writeToFile: @"/tmp/thumb.png" atomically: NO];
        NSData *thumbnailDataGIF = [thumbnailBitmap representationUsingType: NSGIFFileType properties: nil];
        [thumbnailDataGIF writeToFile: @"/tmp/thumb.gif" atomically: NO];
        NSData *thumbnailDataJPG2 = [thumbnailBitmap representationUsingType: NSJPEG2000FileType properties: nil];
        [thumbnailDataJPG2 writeToFile: @"/tmp/thumb.jp2" atomically: NO];
        [thumbnailBitmap release];
        NSLog( @"The thumbnail sizes are %d for JPG, %d for PNG, %d for GIF, and %d for JPEG2000",
                    [thumbnailDataJPG length], 
                    [thumbnailDataPNG length], 
                    [thumbnailDataGIF length], 
                    [thumbnailDataJPG2 length] );
        // At this point I will store the selected NSData object to a BLOB in my database...

    // I change the 1's to 0's and rebuild to try different formats
    #if 1
        CGImageSourceRef thumbSrc = CGImageSourceCreateWithData( (CFDataRef) thumbnailDataJPG2, NULL );
    #elif 1
        CGImageSourceRef thumbSrc = CGImageSourceCreateWithData( (CFDataRef) thumbnailDataGIF, NULL );
    #elif 1
        CGImageSourceRef thumbSrc = CGImageSourceCreateWithData( (CFDataRef) thumbnailDataPNG, NULL );
    #else
        CGImageSourceRef thumbSrc = CGImageSourceCreateWithData( (CFDataRef) thumbnailDataJPG, NULL );
    #endif
        thumbnailImageRef = CGImageSourceCreateImageAtIndex( thumbSrc, 0, NULL );

Then in my drawRect method I do:

CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort]; 
if( thumbnailImageRef ) {
    imgWidth  = (double) CGImageGetWidth( thumbnailImageRef );
    imgHeight = (double) CGImageGetHeight( thumbnailImageRef );
    // Make 4-pixel red border around thumbnail
    CGContextSetRGBFillColor( myContext, 1, 0, 0, 1);
    CGContextFillRect( myContext, CGRectMake( 96, 96, imgWidth+8, imgHeight+8 ) );
    // Draw the thumbnail image
    CGContextDrawImage( myContext, NSMakeRect( 100, 100, imgWidth, imgHeight ), thumbnailImageRef );
}

When I look at the thumb.* files in Preview they all look fine (except the JPG since it has a white background of any non-filled area). But the thumbnailImageRef that is displayed in drawRect has extra black marks outside the image (in the red rectangle I put under it) if I use JPEG-2000. It looks perfect for PNG and GIF and, except for the white background again, looks OK for JPG. But since JPEG-2000 is 1/4 the size of the next smallest object I really want to use it.

I cannot figure out where the little black marks are coming from. Does anyone have any suggestions?

Example screen shots: Image to be thumbnailed (ignoring red square) This is the image that the thumbnail will be created from...

Thumbnail from the above using JPEG-2000 as thumbnailSrc (in the red square) This has the thumbnail from the above in the red square...

Thumbnail from the top using JPEG (in the red square) enter image description here

Thumbnail from the top using GIF (in the red square) enter image description here

The only changes I made to switch between these was the '1's and '0's in the #if/#elif/#endif section.

I would like to use JPEG-2000 for size reasons but have it look like the GIF image does.

Thanks!

LavaSlider
  • 2,494
  • 18
  • 29
  • Please include a screenshot/sample output image. – Peter Hosey Jun 23 '11 at 05:03
  • Images added to post. The first one is the view that will be thumbnailed (ignoring the red square) and the next one shows that thumbnail in the red square. These are each with `thumbnailJPG2` as `thumbSrc`. The next one is with JPEG and the bottom one with GIF. – LavaSlider Jun 23 '11 at 13:05

1 Answers1

0

Have you tried using CGImageDestination directly instead of NSBitmapImageRep to create the thumbnail data?

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • I did not know about `CGImageDestination` but went ahead and tried it. The results were identical (little black marks in exactly the same spots), so identical that the only explanation is that both techniques use the same code. – LavaSlider Jun 25 '11 at 01:44
  • OK, I have pretty much convinced myself that it is the JPEG-2000 creation process that has problems. I tried opening one of the JPEG-2000 files in Photoshop and it looked empty. All the others looked normal (they all look normal in Preview). Then I created a similar file in Photoshop and used it with a `CGImageSourceCreateWithURL` and it looks normal in my code... this leads me to believe it is an Apple problem and not a mistake by me. – LavaSlider Jun 25 '11 at 15:35
  • @LavaSlider: I have the same opinion. I suggest filing a bug: https://bugreport.apple.com/ – Peter Hosey Jun 25 '11 at 21:05