A trivial problem caused by CgBI when parsing PNG images in Node.JS

Prologue

When I was dealing with some PNG images using NodeJS and node-canvas (v2.6.1 as of the time of this post), I encountered a fatal error when reading some PNG files into memory:

load [filename] image error: Error: out of memorynode canvas fatal error: v8::tolocalchecked empty maybelocal.

I Googled this bug, and found an issue post) dating back to 2012, but it is not quite related to the error I encountered. As this error was not present for every single PNG that I had used as source, I decided to try another npm image processor, sharp, and see if this bug persisted. To my surprise, this error did not go away with the specific PNG file that caused problem for node-canvas, and sharp also threw an error.

Locating the problem

When both node-canvas and sharp had failed with exactly the same file but not others, I figured that there might be an beyond the specific png file that I used. Thankfully, sharp reported a more useful error message:

Intermittent Error: Input file has corrupt header.

It seems that this issue might be a problem with PNG’s headers. As I was working on a Mac, and this image was correctly displayed both by Preview.app and as Finder thumbnail, I wondered if this PNG was readable across platform, i.e., if it was encoded correctly.

Failure spotted by pngcheck

After some Googling, I found a useful command line tool named pngcheck, and I installed it with Homebrew:

brew install pngcheck

pngcheck was pretty straightforward to use, and as I wanted detailed output, I used -t (“print contents of tEXt chunks”) and -vv (“test very verbosely (decode & print line filters)”) options:

pngcheck -tvv artwork.png

The result was instantly showing the problem:

File: artwork.png (230109 bytes)
  chunk CgBI at offset 0x0000c, length 4:   first chunk must be IHDR
ERRORS DETECTED in artwork.png

Great, keywords discovered: CgBI and IHDR. It appears that this PNG that I was using did begin with an IHDR header as the first chunk defined by PNG’s documentation.

CgBI: Apple’s PNG-like image format for iOS

Another quick Google search gave me the answer: according to this file format wiki, Apple uses a CgBI header in its PNG conventions so as to optimize PNG display on its iOS devices. As this was not a standard header, the standard png library (libpng) used by node-canvas and sharp could not successfuly parse the Stream of CgBI-headed png.

Solution: converting CgBI to standard PNG

The work that remained was basically converting my original input file into a standard PNG – by trimming the CgBI binary chunk present in the file.

I found a handy piece of code (npm package) named cgbi-to-png that handles this conversion process nicely.

Note: Apparently, the conversion process done by cgbi-to-png is synchronous.