もっと

This commit is contained in:
2026-01-13 02:17:11 +09:00
parent 6582737554
commit 2a0956dfd4

View File

@@ -96,7 +96,7 @@ class Png implements ImageInterface {
public array $frames = []; public array $frames = [];
private ?array $currentFrame = null; private ?array $currentFrame = null;
// TODO: 残り: sBIT, cICP, mDCV, cLLI, iTXt, hIST, sPLT, tIME // TODO: 残り: sBIT, iTXt, hIST, sPLT, tIME
public function parse(string $file): \stdClass { public function parse(string $file): \stdClass {
$png = new \stdClass(); $png = new \stdClass();
$fp = fopen($file, 'rb'); $fp = fopen($file, 'rb');
@@ -114,6 +114,8 @@ class Png implements ImageInterface {
// N = ビッグエンディアン4ビット // N = ビッグエンディアン4ビット
$this->IHDR = new \stdClass; $this->IHDR = new \stdClass;
$this->IHDR->name = 'IHDR';
$this->IHDR->length = $length;
$this->IHDR->width = unpack('N', substr($data, 0, 4))[1]; $this->IHDR->width = unpack('N', substr($data, 0, 4))[1];
$this->IHDR->height = unpack('N', substr($data, 4, 4))[1]; $this->IHDR->height = unpack('N', substr($data, 4, 4))[1];
$this->IHDR->bitDepth = ord($data[8]); // 1, 2, 4, 8, 16 $this->IHDR->bitDepth = ord($data[8]); // 1, 2, 4, 8, 16
@@ -154,6 +156,8 @@ next_chunk:
} }
$this->PLTE = new \stdClass; $this->PLTE = new \stdClass;
$this->PLTE->name = 'PLTE';
$this->PLTE->length = $length;
$this->PLTE->palette = $palette; $this->PLTE->palette = $palette;
$this->PLTE->crc = $crc; $this->PLTE->crc = $crc;
$this->PLTE->crc32 = unpack('N', $crc)[1]; $this->PLTE->crc32 = unpack('N', $crc)[1];
@@ -163,6 +167,8 @@ next_chunk:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x74\x52\x4e\x53") { // tRNS } else if ($nextChunk === "\x74\x52\x4e\x53") { // tRNS
$this->tRNS = new \stdClass; $this->tRNS = new \stdClass;
$this->tRNS->name = 'tRNS';
$this->tRNS->length = $length;
$vals = []; $vals = [];
$bytes = $this->IHDR->colorType === 2 ? 3 : 1; $bytes = $this->IHDR->colorType === 2 ? 3 : 1;
for ($i = 0; $i < $length; $i += $bytes) { for ($i = 0; $i < $length; $i += $bytes) {
@@ -181,6 +187,8 @@ next_chunk:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x63\x48\x52\x4d") { // cHRM } else if ($nextChunk === "\x63\x48\x52\x4d") { // cHRM
$this->cHRM = new \stdClass; $this->cHRM = new \stdClass;
$this->cHRM->name = 'cHRM';
$this->cHRM->length = $length;
$this->cHRM->whitePointX = unpack('N', substr($data, 0, 4))[1]; $this->cHRM->whitePointX = unpack('N', substr($data, 0, 4))[1];
$this->cHRM->whitePointY = unpack('N', substr($data, 4, 4))[1]; $this->cHRM->whitePointY = unpack('N', substr($data, 4, 4))[1];
$this->cHRM->redX = unpack('N', substr($data, 8, 4))[1]; $this->cHRM->redX = unpack('N', substr($data, 8, 4))[1];
@@ -196,6 +204,8 @@ next_chunk:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x67\x41\x4d\x41") { // gAMA } else if ($nextChunk === "\x67\x41\x4d\x41") { // gAMA
$this->gAMA = new \stdClass; $this->gAMA = new \stdClass;
$this->gAMA->name = 'gAMA';
$this->gAMA->length = $length;
$this->gAMA->value = unpack('N', $data)[1]; $this->gAMA->value = unpack('N', $data)[1];
$this->gAMA->crc = $crc; $this->gAMA->crc = $crc;
$this->gAMA->crc32 = unpack('N', $crc)[1]; $this->gAMA->crc32 = unpack('N', $crc)[1];
@@ -204,6 +214,8 @@ next_chunk:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x69\x43\x43\x50") { // iCCP } else if ($nextChunk === "\x69\x43\x43\x50") { // iCCP
$this->iCCP = new \stdClass; $this->iCCP = new \stdClass;
$this->iCCP->name = 'iCCP';
$this->iCCP->length = $length;
$nullPos = strpos($data, "\0"); $nullPos = strpos($data, "\0");
if ($nullPos === false || $nullPos < 1 || $nullPos > 79) { if ($nullPos === false || $nullPos < 1 || $nullPos > 79) {
@@ -235,11 +247,18 @@ iccp_crc:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x73\x42\x49\x54") { // sBIT } else if ($nextChunk === "\x73\x42\x49\x54") { // sBIT
$this->sBIT = new \stdClass; $this->sBIT = new \stdClass;
kys('sBIT'); $this->sBIT->name = 'sBIT';
$this->sBIT->length = $length;
kys(['sBIT', $length, bin2hex($data)]);
$this->sBIT->crc = $crc;
$this->sBIT->crc32 = unpack('N', $crc)[1];
$this->sBIT->crcHex = bin2hex($crc);
$png->significantBits = $this->sBIT; $png->significantBits = $this->sBIT;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x73\x52\x47\x42") { // sRGB } else if ($nextChunk === "\x73\x52\x47\x42") { // sRGB
$this->sRGB = new \stdClass; $this->sRGB = new \stdClass;
$this->sRGB->name = 'sRGB';
$this->sRGB->length = $length;
$this->sRGB->intent = ord($data); $this->sRGB->intent = ord($data);
$name = [ $name = [
0 => 'Perceptual', 0 => 'Perceptual',
@@ -255,34 +274,85 @@ iccp_crc:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x63\x49\x43\x50") { // cICP } else if ($nextChunk === "\x63\x49\x43\x50") { // cICP
$this->cICP = new \stdClass; $this->cICP = new \stdClass;
kys('cICP'); $this->cICP->name = 'cICP';
$this->cICP->length = $length;
$this->cICP->colorPrimaries = ord($data[0]);
$this->cICP->transferFunction = ord($data[1]);
$this->cICP->matrixCoefficients = ord($data[2]);
$this->cICP->videoFullRangeFlag = ord($data[3]);
$this->cICP->crc = $crc;
$this->cICP->crc32 = unpack('N', $crc)[1];
$this->cICP->crcHex = bin2hex($crc);
$png->videoSignalTypeId = $this->cICP; $png->videoSignalTypeId = $this->cICP;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x6d\x44\x43\x56") { // mDCV } else if ($nextChunk === "\x6d\x44\x43\x56") { // mDCV
$this->mDCV = new \stdClass; $this->mDCV = new \stdClass;
kys('mDCV'); $this->mDCV->name = 'mDCV';
$this->mDCV->length = $length;
$u16 = fn($off) => unpack('n', substr($data, $off, 2))[1];
$u32 = fn($off) => unpack('N', substr($data, $off, 4))[1];
$this->mDCV->primaries = [
'red' => ['x' => $u16(0), 'y' => $u16(2)],
'green' => ['x' => $u16(4), 'y' => $u16(6)],
'blue' => ['x' => $u16(8), 'y' => $u16(10)],
];
$this->mDCV->whitePoint = [
'x' => $u16(12),
'y' => $u16(14),
];
$this->mDCV->luminance = [
'max' => $u16(16) * 0.0001,
'min' => $u16(20) * 0.0001,
'maxRaw' => $u16(16),
'minRaw' => $u16(20),
];
$this->mDCV->crc = $crc;
$this->mDCV->crc32 = unpack('N', $crc)[1];
$this->mDCV->crcHex = bin2hex($crc);
$png->masterDisplayColorVolume = $this->mDCV; $png->masterDisplayColorVolume = $this->mDCV;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x63\x4c\x4c\x49") { // cLLI } else if ($nextChunk === "\x63\x4c\x4c\x49") { // cLLI
$this->cLLI = new \stdClass; $this->cLLI = new \stdClass;
kys('cLLI'); $this->cLLI->name = 'cLLI';
$this->cLLI->length = $length;
$this->cLLI->maxCCL = unpack('N', substr($data, 0, 4))[1] * 0.0001;
$this->cLLI->maxCCLRaw = unpack('N', substr($data, 0, 4))[1];
$this->cLLI->maxFALL = unpack('N', substr($data, 4, 4))[1] * 0.0001;
$this->cLLI->maxFALLRaw = unpack('N', substr($data, 4, 4))[1];
$this->cLLI->crc = $crc;
$this->cLLI->crc32 = unpack('N', $crc)[1];
$this->cLLI->crcHex = bin2hex($crc);
$png->contentLightLevelInfo = $this->cLLI; $png->contentLightLevelInfo = $this->cLLI;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x74\x45\x58\x74") { // tEXt } else if ($nextChunk === "\x74\x45\x58\x74") { // tEXt
if (!isset($png->textualData)) $png->textualData = [];
$this->tEXt = new \stdClass; $this->tEXt = new \stdClass;
$this->tEXt->name = 'tEXt';
$this->tEXt->length = $length;
$this->tEXt->text = $data; $this->tEXt->text = $data;
$this->tEXt->crc = $crc; $this->tEXt->crc = $crc;
$this->tEXt->crc32 = unpack('N', $crc)[1]; $this->tEXt->crc32 = unpack('N', $crc)[1];
$this->tEXt->crcHex = bin2hex($crc); $this->tEXt->crcHex = bin2hex($crc);
$png->textualData = $this->tEXt; $png->textualData[] = $this->tEXt;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x69\x54\x58\x74") { // iTXt } else if ($nextChunk === "\x69\x54\x58\x74") { // iTXt
if (!isset($png->internationalTextualData)) $png->textualData = [];
$this->iTXt = new \stdClass; $this->iTXt = new \stdClass;
kys('iTXt'); $this->iTXt->name = 'iTXt';
$png->internationalTextualData = $this->iTXt; $this->iTXt->length = $length;
kys(['iTXt', $length, bin2hex($data)]);
$this->iTXt->crc = $crc;
$this->iTXt->crc32 = unpack('N', $crc)[1];
$this->iTXt->crcHex = bin2hex($crc);
$png->internationalTextualData[] = $this->iTXt;
kys($png);
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x7a\x54\x58\x74") { // zTXt } else if ($nextChunk === "\x7a\x54\x58\x74") { // zTXt
if (!isset($png->compressedTextualData)) $png->textualData = [];
$this->zTXt = new \stdClass; $this->zTXt = new \stdClass;
$this->zTXt->name = 'zTXt';
$this->zTXt->length = $length;
$nullPos = strpos($data, "\0"); $nullPos = strpos($data, "\0");
if ($nullPos === false || $nullPos < 1 || $nullPos > 79) { if ($nullPos === false || $nullPos < 1 || $nullPos > 79) {
@@ -325,10 +395,12 @@ zTXt_crc:
$this->zTXt->crc = $crc; $this->zTXt->crc = $crc;
$this->zTXt->crc32 = unpack('N', $crc)[1]; $this->zTXt->crc32 = unpack('N', $crc)[1];
$this->zTXt->crcHex = bin2hex($crc); $this->zTXt->crcHex = bin2hex($crc);
$png->compressedTextualData = $this->zTXt; $png->compressedTextualData[] = $this->zTXt;
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x62\x4b\x47\x44") { // bKGD } else if ($nextChunk === "\x62\x4b\x47\x44") { // bKGD
$this->bKGD = new \stdClass; $this->bKGD = new \stdClass;
$this->bKGD->name = 'bKGD';
$this->bKGD->length = $length;
if ($this->IHDR->colorType === 2 || $this->IHDR->colorType === 6) { if ($this->IHDR->colorType === 2 || $this->IHDR->colorType === 6) {
$r = (ord($data[0]) << 8) | ord($data[1]); $r = (ord($data[0]) << 8) | ord($data[1]);
$g = (ord($data[2]) << 8) | ord($data[3]); $g = (ord($data[2]) << 8) | ord($data[3]);
@@ -363,11 +435,19 @@ zTXt_crc:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x68\x49\x53\x54") { // hIST } else if ($nextChunk === "\x68\x49\x53\x54") { // hIST
$this->hIST = new \stdClass; $this->hIST = new \stdClass;
kys('hIST'); $this->hIST->name = 'hIST';
$this->hIST->length = $length;
kys(['hIST', $length, bin2hex($data)]);
$this->hIST->crc = $crc;
$this->hIST->crc32 = unpack('N', $crc)[1];
$this->hIST->crcHex = bin2hex($crc);
$png->imageHistogram = $this->hIST; $png->imageHistogram = $this->hIST;
kys($png);
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x70\x48\x59\x73") { // pHYs } else if ($nextChunk === "\x70\x48\x59\x73") { // pHYs
$this->pHYs = new \stdClass; $this->pHYs = new \stdClass;
$this->pHYs->name = 'pHYs';
$this->pHYs->length = $length;
$this->pHYs->x = unpack('N', substr($data, 0, 4))[1]; $this->pHYs->x = unpack('N', substr($data, 0, 4))[1];
$this->pHYs->y = unpack('N', substr($data, 4, 4))[1]; $this->pHYs->y = unpack('N', substr($data, 4, 4))[1];
$unit = [ $unit = [
@@ -383,11 +463,19 @@ zTXt_crc:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x73\x50\x4c\x54") { // sPLT } else if ($nextChunk === "\x73\x50\x4c\x54") { // sPLT
$this->sPLT = new \stdClass; $this->sPLT = new \stdClass;
kys('sPLT'); $this->sPLT->name = 'sPLT';
$this->sPLT->length = $length;
kys(['sPLT', $length, bin2hex($data)]);
$this->sPLT->crc = $crc;
$this->sPLT->crc32 = unpack('N', $crc)[1];
$this->sPLT->crcHex = bin2hex($crc);
$png->suggestedPalette = $this->sPLT; $png->suggestedPalette = $this->sPLT;
kys($png);
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x65\x58\x49\x66") { // eXIf } else if ($nextChunk === "\x65\x58\x49\x66") { // eXIf
$this->eXIf = new \stdClass; $this->eXIf = new \stdClass;
$this->eXIf->name = 'eXIf';
$this->eXIf->length = $length;
if (strlen($data) < 8) { if (strlen($data) < 8) {
$this->eXIf->valid = false; $this->eXIf->valid = false;
@@ -423,11 +511,19 @@ exif_crc:
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x74\x49\x4d\x45") { // tIME } else if ($nextChunk === "\x74\x49\x4d\x45") { // tIME
$this->tIME = new \stdClass; $this->tIME = new \stdClass;
kys('tIME'); $this->tIME->name = 'tIME';
$this->tIME->length = $length;
kys(['tIME', $length, bin2hex($data)]);
$this->tIME->crc = $crc;
$this->tIME->crc32 = unpack('N', $crc)[1];
$this->tIME->crcHex = bin2hex($crc);
$png->lastModificationTime = $this->tIME; $png->lastModificationTime = $this->tIME;
kys($png);
goto next_chunk; goto next_chunk;
} else if ($nextChunk === "\x61\x63\x54\x4c") { // acTL } else if ($nextChunk === "\x61\x63\x54\x4c") { // acTL
$this->acTL = new \stdClass; $this->acTL = new \stdClass;
$this->acTL->name = 'acTL';
$this->acTL->length = $length;
$this->acTL->numFrames = unpack('N', substr($data, 0, 4))[1]; $this->acTL->numFrames = unpack('N', substr($data, 0, 4))[1];
$this->acTL->numPlays = unpack('N', substr($data, 4, 4))[1]; $this->acTL->numPlays = unpack('N', substr($data, 4, 4))[1];
$this->acTL->crc = $crc; $this->acTL->crc = $crc;
@@ -439,6 +535,8 @@ exif_crc:
if ($this->currentFrame !== null) $this->frames[] = (object)$this->currentFrame; if ($this->currentFrame !== null) $this->frames[] = (object)$this->currentFrame;
$this->currentFrame = [ $this->currentFrame = [
'name' => 'fcTL',
'length' => $length,
'sequenceNumber' => unpack('N', substr($data, 0, 4))[1], 'sequenceNumber' => unpack('N', substr($data, 0, 4))[1],
'width' => unpack('N', substr($data, 4, 4))[1], 'width' => unpack('N', substr($data, 4, 4))[1],
'height' => unpack('N', substr($data, 8, 4))[1], 'height' => unpack('N', substr($data, 8, 4))[1],
@@ -467,6 +565,8 @@ exif_crc:
} }
$compressedData = ($nextChunk === "\x66\x64\x41\x54") ? substr($data, 4) : $data; $compressedData = ($nextChunk === "\x66\x64\x41\x54") ? substr($data, 4) : $data;
$this->currentFrame['name'] = 'fdAT';
$this->currentFrame['length'] = $length;
$this->currentFrame['data'] = $compressedData; $this->currentFrame['data'] = $compressedData;
$this->currentFrame['dataBase64'] = base64_encode($compressedData); $this->currentFrame['dataBase64'] = base64_encode($compressedData);
$this->currentFrame['dataHex'] = bin2hex($compressedData); $this->currentFrame['dataHex'] = bin2hex($compressedData);
@@ -477,6 +577,7 @@ exif_crc:
$this->currentFrame['crcHex'] = bin2hex($crc); $this->currentFrame['crcHex'] = bin2hex($crc);
if ($nextChunk === "\x49\x44\x41\x54") { if ($nextChunk === "\x49\x44\x41\x54") {
$this->currentFrame['name'] = 'IDAT';
$this->frames[] = (object)$this->currentFrame; $this->frames[] = (object)$this->currentFrame;
$this->currentFrame = null; $this->currentFrame = null;
} }