Pull to refresh

JPEG сжатие картинки с альфа-каналом или SVG masks

Website development *HTML *Image processing *
Привет, Хабр! Недавняя статья про сжатие в png-8 с сохранение полупрозрачности, напомнила мне об одной технике, которая позволяет применять на сайтах изображения с альфа-каналом, при этом используя алгоритм сжатия с потерями — JPEG, что позволяет существенно сократить их объём.


Предыстория

Некоторое время назад, когда об HTML5 мало кто знал, а flash был довольно распространён, мне пришлось изучить ActionScript и делать красивые «имиджевые» сайты. Обычно такой сайт был довольно насыщен картинками, и более того, для некоторых эффектов требовалось использовать фотографии с прозрачным фоном (например, когда товарная единица с тенью красиво вылетает откуда-нибудь по неоднородному фону). Но как известно JPEG не позволяет хранить в себе альфа-канал, а PNG — это формат сжатия без потерь, из-за чего размер такого фото получался мягко говоря большим. Во flash же возможно было сохранить полупрозрачные изображения с JPEG-компрессией, достигалось это тем, что альфа-канал сохранялся отдельно от изображения, получая два изображения, которые впоследствии сохранялись в JPEG.
Позже верстая один из «обычных» html-сайтов, мне также потребовалось сохранить большой неоднородный полупрозрачный фон, но ни сохранение в png-32, ни в png-8 не давали приемлемый результат — png-8 выглядел отвратно, а png-32 весил слишком много. В поисках технологии, которая бы позволила сохранять изображения с альфа-каналом с потерей качества, я наткнулся на SVG Mask.

Так для чего это нужно?

Возьмём гипотетический пример — нужна нам вот-такая картинка с «дыркой» внутри:
PNG-32 (PNG-32, 1870 Кбайт)

Сжатие в 256ти-цветную палитру PNG-8 «убивает» изображение:
PNG-8 (PNG-8, 305 Кбайт)

Тогда как применяя flash мы получим swf скромного размера, при этом на глаз мало различимого от оригинала:
flash (swf, 453 Кбайт)

В принципе, если бы не сложности с удобством генерации swf, и не полной поддержки flash на всех устройствах, можно было бы на этом и закончить. Но есть более удобная HTML5 альтернатива!

Суть метода

Сам метод не новый и изредка встречается его описание на некоторых ресурсах. Но к сожалению информации о нём относительно мало, по крайней мере на хабре статьи не нашёл, что спешу поправить.
Стандарт SVG, позволяет внедрять в себя растровые изображения, а также позволяет применять маски. Так почему же нам не реализовать всё так же, как и во flash?
Отделяем альфа-канал и сохраняем его в отдельный файл:
imagemask (JPEG, 165/46 Кбайт)

Суммарный объём получился даже меньше, чем .swf — 211 Кбайт!

Пишем SVG:
out.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 альтернативных источников.
index.html
<!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 (прошу сильно не критиковать за качество кода, он написан буквально на коленке):
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. Просьба протестировать его в других браузерах. Спасибо за внимание!

Ссылки

Демо
Архив (зеркало)

PS. Не первый раз натыкаюсь на непонимание, что маска — это произвольное изображение, круговой градиент я использовал как пример, в реальности маска — JPEG-картинка, которая может быть какой угодно геометрической сложности!
Tags:
Hubs:
Total votes 98: ↑98 and ↓0 +98
Views 10K
Comments Comments 17