2

I am trying to write a prototype to prove that RAW conversion from one format to another is possible. I have to convert a Nikon's raw file which is of .NEF format to Canon's .CR2 format. With help of various posts I create the original image TIFF representation's BitmapImageRep and use this to write the output file which has a .CR2 extension.

It does work but only problem for me is, the input file is of 21.5 MB but the output am getting is of 144.4 MB. While using NSTIFFCompressionPackBits gives me 142.1 MB.

I want to understand what is happening, I have tried various compression enums available but with no success.

Please help me understanding it. This is the source code:

@interface NSImage(RawConversion)
- (void) saveAsCR2WithName:(NSString*) fileName;
@end

@implementation NSImage(RawConversion)
- (void) saveAsCR2WithName:(NSString*) fileName
{
    // Cache the reduced image
    NSData *imageData = [self TIFFRepresentation];
    NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
//    http://www.cocoabuilder.com/archive/cocoa/151789-nsbitmapimagerep-compressed-tiff-large-files.html
    NSDictionary *imageProps = [NSDictionary dictionaryWithObjectsAndKeys:  [NSNumber numberWithInt:NSTIFFCompressionJPEG],NSImageCompressionMethod,
                                                                            [NSNumber numberWithFloat: 1.0], NSImageCompressionFactor,
                                                                            nil];
    imageData = [imageRep representationUsingType:NSTIFFFileType properties:imageProps];
    [imageData writeToFile:fileName atomically:NO];
}
@end

How could I get the output file which is in CR2 format but almost around the size of the input file with little variation as required for a CR2 file?

Edit 1: Done changes based on Peter's suggestion of using CGImageDestinationAddImageFromSource method, but still I am getting the same result. The input source NEF file size 21.5 MB but the destination file size after conversion 144.4 MB.

Please review the code:

-(void)saveAsCR2WithCGImageMethodUsingName:(NSString*)inDestinationfileName withSourceFile:(NSString*)inSourceFileName
{
    CGImageSourceRef sourceFile = MyCreateCGImageSourceRefFromFile(inSourceFileName);
    CGImageDestinationRef destinationFile = createCGImageDestinationRefFromFile(inDestinationfileName);
    CGImageDestinationAddImageFromSource(destinationFile, sourceFile, 0, NULL);
//https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/ImageIOGuide/ikpg_dest/ikpg_dest.html
    CGImageDestinationFinalize(destinationFile);
}

CGImageSourceRef MyCreateCGImageSourceRefFromFile (NSString* path)
{
    // Get the URL for the pathname passed to the function.
    NSURL *url = [NSURL fileURLWithPath:path];
    CGImageSourceRef  myImageSource;
    CFDictionaryRef   myOptions = NULL;
    CFStringRef       myKeys[2];
    CFTypeRef         myValues[2];

    // Set up options if you want them. The options here are for
    // caching the image in a decoded form and for using floating-point
    // values if the image format supports them.
    myKeys[0] = kCGImageSourceShouldCache;
    myValues[0] = (CFTypeRef)kCFBooleanTrue;
    myKeys[1] = kCGImageSourceShouldAllowFloat;
    myValues[1] = (CFTypeRef)kCFBooleanTrue;

    // Create the dictionary
    myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
                                   (const void **) myValues, 2,
                                   &kCFTypeDictionaryKeyCallBacks,
                                   & kCFTypeDictionaryValueCallBacks);

    // Create an image source from the URL.
    myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, myOptions);
    CFRelease(myOptions);

    // Make sure the image source exists before continuing
    if (myImageSource == NULL){
        fprintf(stderr, "Image source is NULL.");
        return  NULL;
    }
    return myImageSource;
}

CGImageDestinationRef createCGImageDestinationRefFromFile (NSString *path)
{
    NSURL *url = [NSURL fileURLWithPath:path];
    CGImageDestinationRef myImageDestination;

//https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/ImageIOGuide/ikpg_dest/ikpg_dest.html
    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);

//https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/ImageIOGuide/imageio_basics/ikpg_basics.html#//apple_ref/doc/uid/TP40005462-CH216-SW3
    CFStringRef destFileType = CFSTR("public.tiff");
//    CFStringRef destFileType = kUTTypeJPEG;
    CFArrayRef types = CGImageDestinationCopyTypeIdentifiers(); CFShow(types);

    myImageDestination = CGImageDestinationCreateWithURL((CFURLRef)url, destFileType, 1, myOptions);
    return myImageDestination;
}

Edit 2: Used the second approach told by @Peter. This gives interesting result. It's effect is the same as renaming the file in finder something like "example_image.NEF" to "example_image.CR2". Surprisingly what happens when converting both programmatically and in finder is, the source file which is 21.5 MB will turn out to be 59 KB. This is without any compression set in the code. Please see the code and suggest:

