IOS PNG Errors
Published
XCode corrupts PNG images for iOS builds. This post details the error.
XCode corrupts PNG images on iOS builds
Recently I was packing some data into PNG images to use in mobile applications, and I discovered that XCode changes the RGB values a little. It’s well known that iOS converts images with alpha channels by pre-multiplying the alphas, but this is not issue. This is surprising since PNG is a lossless compression algorithm, and one would expect PNG images to show up pixel perfect when used inside an application.
The error is not pre-multiplication by alpha, or gamma correction, or color space issues - none of these would result in such small (+-1) channel errors.
The problem
XCode changes blue and green pixel values by +- 1 very often when packing RGB images, and extremely rarely does it change red values or change values by more than 1.
Take, for example, this image from the PNG homepage (image here).
Make an XCode iOS app, add this PNG, and then extract the resulting PNG image from the resulting app binary, and compare them pixel by pixel. Here is an image representing the incorrect pixels: black pixels are no error, green pixels have green component either +1 or -1 different from original, blue pixels are similarly blue channel errors, and cyan has both green and blue changed. No red components were changed.
Here is the image extracted from the iOS binary if you want to poke at it.
This is representative of hundreds of images I have tested.
Some notes
- XCode famously uses a proprietary pngcrush derivative, adding their own compression. The console program lets you convert to iOS and back. Using this by hand I have been unable to recreate the issues, so I’m not sure if this is the tool in their build that creates the errors. There are plenty of websites discussing this. Here is a GitHub project with some details.
- Disabling the PNG compression in the build settings does not remove the error.
- This seems to affect XCode apps only on iOS, not when compiled for MacOS.
- It only seems to affect RGB, 8-bits per channel images, not palettized images or grayscale images.
- PNG are reported as compressed with
zip
, ordeepmap2
, orlzfse-deepmap2
.deepmap2
methods are based on Apple proprietary methods,lzfse
is likely a LZ Finite State Entropy based method. It seems the error (or is it on purpose?) is bothdeepmap
methods.
How-to
I discovered this on a project with a M1 Mac, then tested on a friends Mac (Intel?) on a different XCode version. Then, since I was just buying a new Mac, I did the test on a new from box machine with a new XCode download.
Yesterday (Sat, 7/22/2023) I received a new MacBook Pro, 14", 12C CPU, 30C GPU, 32 GB RAM, 1TB SSD, Mac OS Ventura 13.4. I made my account, then did the following to test the issue:
-
Downloaded XCode 14.3.1
-
Create a new iPhone App. I used new organization named “origid”.
-
I added a PNG, selecting the libpng image from above.
-
select assets, import image
-
-
-
Under Product menu, select
Build
, wait till (instantly..) done. -
Under Product menu, select
Show Build Folder in Finder
-
In the opened folder, open folder
Products
thenDebug-iphonesimulator
then right click on the app (mine was named ImageGlitch), and selectShow Package Contents
-
Copy
Assets.car
to Desktop (or wherever) -
To extract the assets, I used AssetCatalogTinkerer from Github.
- I also tried many other asset extractors, all with same results
- Open the saved
Assets.car
, select the extracted image, then export it to some other folder.
-
To compare new and old pixels, I used GIMP 2.10.34 (as well as many other apps)
- Open the original image as a normal file
- Open the other as a layer overlaying the original (or as a new image, but it’s harder to compare)
- Use the color picker, and compare the upper left (0,0) pixel (or just about any other)
- I get the results below: original RGB(227,113,68), after XCode and iOS RGB(227,114,69).
Analysis
To analyze this in a little more depth, I grabbed 70+ RGB PNGs from my hard drive, made an XCode iOS app including them all, extracted them all, and made a tool to aggregate the errors.
The simple tool (C#, here) that parsed the original and retrieved images, tallied all error pixels, sampled 500,000 errors, created diff images, and looked for anomalies.
Quick results:
-
394,736,075 total pixel samples
-
203,567,039 (51.6%) had pixel errors
- One image of size 262k pixels had every one incorrect (radialmap)
- One image of 283k pixels had only 2% errors (N2XcD.png, a low color drawing)
-
Red errors in 0.3% of pixels, green errors in 35% of pixels, blue errors in 34%
- Thus red errors are very rare
- Only 5 images of the 71 had any red error
-
Large (abs value > 1) errors were very rate
- only those 5 with red errors also had errors > 1 in magnitude
Here is a screenshot of all the test images (larger here).
Here are the error map images (larger here):
Here is an Excel screen grab of the total results from above. Here is the file.
The left block is files, sizes, number of error pixels, and basic tallies. The right colored blocks are each case of red, green, and blue combinations, all of this occur. Taking 500,000 sample error pixels uniformly from all errors, and analyzing in Mathematica (notebook here), gives the following:
Each set of graphs is the tally of source color values and destination color values, per channel. Red is all the red pixels from the 500k pixels (none of the rare red errors showed up, so these are from the other errors).
Note that lower colors are more likely to have changes.
Checking if all 256 source and destinations occur, Mathematica shows it to be so.
Finally, I wanted a small image to run through reverse engineering, so I took the upper left corner of the Glow_A.png
image, since it had errors everywhere. Here is a small (24x2) extract that fails (I suspect the top row is not needed, but got tired of fiddling…).
Next steps
Tune in next time when I track down the build-step and executable at fault, reverse engineer it using Ghidra, analyze it to find the math error (or discover if it’s on purpose, another possible outcome, which would be shocking to think they corrupt lossless images on purpose), and binary patch it to be correct.
History
- Initial article, 7/23/2023
Comments
Comment is disabled to avoid unwanted discussions from 'localhost:1313' on your Disqus account...