diff --git a/config/config.sample.php b/config/config.sample.php index 2d76c7f..22f045f 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -29,4 +29,5 @@ define('ACTIVITYPUB_ENABLED', false); define('CURL_ENABLED', true); define('AUTH_ENABLED', false); define('AUTH_REGISTER_ENABLED', false); -define('COPYRIGHT_YEAR', '2018-'.date('Y')); \ No newline at end of file +define('COPYRIGHT_YEAR', '2018-'.date('Y')); +define('DEBUG_MODE', false); \ No newline at end of file diff --git a/newpost.php b/newpost.php index c25066f..770859d 100644 --- a/newpost.php +++ b/newpost.php @@ -2,7 +2,8 @@ if (!isset($argv[1])) die('usage: php newpost.php [slug]'); if (file_exists("blog/{$argv[1]}.md")) die("エラー: ファイル「blog/{$argv[1]}.md」は既に存在します。\n"); -define('CURL_ENABLED', false); // 黙れ・・・ +define('ROOT', realpath(__DIR__)); +require_once ROOT.'/config/config.php'; include('util.php'); $post = fopen("blog/{$argv[1]}.md", "w"); fwrite($post, "title: 【】\n"); @@ -13,5 +14,4 @@ fwrite($post, "thumbnail: \n"); fwrite($post, "thumborient: center\n"); fwrite($post, "category: \n"); fwrite($post, "----\n"); -fclose($post); -?> +fclose($post); \ No newline at end of file diff --git a/route.php b/route.php index 539659f..2ffa1e8 100644 --- a/route.php +++ b/route.php @@ -1,6 +1,4 @@ Gif::class, + 'image/jpeg' => Jpeg::class, + 'image/png' => Png::class, + 'image/x-tga' => Targa::class, + ]; + public function __construct(string $file) { $file = ROOT.$file; - // $file = ROOT.'/public/static/article/mock-screenshot.jpg'; + // $file = ROOT.'/public/static/article/mock-screenshot.tga'; if (!file_exists($file)) return; $fp = fopen($file, 'rb'); if (!$fp) return; - $extBytes = fread($fp, 15); fclose($fp); + $this->bytes = filesize($file); $this->size = $this->formatBytes($this->bytes); $this->extension = pathinfo($file, PATHINFO_EXTENSION); $this->filename = str_replace('.'.$this->extension, '', array_last(explode('/', $file))); - if ($extBytes === false) return; - else if (substr($extBytes, 0, 3) === "\xff\xd8\xff") { - $this->type = 'image/jpeg'; - $this->parseJPEG($file); - } 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'; - // $this->parseWEBP($file); - } else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") { - $this->type = 'image/bmp'; - // $this->parseBMP($file); - } else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") { - $this->type = 'image/png'; - $this->fullInfo = $this->parsePNG($file); - } 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'; - $this->fullInfo = $this->parseGIF($file); - } else if (str_ends_with($file, '.tga')) { - $this->type = 'image/tga'; - // $this->parseTGA($file); - } + if (substr($extBytes, 0, 3) === "\xff\xd8\xff") $this->type = 'image/jpeg'; + 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'; + else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") $this->type = 'image/bmp'; + else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") $this->type = 'image/png'; + 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'; + else if (str_ends_with($file, '.tga')) $this->type = 'image/x-tga'; + + $this->fullInfo = $this->createHandler($this->type)->parse($file); + $this->fullInfo->type = $this->type; + $this->fullInfo->size = $this->size; + $this->fullInfo->bytes = $this->bytes; } - private function parseJPEG(string $file): \stdClass { - $jpg = new \stdClass; - $fp = fopen($file, 'rb'); + /////////////// - $i = 0; - while (!feof($fp)) { - $byte = fread($fp, 1); - if ($byte !== "\xFF") continue; - - $marker = ord(fread($fp, 1)); - if ($marker >= 0xC0 && $marker <= 0xC3) { // 0xC0 = SOF0, 0xC1 = SOF1, 0xC2 = SOF2, 0xC3 = SOF3 - fread($fp, 2); - $this->bpp = ord(fread($fp, 1)); - // n = ビッグエンディアン(2ビット) - $this->height = unpack('n', fread($fp, 2))[1]; - $this->width = unpack('n', fread($fp, 2))[1]; - $jpg->width = $this->width; - $jpg->height = $this->height; - $jpg->bpp = $this->bpp; - } else if ($marker == 0xC4) { // DHT (Huffmanテーブル(複数可)) - // - } else if ($marker == 0xDB) { // DQT (量子化テーブル(複数可)) - // - } else if ($marker == 0xDD) { // DRI (リセット期間) - } else if ($marker == 0xDA) { // SOS(画像開始) - } else if ($marker >= 0xD0 && $marker <= 0xD7) { // RST - } else if ($marker >= 0xE1 && $marker <= 0xEF) { // APP - } else if ($marker == 0xFE) { // COM - } else if ($marker == 0xD9) { // EOI(画像終了) - break; - } - } - - // kys($jpg); - - $i++; - fclose($fp); - return $jpg; - } - - /** - * TODO: 現在、IHDRヘッダーのみを受け取る - */ - private function parsePNG(string $file): \stdClass { - $png = new \stdClass(); - $fp = fopen($file, 'rb'); - - fread($fp, 8); // マジックのスキップ - unpack('N', fread($fp, 4))[1]; - $type = fread($fp, 4); - if ($type !== 'IHDR') { - fclose($fp); - return $png; - } - - // IHDRデータ - $data = fread($fp, 13); - - // N = ビッグエンディアン(4ビット) - $this->width = unpack('N', substr($data, 0, 4))[1]; - $this->height = unpack('N', substr($data, 4, 4))[1]; - $this->bitDepth = ord($data[8]); - $this->numColors = 1 << $this->bitDepth; - $this->colorType = ord($data[9]); - $this->compression = 'DEFLATE'; - $this->filter = ord($data[11]); - $this->interlace = ord($data[12]); // 0 = なし, 1 = Adam7 - $this->bpp = $this->bitDepth * $this->getSamplesPerPixel($this->colorType); - $this->delayTime = 0; - $this->hasAlpha = ($this->colorType === 4 || $this->colorType === 6); - - $png->width = $this->width; - $png->height = $this->height; - $png->bitDepth = $this->bitDepth; - $png->numColors = $this->numColors; - $png->colorType = $this->colorType; - $png->compression = $this->compression; - $png->filter = $this->filter; - $png->interlace = $this->interlace; - $png->bpp = $this->bpp; - $png->delayTime = $this->delayTime; - $png->hasAlpha = $this->hasAlpha; - - fclose($fp); - - return $png; - } - - /** - * TODO: 現在、インタレースした画像が未対応です。 - */ - private function parseGIF(string $file): \stdClass { - $gif = new \stdClass(); - $fp = fopen($file, 'rb'); - $magic = fread($fp, 6); // マジックのスキップ - - // v = リトルエンディアン(2ビット) - $this->width = unpack('v', fread($fp, 2))[1]; - $this->height = unpack('v', fread($fp, 2))[1]; - $packed = ord(fread($fp, 1)); - $this->bitDepth = ($packed & 0x07) + 1; - $this->numColors = 1 << $this->bitDepth; - $hasGct = $this->bitDepth === 8; - - $gif->width = $this->width; - $gif->height = $this->height; - $gif->bitDepth = $this->bitDepth; - $gif->numColors = $this->numColors; - if ($magic === 'GIF87a' || !$hasGct) return $gif; // それ以外情報がない - - $this->bgColor = (int)bin2hex(fread($fp, 1)); - $gif->bgColor = $this->bgColor; - bin2hex(fread($fp, 1)); // いつでも 0:0? - - $gceLen = 0; - while (!feof($fp)) { - $skip = bin2hex(fread($fp, 3)); - $color = new RGB((int)($skip[0].$skip[1]), (int)($skip[2].$skip[3]), (int)($skip[4].$skip[5])); - $this->globalColorTable[] = $color->rgb; - if ($skip[0].$skip[1] === '21' && $skip[2].$skip[3] === 'f9') { - $gceLen = (int)($skip[4].$skip[5]); - break; - } - } - - // GCE開始 - $this->hasAlpha = (ord(fread($fp, 1)) & 0x01) === 0x01; - $this->delayTime = (unpack('v', fread($fp, 2))[1]) / 100.0; - $this->numTransparentPixels = ord(fread($fp, 1)); - $gif->hasAlpha = $this->hasAlpha; - $gif->delayTime = $this->delayTime; - $gif->numTransparentPixels = $this->numTransparentPixels; - fread($fp, 1); // GCE終了 - fread($fp, 1); // 画像指定子開始 - fread($fp, 4); // 画面のXとY、いつでも(0, 0) - fread($fp, 4); // 画像のサイズ、関数の開始で既に保存した - $pack = ord(fread($fp, 1)); - $this->interlace = ($pack & 0x40); // 画像のサイズ、関数の開始で既に保存した - - $this->lzwMinCodeSize = ord(fread($fp, 1)); // 画像データ開始 - $gif->lzwMinCodeSize = $this->lzwMinCodeSize; - $imageData = ''; - while (true) { - $blockSize = ord(fread($fp, 1)); - if ($blockSize === 0) break; - $imageData .= fread($fp, 1); - } - - $this->imageData = base64_encode($imageData); - $this->colorType = 3; - $this->compression = 'LZW'; - $gif->colorType = $this->colorType; - $gif->compression = $this->compression; - $gif->globalColorTable = $this->globalColorTable; - $gif->imageData = $this->imageData; - - fclose($fp); - return $gif; + private function createHandler(string $ext): ImageInterface { + $class = $this->supported[$ext] ?? null; + if (!$class) throw new \RuntimeException("形式のハンドルが存在しない: {$ext}"); + return new $class(); } private function formatBytes(int $size, int $precision = 2): string { @@ -240,15 +76,4 @@ class Image { for ($i = 0; $size > 1024 && $i < count($units) - 1; ++$i) $size /= 1024; return round($size, $precision).' '.$units[$i]; } - - private function getSamplesPerPixel(int $colorType): int { - return match($colorType) { - 0 => 1, // グレースケール - 2 => 3, // RGB - 3 => 1, // 指標した色(ペレット) - 4 => 2, // グレースケール+アルファ - 6 => 4, // RGBA - default => 1, - }; - } } \ No newline at end of file diff --git a/src/Site/Lib/Image/Gif.php b/src/Site/Lib/Image/Gif.php new file mode 100644 index 0000000..dc10ffa --- /dev/null +++ b/src/Site/Lib/Image/Gif.php @@ -0,0 +1,91 @@ +width = unpack('v', fread($fp, 2))[1]; + $this->height = unpack('v', fread($fp, 2))[1]; + $packed = ord(fread($fp, 1)); + $this->bitDepth = ($packed & 0x07) + 1; + $this->numColors = 1 << $this->bitDepth; + $hasGct = $this->bitDepth === 8; + + $gif->width = $this->width; + $gif->height = $this->height; + $gif->bitDepth = $this->bitDepth; + $gif->numColors = $this->numColors; + if ($magic === 'GIF87a' || !$hasGct) return $gif; // それ以外情報がない + + $this->bgColor = (int)bin2hex(fread($fp, 1)); + $gif->bgColor = $this->bgColor; + bin2hex(fread($fp, 1)); // いつでも 0:0? + + $gceLen = 0; + while (!feof($fp)) { + $skip = bin2hex(fread($fp, 3)); + $color = new RGB((int)($skip[0].$skip[1]), (int)($skip[2].$skip[3]), (int)($skip[4].$skip[5])); + $this->globalColorTable[] = $color->rgb; + if ($skip[0].$skip[1] === '21' && $skip[2].$skip[3] === 'f9') { + $gceLen = (int)($skip[4].$skip[5]); + break; + } + } + + // GCE開始 + $this->hasAlpha = (ord(fread($fp, 1)) & 0x01) === 0x01; + $this->delayTime = (unpack('v', fread($fp, 2))[1]) / 100.0; + $this->numTransparentPixels = ord(fread($fp, 1)); + $gif->hasAlpha = $this->hasAlpha; + $gif->delayTime = $this->delayTime; + $gif->numTransparentPixels = $this->numTransparentPixels; + fread($fp, 1); // GCE終了 + fread($fp, 1); // 画像指定子開始 + fread($fp, 4); // 画面のXとY、いつでも(0, 0) + fread($fp, 4); // 画像のサイズ、関数の開始で既に保存した + $pack = ord(fread($fp, 1)); + $this->interlace = ($pack & 0x40); // 画像のサイズ、関数の開始で既に保存した + + $this->lzwMinCodeSize = ord(fread($fp, 1)); // 画像データ開始 + $gif->lzwMinCodeSize = $this->lzwMinCodeSize; + $imageData = ''; + while (true) { + $blockSize = ord(fread($fp, 1)); + if ($blockSize === 0) break; + $imageData .= fread($fp, 1); + } + + $this->imageData = base64_encode($imageData); + $this->colorType = 3; + $this->compression = 'LZW'; + $gif->colorType = $this->colorType; + $gif->compression = $this->compression; + $gif->globalColorTable = $this->globalColorTable; + $gif->imageData = $this->imageData; + + fclose($fp); + return $gif; + } +} \ No newline at end of file diff --git a/src/Site/Lib/Image/ImageInterface.php b/src/Site/Lib/Image/ImageInterface.php new file mode 100644 index 0000000..a527646 --- /dev/null +++ b/src/Site/Lib/Image/ImageInterface.php @@ -0,0 +1,6 @@ += 0xC0 && $marker <= 0xC3) { // 0xC0 = SOF0, 0xC1 = SOF1, 0xC2 = SOF2, 0xC3 = SOF3 + fread($fp, 2); + $this->bpp = ord(fread($fp, 1)); + // n = ビッグエンディアン(2ビット) + $this->height = unpack('n', fread($fp, 2))[1]; + $this->width = unpack('n', fread($fp, 2))[1]; + $jpg->width = $this->width; + $jpg->height = $this->height; + $jpg->bpp = $this->bpp; + } else if ($marker == 0xC4) { // DHT (Huffmanテーブル(複数可)) + // + } else if ($marker == 0xDB) { // DQT (量子化テーブル(複数可)) + // + } else if ($marker == 0xDD) { // DRI (リセット期間) + } else if ($marker == 0xDA) { // SOS(画像開始) + } else if ($marker >= 0xD0 && $marker <= 0xD7) { // RST + } else if ($marker >= 0xE1 && $marker <= 0xEF) { // APP + } else if ($marker == 0xFE) { // COM + } else if ($marker == 0xD9) { // EOI(画像終了) + break; + } + } + + // kys($jpg); + + $i++; + fclose($fp); + return $jpg; + } +} \ No newline at end of file diff --git a/src/Site/Lib/Image/Png.php b/src/Site/Lib/Image/Png.php new file mode 100644 index 0000000..c5e023d --- /dev/null +++ b/src/Site/Lib/Image/Png.php @@ -0,0 +1,74 @@ +width = unpack('N', substr($data, 0, 4))[1]; + $this->height = unpack('N', substr($data, 4, 4))[1]; + $this->bitDepth = ord($data[8]); + $this->numColors = 1 << $this->bitDepth; + $this->colorType = ord($data[9]); + $this->compression = 'DEFLATE'; + $this->filter = ord($data[11]); + $this->interlace = ord($data[12]); // 0 = なし, 1 = Adam7 + $this->bpp = $this->bitDepth * $this->getSamplesPerPixel($this->colorType); + $this->hasAlpha = ($this->colorType === 4 || $this->colorType === 6); + + $png->width = $this->width; + $png->height = $this->height; + $png->bitDepth = $this->bitDepth; + $png->numColors = $this->numColors; + $png->colorType = $this->colorType; + $png->compression = $this->compression; + $png->filter = $this->filter; + $png->interlace = $this->interlace; + $png->bpp = $this->bpp; + $png->hasAlpha = $this->hasAlpha; + + fclose($fp); + + return $png; + } + + /////////////// + + private function getSamplesPerPixel(int $colorType): int { + return match($colorType) { + 0 => 1, // グレースケール + 2 => 3, // RGB + 3 => 1, // 指標した色(ペレット) + 4 => 2, // グレースケール+アルファ + 6 => 4, // RGBA + default => 1, + }; + } +} \ No newline at end of file diff --git a/src/Site/Lib/Image/Targa.php b/src/Site/Lib/Image/Targa.php new file mode 100644 index 0000000..7f3b1eb --- /dev/null +++ b/src/Site/Lib/Image/Targa.php @@ -0,0 +1,115 @@ + 'NO DATA', + 1 => 'UNCOMPRESSED COLOR-MAPPED IMAGE', + 2 => 'UNCOMPRESSED TRUE COLOR IMAGE', + 3 => 'UNCOMPRESSED GRAYSCALE IMAGE', + 9 => 'RUN-LENGTH ENCODED COLOR-MAPPED IMAGE', + 10 => 'RUN-LENGTH ENCODED TRUE-COLOR IMAGE', + 11 => 'RUN-LENGTH ENCODED GRAYSCALE IMAGE', + 32 => 'HUFFMAN-DELTA-RUN-LENGTH ENCODED COLOR-MAPPED IMAGE', + 33 => 'HUFFMAN-DELTA-RUN-LENGTH-4-PASS-QUADTREE-TYPE PROCESS ENCODED COLOR-MAPPED IMAGE', + ]; + + public function parse(string $file): \stdClass { + $tga = new \stdClass(); + $fp = fopen($file, 'rb'); + + $this->id = ord(fread($fp, 1)); + $this->colorMapType = ord(fread($fp, 1)); // 0 = なし, 1 = あり, 2~127 = Truevision向け, 128~255 = 開発者向け + $this->imageTypeByte = ord(fread($fp, 1)); + $this->imageType = $this->imageTypes[$this->imageTypeByte]; + $tga->id = $this->id; + $tga->colorMapType = $this->colorMapType; + $tga->imageTypeByte = $this->imageTypeByte; + $tga->imageType = $this->imageType; + + $this->colorMapEntryIndex = unpack('v', fread($fp, 2))[1]; + $this->colorMapLength = unpack('v', fread($fp, 2))[1]; + $this->colorMapBits = ord(fread($fp, 1)); + $tga->colorMapEntryIndex = $this->colorMapEntryIndex; + $tga->colorMapLength = $this->colorMapLength; + $tga->colorMapBits = $this->colorMapBits; + + $this->x = unpack('v', fread($fp, 2))[1]; + $this->y = unpack('v', fread($fp, 2))[1]; + $this->width = unpack('v', fread($fp, 2))[1]; + $this->height = unpack('v', fread($fp, 2))[1]; + $this->bitDepth = ord(fread($fp, 1)); + $tga->x = $this->x; + $tga->y = $this->y; + $tga->width = $this->width; + $tga->height = $this->height; + $tga->bitDepth = $this->bitDepth; + + $desc = ord(fread($fp, 1)); + $this->interlace = ($desc & 0xC0) >> 6; + $this->isTopToBottom = ($desc & 0x20) !== 0; + $this->isRightToLeft = ($desc & 0x10) !== 0; + $this->alphaBits = $desc & 0x0F; + $this->hasAlpha = ($this->alphaBits == 8) && in_array($this->imageTypeByte == [2, 3, 10, 11]); + $tga->interlace = $this->interlace; + $tga->isRightToLeft = $this->isRightToLeft; + $tga->isTopToBottom = $this->isTopToBottom; + $tga->alphaBits = $this->alphaBits; + $tga->hasAlpha = $this->hasAlpha; + + $needsXFlip = $tga->isRightToLeft; + $needsYFlip = !$tga->isTopToBottom; + // 0010 0000 + + //--------- + $imageIdBytes = $this->id; + $colorMapBytes = 0; + if ($this->colorMapType === 1 && $this->colorMapLength > 0) { + $bytesPerEntry = (int)($this->colorMapBits / 8); + $colorMapBytes = $this->colorMapLength * $bytesPerEntry; + } + + $this->imageId = $imageIdBytes > 0 ? fread($fp, $imageIdBytes) : ''; + $this->colorMapData = $colorMapBytes > 0 ? fread($fp, $colorMapBytes) : ''; + $tga->imageId = $this->imageId; + $tga->colorMapData = $this->colorMapData; + + if (in_array($this->imageTypeByte, [9, 10, 11])) { // RLE + // TODO + } else if (in_array($this->imageTypeByte, [1, 2, 3])) { // 非圧縮化 + $this->bpp = (int)($this->bitDepth / 8); + $bytes = $this->width * $this->height * $this->bpp; + $this->imageData = fread($fp, $bytes); + $tga->bpp = $this->bpp; + $tga->imageData = $this->imageData; + } + + fclose($fp); + return $tga; + } +} \ No newline at end of file diff --git a/util.php b/util.php index 88b2290..547fcaf 100644 --- a/util.php +++ b/util.php @@ -127,6 +127,7 @@ function uuid(): string { } function kys(mixed $arg): void { + if (!DEBUG_MODE) return; if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false'; if (gettype($arg) == 'array' || gettype($arg) == 'object') { foreach ($arg as $a) { if (gettype($a) == 'boolean') $a = $a === true ? 'true' : 'false'; } @@ -140,6 +141,7 @@ function kys(mixed $arg): void { } function ffs(): void { + if (!DEBUG_MODE) return; echo ''; echo '
FOR
FUCKS
SAKE
'; diff --git a/view/common/header.maron b/view/common/header.maron index 5a51afa..d3e96b3 100644 --- a/view/common/header.maron +++ b/view/common/header.maron @@ -31,7 +31,7 @@ {@ if (isset($meta) && isset($meta->thumbnail) && !empty($meta->thumbnail)) @} - {$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail); $} + {$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail)->fullInfo; $}