로그인
KO
games/doom-jumphp / tools / art / Canvas.php · commit 8e1cb9f
<?phpnamespace Tools\Art;use App\Engine\Palette;/** A palette picture drawn in 2D (textures, flats, the status bar): pixels wrap around (tileable). */final class Canvas{    /** @var int[] */    public array $px;    private const FONT = [        'A' => ['.#.', '#.#', '###', '#.#', '#.#'], 'B' => ['##.', '#.#', '##.', '#.#', '##.'], 'C' => ['.##', '#..', '#..', '#..', '.##'],        'D' => ['##.', '#.#', '#.#', '#.#', '##.'], 'E' => ['###', '#..', '##.', '#..', '###'], 'F' => ['###', '#..', '##.', '#..', '#..'],        'G' => ['.##', '#..', '#.#', '#.#', '.##'], 'H' => ['#.#', '#.#', '###', '#.#', '#.#'], 'I' => ['###', '.#.', '.#.', '.#.', '###'],        'K' => ['#.#', '#.#', '##.', '#.#', '#.#'], 'L' => ['#..', '#..', '#..', '#..', '###'], 'M' => ['#.#', '###', '###', '#.#', '#.#'],        'N' => ['##.', '#.#', '#.#', '#.#', '#.#'], 'O' => ['.#.', '#.#', '#.#', '#.#', '.#.'], 'P' => ['##.', '#.#', '##.', '#..', '#..'],        'R' => ['##.', '#.#', '##.', '#.#', '#.#'], 'S' => ['.##', '#..', '.#.', '..#', '##.'], 'T' => ['###', '.#.', '.#.', '.#.', '.#.'],        'U' => ['#.#', '#.#', '#.#', '#.#', '###'], 'V' => ['#.#', '#.#', '#.#', '#.#', '.#.'], 'W' => ['#.#', '#.#', '###', '###', '#.#'],        'X' => ['#.#', '#.#', '.#.', '#.#', '#.#'], 'Y' => ['#.#', '#.#', '.#.', '.#.', '.#.'], 'Z' => ['###', '..#', '.#.', '#..', '###'],        '0' => ['###', '#.#', '#.#', '#.#', '###'], '1' => ['.#.', '##.', '.#.', '.#.', '###'], '2' => ['##.', '..#', '.#.', '#..', '###'],        '3' => ['##.', '..#', '.#.', '..#', '##.'], '4' => ['#.#', '#.#', '###', '..#', '..#'], '5' => ['###', '#..', '##.', '..#', '##.'],        '6' => ['.##', '#..', '###', '#.#', '###'], '7' => ['###', '..#', '.#.', '.#.', '.#.'], '8' => ['###', '#.#', '###', '#.#', '###'],        '9' => ['###', '#.#', '###', '..#', '##.'], '-' => ['...', '...', '###', '...', '...'], ' ' => ['...', '...', '...', '...', '...'],        '%' => ['#.#', '..#', '.#.', '#..', '#.#'],    ];    public function __construct(public int $w, public int $h, int $fill = Palette::TRANSPARENT)    {        $this->px = array_fill(0, $w * $h, $fill);    }    public function set(int $x, int $y, int $c): void    {        $x = (($x % $this->w) + $this->w) % $this->w;        $y = (($y % $this->h) + $this->h) % $this->h;        $this->px[$y * $this->w + $x] = $c;    }    public function get(int $x, int $y): int    {        $x = (($x % $this->w) + $this->w) % $this->w;        $y = (($y % $this->h) + $this->h) % $this->h;        return $this->px[$y * $this->w + $x];    }    public function rect(int $x, int $y, int $w, int $h, int $c): void    {        for ($j = 0; $j < $h; $j++) {            for ($i = 0; $i < $w; $i++) {                $this->set($x + $i, $y + $j, $c);            }        }    }    /** A function of (x, y) => palette index over a rectangle (the whole canvas by default). */    public function paint(callable $f, int $x = 0, int $y = 0, ?int $w = null, ?int $h = null): void    {        $w ??= $this->w;        $h ??= $this->h;        for ($j = 0; $j < $h; $j++) {            for ($i = 0; $i < $w; $i++) {                $c = $f($x + $i, $y + $j, $i, $j);                if ($c !== null) {                    $this->set($x + $i, $y + $j, $c);                }            }        }    }    /** A raised plate: lit top / left edges, dark bottom / right, noisy face. */    public function plate(int $x, int $y, int $w, int $h, int $ramp, int $shade, int $edge = 1, float $noise = 1.5, bool $sunken = false): void    {        $this->paint(function (int $px, int $py, int $i, int $j) use ($w, $h, $ramp, $shade, $edge, $noise, $sunken): int {            $s = $shade + (int)round((Noise::hash($px, $py, 7) - 0.5) * $noise);            $hi = $sunken ? -2 : 2;            if ($i < $edge || $j < $edge) {                $s += $hi;            } elseif ($i >= $w - $edge || $j >= $h - $edge) {                $s -= $hi;            }            return Palette::index($ramp, $s);        }, $x, $y, $w, $h);    }    public function rivet(int $x, int $y, int $ramp, int $shade): void    {        $this->set($x, $y, Palette::index($ramp, $shade + 3));        $this->set($x + 1, $y, Palette::index($ramp, $shade + 1));        $this->set($x, $y + 1, Palette::index($ramp, $shade + 1));        $this->set($x + 1, $y + 1, Palette::index($ramp, $shade - 3));    }    public function line(int $x0, int $y0, int $x1, int $y1, int $c): void    {        $n = max(abs($x1 - $x0), abs($y1 - $y0), 1);        for ($i = 0; $i <= $n; $i++) {            $this->set((int)round($x0 + ($x1 - $x0) * $i / $n), (int)round($y0 + ($y1 - $y0) * $i / $n), $c);        }    }    /** Text in a 3x5 font, $scale pixels per dot. */    public function text(int $x, int $y, string $text, int $c, int $scale = 1, ?int $shadow = null): void    {        foreach (str_split(strtoupper($text)) as $k => $ch) {            $glyph = self::FONT[$ch] ?? self::FONT[' '];            foreach ($glyph as $gy => $row) {                for ($gx = 0; $gx < 3; $gx++) {                    if ($row[$gx] === '#') {                        if ($shadow !== null) {                            $this->rect($x + ($k * 4 + $gx) * $scale + 1, $y + $gy * $scale + 1, $scale, $scale, $shadow);                        }                        $this->rect($x + ($k * 4 + $gx) * $scale, $y + $gy * $scale, $scale, $scale, $c);                    }                }            }        }    }    public function blit(array $sprite, int $x, int $y): void    {        for ($j = 0; $j < $sprite['h']; $j++) {            for ($i = 0; $i < $sprite['w']; $i++) {                $c = ord($sprite['px'][$j * $sprite['w'] + $i]);                if ($c !== Palette::TRANSPARENT) {                    $this->set($x + $i, $y + $j, $c);                }            }        }    }    public function rowMajor(): string    {        return implode('', array_map('chr', $this->px));    }    public function colMajor(): string    {        $s = '';        for ($x = 0; $x < $this->w; $x++) {            for ($y = 0; $y < $this->h; $y++) {                $s .= chr($this->px[$y * $this->w + $x]);            }        }        return $s;    }}