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)
Thumbnail from the above using JPEG-2000 as thumbnailSrc (in the red square)
Thumbnail from the top using JPEG (in the red square)
Thumbnail from the top using GIF (in the red square)
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!