-(void)convertNEFWithTiffIntermediate:(NSString*)inNEFFile toCR2:(NSString*)inCR2File
{
    NSData *fileData = [[NSData alloc] initWithContentsOfFile:inNEFFile];
    if (fileData)
    {
        NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:fileData];
//        [imageRep setCompression:NSTIFFCompressionNone
//                          factor:1.0];
        NSDictionary *imageProps = nil;
        NSData *destinationImageData = [imageRep representationUsingType:NSTIFFFileType properties:imageProps];
        [destinationImageData writeToFile:inCR2File atomically:NO];
    }
}
Raj Pawan Gumdal
  • 7,390
  • 10
  • 60
  • 92
  • Why did you tag this as [cocoa-touch] if you're using NSImage and NSBitmapImageRep? – Peter Hosey Mar 17 '13 at 09:14
  • Sorry, thanks for the correction. I am an iOS developer and am used to tagging it that way. Will try your both mentioned methods now. – Raj Pawan Gumdal Mar 17 '13 at 09:20
  • 1
    Renaming the file in the Finder changes its size to 59 K? That can't be right. – Peter Hosey Mar 17 '13 at 16:11
  • Surprisingly that is what is happening. – Raj Pawan Gumdal Mar 18 '13 at 05:24
  • NSImage can read file of almost all types including NEF and CR2, but NSBitmapImageRep can give us file types of only one among the following: NSTIFFFileType, NSBMPFileType, NSGIFFileType, NSJPEGFileType, NSPNGFileType, NSJPEG2000FileType. Does this mean that there is no way to write a CR2 file out of the BitmapImageRep? – Raj Pawan Gumdal Mar 18 '13 at 05:28

3 Answers3

1

The first thing I would try doesn't involve NSImage or NSBitmapImageRep at all. Instead, I would create a CGImageSource for the source file and a CGImageDestination for the destination file, and use CGImageDestinationAddImageFromSource to transfer all of the images from A to B.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • I have followed this approach but could not find success yet :( Please see my edited question which has recent code. Trying your next approach now. – Raj Pawan Gumdal Mar 17 '13 at 10:23
  • @Raj: You used the same output format, so of course you got the same result. If you want CR2 data, ask for that, not TIFF. – Peter Hosey Mar 18 '13 at 05:41
  • But how do we get CR2 data from the following available? NSTIFFFileType, NSBMPFileType, NSGIFFileType, NSJPEGFileType, NSPNGFileType, NSJPEG2000FileType – Raj Pawan Gumdal Mar 18 '13 at 07:22
  • @Raj: Note which answer we're commenting on. Those types are for NSBitmapImageRep, not CGImageDestination. – Peter Hosey Mar 18 '13 at 07:42
  • Ah yes, you are right again, but for CGImageDestinationRef also only few options are available, CR2 is not among it: kUTTypeImage, kUTTypeJPEG, kUTTypeJPEG2000, kUTTypeTIFF, kUTTypePICT, kUTTypeGIF, kUTTypePNG, kUTTypeQuickTimeImage, kUTTypeAppleICNS, kUTTypeBMP, kUTTypeICO – Raj Pawan Gumdal Mar 18 '13 at 08:12
  • @Raj: There are some differences in the actual list returned by `CGImageDestinationCopyTypeIdentifiers` (as of 10.7.5), but yes, you're right—CR2 isn't there. I suppose you could find out what the UTI is and try it, but if it isn't even on the System-Declared UTIs list, I wouldn't hold my breath. – Peter Hosey Mar 18 '13 at 08:31
  • I checked the system defined UTIs using this code: CFArrayRef types = CGImageDestinationCopyTypeIdentifiers(); CFShow(types); There isnt a UTI for CR2. Does this mean that the camera vendors provide code only to read RAW files but not to write back to it? And, does this also mean that there is no way to do it? Interesting! – Raj Pawan Gumdal Mar 18 '13 at 08:45
  • 1
    @Raj: There are a lot more UTIs than that, but those are the ones CGImageDestination supports. I have no idea what camera vendors did or didn't provide. It does seem, though, that you cannot do this using either CGImageDestination or NSBitmapImageRep; assuming you can't do it using Core Image (which I know can *read* raw images, but that's a solved problem in the other two APIs as well), you will have to obtain someone else's library to do this or write your own code to do it. – Peter Hosey Mar 18 '13 at 09:28
  • Ok I understand now, thanks for all your guidance. I was in an impression that since NSImage reads this, there has to be a reverse mechanism too. Would be a good subject to work upon but photographers will use it with cruel intentions! Actually I started with this effort to prove that a photographer somehow converted our NEF RAW image to CR2 and is now claiming it as his. Anyways that's nothing to do with StackOverflow! – Raj Pawan Gumdal Mar 18 '13 at 09:35
1

You're converting to TIFF twice in this code:

  1. You create an NSImage, I assume from the source file.
  2. You ask the NSImage for its TIFFRepresentation (TIFF conversion #1).
  3. You create an NSBitmapImageRep from the first TIFF data.
  4. You ask the NSBitmapImageRep to generate a second TIFF representation (TIFF conversion #2).

Consider creating an NSBitmapImageRep directly from the source data, and not using NSImage at all. You would then skip directly to step 4 to generate the output data.

(But I still would try CGImageDestinationAddImageFromSource first.)

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
0

Raw image files have their own (proprietary) representation. For example, they may use 14-bit per component, and mosaic patterns, which are not supported by your code. I think you should use a lower-level API and really reverse engineer the RAW format you are trying to save to.

I would start with DNG, which is relatively easy, as Adobe provides an SDK to write it.

Yoav
  • 5,962
  • 5
  • 39
  • 61