§ Дизеринг Флойда-Штейнберга

Как пользоваться:
php make16.php [infile] [outfile]
<?php
$im = imagecreatefrompng($argv[1] ?? "ping.png");

// Палитра может быть любой
$palette = [
    [0x00, 0x00, 0x00],
    [0x00, 0x00, 0x80],
    [0x00, 0x80, 0x00],
    [0x00, 0x80, 0x80],
    [0x80, 0x00, 0x00],
    [0x80, 0x00, 0x80],
    [0x80, 0x80, 0x00],
    [0xcc, 0xcc, 0xcc],
    [0x80, 0x80, 0x80],
    [0x00, 0x00, 0xff],
    [0x00, 0xff, 0x00],
    [0x00, 0xff, 0xff],
    [0xff, 0x00, 0x00],
    [0xff, 0x00, 0xff],
    [0xff, 0xff, 0x00],
    [0xff, 0xff, 0xff],
];

function search($r, $g, $b) {

    global $palette;

    $d = [];
    foreach ($palette as $i => $k) {
        $d[$i] = pow($r - $k[0], 2) + pow($g - $k[1], 2) + pow($b - $k[2], 2);
    }
    asort($d);
    $k = key($d);
    $t = $palette[$k];
    return [$k, $t[0], $t[1], $t[2]];
}

function dither_floyd($im) {

    [$sx, $sy] = [imagesx($im), imagesy($im)];

    $image = [];
    for ($y = 0; $y < $sy; $y++)
    for ($x = 0; $x < $sx; $x++) {

        $k = imagecolorat($im, $x, $y);
        $r = ($k >> 16) & 255;
        $g = ($k >> 8) & 255;
        $b = ($k) & 255;
        $image[$x][$y] = [$r, $g, $b];
    }

    for ($y = 0; $y < $sy; $y++)
    for ($x = 0; $x < $sx; $x++) {

        [$r,  $g,  $b]  = $image[$x][$y];
        [$pix, $rn, $gn, $bn] = search($r, $g, $b);

        // Вычислить отклонение и распространить
        $quant_r = ($r - $rn);
        $quant_g = ($g - $gn);
        $quant_b = ($b - $bn);

        //   x 7
        // 3 5 1

        // [+1, +0] 7/16
        if ($x + 1 < $sx) {
            $image[$x + 1][$y][0] += ($quant_r * 7.0/16.0);
            $image[$x + 1][$y][1] += ($quant_g * 7.0/16.0);
            $image[$x + 1][$y][2] += ($quant_b * 7.0/16.0);
        }

        // [-1, +1] 3/16
        if ($x - 1 >= 0 && $y + 1 < $sy) {
            $image[$x - 1][$y + 1][0] += ($quant_r * 3.0/16.0);
            $image[$x - 1][$y + 1][1] += ($quant_g * 3.0/16.0);
            $image[$x - 1][$y + 1][2] += ($quant_b * 3.0/16.0);
        }

        // [+0, +1] 5/16
        if ($y + 1 < $sy) {
            $image[$x][$y + 1][0] += ($quant_r * 5.0/16.0);
            $image[$x][$y + 1][1] += ($quant_g * 5.0/16.0);
            $image[$x][$y + 1][2] += ($quant_b * 5.0/16.0);
        }

        // [+1, +1] 1/16
        if ($x + 1 < $sx && $y + 1 < $sy) {
            $image[$x + 1][$y + 1][0] += ($quant_r * 1.0/16.0);
            $image[$x + 1][$y + 1][1] += ($quant_g * 1.0/16.0);
            $image[$x + 1][$y + 1][2] += ($quant_b * 1.0/16.0);
        }

        imagesetpixel($im, $x, $y, $rn*65536 + $gn*256 + $bn);
    }

    return $im;
}

$im = dither_floyd($im);
imagepng($im, $argv[2] ?? 'result.png');