Картинка на чистом CSS

Картинка на чистом CSS (130.69КиБ) Сейчас CSS позволяет делать всё более интересные вещи. Раз уж сегодня короткий день, за 20 минут из «сэкономленного» часа накидал программу, которая конвертирует любую картинку (в форматах PNG, JPEG или GIF) в чистый CSS (без использования dataURI). Каждая точка кодируется через одну конструкцию radial-gradient. Очень расточительно, сделал для иллюстрации принципа.

Если заморачиваться серьёзно, то можно выявлять в картинке области с градиентом (так делается в формате PNG для упаковки) и покрывать их целиком одной конструкцией, а так же оптимизировать наложением линейных градиентных областей с прозрачностью (через rgba или hsla). Достаточно интересная программисткая задача, кстати.

Самую очевидную оптимизацию я сделал в пробке по работе домой — выделил самый частый цвет и пустил его на фон, выиграл примерно 11,5КБ на картинке в примере.

За 20 минут я только успел сделать небольшой конвертор (результат работы можно посмотреть в разделе «Храню», только для «Хрома»). Так как синтаксис завязан на префиксы, то я в генераторе сделал указание браузера под который нужно сгенерировать код (поддерживаются: IE, Opera, Firefox и Chrome). Всё это весьма расточительно, картинка 75×75 занимает 164КБ.

У меня заработало для «Хрома» и «Файэрфокса», под «Оперой» не завелось, лень проверять что она не поддерживает, IE попробовать не могу — его не бывает на «Маках».
// Конвертор картинки в чистый CSS, http://bolknote.ru/ ноябрь 2011 

<?
// ffccbb → fcb
// ff0000 → red
// abcdef → abcdef
function Bo_shortcolor($color) {
    static $shortnames = array(
        "#000080" => "navy",
        "#008000" => "green",
        "#008080" => "teal",
        "#4b0082" => "indigo",
        "#800000" => "maroon",
        "#800080" => "purple",
        "#808000" => "olive",
        //"#808080" => "gray",
        "#808080" => "grey",
        "#a0522d" => "sienna",
        "#a52a2a" => "brown",
        "#c0c0c0" => "silver",
        "#cd853f" => "peru",
        "#d2b48c" => "tan",
        "#da70d6" => "orchid",
        "#dda0dd" => "plum",
        "#ee82ee" => "violet",
        "#f0e68c" => "khaki",
        "#f0ffff" => "azure",
        "#f5deb3" => "wheat",
        "#f5f5dc" => "beige",
        "#fa8072" => "salmon",
        "#faf0e6" => "linen",
        "#ff0000" => "red",
        "#ff6347" => "tomato",
        "#ff7f50" => "coral",
        "#ffa500" => "orange",
        "#ffc0cb" => "pink",
        "#ffd700" => "gold",
        "#ffe4c4" => "bisque",
        "#fffafa" => "snow",
        "#fffff0" => "ivory",
    );

    $c = strtolower($color);
    if (isset($shortnames[$c])) {
        return $shortnames[$c];
    }

    if ($c[0] == $c[1] && $c[2] == $c[3] && $c[4] == $c[5]) {
        return '#' . $c[1] . $c[3] . $c[5];
    }

    return '#' . $c;
}

function Bo_convert($filename, $prefix = '') {
    if (!file_exists($filename) && !is_readable($filename)) {
        throw new Exception('File not found.');
    }

    $types = array(
        IMAGETYPE_JPG => 'jpeg',
        IMAGETYPE_PNG => 'png',
        IMAGETYPE_GIF => 'gif',
    );

    $imagedata = @getimagesize($filename);
    if (!is_array($imagedata) || !isset($imagedata[2]) || !isset($types[$imagedata[2]])) {
        throw new Exception('Unknown image format');
    }

    $im = call_user_func("imagecreatefrom" . $types[$imagedata[2]], $filename);
    $w  = imagesx($im);
    $h  = imagesy($im);

    // Самый частый цвет идёт на background-color
    $colorfreq = array();
    for ($x = 0; $x<$w; $x++) for ($y = 0; $y<$h; $y++) @$colorfreq[imagecolorat($im, $x, $y)]++;

    $bg = array_search(max($colorfreq), $colorfreq);

    for ($x = 0; $x<$w; $x++)
    for ($y = 0; $y<$h; $y++) {
        $pixel = imagecolorat($im, $x, $y);
        if ($pixel != $bg) {
            $color = Bo_shortcolor(sprintf('%06x', $pixel));

            $dots[] = "{$prefix}radial-gradient($color,$color) {$x}px {$y}px";
        }
    }

    $dots = join(',', $dots);
    $bg   = Bo_shortcolor(sprintf('%06x', $bg));

    return <<<HTML
<head>
<title>$filename converted in background by Evgeny Stepanischev http://bolknote.ru</title>
<style text="text/css">
    div {
        background: $dots;
        background-repeat: no-repeat;
        {$prefix}background-size: 1px 1px;
        background-color: $bg;
        background-size: 1px 1px;
        width: {$w}; height: {$h};
    }
</style>
<body>
<div></div>
</body>
HTML;
}

