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.