画像ライブラリーはクラス化、TGA対応の追加
This commit is contained in:
@@ -30,3 +30,4 @@ define('CURL_ENABLED', true);
|
|||||||
define('AUTH_ENABLED', false);
|
define('AUTH_ENABLED', false);
|
||||||
define('AUTH_REGISTER_ENABLED', false);
|
define('AUTH_REGISTER_ENABLED', false);
|
||||||
define('COPYRIGHT_YEAR', '2018-'.date('Y'));
|
define('COPYRIGHT_YEAR', '2018-'.date('Y'));
|
||||||
|
define('DEBUG_MODE', false);
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
if (!isset($argv[1])) die('usage: php newpost.php [slug]');
|
if (!isset($argv[1])) die('usage: php newpost.php [slug]');
|
||||||
if (file_exists("blog/{$argv[1]}.md")) die("エラー: ファイル「blog/{$argv[1]}.md」は既に存在します。\n");
|
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');
|
include('util.php');
|
||||||
$post = fopen("blog/{$argv[1]}.md", "w");
|
$post = fopen("blog/{$argv[1]}.md", "w");
|
||||||
fwrite($post, "title: 【】\n");
|
fwrite($post, "title: 【】\n");
|
||||||
@@ -14,4 +15,3 @@ fwrite($post, "thumborient: center\n");
|
|||||||
fwrite($post, "category: \n");
|
fwrite($post, "category: \n");
|
||||||
fwrite($post, "----\n");
|
fwrite($post, "----\n");
|
||||||
fclose($post);
|
fclose($post);
|
||||||
?>
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
|
|
||||||
require_once __DIR__.DIRECTORY_SEPARATOR.'/autoload.php';
|
require_once __DIR__.DIRECTORY_SEPARATOR.'/autoload.php';
|
||||||
|
|
||||||
@@ -16,8 +14,13 @@ use Site\Controller\User;
|
|||||||
define('ROOT', realpath(__DIR__));
|
define('ROOT', realpath(__DIR__));
|
||||||
|
|
||||||
include(ROOT.'/config/config.php');
|
include(ROOT.'/config/config.php');
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
}
|
||||||
include(ROOT.'/util.php');
|
include(ROOT.'/util.php');
|
||||||
|
|
||||||
|
|
||||||
$routes = [
|
$routes = [
|
||||||
Route::add('GET', 'blog/{page}', Home::class.'@article'),
|
Route::add('GET', 'blog/{page}', Home::class.'@article'),
|
||||||
Route::add('GET', 'about', Page::class.'@about'),
|
Route::add('GET', 'about', Page::class.'@about'),
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Site\Lib;
|
namespace Site\Lib;
|
||||||
|
|
||||||
|
use Site\Lib\Image\ImageInterface;
|
||||||
|
use Site\Lib\Image\Gif;
|
||||||
|
use Site\Lib\Image\Jpeg;
|
||||||
|
use Site\Lib\Image\Png;
|
||||||
|
use Site\Lib\Image\Targa;
|
||||||
|
|
||||||
class RGB {
|
class RGB {
|
||||||
public int $r;
|
public int $r;
|
||||||
public int $g;
|
public int $g;
|
||||||
@@ -19,220 +25,50 @@ class Image {
|
|||||||
public string $filename = 'UNKNOWN';
|
public string $filename = 'UNKNOWN';
|
||||||
public string $extension = 'UNKNOWN';
|
public string $extension = 'UNKNOWN';
|
||||||
public string $type = 'UNSUPPORTED';
|
public string $type = 'UNSUPPORTED';
|
||||||
public int $width = 0;
|
|
||||||
public int $height = 0;
|
|
||||||
public int $bpp = 0;
|
|
||||||
public int $bitDepth = 0;
|
|
||||||
public int $numColors = 0; // GIF, PNG
|
|
||||||
public int $colorType = 0; // PNG
|
|
||||||
public string $compression = 'NONE'; // GIF, PNG
|
|
||||||
public int $filter = 0; // PNG
|
|
||||||
public int $interlace = 0; // GIF, PNG
|
|
||||||
public bool $hasAlpha = false;
|
|
||||||
public int $bgColor = 0; // GIF
|
|
||||||
public float $delayTime = 0; // GIF
|
|
||||||
public array $globalColorTable = []; // GIF
|
|
||||||
public int $numTransparentPixels = 0; // GIF
|
|
||||||
public int $lzwMinCodeSize = 0; // GIF
|
|
||||||
public string $imageData = '';
|
|
||||||
public string $size = '0 B';
|
public string $size = '0 B';
|
||||||
public int $bytes = 0;
|
public int $bytes = 0;
|
||||||
public \stdClass $fullInfo;
|
public \stdClass $fullInfo;
|
||||||
|
|
||||||
|
private array $supported = [
|
||||||
|
'image/gif' => Gif::class,
|
||||||
|
'image/jpeg' => Jpeg::class,
|
||||||
|
'image/png' => Png::class,
|
||||||
|
'image/x-tga' => Targa::class,
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct(string $file) {
|
public function __construct(string $file) {
|
||||||
$file = ROOT.$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;
|
if (!file_exists($file)) return;
|
||||||
$fp = fopen($file, 'rb');
|
$fp = fopen($file, 'rb');
|
||||||
if (!$fp) return;
|
if (!$fp) return;
|
||||||
|
|
||||||
$extBytes = fread($fp, 15);
|
$extBytes = fread($fp, 15);
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
|
|
||||||
$this->bytes = filesize($file);
|
$this->bytes = filesize($file);
|
||||||
$this->size = $this->formatBytes($this->bytes);
|
$this->size = $this->formatBytes($this->bytes);
|
||||||
$this->extension = pathinfo($file, PATHINFO_EXTENSION);
|
$this->extension = pathinfo($file, PATHINFO_EXTENSION);
|
||||||
$this->filename = str_replace('.'.$this->extension, '', array_last(explode('/', $file)));
|
$this->filename = str_replace('.'.$this->extension, '', array_last(explode('/', $file)));
|
||||||
|
|
||||||
if ($extBytes === false) return;
|
if (substr($extBytes, 0, 3) === "\xff\xd8\xff") $this->type = 'image/jpeg';
|
||||||
else if (substr($extBytes, 0, 3) === "\xff\xd8\xff") {
|
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->type = 'image/jpeg';
|
else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") $this->type = 'image/bmp';
|
||||||
$this->parseJPEG($file);
|
else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") $this->type = 'image/png';
|
||||||
} else if (substr($extBytes, 0, 15) === "\x52\x49\x46\x46\x1a\x28\x00\x00\x57\x45\x42\x50\x56\x50\x38") {
|
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->type = 'image/webp';
|
else if (str_ends_with($file, '.tga')) $this->type = 'image/x-tga';
|
||||||
// $this->parseWEBP($file);
|
|
||||||
} else if (substr($extBytes, 0, 3) === "\x42\x4d\x36") {
|
$this->fullInfo = $this->createHandler($this->type)->parse($file);
|
||||||
$this->type = 'image/bmp';
|
$this->fullInfo->type = $this->type;
|
||||||
// $this->parseBMP($file);
|
$this->fullInfo->size = $this->size;
|
||||||
} else if (substr($extBytes, 0, 6) === "\x89\x50\x4e\x47\x0d\x0a") {
|
$this->fullInfo->bytes = $this->bytes;
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseJPEG(string $file): \stdClass {
|
///////////////
|
||||||
$jpg = new \stdClass;
|
|
||||||
$fp = fopen($file, 'rb');
|
|
||||||
|
|
||||||
$i = 0;
|
private function createHandler(string $ext): ImageInterface {
|
||||||
while (!feof($fp)) {
|
$class = $this->supported[$ext] ?? null;
|
||||||
$byte = fread($fp, 1);
|
if (!$class) throw new \RuntimeException("形式のハンドルが存在しない: {$ext}");
|
||||||
if ($byte !== "\xFF") continue;
|
return new $class();
|
||||||
|
|
||||||
$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 formatBytes(int $size, int $precision = 2): string {
|
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;
|
for ($i = 0; $size > 1024 && $i < count($units) - 1; ++$i) $size /= 1024;
|
||||||
return round($size, $precision).' '.$units[$i];
|
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
91
src/Site/Lib/Image/Gif.php
Normal file
91
src/Site/Lib/Image/Gif.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib\Image;
|
||||||
|
|
||||||
|
use Site\Lib\Image\ImageInterface;
|
||||||
|
|
||||||
|
class Gif implements ImageInterface {
|
||||||
|
public int $width = 0;
|
||||||
|
public int $height = 0;
|
||||||
|
public int $bitDepth = 0;
|
||||||
|
public int $numColors = 0;
|
||||||
|
public string $compression = 'NONE';
|
||||||
|
public int $interlace = 0;
|
||||||
|
public int $bgColor = 0;
|
||||||
|
public float $delayTime = 0;
|
||||||
|
public array $globalColorTable = [];
|
||||||
|
public int $numTransparentPixels = 0;
|
||||||
|
public int $lzwMinCodeSize = 0;
|
||||||
|
public string $imageData = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 現在、インタレースした画像が未対応です。
|
||||||
|
*/
|
||||||
|
public function parse(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/Site/Lib/Image/ImageInterface.php
Normal file
6
src/Site/Lib/Image/ImageInterface.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib\Image;
|
||||||
|
|
||||||
|
interface ImageInterface {
|
||||||
|
public function parse(string $file): \stdClass;
|
||||||
|
}
|
||||||
54
src/Site/Lib/Image/Jpeg.php
Normal file
54
src/Site/Lib/Image/Jpeg.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib\Image;
|
||||||
|
|
||||||
|
use Site\Lib\Image\ImageInterface;
|
||||||
|
|
||||||
|
class Jpeg implements ImageInterface {
|
||||||
|
public int $width = 0;
|
||||||
|
public int $height = 0;
|
||||||
|
public int $bpp = 0;
|
||||||
|
public string $imageData = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 現在、IHDRヘッダーのみを受け取る
|
||||||
|
*/
|
||||||
|
public function parse(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/Site/Lib/Image/Png.php
Normal file
74
src/Site/Lib/Image/Png.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib\Image;
|
||||||
|
|
||||||
|
use Site\Lib\Image\ImageInterface;
|
||||||
|
|
||||||
|
class Png implements ImageInterface {
|
||||||
|
public int $width = 0;
|
||||||
|
public int $height = 0;
|
||||||
|
public int $bpp = 0;
|
||||||
|
public int $bitDepth = 0;
|
||||||
|
public int $numColors = 0;
|
||||||
|
public int $colorType = 0;
|
||||||
|
public string $compression = 'NONE';
|
||||||
|
public int $filter = 0;
|
||||||
|
public int $interlace = 0;
|
||||||
|
public bool $hasAlpha = false;
|
||||||
|
public string $imageData = '';
|
||||||
|
|
||||||
|
public function parse(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->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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/Site/Lib/Image/Targa.php
Normal file
115
src/Site/Lib/Image/Targa.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib\Image;
|
||||||
|
|
||||||
|
use Site\Lib\Image\ImageInterface;
|
||||||
|
|
||||||
|
class Targa implements ImageInterface {
|
||||||
|
public int $id = 0;
|
||||||
|
public int $colorMapType = 0;
|
||||||
|
public int $imageTypeByte = 0;
|
||||||
|
public string $imageType = '';
|
||||||
|
public int $colorMapEntryIndex = 0;
|
||||||
|
public int $colorMapLength = 0;
|
||||||
|
public int $colorMapBits = 0;
|
||||||
|
public int $x = 0;
|
||||||
|
public int $y = 0;
|
||||||
|
public int $width = 0;
|
||||||
|
public int $height = 0;
|
||||||
|
public int $bitDepth = 0;
|
||||||
|
public int $bpp = 0;
|
||||||
|
public int $interlace = 0;
|
||||||
|
public int $isRightToLeft = 0; // 0 = 左→右, 1 = 右→左
|
||||||
|
public int $isTopToBottom = 0; // 0 = 下→上, 1 = 上→下
|
||||||
|
public int $alphaBits = 0;
|
||||||
|
public bool $hasAlpha = false;
|
||||||
|
//--------
|
||||||
|
public string $imageId = '';
|
||||||
|
public string $colorMapData = '';
|
||||||
|
public string $imageData = '';
|
||||||
|
|
||||||
|
private array $imageTypes = [
|
||||||
|
0 => '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;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
util.php
2
util.php
@@ -127,6 +127,7 @@ function uuid(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function kys(mixed $arg): void {
|
function kys(mixed $arg): void {
|
||||||
|
if (!DEBUG_MODE) return;
|
||||||
if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false';
|
if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false';
|
||||||
if (gettype($arg) == 'array' || gettype($arg) == 'object') {
|
if (gettype($arg) == 'array' || gettype($arg) == 'object') {
|
||||||
foreach ($arg as $a) { if (gettype($a) == 'boolean') $a = $a === true ? 'true' : 'false'; }
|
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 {
|
function ffs(): void {
|
||||||
|
if (!DEBUG_MODE) return;
|
||||||
echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
|
echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
|
||||||
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>';
|
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>';
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
{@ if (isset($meta) && isset($meta->thumbnail) && !empty($meta->thumbnail)) @}
|
{@ if (isset($meta) && isset($meta->thumbnail) && !empty($meta->thumbnail)) @}
|
||||||
<meta property="og:image" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
<meta property="og:image" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
||||||
<meta property="og:image:secure_url" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
<meta property="og:image:secure_url" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
||||||
{$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail); $}
|
{$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail)->fullInfo; $}
|
||||||
<meta property="og:image:type" content="{{ $imgspec->type }}" />
|
<meta property="og:image:type" content="{{ $imgspec->type }}" />
|
||||||
<meta property="og:image:width" content="{{ $imgspec->width }}" />
|
<meta property="og:image:width" content="{{ $imgspec->width }}" />
|
||||||
<meta property="og:image:height" content="{{ $imgspec->height }}" />
|
<meta property="og:image:height" content="{{ $imgspec->height }}" />
|
||||||
|
|||||||
Reference in New Issue
Block a user