if ($_SERVER['argc'] < 2) {
    echo <<<HELP
Image to background CSS convertor by Evgeny Stepanischev. http://bolknote.ru Nov 2011

Usage: {$_SERVER['argv'][0]} filename [prefix]

filename  - image file name (PNG, JPEG or GIF)
prefix    - your browser name (Opera, IE, FF, Safari, Chrome)
HELP;
exit;
}

$browsers = array(
    'ie'     => '-ms-',
    'opera'  => '-o-',
    'safari' => '-webkit-',
    'chrome' => '-webkit-',
    'chromium' => '-webkit-',
    'ff' => '-moz-',
    'firefox' => '-moz-',
);

if (isset($_SERVER['argv'][2])) {
    $type = strtolower($_SERVER['argv'][2]);
    $prefix = isset($browsers[$type]) ? $browsers[$type] : '-' . $type . '-';
} else {
    $prefix = '';
}

try {
    echo Bo_convert($_SERVER['argv'][1], $prefix);
} catch (Exception $e) {
    echo "Error: ", $e->getMessage(), "\n";
};
3 ноября 2011 17:34

bolk (bolknote.ru)
3 ноября 2011, 17:01

Пример использования:

php bin2css.php someimage.png firefox > someimage.html

bolk (bolknote.ru)
3 ноября 2011, 18:40

Кстати, исключительно хорошо жмётся. Исходная картинка (PNG) занимает 6,8КБ, несжатый HTML  — 135КБ, сжатый (gzip) — 26КБ.

bolk (bolknote.ru)
3 ноября 2011, 20:22

Внезапно логотип студии Лебедева занял в таком виде (сжатый в gzip) меньше места:

bolk@dhcp-174 ~/Projects/Pic2css $ ll *.gz *.png
-rw-r--r-- 1 bolk staff 3998 3 ноя 21:16 logo.html.gz
-rw-r--r--@ 1 bolk staff 6895 3 ноя 21:20 logo.png

Дмитрий Радищев (dibr.livejournal.com)
3 ноября 2011, 21:01

Когда-то давно, в доCSSную эру, по приколу делал похожее через таблицу с раскрашенными ячейками размером 1х1. Потом догадался растровые данные засунуть в яваскрипт, генерирующий собственно таблицу - размер сократился. Исходники сейчас найти не удалось :-)

Самое забавное, что проблема тогда была не столько в размере файла, сколько в том, что тогдашние браузеры довольно тормозно рендерили большие таблицы, а кто-то (кажется, нетскейп/мозилла) вроде даже ухитрялся на таком падать... :-)

bolk (bolknote.ru)
4 ноября 2011, 09:20, ответ предназначен Дмитрий Радищев (dibr.livejournal.com):

Наверное многие этим баловались, у меня лично проблема была в том, что браузер «Нетскейп» (ака «Нетшкаф») нервно относился к таблицам. У IE было как-то всё повеселее.

Сейчас можно рисовать растр через тег «Канвас», это более выгодно, но с CSS просто интереснее :) Тем более, что в этом подходе сокрыты громадные возможности, если научиться их использовать.

bolk (bolknote.ru)
4 ноября 2011, 22:40

Браузеры, поддерживающие это всё:

IE 10+
Opera 11.60+ (и 12+)
FF 3.6+
Chrome 10+ (можно научить 3-й)
Safari 6+ (можно научить 4-й)

bolk (bolknote.ru)
12 ноября 2011, 00:22

Новая версия: http://bolknote.ru/2011/11/12/~3478 (создаёт картинки меньшего объёма и исправлен баг с картинками с индексированной палитрой).

Целеустремлённый молодой человек с мечтой (del-ka.livejournal.com)
12 ноября 2011, 10:58

Ты маньяк в хорошем смысле этого слова! ;)

bolk (bolknote.ru)
12 ноября 2011, 13:30, ответ предназначен del-ka.livejournal.com:

Спасибо :-)

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)