- PVSM.RU - https://www.pvsm.ru -

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

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

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

Для разделения я использую консольные утилиты ImageMagik [4].
Эта команда отделяет альфа-канал:
   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 [5].
С JPEG ситуация интереснее. Можно было бы ограничиться только заданием качества, но недавно мне попалась замечательная утилита jpegrescan от Loren Merritt, одного из разработчиков ffmpeg и кодировщика x264 (на счет него же есть подозрения [6], что он является представителем инопланетного разума или кибернетического мозга [7]).
Утилита использует необычный подход: подбирает разные коэффициенты для Progressive сжатия и выбирает наиболее оптимальные. Выигрышь получается от 5 до 15% с идентичным качеством картинки. Собственной страницы у утилиты нет, только топик [8] с обсуждением и сам perl-код: pastebin.com/f78dbc4bc [9]

Чтобы не вводить команды каждый раз вручную, я написал простенький скрипт на 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

Вот результат работы скрипта:
Используем JPEG с прозрачностью
В моем случае из файла в 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().

Профит

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

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

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

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

P.S. ImageMagick и optipng ставятся из портов (MacPorts/Fink/Brew) и называются так же. Скрипт jpegrescan качается с pastebin [9] и использует порт jpeg

Автор: Nomad1


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/png/15863

Ссылки в тексте:

[1] PaulZi: http://habrahabr.ru/users/paulzi/

[2] предложил: http://habrahabr.ru/post/146804/

[3] предлагает: http://jim.studt.net/jpeg-alpha/

[4] ImageMagik: http://www.imagemagick.org/script/index.php

[5] www.tinypng.org: http://tinypng.org/

[6] есть подозрения: http://www.x264.nl/developers/Dark_Shikari/loren.html

[7] мозга: http://www.braintools.ru

[8] топик: http://news.ycombinator.com/item?id=803839

[9] pastebin.com/f78dbc4bc: http://pastebin.com/f78dbc4bc