Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Картинка на чистом 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, / ноябрь 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 //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. //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";
};
9 комментариев
Евгений Степанищев (bolknote.ru) 2011

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

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

Евгений Степанищев (bolknote.ru) 2011

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

Евгений Степанищев (bolknote.ru) 2011

Внезапно логотип студии Лебедева занял в таком виде (сжатый в 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) 2011

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

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

Евгений Степанищев (bolknote.ru) 2011

Комментарий для dibr.livejournal.com:

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

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

Евгений Степанищев (bolknote.ru) 2011

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

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

Евгений Степанищев (bolknote.ru) 2011

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

Целеустремлённый молодой человек с мечтой (del-ka.livejournal.com) 2011

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

Евгений Степанищев (bolknote.ru) 2011

Комментарий для del-ka.livejournal.com:

Спасибо :-)