JPEG сжатие картинки с альфа-каналом или SVG masksПривет! Недавняя статья про сжатие в png-8 с сохранение полупрозрачности, напомнила мне об одной технике, которая позволяет применять на сайтах изображения с альфа-каналом, при этом используя алгоритм сжатия с потерями — JPEG, что позволяет существенно сократить их объём.
Предыстория
Некоторое время назад, когда об HTML5 мало кто знал, а flash был довольно распространён, мне пришлось изучить ActionScript и делать красивые «имиджевые» сайты. Обычно такой сайт был довольно насыщен картинками, и более того, для некоторых эффектов требовалось использовать фотографии с прозрачным фоном (например, когда товарная единица с тенью красиво вылетает откуда-нибудь по неоднородному фону). Но как известно JPEG не позволяет хранить в себе альфа-канал, а PNG — это формат сжатия без потерь, из-за чего размер такого фото получался мягко говоря большим. Во flash же возможно было сохранить полупрозрачные изображения с JPEG-компрессией, достигалось это тем, что альфа-канал сохранялся отдельно от изображения, получая два изображения, которые впоследствии сохранялись в JPEG.
Позже верстая один из «обычных» html-сайтов, мне также потребовалось сохранить большой неоднородный полупрозрачный фон, но ни сохранение в png-32, ни в png-8 не давали приемлемый результат — png-8 выглядел отвратно, а png-32 весил слишком много. В поисках технологии, которая бы позволила сохранять изображения с альфа-каналом с потерей качества, я наткнулся на SVG Mask.
Так для чего это нужно?
Возьмём гипотетический пример — нужна нам вот-такая картинка с «дыркой» внутри:
(PNG-32, 1870 Кбайт)
Сжатие в 256ти-цветную палитру PNG-8 «убивает» изображение:
(PNG-8, 305 Кбайт)
Тогда как применяя flash мы получим swf скромного размера, при этом на глаз мало различимого от оригинала:
flash (swf, 453 Кбайт)
В принципе, если бы не сложности с удобством генерации swf, и не полной поддержки flash на всех устройствах, можно было бы на этом и закончить. Но есть более удобная HTML5 альтернатива!
Суть метода
Сам метод не новый и изредка встречается его описание на некоторых ресурсах. Но к сожалению информации о нём относительно мало, по крайней мере на хабре статьи не нашёл, что спешу поправить.
Стандарт SVG, позволяет внедрять в себя растровые изображения, а также позволяет применять маски. Так почему же нам не реализовать всё так же, как и во flash?
Отделяем альфа-канал и сохраняем его в отдельный файл:
(JPEG, 165/46 Кбайт)
Суммарный объём получился даже меньше, чем .swf — 211 Кбайт!
Пишем SVG:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 240" width="300px" height="240px" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
svg { -webkit-background-clip: text; }
</style>
<defs>
<mask id="mymask">
<image xlink:href="mask.jpg" x="0" y="0" width="300px" height="240px" />
</mask>
</defs>
<image xlink:href="image.jpg" mask="url(#mymask)" x="0" y="0" width="300px" height="240px" />
</svg>
Как вариант можно внедрить файлы прямо в svg (уменьшаем кол-во запросов), с помощью data: URI, правда в этом случае размер возрастёт в среднем на 33%, хотя это можно обойти с помощью gzip-сжатия. Пример.
Как на счёт кроссбраузерности?
В своё время я этот метод оставил на «светлое будущее», т. к. тогда поддержка SVG браузерами была не очень, да и заказчики с требованиями к ie6 ещё оставались, но время идёт, и сейчас уже можно применять этот метод в «боевых условиях».
Про внедрение SVG в HTML уже были статьи на хабре: раз, два, поэтому особо подробно останавливаться на этом вопросе не буду. Кроме того вы возможно заметили, что прописал -webkit-background-clip: text;, это нужно для прозрачности внедряемого объекта в старых safari и chrome (в современных версиях этот баг уже закрыли).
Но как же быть с ie7, ie8? Для этой «умирающей» аудитории мы можем предложить flash, а если и его нет, то, на худой конец, пусть грузят тяжёлый PNG-32 (или какую-нибудь заглушку).
Решение
Начнём с того как подключать SVG?
Для обзора различных методов внедрения SVG, я сделал специальную демо-страничку.
Кроме стандартного способа подключения через object/embed (ссылки на статьи выше), можно подключать SVG через <img> или CSS. Но как показала практика, через <img> SVG в firefox и chrome показывается только с внедрёнными через data: URI файлами, более того, некоторые устаревшие версии браузеров не поддерживают внедрённые таким способом SVG (например, firefox 3.6), кроме того, в этом случае невозможно управлять SVG с помощью JavaScript. Но если отбросить эти факты, то этот способ даже более предпочтителен для «статичных» картинок.
Если поддержки SVG нет (ie6-8, android <3.0 и др.), то можно подменить его на flash или png. В поисках готового решения, я ничего не нашёл, поэтому написал свой велосипед (с использованием jQuery и SWFObject).
В HTML подключаем библиотеки jQuery, swfobject и скрипт-фикс. Затем внедряем любым понравившимся методом svg-изображение. Для поддержки кроссбраузерности выбираем назначаем CSS-класс для внедрённых svg-элементов, и указываем через атрибуты data-altflash и/или data-altpng URL альтернативных источников.
<!DOCTYPE html>
<html>
<head>
<title>SVG alpha</title>
<style>
body { margin: 0; padding: 0; font: 12px/1 Arial, sans-serif; text-align: center; background: #ccc url(bg.png) repeat fixed left top; }
h2 { margin-top: 2em; font-size: 120%; text-align: center; }
table { width: 800px; margin: 0 auto; }
table td { width: 300px; }
object,iframe,embed,img,.svg-bg { width: 300px; height: 240px; margin: 0; padding: 0; border: 0px none; }
object { -webkit-background-clip: text; }
.svg-bg { background: transparent none no-repeat scroll left top; }
</style>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" type="text/javascript"></script>
<script type="text/javascript" src="svg.js"></script>
<script type="text/javascript">
$(function(){ $('.svg').svg(); }); // init
</script>
</head>
<body>
<table>
<tr>
<th>через object</th>
<th>через img (data:URI)</th>
</tr>
<tr>
<td><object class="svg" data="out.svg" type="image/svg+xml" data-altflash="flash.swf" data-altpng="png-32.png"></object></td>
<td><img class="svg" src="in.svg" alt="" data-altpng="png-32.png"/></td>
</tr>
</table>
</body>
</html>
svg.js (прошу сильно не критиковать за качество кода, он написан буквально на коленке):
$.supportSvg = function()
{
return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1");
};
$.svgReplaceImage = function()
{
var img = new Image();
img.src = $(this).data('altpng');
$(this).replaceWith(img);
};
$.fn.svg = function()
{
if($.supportSvg()) return;
this.filter('[data-altpng]:not([data-altflash])').each($.svgReplaceImage);
var i = 0;
this.filter('[data-altflash]').each(function()
{
var obj = this;
this.id = 'svg-alt-'+(++i);
swfobject.embedSWF($(this).data('altflash'), this.id, $(this).width(), $(this).height(), '10.0.0', 'expressInstall.swf', false, {wmode: 'transparent'}, false,
function(e){ if(!e.success) $.svgReplaceImage.apply(obj); });
});
};
Итого
Таким образом мы можем существенно сократить размер изображений. Данный метод протестирован на ie7-ie9, firefox 13, chrome 19, opera 12, Safari 5 (win), android 2.3. Просьба протестировать его в других браузерах. Спасибо за внимание!
Ссылки
Автор: PaulZi