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

Как пользоваться:
php make16.php [infile] [outfile]
1<?php
2$im = imagecreatefrompng($argv[1] ?? "ping.png");
3
4// Палитра может быть любой
5$palette = [
6    [0x00, 0x00, 0x00],
7    [0x00, 0x00, 0x80],
8    [0x00, 0x80, 0x00],
9    [0x00, 0x80, 0x80],
10    [0x80, 0x00, 0x00],
11    [0x80, 0x00, 0x80],
12    [0x80, 0x80, 0x00],
13    [0xcc, 0xcc, 0xcc],
14    [0x80, 0x80, 0x80],
15    [0x00, 0x00, 0xff],
16    [0x00, 0xff, 0x00],
17    [0x00, 0xff, 0xff],
18    [0xff, 0x00, 0x00],
19    [0xff, 0x00, 0xff],
20    [0xff, 0xff, 0x00],
21    [0xff, 0xff, 0xff],
22];
23
24function search($r, $g, $b) {
25
26    global $palette;
27
28    $d = [];
29    foreach ($palette as $i => $k) {
30        $d[$i] = pow($r - $k[0], 2) + pow($g - $k[1], 2) + pow($b - $k[2], 2);
31    }
32    asort($d);
33    $k = key($d);
34    $t = $palette[$k];
35    return [$k, $t[0], $t[1], $t[2]];
36}
37
38function dither_floyd($im) {
39
40    [$sx, $sy] = [imagesx($im), imagesy($im)];
41
42    $image = [];
43    for ($y = 0; $y < $sy; $y++)
44    for ($x = 0; $x < $sx; $x++) {
45
46        $k = imagecolorat($im, $x, $y);
47        $r = ($k >> 16) & 255;
48        $g = ($k >> 8) & 255;
49        $b = ($k) & 255;
50        $image[$x][$y] = [$r, $g, $b];
51    }
52
53    for ($y = 0; $y < $sy; $y++)
54    for ($x = 0; $x < $sx; $x++) {
55
56        [$r,  $g,  $b]  = $image[$x][$y];
57        [$pix, $rn, $gn, $bn] = search($r, $g, $b);
58
59        // Вычислить отклонение и распространить
60        $quant_r = ($r - $rn);
61        $quant_g = ($g - $gn);
62        $quant_b = ($b - $bn);
63
64        //   x 7
65        // 3 5 1
66
67        // [+1, +0] 7/16
68        if ($x + 1 < $sx) {
69            $image[$x + 1][$y][0] += ($quant_r * 7.0/16.0);
70            $image[$x + 1][$y][1] += ($quant_g * 7.0/16.0);
71            $image[$x + 1][$y][2] += ($quant_b * 7.0/16.0);
72        }
73
74        // [-1, +1] 3/16
75        if ($x - 1 >= 0 && $y + 1 < $sy) {
76            $image[$x - 1][$y + 1][0] += ($quant_r * 3.0/16.0);
77            $image[$x - 1][$y + 1][1] += ($quant_g * 3.0/16.0);
78            $image[$x - 1][$y + 1][2] += ($quant_b * 3.0/16.0);
79        }
80
81        // [+0, +1] 5/16
82        if ($y + 1 < $sy) {
83            $image[$x][$y + 1][0] += ($quant_r * 5.0/16.0);
84            $image[$x][$y + 1][1] += ($quant_g * 5.0/16.0);
85            $image[$x][$y + 1][2] += ($quant_b * 5.0/16.0);
86        }
87
88        // [+1, +1] 1/16
89        if ($x + 1 < $sx && $y + 1 < $sy) {
90            $image[$x + 1][$y + 1][0] += ($quant_r * 1.0/16.0);
91            $image[$x + 1][$y + 1][1] += ($quant_g * 1.0/16.0);
92            $image[$x + 1][$y + 1][2] += ($quant_b * 1.0/16.0);
93        }
94
95        imagesetpixel($im, $x, $y, $rn*65536 + $gn*256 + $bn);
96    }
97
98    return $im;
99}
100
101$im = dither_floyd($im);
102imagepng($im, $argv[2] ?? 'result.png');