Оптимизация ресурсов iOS приложений

При сборке приложений под 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 $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 );


Тут перебираются все файлы в 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 ситуация была получше и файл таки уменьшался:

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)


Но есть еще и другой путь — 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 $dstroot\n";
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: $name\n";
	}
}

Я сознательно не стал повторять результат выполнения программы, потому что в случае неудачи оптимизации она просто не изменяет исходный файл.
Работает утилита очень быстро и выводит минимум информации:

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
Поделиться публикацией

Комментарии 16

    0
    А стоит ли овчинка выделки? Сколько места удалось сэкономить на реальном приложении?
      0
      Все зависит от проекта и контента. Если у вас порядка 1мб картинок GUI, то и родной pngcrush, и optipng дадут порядка 1-2% оптимизации, т.е. 10-20кб. Но если речь об игре на 20+мб, где есть и текстуры, и музыка, и сплеш-скрины в JPG, то пару мегабайт можно выиграть. Тут уже каждому стоит самому решать что в скрипт писать и какой контент оптимизировать.

      У меня был случай, когда для моб. проекта музыку выдавали в MP3, сжатом c CBR 320кбит, стерео (160кбит на канал). Простейшая команда
      ffmpeg -i music.mp3 -ac 1 music_tmp.mp3
      делает из нее 64кбит VBR моно, который в 5 раз меньше, а из динамика телефона звучит точно так же. Конечно, это крайний случай и я не призываю так ужимать музыку, но если политика студии позволяет, то вполне можно подбную конверсию писать в iphoneos-optimize, а в проекте хранить в исходном качестве.
        +1
        А ничего, что музыку на телефоне многие слушают в наушниках?
        +1
        Дело в том, что приложение по 3G на iOS можно загрузить только до 20Мб. И если у вас, скажем, игра 100Мб — то вам все равно. А вот если она 23-25Мб — то начинаются танцы с бубном в борьбе за каждые 100 байт. Просто многие, если не могут скачать сейчас — не скачают никогда.

        Сам в скриптах не копался, просто подготавливал ресурсы и прогонял их через оптимизаторы для наших игр. Каково же было мое удивление, когда я отключил встроенный оптимизатор XCodе и игра стала весить не 22 а 18Мб!
          +1
          Сейчас ограничение на размер приложения в 20Мб сместили до 50Мб.
          Стало немного вольготнее. Но все равно не стоит злоупотреблять размерами приложения. ИМХО чем меньше приложение тем лучше, не в ущерб качеству конечно.
            0
            Это точно. PC стали вместительнее, и вместе с этим стал расти софт непропорционально сложности — все перестали экономить место, и винт в 120 Гб, казавшийся N лет назад жутко большим, уже можно выбрасывать на свалку. Я потому и пришёл в мобильные приложения, что здесь умеют экономить ресурсы.
              0
              это из-за ретина-ipad. Увеличение допустимого размера произошло одновременно с его выходом. И не зря. Размер приложения увеличивается как раз раза в 2 с его поддержкой
          +6
          imageoptim.com/ — оптимизация картинок без консольки
            0
            Отличная программа! Тоже рекомендую! Особенно в ней хорошо, что можно перетащить хоть весь проект и она там все «наоптимизирует» и положит все там, где взяла. Никаких копирований, замен файлов и т.д. Только, как писал выше, нужно встроенный оптимизатор отключать, а то толку будет меньше.
              0
              Спасибо! Эта программа отлично подошла для оптимизации 945-ти картинок в проекте на Corona SDK, не потребовалось никаких дополнительных настроек. Очень рекомендую.
                0
                Не пробовали зашивать это все дело в спрайтшиты? Мне такой подход позволил значительно сократить размер приложения и расход видеопамяти.
                  0
                  Спрайтшиты очень помогают.
                  Для создани рекомендую использовать www.codeandweb.com/texturepacker
                  Поддерживается многоми игровыми движками
                  Позволяет довольно неплохо уплотнять текстуры и сохранять их в разных форматах.
              +1
              >>CBR 320кбит, стерео (160кбит на канал).
              >>Простейшая команда делает из нее 64кбит VBR моно
              Лёгким движением руки брюки превращаются…

              Есть такие странные люди, которые вставляют в уши такие маленькие затычки с проводками.
                0
                1. >> Конечно, это крайний случай и я не призываю так ужимать музыку

                2. минимальное изменение и команда
                ffmpeg -i music.mp3 -ab 128 music_tmp.mp3
                дает нам в 2.5 раза уменьшеный файл c VBR 128кбит при сравнимом качестве с оригиналом (CBR 320кбит).

                да и вообще речь не о том. iphone-optimize вызывается самим XCode, потому сам Бог велел туда добавлять свои оптимизации чего-угодно и в каком-угодно виде.
                0
                Разительного эффекта оптимизации можно достигнуть, обнаружив, что настройки проекта на самом таковы, что strip не выполняется. =)

                На больших проектах также помогает --gc-sections.
                  0
                  Тут вот спрашивали о выигрыше с этой оптимизацией. Пересобирал на днях игру (не анонсированный проект, не могу детальнее), размер .IPA файла вышел 38,2Mb — HD версия для iPad 3. Сегодня собрал с скриптом, который на paste2 и получилось 36,5Mb. Все текстуры в pvr.gz, потому оптимизировлись только JPG/PNG файлы интерфейса:
                  image

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое