При сборке приложений под iOS для оптимизации ресурсов используется скрипт iphoneos-optimize из набора XCode. Работает он отлично, но если копнуть поглубже, то становится ясно, что некоторые файлы не пережимаются, а другие хоть и немного уменьшаются, но все-равно далеки от идеала. Можно сказать, что задача скрипта сделать файлы более совместимыми с iPhone, чтобы они быстрее читались или распаковывались, но скорее всего это имело смысл лишь на старых iPhone 1 и иже с ними, а уже на процессорах 1ГГц с ARM 7 это откровенно не актуально.
С помощью простых оптимизаций и парочки программ из набора MacPorts можно добиться существенного уменьшения PNG и JPG картинок в конечной программе, а при желании и других видов данных.
Разведка
Для начала посмотрим, что собой представляет оригинальный iphoneos-optimize. Это небольшой Perl скрипт, который лежит в папке /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/. Для XCode 4.3 и выше это будет папка /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/
Основная часть скрипта довольно проста и хорошо читабельна:
print "$SCRIPT_NAME: Converting plists to binary in $dstrootn";
find( { wanted => &optimizePlists }, $dstroot );
exit(0) if defined $options and $options =~ /-skip-PNGs/;
print "$SCRIPT_NAME: Optimizing PNGs in $dstrootn";
find( { wanted => &optimizePNGs }, $dstroot );
Тут перебираются все файлы в dstroot и отправляются на вход функциям optimizePlists и OptimizePNGs. Для оптимизации PNG используется модифицированная Apple версия pngcrunch c ключом iphone:
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
Что именно делает ключ -iphone нам не узнать, но по большому счету это и не так важно. При детальном рассмотрении видно, что если pngcrush вернул ошибку или не смог уменьшить файл, то временный файл удаляется, а основной остается без изменений. В моем случае так происходило с неким файлом browser.png:
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)
Без ключа -iphone ситуация была получше и файл таки уменьшался:
CPU time used = 3.949 seconds (decoding 0.176, 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)
encoding 3.766, other 0.007 seconds)
Но есть еще и другой путь — GPL утилита optipng. Из MacPorts она устанавливается без проблем командой
sudo port install optipng
Вот результат работы утилиты с тем же самым 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)
Как видно, размер файла стал еще меньше, чем у pngcrush, а скорость работы при этом заметно выше. В некоторых других случаях разрыв еще более заметен.
Важнее всего то, что полученные PNG файлы отлично открываются на iPhone и iPad, никаких визуальных искажений в них нет, разница в скорости открытия отсутствует или не заметна.
Интегрировать optipng в скрипт довольно просто: в шапке скрипта мы меняем переменную с указанием на PNG оптимизатор и путь к нему:
my $PNGCRUSH_NAME = "optipng";
my $PNGCRUSH = "/opt/local/bin/$PNGCRUSH_NAME";
В теле функции optimizePNGs меняется только строка с параметрами. Оптимальный результат я получил с параметрами -o2 и -f0:
my @args = ( $PNGCRUSH, "-o2", "-f0", $name, "-out", $crushedname );
Конечно, надо не забывать делать бекап скрипта, а заодно иметь права администратора на его редактирование.
Оптимизация JPG
JPEG файлы часто содержат EXIF информацию, а порой и различный мусор, не нужный на телефоне. Также есть разница при использовании прогрессивного режима и других настроек. Удобнее всего воспользоваться утилитой jpegoptim, которая сама откинет ненужное, оптимизирует таблицы Хафмана и выберет оптимальные настройки при том же уровне качества. При желании, можно задать параметры, чтобы утилита уменьшила качество изображения, тогда оно будет сжато заново с указанным качеством. Устанавливать ее тоже можно из MacPorts:
sudo port install jpegoptim
Остается только добавить вызов этой программы в iphoneos-optimize.
В заголовке:
my $JPGCRUSH_NAME = "jpegoptim";
my $JPGCRUSH = "/opt/local/bin/$JPGCRUSH_NAME";
В теле:
print "$SCRIPT_NAME: Optimizing jpgs in $dstrootn";
find( { wanted => &optimizeJPGs }, $dstroot );
Новая функция:
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: $namen";
}
}
Я сознательно не стал повторять результат выполнения программы, потому что в случае неудачи оптимизации она просто не изменяет исходный файл.
Работает утилита очень быстро и выводит минимум информации:
jpegoptim --strip-all english.jpg
english.jpg 320x480 24bit JFIF [OK] 66466 --> 59686 bytes (10.20%), optimized.
Теперь достаточно пересобрать проект в XCode и процесс оптимизации ресурсов с iphoneos-optimize пройдет заметно быстрее, а результат будет на 10-15% меньше.
Другие оптимизации
Дополнительно можно дописать в скрипт другие расширения (JPEG, JFIF, JPE, JIF, etc.), добавить уменьшение качества и пр. Да и вообще, в сети достаточно разных оптимизаторов PNG, JPEG, CAF и других файлов, которые можно было бы использовать. К примеру, базы SQLite можно оптимизировать с помощью такой команды:
sqlite3 database.sqlite vacuum;
Команда пересоздаст базу со всеми данными, выбросив различный мусор, остатки старых транзакций и т.п. Интеграцию этой и других команд в скрипт оставим на усмотрение читателя.
На самом деле, есть еще как минимум два интересных метода уменьшения размера PNG файлов (если откинуть другие программы-оптимизаторы и ручное упрощение рисунка). Первый не совсем канонический и может быть воспринят как ересь, но факт есть факт: компилятор Android (точнее apktool) умеет отслеживать картинки с малым количеством цветов и переводить их в Paletted формат. Более того, даже полноцветные PNG могут стать еще меньше. Достаточно добавить нужные изображения в папку res/drawable рабочего проекта, собрать его и затем списать оптимизированные файлы из папки bin/res/drawable. Конечно, это требует некоторых навыков работы с Android SDK и не совсем относится к разработке под iOS.
Второй метод более универсальный: уменьшить цветовой охват файла с RGBA8888 до RGB565 или RGBA4444. При этом размер может как вырасти из-за dithering, так и заметно уменьшится в случае с рисованными изображениями. Для этих операция я написал собственную консольную утилиту, но ее рассмотрение лежит уже за пределами этой статьи.
P.S. Текст готового скрипта iphoneos-optimize: paste2.org/p/2045147
Автор: Nomad1