PNGライブラリの延長

諏訪子 <suwako@076.moe>、2026-01-04 03:14
コミット 6582737554fbd8ba69c7f249cb8530b396774fee ・親 105cbde ・ファイルを見る

3個のファイル、+480 -38

@@ -17,7 +17,7 @@
1717 $this->r = $r;
1818 $this->g = $g;
1919 $this->b = $b;
20- $this->rgb = [$r, $g, $b];
20+ $this->rgb = ['r' => $r, 'g' => $g, 'b' => $b];
2121 }
2222 }
2323
@@ -4,58 +4,499 @@
44 use Site\Lib\Image\ImageInterface;
55
66 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 = '';
7+ public \stdClass $IHDR; // 画像ヘッダー
8+ // 横(4バイト)
9+ // 丈(4バイト)
10+ // ビット深さ(1バイト)
11+ // 色類(1バイト)
12+ //// | PNG画像タイプ | 色類 | 許可したビット深さ | 解釈 |
13+ //// |--------------------------|------|--------------------|----------------------------------------------- |
14+ //// | グレースケール | 0 | 1,2,4,8,16 | 各ピクセルはグレースケールのサンプル |
15+ //// | トゥルーカラー | 2 | 8,16 | 各ピクセルはR,G,Bトリプル |
16+ //// | 指標した色 | 3 | 1,2,4,8 | 各ピクセルはパレット指標(PLTEチャンクあり) |
17+ //// | グレースケール+アルファ | 4 | 8,16 | 各ピクセルはグレースケール+アルファのサンプル |
18+ //// | トゥルーカラー+アルファ | 6 | 8,16 | 各ピクセルはR,G,B,Aトリプル |
19+ // 圧縮関数(1バイト)
20+ //// 0 (DEFLATE)しかない
21+ // フィルター関数(1バイト)
22+ //// 0(アダプティブ・フィルタリング)しかない
23+ // インタレース関数(1バイト)
24+ //// 0 = なし
25+ //// 1 = Adam7
26+ public \stdClass $PLTE; // パレット(赤、緑、青)
27+ // 色類3 = 必須
28+ // 色類2と6 = 任意
29+ public \stdClass $IDAT; // 画像データ
30+ public \stdClass $IEND; // 画像終了
1831
32+ public \stdClass $tRNS; // 透明情報
33+ // 色類0 = グレーサンプル値(2バイト)
34+ // 色類2 = RGBサンプル値(2x2x2バイト)
35+ // 色類3 = パレット指標のアルファ(各1バイト)
36+ public \stdClass $cHRM; // 主な色度とホワイトポイント
37+ // 白X(4バイト)
38+ // 白Y(4バイト)
39+ // 赤X(4バイト)
40+ // 赤Y(4バイト)
41+ // 緑X(4バイト)
42+ // 緑Y(4バイト)
43+ // 青X(4バイト)
44+ // 青Y(4バイト)
45+ public \stdClass $gAMA; // ガンマ(4バイト)
46+ public \stdClass $iCCP; // 組み込みしたICCプロファイル
47+ // プロファイル名(1~79バイト)
48+ // NULL分離子(1バイト)
49+ // 圧縮関数(1バイト)
50+ // 圧縮プロファイル(nバイト)
51+ public \stdClass $sBIT; // 上位ビット(4バイト)
52+ // 色類0 = グレーサンプルビット(1バイト)
53+ // 色類2, 3 = RGBサンプルビット(1x1x1バイト)
54+ // 色類4 = グレーサンプルビット(1x1バイト)
55+ // 色類6 = RGBサンプルビット(1x1x1x1バイト)
56+ public \stdClass $sRGB; // レンダリングインテント(1バイト)
57+ // 0 = 知覚
58+ // 1 = 相対カラー・メトリック
59+ // 2 = 彩度
60+ // 3 = 絶対カラー・メトリック
61+ public \stdClass $cICP; // (1バイト)
62+ public \stdClass $mDCV; // (1バイト)
63+ public \stdClass $cLLI; // (1バイト)
64+ public \stdClass $tEXt; // 文字データ
65+ public \stdClass $iTXt; // 圧縮した文字データ
66+ public \stdClass $zTXt; // 国際文字データ
67+ public \stdClass $bKGD; // 拝啓色
68+ public \stdClass $hIST; // 画像ヒストグラム
69+ public \stdClass $pHYs; // 物理的なぷくセルのサイズ
70+ // 単位当たりのピクセル数(PPU)のX軸(4バイト)
71+ // 単位当たりのピクセル数(PPU)のY軸(4バイト)
72+ // ユニット指定子(1バイト)
73+ //// 0 = 不明
74+ //// 1 = メートル
75+ public \stdClass $sPLT; // 提案されている配色パターン
76+ // パレット名(1~79バイト)
77+ // NULL分離子(1バイト)
78+ // 赤(1か2バイト)
79+ // 緑(1か2バイト)
80+ // 青(1か2バイト)
81+ // アルファ(1か2バイト)
82+ // 周波数(2バイト)
83+ // 等
84+ public \stdClass $eXIf; // Exifプロファイル
85+ public \stdClass $tIME; // 画像の最後変更日
86+ // 年(2バイト)
87+ // 月(1バイト:1-12)
88+ // 日(1バイト:1-31)
89+ // 時(1バイト:0-23)
90+ // 分(1バイト:0-59)
91+ // 秒(1バイト:0-60)
92+ public \stdClass $acTL; // アニメーション・コントロール
93+ // フレーム数(4バイト)
94+ // 再生数(4バイト、0 = 無限)
95+
96+ public array $frames = [];
97+ private ?array $currentFrame = null;
98+
99+ // TODO: 残り: sBIT, cICP, mDCV, cLLI, iTXt, hIST, sPLT, tIME
19100 public function parse(string $file): \stdClass {
20101 $png = new \stdClass();
21102 $fp = fopen($file, 'rb');
22103
23- fread($fp, 8); // マジックのスキップ
24- unpack('N', fread($fp, 4))[1];
25- $type = fread($fp, 4);
26- if ($type !== 'IHDR') {
104+ $magic = bin2hex(fread($fp, 8)); // マジックのスキップ
105+ $length = unpack('N', fread($fp, 4))[1];
106+ $header = fread($fp, 4);
107+ if ($header !== 'IHDR') {
27108 fclose($fp);
28109 return $png;
29110 }
30111
31112 // IHDRデータ
32- $data = fread($fp, 13);
113+ $data = fread($fp, $length);
33114
34115 // 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);
116+ $this->IHDR = new \stdClass;
117+ $this->IHDR->width = unpack('N', substr($data, 0, 4))[1];
118+ $this->IHDR->height = unpack('N', substr($data, 4, 4))[1];
119+ $this->IHDR->bitDepth = ord($data[8]); // 1, 2, 4, 8, 16
120+ $this->IHDR->numColors = 1 << $this->IHDR->bitDepth;
121+ $this->IHDR->colorType = ord($data[9]); // 0, 2, 3, 4, 6
122+ $this->IHDR->compressionType = ord($data[10]);
123+ $this->IHDR->compression = 'DEFLATE';
124+ $this->IHDR->filter = ord($data[11]);
125+ $this->IHDR->interlace = ord($data[12]); // 0 = なし, 1 = Adam7
126+ $this->IHDR->bpp = $this->IHDR->bitDepth * $this->getSamplesPerPixel($this->IHDR->colorType);
127+ $this->IHDR->hasAlpha = ($this->IHDR->colorType === 4 || $this->IHDR->colorType === 6);
128+ $crc = fread($fp, 4);
129+ $this->IHDR->crc = $crc;
130+ $this->IHDR->crc32 = unpack('N', $crc)[1];
131+ $this->IHDR->crcHex = bin2hex($crc);
45132
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;
133+ $png->header = $this->IHDR;
56134
57- fclose($fp);
135+next_chunk:
136+ $lengthBytes = fread($fp, 4);
137+ if (strlen($lengthBytes) < 4) {
138+ logger(LogType::Image, 'バイト長さが悪い。ファイル:'.$file.'、バイト長さ:'.'$lengthBytes');
139+ fclose($fp);
140+ return $png;
141+ }
58142
143+ $length = unpack('N', $lengthBytes)[1];
144+ $nextChunk = fread($fp, 4);
145+ $data = $length > 0 ? fread($fp, $length) : '';
146+ $crc = fread($fp, 4);
147+
148+ // PLTEデータ
149+ if ($nextChunk === "\x50\x4c\x54\x45") { // PLTE
150+ $palette = [];
151+ for ($i = 0; $i < $length; $i += 3) {
152+ $colors = new \Site\Lib\RGB(ord($data[$i]), ord($data[$i+1]), ord($data[$i+2]));
153+ $palette[] = $colors->rgb;
154+ }
155+
156+ $this->PLTE = new \stdClass;
157+ $this->PLTE->palette = $palette;
158+ $this->PLTE->crc = $crc;
159+ $this->PLTE->crc32 = unpack('N', $crc)[1];
160+ $this->PLTE->crcHex = bin2hex($crc);
161+
162+ $png->palette = $this->PLTE;
163+ goto next_chunk;
164+ } else if ($nextChunk === "\x74\x52\x4e\x53") { // tRNS
165+ $this->tRNS = new \stdClass;
166+ $vals = [];
167+ $bytes = $this->IHDR->colorType === 2 ? 3 : 1;
168+ for ($i = 0; $i < $length; $i += $bytes) {
169+ if ($this->IHDR->colorType === 2) {
170+ $colors = new \Site\Lib\RGB(ord($data[$i]), ord($data[$i+1]), ord($data[$i+2]));
171+ $vals[] = $colors->rgb;
172+ } else {
173+ $vals[] = ord($data[$i]);
174+ }
175+ }
176+ $this->tRNS->values = $vals;
177+ $this->tRNS->crc = $crc;
178+ $this->tRNS->crc32 = unpack('N', $crc)[1];
179+ $this->tRNS->crcHex = bin2hex($crc);
180+ $png->transparency = $this->tRNS;
181+ goto next_chunk;
182+ } else if ($nextChunk === "\x63\x48\x52\x4d") { // cHRM
183+ $this->cHRM = new \stdClass;
184+ $this->cHRM->whitePointX = unpack('N', substr($data, 0, 4))[1];
185+ $this->cHRM->whitePointY = unpack('N', substr($data, 4, 4))[1];
186+ $this->cHRM->redX = unpack('N', substr($data, 8, 4))[1];
187+ $this->cHRM->redY = unpack('N', substr($data, 12, 4))[1];
188+ $this->cHRM->greenX = unpack('N', substr($data, 16, 4))[1];
189+ $this->cHRM->greenY = unpack('N', substr($data, 20, 4))[1];
190+ $this->cHRM->blueX = unpack('N', substr($data, 24, 4))[1];
191+ $this->cHRM->blueY = unpack('N', substr($data, 28, 4))[1];
192+ $this->cHRM->crc = $crc;
193+ $this->cHRM->crc32 = unpack('N', $crc)[1];
194+ $this->cHRM->crcHex = bin2hex($crc);
195+ $png->chromaticities = $this->cHRM;
196+ goto next_chunk;
197+ } else if ($nextChunk === "\x67\x41\x4d\x41") { // gAMA
198+ $this->gAMA = new \stdClass;
199+ $this->gAMA->value = unpack('N', $data)[1];
200+ $this->gAMA->crc = $crc;
201+ $this->gAMA->crc32 = unpack('N', $crc)[1];
202+ $this->gAMA->crcHex = bin2hex($crc);
203+ $png->gamma = $this->gAMA;
204+ goto next_chunk;
205+ } else if ($nextChunk === "\x69\x43\x43\x50") { // iCCP
206+ $this->iCCP = new \stdClass;
207+
208+ $nullPos = strpos($data, "\0");
209+ if ($nullPos === false || $nullPos < 1 || $nullPos > 79) {
210+ $this->iCCP->valid = false;
211+ $this->iCCP->error = "Missing or invalid profile name terminator";
212+ goto iccp_crc;
213+ }
214+
215+ $this->iCCP->profileName = substr($data, 0, $nullPos);
216+ $compressionMethod = ord($data[$nullPos + 1]);
217+ $this->iCCP->compressionMethod = $compressionMethod;
218+
219+ if ($compressionMethod !== 0) {
220+ $this->iCCP->valid = false;
221+ $this->iCCP->error = "Unsupported compression method: {$compressionMethod}";
222+ goto iccp_crc;
223+ }
224+
225+ $compressedData = substr($data, $nullPos + 2);
226+ $this->iCCP->compressedData = $compressedData;
227+ $this->iCCP->compressedSize = strlen($compressedData);
228+ $this->iCCP->valid = true;
229+
230+iccp_crc:
231+ $this->iCCP->crc = $crc;
232+ $this->iCCP->crc32 = unpack('N', $crc)[1];
233+ $this->iCCP->crcHex = bin2hex($crc);
234+ $png->iccProfile = $this->iCCP;
235+ goto next_chunk;
236+ } else if ($nextChunk === "\x73\x42\x49\x54") { // sBIT
237+ $this->sBIT = new \stdClass;
238+ kys('sBIT');
239+ $png->significantBits = $this->sBIT;
240+ goto next_chunk;
241+ } else if ($nextChunk === "\x73\x52\x47\x42") { // sRGB
242+ $this->sRGB = new \stdClass;
243+ $this->sRGB->intent = ord($data);
244+ $name = [
245+ 0 => 'Perceptual',
246+ 1 => 'Relative colorimetric',
247+ 2 => 'Saturation',
248+ 3 => 'Absolute colorimetric',
249+ ];
250+ $this->sRGB->intentName = $name[$this->sRGB->intent];
251+ $this->sRGB->crc = $crc;
252+ $this->sRGB->crc32 = unpack('N', $crc)[1];
253+ $this->sRGB->crcHex = bin2hex($crc);
254+ $png->rgbColorSpace = $this->sRGB;
255+ goto next_chunk;
256+ } else if ($nextChunk === "\x63\x49\x43\x50") { // cICP
257+ $this->cICP = new \stdClass;
258+ kys('cICP');
259+ $png->videoSignalTypeId = $this->cICP;
260+ goto next_chunk;
261+ } else if ($nextChunk === "\x6d\x44\x43\x56") { // mDCV
262+ $this->mDCV = new \stdClass;
263+ kys('mDCV');
264+ $png->masterDisplayColorVolume = $this->mDCV;
265+ goto next_chunk;
266+ } else if ($nextChunk === "\x63\x4c\x4c\x49") { // cLLI
267+ $this->cLLI = new \stdClass;
268+ kys('cLLI');
269+ $png->contentLightLevelInfo = $this->cLLI;
270+ goto next_chunk;
271+ } else if ($nextChunk === "\x74\x45\x58\x74") { // tEXt
272+ $this->tEXt = new \stdClass;
273+ $this->tEXt->text = $data;
274+ $this->tEXt->crc = $crc;
275+ $this->tEXt->crc32 = unpack('N', $crc)[1];
276+ $this->tEXt->crcHex = bin2hex($crc);
277+ $png->textualData = $this->tEXt;
278+ goto next_chunk;
279+ } else if ($nextChunk === "\x69\x54\x58\x74") { // iTXt
280+ $this->iTXt = new \stdClass;
281+ kys('iTXt');
282+ $png->internationalTextualData = $this->iTXt;
283+ goto next_chunk;
284+ } else if ($nextChunk === "\x7a\x54\x58\x74") { // zTXt
285+ $this->zTXt = new \stdClass;
286+
287+ $nullPos = strpos($data, "\0");
288+ if ($nullPos === false || $nullPos < 1 || $nullPos > 79) {
289+ $this->zTXt->valid = false;
290+ $this->zTXt->error = "Missing or invalid profile name terminator";
291+ goto zTXt_crc;
292+ }
293+
294+ $this->zTXt->keyword = rtrim(substr($data, 0, $nullPos));
295+ $compressionMethod = ord($data[$nullPos + 1] ?? "\xFF");
296+
297+ $this->zTXt->compressionMethod = $compressionMethod;
298+
299+ if ($compressionMethod !== 0) {
300+ $this->zTXt->valid = false;
301+ $this->zTXt->error = "Unsupported compression method";
302+ goto zTXt_crc;
303+ }
304+
305+ $compressedText = substr($data, $nullPos + 2);
306+ fread($fp, 13); // ????????????????????????
307+ $this->zTXt->compressedText = $compressedText;
308+ $this->zTXt->compressedLength = strlen($compressedText);
309+
310+ $text = zlib_decode($compressedText);
311+
312+ if ($text === false) {
313+ $this->zTXt->valid = false;
314+ $this->zTXt->decompressionError = true;
315+ $this->zTXt->text = null;
316+ goto zTXt_crc;
317+ } else {
318+ $this->zTXt->text = $text;
319+ $this->zTXt->textUtf8 = mb_convert_encoding($text, 'UTF-8', 'ISO-8859-1');
320+ }
321+
322+ $this->zTXt->valid = true;
323+
324+zTXt_crc:
325+ $this->zTXt->crc = $crc;
326+ $this->zTXt->crc32 = unpack('N', $crc)[1];
327+ $this->zTXt->crcHex = bin2hex($crc);
328+ $png->compressedTextualData = $this->zTXt;
329+ goto next_chunk;
330+ } else if ($nextChunk === "\x62\x4b\x47\x44") { // bKGD
331+ $this->bKGD = new \stdClass;
332+ if ($this->IHDR->colorType === 2 || $this->IHDR->colorType === 6) {
333+ $r = (ord($data[0]) << 8) | ord($data[1]);
334+ $g = (ord($data[2]) << 8) | ord($data[3]);
335+ $b = (ord($data[4]) << 8) | ord($data[5]);
336+
337+ $this->bKGD->type = 'rgb16';
338+ $this->bKGD->r = $r;
339+ $this->bKGD->g = $g;
340+ $this->bKGD->b = $b;
341+
342+ $this->bKGD->r8 = $r >> 8;
343+ $this->bKGD->g8 = $g >> 8;
344+ $this->bKGD->b8 = $b >> 8;
345+ $this->bKGD->webColor = sprintf("#%02x%02x%02x", $this->bKGD->r8, $this->bKGD->g8, $this->bKGD->b8);
346+ } else if ($this->IHDR->colorType === 2 || $this->IHDR->colorType === 6) {
347+ $gray = (ord($data[0]) << 8) | ord($data[1]);
348+ $this->bKGD->type = 'gray16';
349+ $this->bKGD->gray = $gray;
350+ $this->bKGD->gray8 = $gray >> 8;
351+ } else if ($this->IHDR->colorType === 3) {
352+ $this->bKGD->type = 'palette_index';
353+ $this->bKGD->value = ord($data);
354+ } else {
355+ $this->bKGD->type = 'unknown';
356+ $this->bKGD->error = 'Invalid color type for bKGD';
357+ }
358+
359+ $this->bKGD->crc = $crc;
360+ $this->bKGD->crc32 = unpack('N', $crc)[1];
361+ $this->bKGD->crcHex = bin2hex($crc);
362+ $png->backgroundColor = $this->bKGD;
363+ goto next_chunk;
364+ } else if ($nextChunk === "\x68\x49\x53\x54") { // hIST
365+ $this->hIST = new \stdClass;
366+ kys('hIST');
367+ $png->imageHistogram = $this->hIST;
368+ goto next_chunk;
369+ } else if ($nextChunk === "\x70\x48\x59\x73") { // pHYs
370+ $this->pHYs = new \stdClass;
371+ $this->pHYs->x = unpack('N', substr($data, 0, 4))[1];
372+ $this->pHYs->y = unpack('N', substr($data, 4, 4))[1];
373+ $unit = [
374+ 0 => 'unit is unknown',
375+ 1 => 'unit is the metre',
376+ ];
377+ $this->pHYs->unitId = ord($data[8]);
378+ $this->pHYs->unit = $unit[$this->pHYs->unitId];
379+ $this->pHYs->crc = $crc;
380+ $this->pHYs->crc32 = unpack('N', $crc)[1];
381+ $this->pHYs->crcHex = bin2hex($crc);
382+ $png->physicalPixelDimensions = $this->pHYs;
383+ goto next_chunk;
384+ } else if ($nextChunk === "\x73\x50\x4c\x54") { // sPLT
385+ $this->sPLT = new \stdClass;
386+ kys('sPLT');
387+ $png->suggestedPalette = $this->sPLT;
388+ goto next_chunk;
389+ } else if ($nextChunk === "\x65\x58\x49\x66") { // eXIf
390+ $this->eXIf = new \stdClass;
391+
392+ if (strlen($data) < 8) {
393+ $this->eXIf->valid = false;
394+ $this->eXIf->error = "eXIf chunk too short";
395+ goto exif_crc;
396+ }
397+
398+ $byteOrder = substr($data, 0, 2);
399+ $magic = substr($data, 2, 2);
400+
401+ $this->eXIf->rawData = $data;
402+ $this->eXIf->rawLength = $length;
403+ $this->eXIf->byteOrder = $byteOrder; // II = リトルエンディアン、MM = ビッグエンディアン
404+ $this->eXIf->isBigEndian = ($byteOrder === 'MM');
405+ $this->eXIf->magic = unpack('n', $magic)[1]; // いつでも42ではず
406+
407+ if (strlen($data) < 8) {
408+ $this->eXIf->valid = false;
409+ $this->eXIf->error = "Invalid TIFF magic number";
410+ goto exif_crc;
411+ }
412+
413+ $this->eXIf->valid = true;
414+
415+ $ifdOffset = unpack($this->eXIf->isBigEndian ? 'N' : 'V', substr($data, 4, 4))[1];
416+ $this->eXIf->firstIfdOffset = $ifdOffset;
417+
418+exif_crc:
419+ $this->eXIf->crc = $crc;
420+ $this->eXIf->crc32 = unpack('N', $crc)[1];
421+ $this->eXIf->crcHex = bin2hex($crc);
422+ $png->exifProfile = $this->eXIf;
423+ goto next_chunk;
424+ } else if ($nextChunk === "\x74\x49\x4d\x45") { // tIME
425+ $this->tIME = new \stdClass;
426+ kys('tIME');
427+ $png->lastModificationTime = $this->tIME;
428+ goto next_chunk;
429+ } else if ($nextChunk === "\x61\x63\x54\x4c") { // acTL
430+ $this->acTL = new \stdClass;
431+ $this->acTL->numFrames = unpack('N', substr($data, 0, 4))[1];
432+ $this->acTL->numPlays = unpack('N', substr($data, 4, 4))[1];
433+ $this->acTL->crc = $crc;
434+ $this->acTL->crc32 = unpack('N', $crc)[1];
435+ $this->acTL->crcHex = bin2hex($crc);
436+ $png->aniationControl = $this->acTL;
437+ goto next_chunk;
438+ } else if ($nextChunk === "\x66\x63\x54\x4c") { // fcTL
439+ if ($this->currentFrame !== null) $this->frames[] = (object)$this->currentFrame;
440+
441+ $this->currentFrame = [
442+ 'sequenceNumber' => unpack('N', substr($data, 0, 4))[1],
443+ 'width' => unpack('N', substr($data, 4, 4))[1],
444+ 'height' => unpack('N', substr($data, 8, 4))[1],
445+ 'xOffset' => unpack('N', substr($data, 12, 4))[1],
446+ 'yOffset' => unpack('N', substr($data, 16, 4))[1],
447+ 'delayNum' => unpack('n', substr($data, 20, 2))[1],
448+ 'delayDen' => unpack('n', substr($data, 22, 2))[1],
449+ 'disposeOp' => ord($data[24]),
450+ 'blendOp' => ord($data[25]),
451+ 'data' => null,
452+ 'crc' => $crc,
453+ 'crc32' => unpack('N', $crc)[1],
454+ 'crcHex' => bin2hex($crc),
455+ ];
456+
457+ goto next_chunk;
458+ } else if ($nextChunk === "\x49\x44\x41\x54" || $nextChunk === "\x66\x64\x41\x54") { // IDAT || fdAT
459+ if ($this->currentFrame === null) {
460+ $this->currentFrame = [
461+ 'sequenceNumber' => 0,
462+ 'isFirstFrame' => true,
463+ 'width' => $this->IHDR->width,
464+ 'height' => $this->IHDR->height,
465+ 'data' => null,
466+ ];
467+ }
468+
469+ $compressedData = ($nextChunk === "\x66\x64\x41\x54") ? substr($data, 4) : $data;
470+ $this->currentFrame['data'] = $compressedData;
471+ $this->currentFrame['dataBase64'] = base64_encode($compressedData);
472+ $this->currentFrame['dataHex'] = bin2hex($compressedData);
473+ $this->currentFrame['dataLength'] = strlen($compressedData);
474+ $this->currentFrame['dataChunkType'] = $nextChunk;
475+ $this->currentFrame['crc'] = $crc;
476+ $this->currentFrame['crcBase64'] = unpack('N', $crc)[1];
477+ $this->currentFrame['crcHex'] = bin2hex($crc);
478+
479+ if ($nextChunk === "\x49\x44\x41\x54") {
480+ $this->frames[] = (object)$this->currentFrame;
481+ $this->currentFrame = null;
482+ }
483+
484+ $png->data = $this->frames;
485+ goto next_chunk;
486+ } else if ($nextChunk === "\x49\x45\x4e\x44") { // IEND
487+ if ($this->currentFrame !== null) {
488+ $this->frames[] = (object)$this->currentFrame;
489+ }
490+ $png->end = (object)['crc' => $crc, 'crc32' => unpack('N', $crc)[1], 'crcHex' => bin2hex($crc)];
491+ } else {
492+ fclose($fp);
493+ $err = '不明なチャンク:'.$nextChunk.'、HEX:'.bin2hex($nextChunk);
494+ kys($err);
495+ logger(\LogType::Image, '【PNG】'.$err);
496+ return $png;
497+ }
498+
499+ fclose($fp);
59500 return $png;
60501 }
61502
変更 util.php
@@ -5,6 +5,7 @@
55 case ActivityPub;
66 case Auth;
77 case Mailer;
8+ case Image;
89 }
910
1011 class Result {