When building applications for iOS, the iphoneos-optimize script from the Xcode set is used to optimize resources. It works fine, but if you dig deeper, it becomes clear that some files are not pinched, while others are slightly smaller, but still far from ideal. We can say that the task of the script is to make the files more compatible with the iPhone, so that they can be read or unpacked faster, but most likely it made sense only on old iPhones 1 and others like them, and already on 1GHz processors with ARM 7 this is clearly not relevant.
Using simple optimizations and a couple of programs from the MacPorts suite, you can achieve a significant reduction in PNG and JPG images in the final program, and, if desired, other types of data.
Intelligence service
First, let's see what the original iphoneos-optimize is. This is a small Perl script that lies in the /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ folder. For Xcode 4.3 and higher, this will be the folder /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/
The main part of the script is quite simple and well readable:
print "$SCRIPT_NAME: Converting plists to binary in $dstroot\n"; find( { wanted => \&optimizePlists }, $dstroot ); exit(0) if defined $options and $options =~ /-skip-PNGs/; print "$SCRIPT_NAME: Optimizing PNGs in $dstroot\n"; find( { wanted => \&optimizePNGs }, $dstroot );
All files in dstroot are searched and sent to the optimizePlists and OptimizePNGs functions. To optimize PNG, use the modified Apple version of the pngcrunch with the iphone key:
')
my @args = ( $PNGCRUSH, "-iphone", "-f", "0", $name, $crushedname ); if (system(@args) != 0) { print STDERR "$SCRIPT_NAME: Unable to convert $name to an optimized png!\n"; return; } unlink $name or die "Unable to delete original file: $name"; rename($crushedname, $name) or die "Unable to rename $crushedname to $name";
Pngcrush vs OptiPNG
What exactly does the-iphone key do not recognize us, but by and large it is not so important. Upon closer inspection, it is clear that if pngcrush returned an error or could not shrink the file, the temporary file is deleted, but the main file remains unchanged. In my case this was the case with a certain browser.png file:
Recompressing browser.png
Total length of data found in IDAT chunks = 433949
IDAT length with method 120 (fm 1 zl 9 zs 1) = 512501
Best pngcrush method = 120 (fm 1 zl 9 zs 1) for browser_iphone.png
(18.10% IDAT increase)
(18.11% filesize increase)
CPU time used = 2.569 seconds (decoding 0.040,
encoding 2.490, other 0.039 seconds)
Without the -iphone switch, the situation was better and the file did decrease:
Recompressing browser.png
Total length of data found in IDAT chunks = 433949
IDAT length with method 1 (fm 0 zl 4 zs 0) = 740769
IDAT length with method 2 (fm 1 zl 4 zs 0) = 611778
IDAT length with method 3 (fm 5 zl 4 zs 1) = 485419
IDAT length with method 9 (fm 5 zl 2 zs 2) = 743935
IDAT length with method 10 (fm 5 zl 9 zs 1) = 427514
Best pngcrush method = 10 (fm 5 zl 9 zs 1) for browser_tmp.png
(1.48% IDAT reduction)
(1.47% filesize reduction)
CPU time used = 3.949 seconds (decoding 0.176,
encoding 3.766, other 0.007 seconds)
But there is another way - GPL
optipng utility. From MacPorts, it installs without problems with the command
sudo port install optipng
Here is the result of the utility with the same browser.png:
** Processing: browser_opti.png
1024x1024 pixels, 4x8 bits/pixel, RGB+alpha
Input IDAT size = 433949 bytes
Input file size = 434043 bytes
Trying:
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 427390
Selecting parameters:
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 427390
Output IDAT size = 427390 bytes (6559 bytes decrease)
Output file size = 427484 bytes (6559 bytes = 1.51% decrease)
As you can see, the file size is even smaller than that of pngcrush, and the speed of operation is much higher. In some other cases, the gap is even more noticeable.
The most important thing is that the PNG files obtained are perfectly open on the iPhone and iPad, there are no visual distortions in them, the difference in opening speed is missing or not noticeable.
Integrating optipng into a script is quite simple: in the header of the script we change the variable indicating the PNG optimizer and the path to it:
my $PNGCRUSH_NAME = "optipng"; my $PNGCRUSH = "/opt/local/bin/$PNGCRUSH_NAME";
In the body of the optimizePNGs function, only the string with parameters is changed. I got the optimal result with the -o2 and -f0 parameters:
my @args = ( $PNGCRUSH, "-o2", "-f0", $name, "-out", $crushedname );
Of course, we must not forget to backup the script, and at the same time have administrator rights to edit it.
Jpg optimization
JPEG files often contain EXIF information, and sometimes various garbage not needed on the phone. There is also a difference when using progressive mode and other settings. The most convenient way is to use the jpegoptim utility, which itself discards the unnecessary, optimizes the Huffman tables and selects the optimal settings for the same quality level. If desired, you can set the parameters so that the utility reduces the quality of the image, then it will be compressed again with the specified quality. You can also install it from MacPorts:
sudo port install jpegoptim
It remains only to add a call to this program in iphoneos-optimize.
In the title:
my $JPGCRUSH_NAME = "jpegoptim"; my $JPGCRUSH = "/opt/local/bin/$JPGCRUSH_NAME";
In body:
print "$SCRIPT_NAME: Optimizing jpgs in $dstroot\n"; find( { wanted => \&optimizeJPGs }, $dstroot );
New feature:
sub optimizeJPGs { my $name = $File::Find::name; if ( -f $name && $name =~ /^(.*)\.jpg$/i) { my @args = ( $JPGCRUSH, "--strip-all", $name ); if (system(@args) != 0) { print STDERR "$SCRIPT_NAME: Unable to convert $name to an optimized jpg!\n"; return; } print "$SCRIPT_NAME: Optimized JPG: $name\n"; } }
I deliberately did not repeat the result of the program, because in case of failure of optimization, it simply does not change the source file.
The utility works very quickly and displays a minimum of information:
jpegoptim --strip-all english.jpg
english.jpg 320x480 24bit JFIF [OK] 66466 --> 59686 bytes (10.20%), optimized.
Now it is enough to rebuild the project in XCode and the process of optimizing resources with iphoneos-optimize will go much faster, and the result will be 10-15% less.
Other optimizations
Additionally, you can add other extensions to the script (JPEG, JFIF, JPE, JIF, etc.), add a reduction in quality, etc. Anyway, there are enough different optimizers PNG, JPEG, CAF and other files that can be used on the network. For example, SQLite databases can be optimized using the following command:
sqlite3 database.sqlite vacuum;
The team will re-create the database with all the data, discarding various garbage, remnants of old transactions, etc. The integration of this and other commands in the script will be left to the discretion of the reader.
In fact, there are at least two interesting methods for reducing the size of PNG files (if you tilt the other optimizers and manually simplify the drawing). The first is not entirely canonical and can be perceived as heresy, but fact is a fact: the Android compiler (or rather, apktool) can track pictures with a small number of colors and translate them into the Paletted format. Moreover, even full-color PNGs can become even smaller. It is enough to add the necessary images to the res / drawable folder of the working project, to collect it and then write off the optimized files from the bin / res / drawable folder. Of course, this requires some skills with the Android SDK and does not really relate to iOS development.
The second method is more universal: reduce the color gamut of a file from RGBA8888 to RGB565 or RGBA4444. At the same time, the size can both grow due to dithering, and significantly decrease in the case of hand-drawn images. For these operations, I wrote my own console utility, but its consideration is beyond the scope of this article.
PS The text of the finished script iphoneos-optimize:
paste2.org/p/2045147