画像ライブラリーはクラス化、TGA対応の追加

諏訪子 <suwako@076.moe>、2025-12-31 10:37
コミット 105cbdec7de32b56df6b33e8eec017779ba4ce60 ・親 9a5d5db ・ファイルを見る

11個のファイル、+382 -211

@@ -30,3 +30,4 @@
3030 define('AUTH_ENABLED', false);
3131 define('AUTH_REGISTER_ENABLED', false);
3232 define('COPYRIGHT_YEAR', '2018-'.date('Y'));
33+define('DEBUG_MODE', false);
変更 newpost.php
@@ -2,7 +2,8 @@
22 if (!isset($argv[1])) die('usage: php newpost.php [slug]');
33 if (file_exists("blog/{$argv[1]}.md")) die("エラー: ファイル「blog/{$argv[1]}.md」は既に存在します。\n");
44
5-define('CURL_ENABLED', false); // 黙れ・・・
5+define('ROOT', realpath(__DIR__));
6+require_once ROOT.'/config/config.php';
67 include('util.php');
78 $post = fopen("blog/{$argv[1]}.md", "w");
89 fwrite($post, "title: 【】\n");
@@ -14,4 +15,3 @@
1415 fwrite($post, "category: \n");
1516 fwrite($post, "----\n");
1617 fclose($post);
17-?>
変更 route.php
@@ -1,6 +1,4 @@
11 <?php
2-error_reporting(E_ALL);
3-ini_set('display_errors', 1);
42
53 require_once __DIR__.DIRECTORY_SEPARATOR.'/autoload.php';
64
@@ -16,7 +14,12 @@
1614 define('ROOT', realpath(__DIR__));
1715
1816 include(ROOT.'/config/config.php');
17+if (DEBUG_MODE) {
18+ error_reporting(E_ALL);
19+ ini_set('display_errors', 1);
20+}
1921 include(ROOT.'/util.php');
22+
2023
2124 $routes = [
2225 Route::add('GET', 'blog/{page}', Home::class.'@article'),
@@ -1,6 +1,12 @@
11 <?php
22 namespace Site\Lib;
33
4+use Site\Lib\Image\ImageInterface;
5+use Site\Lib\Image\Gif;
6+use Site\Lib\Image\Jpeg;
7+use Site\Lib\Image\Png;
8+use Site\Lib\Image\Targa;
9+
410 class RGB {
511 public int $r;
612 public int $g;
@@ -19,236 +25,55 @@
1925 public string $filename = 'UNKNOWN';
2026 public string $extension = 'UNKNOWN';
2127 public string $type = 'UNSUPPORTED';
22- public int $width = 0;
23- public int $height = 0;
24- public int $bpp = 0;
25- public int $bitDepth = 0;
26- public int $numColors = 0; // GIF, PNG
27- public int $colorType = 0; // PNG
28- public string $compression = 'NONE'; // GIF, PNG
29- public int $filter = 0; // PNG
30- public int $interlace = 0; // GIF, PNG
31- public bool $hasAlpha = false;
32- public int $bgColor = 0; // GIF
33- public float $delayTime = 0; // GIF
34- public array $globalColorTable = []; // GIF
35- public int $numTransparentPixels = 0; // GIF
36- public int $lzwMinCodeSize = 0; // GIF
37- public string $imageData = '';
3828 public string $size = '0 B';
3929 public int $bytes = 0;
4030 public \stdClass $fullInfo;
4131
32+ private array $supported = [
33+ 'image/gif' => Gif::class,
34+ 'image/jpeg' => Jpeg::class,
35+ 'image/png' => Png::class,
36+ 'image/x-tga' => Targa::class,
37+ ];
38+
4239 public function __construct(string $file) {
4340 $file = ROOT.$file;
44- // $file = ROOT.'/public/static/article/mock-screenshot.jpg';
41+ // $file = ROOT.'/public/static/article/mock-screenshot.tga';
4542 if (!file_exists($file)) return;
4643 $fp = fopen($file, 'rb');
4744 if (!$fp) return;
48-
4945 $extBytes = fread($fp, 15);
5046 fclose($fp);
47+
5148 $this->bytes = filesize($file);
5249 $this->size = $this->formatBytes($this->bytes);
5350 $this->extension = pathinfo($file, PATHINFO_EXTENSION);
5451 $this->filename = str_replace('.'.$this->extension, '', array_last(explode('/', $file)));
5552
56- if ($extBytes === false) return;
57- else if (substr($extBytes, 0, 3) === "\xff\xd8\xff") {
58- $this->type = 'image/jpeg';
59- $this->parseJPEG($file);
60- } else if (substr($extBytes, 0, 15) === "\x52\x49\x46\x46\x1a\x28\x00\x00\x57\x45\x42\x50\x56\x50\x38") {
61- $this->type = 'image/webp';
62- // $this->parseWEBP($file);
63- } else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") {
64- $this->type = 'image/bmp';
65- // $this->parseBMP($file);
66- } else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") {
67- $this->type = 'image/png';
68- $this->fullInfo = $this->parsePNG($file);
69- } else if (substr($extBytes, 0, 6) === "\x47\x49\x46\x38\x37\x61" || substr($extBytes, 0, 6) === "\x47\x49\x46\x38\x39\x61") {
70- $this->type = 'image/gif';
71- $this->fullInfo = $this->parseGIF($file);
72- } else if (str_ends_with($file, '.tga')) {
73- $this->type = 'image/tga';
74- // $this->parseTGA($file);
75- }
53+ if (substr($extBytes, 0, 3) === "\xff\xd8\xff") $this->type = 'image/jpeg';
54+ else if (substr($extBytes, 0, 15) === "\x52\x49\x46\x46\x1a\x28\x00\x00\x57\x45\x42\x50\x56\x50\x38") $this->type = 'image/webp';
55+ else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") $this->type = 'image/bmp';
56+ else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") $this->type = 'image/png';
57+ else if (substr($extBytes, 0, 6) === "\x47\x49\x46\x38\x37\x61" || substr($extBytes, 0, 6) === "\x47\x49\x46\x38\x39\x61") $this->type = 'image/gif';
58+ else if (str_ends_with($file, '.tga')) $this->type = 'image/x-tga';
59+
60+ $this->fullInfo = $this->createHandler($this->type)->parse($file);
61+ $this->fullInfo->type = $this->type;
62+ $this->fullInfo->size = $this->size;
63+ $this->fullInfo->bytes = $this->bytes;
7664 }
7765
78- private function parseJPEG(string $file): \stdClass {
79- $jpg = new \stdClass;
80- $fp = fopen($file, 'rb');
66+ ///////////////
8167
82- $i = 0;
83- while (!feof($fp)) {
84- $byte = fread($fp, 1);
85- if ($byte !== "\xFF") continue;
86-
87- $marker = ord(fread($fp, 1));
88- if ($marker >= 0xC0 && $marker <= 0xC3) { // 0xC0 = SOF0, 0xC1 = SOF1, 0xC2 = SOF2, 0xC3 = SOF3
89- fread($fp, 2);
90- $this->bpp = ord(fread($fp, 1));
91- // n = ビッグエンディアン(2ビット)
92- $this->height = unpack('n', fread($fp, 2))[1];
93- $this->width = unpack('n', fread($fp, 2))[1];
94- $jpg->width = $this->width;
95- $jpg->height = $this->height;
96- $jpg->bpp = $this->bpp;
97- } else if ($marker == 0xC4) { // DHT (Huffmanテーブル(複数可))
98- //
99- } else if ($marker == 0xDB) { // DQT (量子化テーブル(複数可))
100- //
101- } else if ($marker == 0xDD) { // DRI (リセット期間)
102- } else if ($marker == 0xDA) { // SOS(画像開始)
103- } else if ($marker >= 0xD0 && $marker <= 0xD7) { // RST
104- } else if ($marker >= 0xE1 && $marker <= 0xEF) { // APP
105- } else if ($marker == 0xFE) { // COM
106- } else if ($marker == 0xD9) { // EOI(画像終了)
107- break;
108- }
109- }
110-
111- // kys($jpg);
112-
113- $i++;
114- fclose($fp);
115- return $jpg;
68+ private function createHandler(string $ext): ImageInterface {
69+ $class = $this->supported[$ext] ?? null;
70+ if (!$class) throw new \RuntimeException("形式のハンドルが存在しない: {$ext}");
71+ return new $class();
11672 }
11773
118- /**
119- * TODO: 現在、IHDRヘッダーのみを受け取る
120- */
121- private function parsePNG(string $file): \stdClass {
122- $png = new \stdClass();
123- $fp = fopen($file, 'rb');
124-
125- fread($fp, 8); // マジックのスキップ
126- unpack('N', fread($fp, 4))[1];
127- $type = fread($fp, 4);
128- if ($type !== 'IHDR') {
129- fclose($fp);
130- return $png;
131- }
132-
133- // IHDRデータ
134- $data = fread($fp, 13);
135-
136- // N = ビッグエンディアン(4ビット)
137- $this->width = unpack('N', substr($data, 0, 4))[1];
138- $this->height = unpack('N', substr($data, 4, 4))[1];
139- $this->bitDepth = ord($data[8]);
140- $this->numColors = 1 << $this->bitDepth;
141- $this->colorType = ord($data[9]);
142- $this->compression = 'DEFLATE';
143- $this->filter = ord($data[11]);
144- $this->interlace = ord($data[12]); // 0 = なし, 1 = Adam7
145- $this->bpp = $this->bitDepth * $this->getSamplesPerPixel($this->colorType);
146- $this->delayTime = 0;
147- $this->hasAlpha = ($this->colorType === 4 || $this->colorType === 6);
148-
149- $png->width = $this->width;
150- $png->height = $this->height;
151- $png->bitDepth = $this->bitDepth;
152- $png->numColors = $this->numColors;
153- $png->colorType = $this->colorType;
154- $png->compression = $this->compression;
155- $png->filter = $this->filter;
156- $png->interlace = $this->interlace;
157- $png->bpp = $this->bpp;
158- $png->delayTime = $this->delayTime;
159- $png->hasAlpha = $this->hasAlpha;
160-
161- fclose($fp);
162-
163- return $png;
164- }
165-
166- /**
167- * TODO: 現在、インタレースした画像が未対応です。
168- */
169- private function parseGIF(string $file): \stdClass {
170- $gif = new \stdClass();
171- $fp = fopen($file, 'rb');
172- $magic = fread($fp, 6); // マジックのスキップ
173-
174- // v = リトルエンディアン(2ビット)
175- $this->width = unpack('v', fread($fp, 2))[1];
176- $this->height = unpack('v', fread($fp, 2))[1];
177- $packed = ord(fread($fp, 1));
178- $this->bitDepth = ($packed & 0x07) + 1;
179- $this->numColors = 1 << $this->bitDepth;
180- $hasGct = $this->bitDepth === 8;
181-
182- $gif->width = $this->width;
183- $gif->height = $this->height;
184- $gif->bitDepth = $this->bitDepth;
185- $gif->numColors = $this->numColors;
186- if ($magic === 'GIF87a' || !$hasGct) return $gif; // それ以外情報がない
187-
188- $this->bgColor = (int)bin2hex(fread($fp, 1));
189- $gif->bgColor = $this->bgColor;
190- bin2hex(fread($fp, 1)); // いつでも 0:0?
191-
192- $gceLen = 0;
193- while (!feof($fp)) {
194- $skip = bin2hex(fread($fp, 3));
195- $color = new RGB((int)($skip[0].$skip[1]), (int)($skip[2].$skip[3]), (int)($skip[4].$skip[5]));
196- $this->globalColorTable[] = $color->rgb;
197- if ($skip[0].$skip[1] === '21' && $skip[2].$skip[3] === 'f9') {
198- $gceLen = (int)($skip[4].$skip[5]);
199- break;
200- }
201- }
202-
203- // GCE開始
204- $this->hasAlpha = (ord(fread($fp, 1)) & 0x01) === 0x01;
205- $this->delayTime = (unpack('v', fread($fp, 2))[1]) / 100.0;
206- $this->numTransparentPixels = ord(fread($fp, 1));
207- $gif->hasAlpha = $this->hasAlpha;
208- $gif->delayTime = $this->delayTime;
209- $gif->numTransparentPixels = $this->numTransparentPixels;
210- fread($fp, 1); // GCE終了
211- fread($fp, 1); // 画像指定子開始
212- fread($fp, 4); // 画面のXとY、いつでも(0, 0)
213- fread($fp, 4); // 画像のサイズ、関数の開始で既に保存した
214- $pack = ord(fread($fp, 1));
215- $this->interlace = ($pack & 0x40); // 画像のサイズ、関数の開始で既に保存した
216-
217- $this->lzwMinCodeSize = ord(fread($fp, 1)); // 画像データ開始
218- $gif->lzwMinCodeSize = $this->lzwMinCodeSize;
219- $imageData = '';
220- while (true) {
221- $blockSize = ord(fread($fp, 1));
222- if ($blockSize === 0) break;
223- $imageData .= fread($fp, 1);
224- }
225-
226- $this->imageData = base64_encode($imageData);
227- $this->colorType = 3;
228- $this->compression = 'LZW';
229- $gif->colorType = $this->colorType;
230- $gif->compression = $this->compression;
231- $gif->globalColorTable = $this->globalColorTable;
232- $gif->imageData = $this->imageData;
233-
234- fclose($fp);
235- return $gif;
236- }
237-
23874 private function formatBytes(int $size, int $precision = 2): string {
23975 $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
24076 for ($i = 0; $size > 1024 && $i < count($units) - 1; ++$i) $size /= 1024;
24177 return round($size, $precision).' '.$units[$i];
242- }
243-
244- private function getSamplesPerPixel(int $colorType): int {
245- return match($colorType) {
246- 0 => 1, // グレースケール
247- 2 => 3, // RGB
248- 3 => 1, // 指標した色(ペレット)
249- 4 => 2, // グレースケール+アルファ
250- 6 => 4, // RGBA
251- default => 1,
252- };
25378 }
25479 }
@@ -0,0 +1,91 @@
1+<?php
2+namespace Site\Lib\Image;
3+
4+use Site\Lib\Image\ImageInterface;
5+
6+class Gif implements ImageInterface {
7+ public int $width = 0;
8+ public int $height = 0;
9+ public int $bitDepth = 0;
10+ public int $numColors = 0;
11+ public string $compression = 'NONE';
12+ public int $interlace = 0;
13+ public int $bgColor = 0;
14+ public float $delayTime = 0;
15+ public array $globalColorTable = [];
16+ public int $numTransparentPixels = 0;
17+ public int $lzwMinCodeSize = 0;
18+ public string $imageData = '';
19+
20+ /**
21+ * TODO: 現在、インタレースした画像が未対応です。
22+ */
23+ public function parse(string $file): \stdClass {
24+ $gif = new \stdClass();
25+ $fp = fopen($file, 'rb');
26+ $magic = fread($fp, 6); // マジックのスキップ
27+
28+ // v = リトルエンディアン(2ビット)
29+ $this->width = unpack('v', fread($fp, 2))[1];
30+ $this->height = unpack('v', fread($fp, 2))[1];
31+ $packed = ord(fread($fp, 1));
32+ $this->bitDepth = ($packed & 0x07) + 1;
33+ $this->numColors = 1 << $this->bitDepth;
34+ $hasGct = $this->bitDepth === 8;
35+
36+ $gif->width = $this->width;
37+ $gif->height = $this->height;
38+ $gif->bitDepth = $this->bitDepth;
39+ $gif->numColors = $this->numColors;
40+ if ($magic === 'GIF87a' || !$hasGct) return $gif; // それ以外情報がない
41+
42+ $this->bgColor = (int)bin2hex(fread($fp, 1));
43+ $gif->bgColor = $this->bgColor;
44+ bin2hex(fread($fp, 1)); // いつでも 0:0?
45+
46+ $gceLen = 0;
47+ while (!feof($fp)) {
48+ $skip = bin2hex(fread($fp, 3));
49+ $color = new RGB((int)($skip[0].$skip[1]), (int)($skip[2].$skip[3]), (int)($skip[4].$skip[5]));
50+ $this->globalColorTable[] = $color->rgb;
51+ if ($skip[0].$skip[1] === '21' && $skip[2].$skip[3] === 'f9') {
52+ $gceLen = (int)($skip[4].$skip[5]);
53+ break;
54+ }
55+ }
56+
57+ // GCE開始
58+ $this->hasAlpha = (ord(fread($fp, 1)) & 0x01) === 0x01;
59+ $this->delayTime = (unpack('v', fread($fp, 2))[1]) / 100.0;
60+ $this->numTransparentPixels = ord(fread($fp, 1));
61+ $gif->hasAlpha = $this->hasAlpha;
62+ $gif->delayTime = $this->delayTime;
63+ $gif->numTransparentPixels = $this->numTransparentPixels;
64+ fread($fp, 1); // GCE終了
65+ fread($fp, 1); // 画像指定子開始
66+ fread($fp, 4); // 画面のXとY、いつでも(0, 0)
67+ fread($fp, 4); // 画像のサイズ、関数の開始で既に保存した
68+ $pack = ord(fread($fp, 1));
69+ $this->interlace = ($pack & 0x40); // 画像のサイズ、関数の開始で既に保存した
70+
71+ $this->lzwMinCodeSize = ord(fread($fp, 1)); // 画像データ開始
72+ $gif->lzwMinCodeSize = $this->lzwMinCodeSize;
73+ $imageData = '';
74+ while (true) {
75+ $blockSize = ord(fread($fp, 1));
76+ if ($blockSize === 0) break;
77+ $imageData .= fread($fp, 1);
78+ }
79+
80+ $this->imageData = base64_encode($imageData);
81+ $this->colorType = 3;
82+ $this->compression = 'LZW';
83+ $gif->colorType = $this->colorType;
84+ $gif->compression = $this->compression;
85+ $gif->globalColorTable = $this->globalColorTable;
86+ $gif->imageData = $this->imageData;
87+
88+ fclose($fp);
89+ return $gif;
90+ }
91+}
@@ -0,0 +1,6 @@
1+<?php
2+namespace Site\Lib\Image;
3+
4+interface ImageInterface {
5+ public function parse(string $file): \stdClass;
6+}
@@ -0,0 +1,54 @@
1+<?php
2+namespace Site\Lib\Image;
3+
4+use Site\Lib\Image\ImageInterface;
5+
6+class Jpeg implements ImageInterface {
7+ public int $width = 0;
8+ public int $height = 0;
9+ public int $bpp = 0;
10+ public string $imageData = '';
11+
12+ /**
13+ * TODO: 現在、IHDRヘッダーのみを受け取る
14+ */
15+ public function parse(string $file): \stdClass {
16+ $jpg = new \stdClass;
17+ $fp = fopen($file, 'rb');
18+
19+ $i = 0;
20+ while (!feof($fp)) {
21+ $byte = fread($fp, 1);
22+ if ($byte !== "\xFF") continue;
23+
24+ $marker = ord(fread($fp, 1));
25+ if ($marker >= 0xC0 && $marker <= 0xC3) { // 0xC0 = SOF0, 0xC1 = SOF1, 0xC2 = SOF2, 0xC3 = SOF3
26+ fread($fp, 2);
27+ $this->bpp = ord(fread($fp, 1));
28+ // n = ビッグエンディアン(2ビット)
29+ $this->height = unpack('n', fread($fp, 2))[1];
30+ $this->width = unpack('n', fread($fp, 2))[1];
31+ $jpg->width = $this->width;
32+ $jpg->height = $this->height;
33+ $jpg->bpp = $this->bpp;
34+ } else if ($marker == 0xC4) { // DHT (Huffmanテーブル(複数可))
35+ //
36+ } else if ($marker == 0xDB) { // DQT (量子化テーブル(複数可))
37+ //
38+ } else if ($marker == 0xDD) { // DRI (リセット期間)
39+ } else if ($marker == 0xDA) { // SOS(画像開始)
40+ } else if ($marker >= 0xD0 && $marker <= 0xD7) { // RST
41+ } else if ($marker >= 0xE1 && $marker <= 0xEF) { // APP
42+ } else if ($marker == 0xFE) { // COM
43+ } else if ($marker == 0xD9) { // EOI(画像終了)
44+ break;
45+ }
46+ }
47+
48+ // kys($jpg);
49+
50+ $i++;
51+ fclose($fp);
52+ return $jpg;
53+ }
54+}
@@ -0,0 +1,74 @@
1+<?php
2+namespace Site\Lib\Image;
3+
4+use Site\Lib\Image\ImageInterface;
5+
6+class Png implements ImageInterface {
7+ public int $width = 0;
8+ public int $height = 0;
9+ public int $bpp = 0;
10+ public int $bitDepth = 0;
11+ public int $numColors = 0;
12+ public int $colorType = 0;
13+ public string $compression = 'NONE';
14+ public int $filter = 0;
15+ public int $interlace = 0;
16+ public bool $hasAlpha = false;
17+ public string $imageData = '';
18+
19+ public function parse(string $file): \stdClass {
20+ $png = new \stdClass();
21+ $fp = fopen($file, 'rb');
22+
23+ fread($fp, 8); // マジックのスキップ
24+ unpack('N', fread($fp, 4))[1];
25+ $type = fread($fp, 4);
26+ if ($type !== 'IHDR') {
27+ fclose($fp);
28+ return $png;
29+ }
30+
31+ // IHDRデータ
32+ $data = fread($fp, 13);
33+
34+ // N = ビッグエンディアン(4ビット)
35+ $this->width = unpack('N', substr($data, 0, 4))[1];
36+ $this->height = unpack('N', substr($data, 4, 4))[1];
37+ $this->bitDepth = ord($data[8]);
38+ $this->numColors = 1 << $this->bitDepth;
39+ $this->colorType = ord($data[9]);
40+ $this->compression = 'DEFLATE';
41+ $this->filter = ord($data[11]);
42+ $this->interlace = ord($data[12]); // 0 = なし, 1 = Adam7
43+ $this->bpp = $this->bitDepth * $this->getSamplesPerPixel($this->colorType);
44+ $this->hasAlpha = ($this->colorType === 4 || $this->colorType === 6);
45+
46+ $png->width = $this->width;
47+ $png->height = $this->height;
48+ $png->bitDepth = $this->bitDepth;
49+ $png->numColors = $this->numColors;
50+ $png->colorType = $this->colorType;
51+ $png->compression = $this->compression;
52+ $png->filter = $this->filter;
53+ $png->interlace = $this->interlace;
54+ $png->bpp = $this->bpp;
55+ $png->hasAlpha = $this->hasAlpha;
56+
57+ fclose($fp);
58+
59+ return $png;
60+ }
61+
62+ ///////////////
63+
64+ private function getSamplesPerPixel(int $colorType): int {
65+ return match($colorType) {
66+ 0 => 1, // グレースケール
67+ 2 => 3, // RGB
68+ 3 => 1, // 指標した色(ペレット)
69+ 4 => 2, // グレースケール+アルファ
70+ 6 => 4, // RGBA
71+ default => 1,
72+ };
73+ }
74+}
@@ -0,0 +1,115 @@
1+<?php
2+namespace Site\Lib\Image;
3+
4+use Site\Lib\Image\ImageInterface;
5+
6+class Targa implements ImageInterface {
7+ public int $id = 0;
8+ public int $colorMapType = 0;
9+ public int $imageTypeByte = 0;
10+ public string $imageType = '';
11+ public int $colorMapEntryIndex = 0;
12+ public int $colorMapLength = 0;
13+ public int $colorMapBits = 0;
14+ public int $x = 0;
15+ public int $y = 0;
16+ public int $width = 0;
17+ public int $height = 0;
18+ public int $bitDepth = 0;
19+ public int $bpp = 0;
20+ public int $interlace = 0;
21+ public int $isRightToLeft = 0; // 0 = 左→右, 1 = 右→左
22+ public int $isTopToBottom = 0; // 0 = 下→上, 1 = 上→下
23+ public int $alphaBits = 0;
24+ public bool $hasAlpha = false;
25+ //--------
26+ public string $imageId = '';
27+ public string $colorMapData = '';
28+ public string $imageData = '';
29+
30+ private array $imageTypes = [
31+ 0 => 'NO DATA',
32+ 1 => 'UNCOMPRESSED COLOR-MAPPED IMAGE',
33+ 2 => 'UNCOMPRESSED TRUE COLOR IMAGE',
34+ 3 => 'UNCOMPRESSED GRAYSCALE IMAGE',
35+ 9 => 'RUN-LENGTH ENCODED COLOR-MAPPED IMAGE',
36+ 10 => 'RUN-LENGTH ENCODED TRUE-COLOR IMAGE',
37+ 11 => 'RUN-LENGTH ENCODED GRAYSCALE IMAGE',
38+ 32 => 'HUFFMAN-DELTA-RUN-LENGTH ENCODED COLOR-MAPPED IMAGE',
39+ 33 => 'HUFFMAN-DELTA-RUN-LENGTH-4-PASS-QUADTREE-TYPE PROCESS ENCODED COLOR-MAPPED IMAGE',
40+ ];
41+
42+ public function parse(string $file): \stdClass {
43+ $tga = new \stdClass();
44+ $fp = fopen($file, 'rb');
45+
46+ $this->id = ord(fread($fp, 1));
47+ $this->colorMapType = ord(fread($fp, 1)); // 0 = なし, 1 = あり, 2~127 = Truevision向け, 128~255 = 開発者向け
48+ $this->imageTypeByte = ord(fread($fp, 1));
49+ $this->imageType = $this->imageTypes[$this->imageTypeByte];
50+ $tga->id = $this->id;
51+ $tga->colorMapType = $this->colorMapType;
52+ $tga->imageTypeByte = $this->imageTypeByte;
53+ $tga->imageType = $this->imageType;
54+
55+ $this->colorMapEntryIndex = unpack('v', fread($fp, 2))[1];
56+ $this->colorMapLength = unpack('v', fread($fp, 2))[1];
57+ $this->colorMapBits = ord(fread($fp, 1));
58+ $tga->colorMapEntryIndex = $this->colorMapEntryIndex;
59+ $tga->colorMapLength = $this->colorMapLength;
60+ $tga->colorMapBits = $this->colorMapBits;
61+
62+ $this->x = unpack('v', fread($fp, 2))[1];
63+ $this->y = unpack('v', fread($fp, 2))[1];
64+ $this->width = unpack('v', fread($fp, 2))[1];
65+ $this->height = unpack('v', fread($fp, 2))[1];
66+ $this->bitDepth = ord(fread($fp, 1));
67+ $tga->x = $this->x;
68+ $tga->y = $this->y;
69+ $tga->width = $this->width;
70+ $tga->height = $this->height;
71+ $tga->bitDepth = $this->bitDepth;
72+
73+ $desc = ord(fread($fp, 1));
74+ $this->interlace = ($desc & 0xC0) >> 6;
75+ $this->isTopToBottom = ($desc & 0x20) !== 0;
76+ $this->isRightToLeft = ($desc & 0x10) !== 0;
77+ $this->alphaBits = $desc & 0x0F;
78+ $this->hasAlpha = ($this->alphaBits == 8) && in_array($this->imageTypeByte == [2, 3, 10, 11]);
79+ $tga->interlace = $this->interlace;
80+ $tga->isRightToLeft = $this->isRightToLeft;
81+ $tga->isTopToBottom = $this->isTopToBottom;
82+ $tga->alphaBits = $this->alphaBits;
83+ $tga->hasAlpha = $this->hasAlpha;
84+
85+ $needsXFlip = $tga->isRightToLeft;
86+ $needsYFlip = !$tga->isTopToBottom;
87+ // 0010 0000
88+
89+ //---------
90+ $imageIdBytes = $this->id;
91+ $colorMapBytes = 0;
92+ if ($this->colorMapType === 1 && $this->colorMapLength > 0) {
93+ $bytesPerEntry = (int)($this->colorMapBits / 8);
94+ $colorMapBytes = $this->colorMapLength * $bytesPerEntry;
95+ }
96+
97+ $this->imageId = $imageIdBytes > 0 ? fread($fp, $imageIdBytes) : '';
98+ $this->colorMapData = $colorMapBytes > 0 ? fread($fp, $colorMapBytes) : '';
99+ $tga->imageId = $this->imageId;
100+ $tga->colorMapData = $this->colorMapData;
101+
102+ if (in_array($this->imageTypeByte, [9, 10, 11])) { // RLE
103+ // TODO
104+ } else if (in_array($this->imageTypeByte, [1, 2, 3])) { // 非圧縮化
105+ $this->bpp = (int)($this->bitDepth / 8);
106+ $bytes = $this->width * $this->height * $this->bpp;
107+ $this->imageData = fread($fp, $bytes);
108+ $tga->bpp = $this->bpp;
109+ $tga->imageData = $this->imageData;
110+ }
111+
112+ fclose($fp);
113+ return $tga;
114+ }
115+}
変更 util.php
@@ -127,6 +127,7 @@
127127 }
128128
129129 function kys(mixed $arg): void {
130+ if (!DEBUG_MODE) return;
130131 if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false';
131132 if (gettype($arg) == 'array' || gettype($arg) == 'object') {
132133 foreach ($arg as $a) { if (gettype($a) == 'boolean') $a = $a === true ? 'true' : 'false'; }
@@ -140,6 +141,7 @@
140141 }
141142
142143 function ffs(): void {
144+ if (!DEBUG_MODE) return;
143145 echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
144146 echo '<header style="padding: 10px 0; display: flex; justify-content: space-evenly; background-color: #b61729; margin-bottom: 20px; position: sticky; top: 0;"><div><b>F</b>OR</div> <div><b>F</b>UCKS</div> <div><b>S</b>AKE</div></header>';
145147
@@ -31,7 +31,7 @@
3131 {@ if (isset($meta) && isset($meta->thumbnail) && !empty($meta->thumbnail)) @}
3232 <meta property="og:image" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
3333 <meta property="og:image:secure_url" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
34- {$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail); $}
34+ {$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail)->fullInfo; $}
3535 <meta property="og:image:type" content="{{ $imgspec->type }}" />
3636 <meta property="og:image:width" content="{{ $imgspec->width }}" />
3737 <meta property="og:image:height" content="{{ $imgspec->height }}" />