Search code examples
iosobjective-cjbig2

iOS Objective-C - Render JBIG2 image format


JBIG2 images have been supported in PDFs since PDF specification 1.4. Therefore all PDF reader applications can read JBIG2 images. I can confirm that a PDF containing JBIG2 images is correctly rendered on iPhone & iPad.

What I want to do is to render (or convert to PNG) a JBIG2 image from Objective-C without it having to be inside a PDF. JBIG2 images are stored inside PDF files just as a normal image object, in their own JBIG2 raw format (no conversion of any kind) so it is obvious that somewhere in iOS there is a JBIG2 decoder library, else these could not be decoded.

So how can I render a JBIG2 image on iOS without that image being inside a PDF wrapper? It's exactly the same data that exists inside that PDF image object, so it would use exactly the same decoder.

It would be a massive waste of resources to add a tiny little PDF wrapper around the JBIG2 image just to be able to render it out. This JBIG2 decoder must exist somewhere already in iOS, so how to use it?

UPDATE

If the JBIG2 decoder is not available natively in iOS then that would mean PDF readers are using their own... in this case it should be possible to rip the decoder out of an open-source PDF reader.

Here is an example PDF containing JBIG2s and raw JIBG2s: http://www.filedropper.com/jbig2samples


Solution

  • First of all you're right that the native iOS (and Mac OS) frameworks do support JBIG2 images embedded in PDF data streams—actually it's part of Core Graphics.

    The public API to read images in iOS is ImageIO. It extends Core Graphics by adding generic image file reading and writing functions. It creates CGImage objects that can be used in CGContexts to decompress and render. Sadly it is not able to read jbig2 image files.

    On the other hand a PDF containing JBIG2 images can be rendered. That seems to be possible by Core Graphics adding custom filters to CGImage which are used only when rendering PDFs:

    > cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/Frameworks
    > nm -arch armv7 ./CoreGraphics.framework/CoreGraphics | grep jbig2
    000f4f6c t _jbig2_create_state
    00081e68 t _jbig2_filter_finalize
    00081e44 t _jbig2_filter_refill
    00081e24 t _jbig2_filter_rewind
    000f500c t _jbig2_read_bytes
    000f4fc0 t _jbig2_release_state
    000f5064 t _jbig2_rewind
    0013b78c b _jbig2_vtable
    00081d9c t _pdf_source_create_jbig2_filter
    001247f0 s _pdf_source_create_jbig2_filter.callbacks
    

    Displaying a PDF in Preview while running Instruments reveals the library where JBIG2 support is implemented:

    Instruments analyzing Preview.app

    Here's the actual library:

    > nm -arch armv7 ./CoreGraphics.framework/Resources/libJBIG2.dylib | c++filt
    ...
    00001f68 unsigned short JBIG2Bitmap::JBIG2Bitmap(unsigned int, JBIG2Bitmap*)
    00007adc unsigned short JBIG2Stream::readGenericBitmap(int, int, int, int, int, int, JBIG2Bitmap*, int*, int*, int)
    ...
    

    This library seems to include some xpdf-3 code but is mostly Apple's private implementation. There are no headers for this library, so it's to be considered private, especially on iOS.

    That leaves us with only one option of how to use iOS native JBIG2 decompression: You have to wrap JBIG2 files into a minimal PDF. I don't think that the runtime overhead is relevant.

    Addition to illustrate comment: Code to create an image from a PDF. This assumes that the PDF consists of one page that contains the JBIG2 image borderless in 72 dpi.

    // create PDF document
    CGPDFDocumentRef document = CGPDFDocumentCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path]);
    
    // get the first page
    CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
    
    // create a bitmap context
    CGSize size = CGPDFPageGetBoxRect(page, kCGPDFMediaBox).size;
    UIGraphicsBeginImageContextWithOptions(size, YES, 1);
    
    // flip the context
    CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, mediaBox.size.height);
    CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1, -1);
    
    // draw the page into the bitmap context
    CGContextDrawPDFPage(UIGraphicsGetCurrentContext(), page);
    CGPDFDocumentRelease(document);
    
    // get the image from the context
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // save image as PNG file
    [UIImagePNGRepresentation(image) writeToFile:somePath atomically:YES];