Пишу, по большей части, про историю, свою жизнь и немного про программирование.

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

Ещё картинка на чистом CSS (82.31КиБ)

Я тут всё это время думал как дёшево улучшить свой конвертор картинок в чистый CSS и придумал.

Выкинул «radial-gradient» и стал использовать «linear-gradient». Картинка, которая в прошлой версии занимала 164КБ, сейчас занимает 89 79. Это уже неплохо, в сжатом (gzip) виде этот код занимает 19КБ, что всего в два с половиной раза больше, чем исходное изображение в PNG. Результат можно сильно улучшить, если отказаться от повтора цвета, но изображения с мелкими деталями (с текстом, например) пострадают.

Кстати говоря, логотип студии Лебедева при конвертации из PNG (6,7КБ) уменьшился до 1,6КБ 1,3КБ. Это уже реальная экономия, если бы не префиксы браузеров.

Отказ от радиального градиента в пользу линейного позволил мне кодировать целую строку изображения одной конструкцией, что, в свою очередь, позволило сильно экономить на повторяющихся байтах (кстати, можно попробовать группировать пиксели по вертикали и горизонтали, а потом выбирать лучший вариант).

Кстати этот раз «Опера», увы, не смогла показать картинку, так как, похоже, не поддерживает позиционирование для линейных градиентов.

Добавлено позднее: сделал оптимизацию, теперь конвертор делает два прогона: один просматривает пиксели по горизонтали, второй по вертикали. Выбирается тот, который даёт наименьший результат.

Ключи запуска те же.

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

    $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;
}

class Bo {
    public function __get($num) {
        return $num ? $num . 'px' : 0;
    }
}

function Bo_convert_helper($im, $lim1, $lim2, $getpix, $dotmask, $z) {
    $end  = $lim1 - 1;
    $dots = array();

    for ($o1 = 0; $o1<$lim2; $o1++) {
        $coll = array();
        $prev    = null;
        $start   = 0;

        for ($o2 = 0; $o2<$lim1; $o2++) {
            $pixel = $getpix($im, $o2, $o1);

            if ($prev !== null && $prev !== $pixel || $o2 == $end) {
                if ($prev !== null) {
                    $color = Bo_shortcolor(vsprintf('%02x%02x%02x', $prev));

                    $coll[] = "$color {$z->$start},$color {$z->$o2}";
                }

                if ($o2 == $end && $prev != $pixel) {
                    $color = Bo_shortcolor(vsprintf('%02x%02x%02x', $pixel));
                    $cell[] = "$color {$z->$o2}";
                }

                $start = $o2;
            }

            $prev = $pixel;
        }

        $dots[] = sprintf($dotmask, implode(',', $coll), $z->$o1);
    }

    return $dots;
}


function Bo_convert($filename, $prefix = '') {
    $z = new Bo();

    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);
    $sw = $w = imagesx($im);
    $sh = $h = imagesy($im);

    $hfunc = create_function('$im, $x, $y', 'return imagecolorsforindex($im, imagecolorat($im, $x, $y));');
    $hmask = "{$prefix}linear-gradient(0,%s) 0 %s";

    $hdots = join(',', Bo_convert_helper($im, $w, $h, $hfunc, $hmask, $z));

    $vfunc = create_function('$im, $y, $x', 'return imagecolorsforindex($im, imagecolorat($im, $x, $y));');
    $vmask = "{$prefix}linear-gradient(90,%s) %s 0";

    $vdots = join(',', Bo_convert_helper($im, $h, $w, $vfunc, $vmask, $z));

    if (strlen($hdots) > strlen($vdots)) {
        $dots = $vdots;
        $sw = 1;
    } else {
        $dots = $hdots;
        $sh = 1;
    }

    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: {$sw}px {$sh}px;
        background-size: {$sw}px {$sh}px;
        width: {$w}px; height: {$h}px;
    }
</style>
<body>
<div></div>
</body>
HTML;
}

if ($_SERVER['argc'] < 2) {
    echo <<<HELP
Image to background CSS convertor v2 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";
    exit(1);
};