Anmelden
DE
games/doom-jumphp / src / Engine / Palette.php · commit 8e1cb9f
<?phpnamespace App\Engine;/** * The 256 colours of the game: 16 ramps of 16 shades (index = ramp * 16 + shade, shade 0 dark .. 15 bright). * Like the original engine's 8-bit renderer, every picture is a string of palette indices and the light is a * "colormap": a 256-byte table that maps each colour to a darker one. Ramps make the colormaps exact and cheap: * darkening a colour moves it down its own ramp. * * Index 255 (the brightest teal) is never drawn: it is the transparent pixel of sprites. */final class Palette{    public const TRANSPARENT = 255;    public const LEVELS = 32;          // colormaps: 0 = full bright .. 31 = nearly black    public const GREY = 0, BROWN = 1, TAN = 2, RED = 3, ORANGE = 4, YELLOW = 5, GREEN = 6, OLIVE = 7, BLUE = 8,        CYAN = 9, PURPLE = 10, FLESH = 11, STEEL = 12, RUST = 13, BRICK = 14, TEAL = 15;    /** The bright end of each ramp. */    private const RAMPS = [        [255, 255, 255], [196, 136, 84], [232, 204, 156], [255, 44, 32], [255, 152, 44], [255, 236, 96],        [116, 255, 64], [156, 172, 92], [84, 128, 255], [96, 232, 255], [196, 96, 255], [255, 176, 152],        [176, 194, 220], [208, 108, 64], [184, 76, 62], [88, 204, 168],    ];    /** @var array<int, array{0:int,1:int,2:int}>|null */    private static ?array $rgb = null;    /** @var string[]|null the 32 colormaps */    private static ?array $maps = null;    private static ?string $identity = null;    /** @return array<int, array{0:int,1:int,2:int}> */    public static function rgb(): array    {        if (self::$rgb === null) {            $colors = [];            foreach (self::RAMPS as $ramp => [$r, $g, $b]) {                for ($s = 0; $s < 16; $s++) {                    $t = (($s + 1) / 16) ** 1.15;                    $white = $s > 12 ? ($s - 12) * 0.07 : 0.0;                    $c = [];                    foreach ([$r, $g, $b] as $v) {                        $x = $v * $t;                        $c[] = (int)max(0, min(255, round($x + (255 - $x) * $white)));                    }                    $colors[$ramp * 16 + $s] = $c;                }            }            $colors[0] = [0, 0, 0];            self::$rgb = $colors;        }        return self::$rgb;    }    public static function index(int $ramp, int $shade): int    {        $i = $ramp * 16 + max(0, min(15, $shade));        return $i === self::TRANSPARENT ? 254 : $i;    }    /** The colour of a palette index darkened to a light level (0 bright .. 31 dark). */    public static function shade(int $index, int $level): int    {        if ($index === self::TRANSPARENT) {            return $index;        }        $f = 1.0 - $level / self::LEVELS;        $s = (int)round((($index & 15) + 1) * $f) - 1;        return $s < 0 ? 0 : ($index & 0xF0) + $s;    }    /** Colormap $level as a 256-byte string (for strtr() and lookups). */    public static function colormap(int $level): string    {        if (self::$maps === null) {            self::$maps = [];            for ($l = 0; $l < self::LEVELS; $l++) {                $m = '';                for ($i = 0; $i < 256; $i++) {                    $m .= chr(self::shade($i, $l));                }                self::$maps[$l] = $m;            }        }        return self::$maps[max(0, min(self::LEVELS - 1, $level))];    }    /** "\x00\x01...\xFF": the identity map, first argument of strtr(). */    public static function identity(): string    {        return self::$identity ??= implode('', array_map('chr', range(0, 255)));    }    /** A picture's pixels at a light level (strtr: done in C, fast). */    public static function darken(string $pixels, int $level): string    {        return $level <= 0 ? $pixels : strtr($pixels, self::identity(), self::colormap($level));    }    /**     * index char => RGBA bytes, for strtr($frame, $map): the whole 8-bit frame to RGBA in one C call.     * $tint = [r, g, b, amount 0..1]: the damage / pickup flash mixed into every colour.     * @return array<string, string>     */    public static function rgbaMap(?array $tint = null): array    {        $map = [];        foreach (self::rgb() as $i => [$r, $g, $b]) {            if ($tint !== null) {                [$tr, $tg, $tb, $a] = $tint;                $r = (int)($r + ($tr - $r) * $a);                $g = (int)($g + ($tg - $g) * $a);                $b = (int)($b + ($tb - $b) * $a);            }            $map[chr($i)] = chr($r) . chr($g) . chr($b) . "\xFF";        }        return $map;    }    /** The nearest palette index of an RGB colour (tools, slow). */    public static function nearest(int $r, int $g, int $b): int    {        $best = 0;        $bestD = PHP_INT_MAX;        foreach (self::rgb() as $i => [$pr, $pg, $pb]) {            if ($i === self::TRANSPARENT) {                continue;            }            $d = ($pr - $r) ** 2 * 3 + ($pg - $g) ** 2 * 4 + ($pb - $b) ** 2 * 2;            if ($d < $bestD) {                $bestD = $d;                $best = $i;            }        }        return $best;    }}