画像パーシングライブラリーの追加
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
サンプル:[https://lbdemo.technicalsuwako.moe/](https://lbdemo.technicalsuwako.moe/)
|
||||
|
||||
## Little Beast とは?
|
||||
Little Beast は PHP 8.3 以上向けのフレームワークで、076.moe(ゲーム開発会社)と technicalsuwako.moe(社長のブログ)向けに作られました。\
|
||||
Little Beast は PHP 8.5 以上向けのフレームワークで、076.moe(ゲーム開発会社)と technicalsuwako.moe(社長のブログ)向けに作られました。\
|
||||
メイン考え方は「必要な物だけ拡張し、不要な物は削除する」です。\
|
||||
各コア機能はライブラリに分割されている為、必要な物だけを選び易い設計になっています。\
|
||||
全てのモジュールはゼロから書かれており、データベースは一切必要ありません。
|
||||
|
||||
@@ -7,7 +7,7 @@ Simple, Pragmatic, Anti-bloat
|
||||
Demo installation: [https://lbdemo.technicalsuwako.moe/](https://lbdemo.technicalsuwako.moe/)
|
||||
|
||||
## What is Little Beast?
|
||||
Little Beast is a PHP 8.3 or above framework made for 076.moe (game developer company) and technicalsuwako.moe (CEO's blog).\
|
||||
Little Beast is a PHP 8.5 or above framework made for 076.moe (game developer company) and technicalsuwako.moe (CEO's blog).\
|
||||
The core mentality is to extend what you need, and remove what you don't need.\
|
||||
Each core feature is split into libraries, so it is easy to choose what you need.\
|
||||
All modules are written from scratch, nothing is to be require a database.
|
||||
|
||||
254
src/Site/Lib/Image.php
Normal file
254
src/Site/Lib/Image.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
namespace Site\Lib;
|
||||
|
||||
class RGB {
|
||||
public int $r;
|
||||
public int $g;
|
||||
public int $b;
|
||||
public array $rgb;
|
||||
|
||||
public function __construct(int $r, int $g, int $b) {
|
||||
$this->r = $r;
|
||||
$this->g = $g;
|
||||
$this->b = $b;
|
||||
$this->rgb = [$r, $g, $b];
|
||||
}
|
||||
}
|
||||
|
||||
class Image {
|
||||
public string $filename = 'UNKNOWN';
|
||||
public string $extension = 'UNKNOWN';
|
||||
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 int $bytes = 0;
|
||||
public \stdClass $fullInfo;
|
||||
|
||||
public function __construct(string $file) {
|
||||
$file = ROOT.$file;
|
||||
// $file = ROOT.'/public/static/article/mock-screenshot.jpg';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 formatBytes(int $size, int $precision = 2): string {
|
||||
$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
5
util.php
5
util.php
@@ -380,4 +380,9 @@ function countmatch(string $str): bool {
|
||||
$sum = $numUpper + $numLower + $numDigit + $numSymbol;
|
||||
|
||||
return $len == $sum;
|
||||
}
|
||||
|
||||
function getImageInfo(string $url): \Site\Lib\Image {
|
||||
$img = new \Site\Lib\Image($url);
|
||||
return $img;
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
{@ if (isset($meta->author)) @}
|
||||
<meta name="author" content="{{ $meta->author }}" />
|
||||
{@ endif @}
|
||||
{@ if (isset($meta->thumbnail)) @}
|
||||
{@ if (sset($meta->thumbnail) && !empty($meta->thumbnail)) @}
|
||||
<meta name="thumbnail" content="/static/{{ $meta->thumbnail }}" />
|
||||
{@ endif @}
|
||||
{@ endif @}
|
||||
@@ -24,19 +24,28 @@
|
||||
|
||||
<meta property="og:title" content="{{ SITEINFO['title'] }}: {{ $pagetit }}" />
|
||||
<meta property="og:description" content="{{ $description }}" />
|
||||
<meta property="og:locale" content="{{ str_contains($_SERVER['REQUEST_URI'], '/enblog') ? 'en_US' : 'ja_JP' }}" />
|
||||
<meta property="og:site_name" content="{{ SITEINFO['title'] }}" />
|
||||
<meta property="og:type" content="{{ isset($meta) && isset($meta->thumbnail) ? 'article' : 'website' }}" />
|
||||
<meta property="og:url" content="{{ isset($_SERVER['REQUEST_URI']) ? 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] : 'https://'.$_SERVER['HTTP_HOST'] }}" />
|
||||
{@ if (isset($meta) && isset($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:secure_url" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
||||
{$ $imgspec = getImageInfo('/public/static/article/'.$meta->thumbnail); $}
|
||||
<meta property="og:image:type" content="{{ $imgspec->type }}" />
|
||||
<meta property="og:image:width" content="{{ $imgspec->width }}" />
|
||||
<meta property="og:image:height" content="{{ $imgspec->height }}" />
|
||||
<meta property="og:image:alt" content="{{ $meta->title }}" />
|
||||
{@ endif @}
|
||||
|
||||
{@ if (TWITTER_HANDLE != '') @}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:card" content="{{ TWITTER_HANDLE }}" />
|
||||
<meta name="twitter:site" content="{{ TWITTER_HANDLE }}" />
|
||||
<meta name="twitter:creator" content="{{ TWITTER_HANDLE }}" />
|
||||
<meta name="twitter:title" content="{{ SITEINFO['title'] }}: {{ $pagetit }}" />
|
||||
<meta name="twitter:url" content="{{ isset($_SERVER['REQUEST_URI']) ? 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] : 'https://'.$_SERVER['HTTP_HOST'] }}" />
|
||||
<meta name="twitter:description" content="{{ $description }}" />
|
||||
{@ if (isset($meta) && isset($meta->thumbnail)) @}
|
||||
{@ if (isset($meta) && isset($meta->thumbnail) && !empty($meta->thumbnail)) @}
|
||||
<meta name="twitter:image:src" content="https://{{ $_SERVER['HTTP_HOST'] }}/static/article/{{ $meta->thumbnail }}" />
|
||||
{@ endif @}
|
||||
{@ endif @}
|
||||
|
||||
Reference in New Issue
Block a user