views:

982

answers:

3

I have a CGImage (core graphics, C/C++). It's grayscale. Well, originally it was B/W, but the CGImage may be RGB. That shouldn't matter. I want to create a CCITT-Group 4 TIFF.

I can create an LZW TIFF (grayscale or color) via creating a destination with the correct dictionary and adding the image in. No problem.

However, there doesn't seem to be an equivalent kCGImagePropertyTIFFCompression value to represent CCITT-4. It should be 4, but that produces uncompressed.

I have a manual CCITT compression routine, so if I can get the binary (1 bit per pixel) data, I'm set. But I can't seem to get 1 BPP data out of a CGImage. I have code that is supposed to put the CGImage into a CGBitmapContext and then give me the data, but it seems to be giving me all black.

I've asked a couple of questions today trying to get at this, but I just figured, lets ask the question I REALLY want answered and see if someone can answer it.

There's GOT to be a way to do this. I've got to be missing something dumb. What is it?

+1  A: 

ImageMagick can convert from and to almost any image format. As it is open source you can go and read the source code to find the answer to your question.

You can even use the ImageMagick API in you app if you use C++.

Edit:
If you can get the data from CGImage in any format (and it sounded like you can) you can use ImageMagick to convert it from whatever the format is that you get from CGImage to any other format supported by ImageMagick (your desired TIFF format).

Edit: Technical Q&A QA1509 Getting the pixel data from a CGImage object states:

On Mac OS X 10.5 or later, a new call has been added that allows you to obtain the actual pixel data from a CGImage object. This call, CGDataProviderCopyData, returns a CFData object that contains the pixel data from the image in question.

Once you have the pixel data you can use ImageMagick to convert it.

lothar
that doesn't help because it doesn't actually DISPLAY the images at all. So, it doesn't deal with CGImage objects. I already have code for reading Group 4 files and writing them, assuming that I have the 1Bit data. I need to get the data out of the CGImage in such a way that I can write the TIFF...
Brian Postow
If you can get the data from CGImage in any format ( and it sounded like you can) you can use ImageMagick to convert it from whatever the format is that you get from CGImage to any other format supported by ImageMagick (your desired TIFF format).
lothar
Well, Ican't really seem to get binary data out of a CGImage... I can SAVE the document in some other format, but I'd rather not save, and then convert...
Brian Postow
@Brian Postow It's not important in what format you can get the data from CGImage. Any format will do, as ImageMagick is very flexible and can convert it to many other formats. That's the purpose of the library.
lothar
the problem is that I can't really get it in ANY format, other than CGImage. if I could get the bitmap out correctly, I'd be done already...I can save the images using macs saving routines, but they won't save CCITT....
Brian Postow
@Brian Postow Check http://developer.apple.com/qa/qa2007/qa1509.html on how to get the pixel data from a CGImage.
lothar
+1  A: 

NSBitmapImageRep claims to be able to generate a CCITT FAX Group 4 compressed TIFF. So something like this might do the trick (untested):

CFDataRef tiffFaxG4DataForCGImage(CGImageRef cgImage) {
  NSBitmapImageRep *imageRep =
      [[[NSBitmapImageRep alloc] initWithCGImage:cgImage] autorelease];
  NSData *tiffData =
      [imageRep TIFFRepresentationUsingCompression:NSTIFFCompressionCCITTFAX4
                                            factor:0.0f];
  return (CFDataRef) tiffData;
}

This function should return the data you seek.

Christian Brunschen
That's Objective C, not C++ I think.
Brian Postow
Yes - but why is that a problem? Objective-C is a strict superset of C, and as you can see you can easily wrap Objective-C code in a C function and then use it from C code. An fact, Apple do this for some of their C APIs - wrap C functions around an Objective-C implementation.
Christian Brunschen
Autoreleasing an object will cause Console spam and a memory leak (a pretty significant one, in this case) when there is no autorelease pool.
Peter Hosey
+2  A: 

This seems to work and produce not-all-black output. There may be a way to do it that doesn't involve a manual conversion to grayscale first, but at least it works!

static void WriteCCITTTiffWithCGImage_URL_(CGImageRef im, CFURLRef url) {
    // produce grayscale image
    CGImageRef grayscaleImage;
    {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
        CGContextRef bitmapCtx = CGBitmapContextCreate(NULL, CGImageGetWidth(im), CGImageGetHeight(im), 8, 0, colorSpace, kCGImageAlphaNone);
        CGContextDrawImage(bitmapCtx, CGRectMake(0,0,CGImageGetWidth(im), CGImageGetHeight(im)), im);
        grayscaleImage = CGBitmapContextCreateImage(bitmapCtx);
        CFRelease(bitmapCtx);
        CFRelease(colorSpace);
    }

    // generate options for ImageIO. Man this sucks in C.
    CFMutableDictionaryRef options = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    {
        {
            CFMutableDictionaryRef tiffOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            int fourInt = 4;
            CFNumberRef fourNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fourInt);
            CFDictionarySetValue(tiffOptions, kCGImagePropertyTIFFCompression, fourNumber);
            CFRelease(fourNumber);

            CFDictionarySetValue(options, kCGImagePropertyTIFFDictionary, tiffOptions);

            CFRelease(tiffOptions);                
        }

        {
            int oneInt = 1;
            CFNumberRef oneNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &oneInt);

            CFDictionarySetValue(options, kCGImagePropertyDepth, oneNumber);

            CFRelease(oneNumber);
        }
    }

    // write file
    CGImageDestinationRef idst = CGImageDestinationCreateWithURL(url, kUTTypeTIFF, 1, NULL);
    CGImageDestinationAddImage(idst, grayscaleImage, options);
    CGImageDestinationFinalize(idst);

    // clean up
    CFRelease(idst);
    CFRelease(options);
    CFRelease(grayscaleImage);
}


Nepheli:tmp ken$ tiffutil -info /tmp/output.tiff 
Directory at 0x1200
  Image Width: 842 Image Length: 562
  Bits/Sample: 1
  Sample Format: unsigned integer
  Compression Scheme: CCITT Group 4 facsimile encoding
  Photometric Interpretation: "min-is-black"
  Orientation: row 0 top, col 0 lhs
  Samples/Pixel: 1
  Number of Strips: 1
  Planar Configuration: Not planar
Is that Objective C or C++? It looks like a wierd combination. In C++ I don't think that compression 4 works. It gives uncompressed...
Brian Postow
The language you write in is not going to affect the behavior of the APIs. This is Objective-C. I'll rewrite it as pure C for you.
Okay, edited to pure C. It should compile as C++.CGImageDestination covers libtiff and passes the compression through unmolested. I found that I had to explicitly pass depth == 1 and a grayscale image to get it to work. If you google for libtiff and "CCITT Group 4" you'll see some people asking questions about it.
int fourInt = 4;I would change this to something like int comprType = NSTIFFCompressionLZW; It's much more readable.
Matthieu Cormier
Matthieu Cormier: If the questioner is not using Cocoa, then that constant is not available.
Peter Hosey
I'm using a version of this to compress an image and it works, but it produces a reversed image (i.e., black is white and vice versa). Any idea as to why?
Schnapple