25 September 2012

Используем JPEG с прозрачностью

Development for iOSImage processing
Конечно же, формат JPEG не поддерживает прозрачность, но сама идея использовать JPEG вместо PNG для прозрачных текстур будоражит умы довольно давно. Камрад PaulZi не так давно предложил использовать для HTML формат SVG, в котором хранится само изображение и маска. Jim Studt предлагает использовать EXIF поля в JPEG и хранить там маски, а отображать на веб-странице с помощью Canvas.
Оба метода относительно сложны для использования, да и рассчитаны на веб, потому я остановился на самом простом варианте: хранить отдельно lossy JPEG для RGB и lossless маску в PNG, а совмещать их на этапе получения UIImage в программе. Сразу хочу сказать, что пишу на MonoTouch, потому код привожу на C#, хотя в ObjC это делается почти точно так же, с учетом синтаксиса.


Разделение каналов


Для разделения я использую консольные утилиты ImageMagik.
Эта команда отделяет альфа-канал:
convert file.png -channel Alpha -separate file.mask.png

Следующая команда создает JPEG файл, отбрасывая данные о прозрачности. Что характерно, некоторые другие утилиты (в т.ч. и Photoshop) при конверсии PNG файла в JPEG добавляют к нему некую одноцветную подложку и лишь затем сохраняют в RGB, что дает красивую, но неверную картинку с pre-multiplied alpha.
convert file.png -quality 90 -alpha off file.jpg

Качество полученного файла регурируется параметром quality 90. 90% качества для JPEG это больше, чем Apple ставит для скриншотов программ и фильмов. Думаю, каждый сможет подстроить под свой вкус это значение.

Оптимизация


Маска получается в виде 8-битного Grayscale PNG без прозрачности. Такой формат отлично сжимается через optipng или через веб-сайт www.tinypng.org.
С JPEG ситуация интереснее. Можно было бы ограничиться только заданием качества, но недавно мне попалась замечательная утилита jpegrescan от Loren Merritt, одного из разработчиков ffmpeg и кодировщика x264 (на счет него же есть подозрения, что он является представителем инопланетного разума или кибернетического мозга).
Утилита использует необычный подход: подбирает разные коэффициенты для Progressive сжатия и выбирает наиболее оптимальные. Выигрыш получается от 5 до 15% с идентичным качеством картинки. Собственной страницы у утилиты нет, только топик с обсуждением и сам perl-код: pastebin.com/f78dbc4bc

Чтобы не вводить команды каждый раз вручную, я написал простенький скрипт на bash:
#!/bin/bash
basefile=${1##*/}
maskfile="${basefile%.*}.mask.png"
jpegfile="${basefile%.*}.jpg"
convert $1 -channel Alpha -separate $maskfile
convert $1 -quality 90 -alpha off $jpegfile
optipng $maskfile
jpegrescan $jpegfile $jpegfile


Вот результат работы скрипта:

В моем случае из файла в 1,8Мб получилось два файла на 380Кб и 35Кб.

Склеивание


Само склеивание делается очень просто — загружается две картинки в UIImage, затем создается на их основе CGImage методом WithMask (в ObjC это CGImageRef и initWithMask соответственно), а потом оборачивается в новый UIImage.
		UIImage result;
		using(UIImage uiimage = UIImage.FromFile(file))
		using(UIImage mask = UIImage.FromFile(maskFile))
		{
			CGImage image = uiimage.CGImage;
			image = image.WithMask(mask.CGImage);
			result = UIImage.FromImage(image, uiimage.CurrentScale, uiimage.Orientation);
		}


В реальном проекте я сделал чуть сложнее и проверяю наличие файла *.mask.png и если он отсутствует, то возвращаю обычный UIImage.FromFile().

Профит




Визуально игра не изменилась никак. Задержка загрузки и склеивания текстур на глаз не заметна, потому я и не стал ее замерять. Сам же проект уменьшился на 6 (!!) мегабайт, как в .ipa виде, так и в iTunes и на устройстве.



Скрин из игры в PNG. Никаких артефактов или проблем сжатия/прозрачности не видно.
Немного смущает удвоенное количество картинок в папке проекта, но это можно пережить. Изменения кода минимальные. Для графики интерфейса этот метод не идеален из-за необходимости вручную присваивать UIImage, а не загружать из NIB/XIB, но для собственных контролов или текстур подходит вполне. Даже если JPEG сохранять с 100% качеством, размер полученных файлов может быть меньше, чем изначального PNG без потерь качества.

P.S. ImageMagick и optipng ставятся из портов (MacPorts/Fink/Brew) и называются так же. Скрипт jpegrescan качается с pastebin и использует порт jpeg
Tags:jpegpngtransparencyпрозрачностьальфаmonotouchuiimageoptipng
Hubs: Development for iOS Image processing
+38
32.4k 167
Comments 34
Popular right now
C/C++ Developer, Digital image processing
from 2,000 to 2,500 $Almalence, Inc.Новосибирск
IOS Developer
from 180,000 to 220,000 ₽БастионМосква
Senior iOS Developer (REMOTE)
to 4,500 $HyprrRemote job
Senior iOS Developer
from 3,000 to 3,500 €Pure AppRemote job
IOS-разработчик
from 190,000 to 300,000 ₽ENJOY PROСанкт-ПетербургRemote job
Top of the last 24 hours