PNG Images

From The Apple Wiki

To optimize for the native pixel format of the iPhone's early PowerVR GPUs, Apple implemented a non-standard PNG format where the red and blue pixels are flipped (BGRA instead of RGBA). The format additionally includes extra data before the PNG header, and compressed image data without the traditional headers and footers. All iPhone PNG images appear to follow this format.

The PNG format consists of a header, followed by a set of data atoms, or chunks. According to the PNG spec, the 'IHDR', or PNG header chunk, should always come first. In Apple's iPhone format, a 'CgBI' chunk appears before the header. This chunk's data is four bytes long and contains a value of 0x40 (48 decimal) and is marked mandatory and private, which means that the data contained in the 'CgBI' chunk is a third party extension to the PNG format that must be implemented by the parser. The purpose of this chunk, other than to signify that the PNG in iPhone format, is unknown. It could be a format version identifier.

Compressed image data, stored in the 'IDAT' chunk, contains DEFLATE-compressed data without the zlib headers, footers, or checksums that normal PNGs contain. When using zlib to decompress data, a negative value must be passed as the windowSize to use zlib's undocumented 'skip headers and crc' feature. There does not appear to be a good technical reason for using this format instead of the standard.

Practically speaking, there is no reason to use the proprietary format on modern Apple devices, as the performance benefit is imperceptible. More benefit can be found by optimizing PNGs for file size using optimizing tools. The format is supported on iOS and macOS only in apps that use CoreGraphics for image decoding.

Differences from PNG

These modifications cause the generated images to be invalid as per the current version of the PNG standard.

  • extra critical chunk (CgBI)
  • byteswapped (RGBA -> BGRA) pixel data, presumably for high-speed direct blitting to the framebuffer
  • zlib header, footer, and CRC removed from the IDAT chunk
  • premultiplied alpha (color' = color * alpha / 255)

To account for or apply these modifications, libpng needs to be patched to accept the "invalid" zlib compression window (a 15-bit window is the default, supplying -15 to zlib will cause it to omit headers/footers/CRC info).

CgBI Chunk

CgBI (likely an abbreviation of CoreGraphics Bitmap Info) is a four-byte critical chunk added to the beginning of all "optimized" PNG files.

Known Contents

  • 0x50 0x00 0x20 0x06
  • 0x50 0x00 0x20 0x02 (seen if input image was already RGBA)

At least the lower parts of the content correspond to the documented CGBitmapInfo bitmask.

Converting to a standard PNG

To convert an iPhone PNG to a standard PNG:

  1. Remove the 'CgBI' chunk
  2. Decompress the data using zlib with the windowSize set to a negative number
  3. Swap the red pixel with the blue pixel in the data
  4. Recompress the data using zlib using the default headers and CRC
  5. Replace the image data chunk with the new compressed data in the PNG, and create a new checksum.

Implementations

Apple includes a modified version of pngcrush with the iOS SDK that adds two additional arguments to convert PNGs to (--iphone) and from (--revert-iphone-optimizations) this modified format.

The format was also reverse-engineered, and third parties have created their own utilities to convert between formats:

  • Encoding:
    • Apple's modified pngcrush (xcrun -sdk iphoneos pngcrush -iphone)
    • pincrush
  • Decoding:
    • Apple's modified pngcrush (xcrun -sdk iphoneos pngcrush -revert-iphone-optimizations)
    • ipin.py ("iPhone PNG Image Normalizer")

Links