書いた範囲
PHPで、ビットマップのサムネイルを作って表示したいと。
なんですが、PHP、というかGDはビットマップに対応してませんでした。
とりあえず、JPEGに変換して表示する実験。
変換して表示できれば満足で、サムネイルにはしてません。
もとのビットマップが50MB位あると、2枚で5分30秒とか、同期処理を諦めた方がいいレベルです。
いったん、ImageMagickでの実験を行おうかと。
ImageCreateFromBMP
そんな関数はありませんが、
http://php.net/manual/ja/function.imagecreate.php
でDHKoldさんが公開してくれてます。これを利用している方も多いみたいです。
<?php
function ImageCreateFromBMP($filename) {
//画像ファイルをバイナリモードでOpen
if (! $f1 = fopen($filename, "rb")) return FALSE;
//1: 概要データのロード
$FILE = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1, 14));
if ($FILE['file_type'] != 19778) return FALSE;
//2: BMPデータのロード
$BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'.
'/Vcompression/Vsize_bitmap/Vhoriz_resolution'.
'/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1, 40));
$BMP['colors'] = pow(2, $BMP['bits_per_pixel']);
if ($BMP['size_bitmap'] == 0) $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset'];
$BMP['bytes_per_pixel'] = $BMP['bits_per_pixel']/8;
$BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']);
$BMP['decal'] = ($BMP['width']*$BMP['bytes_per_pixel']/4);
$BMP['decal'] -= floor($BMP['width']*$BMP['bytes_per_pixel']/4);
$BMP['decal'] = 4-(4*$BMP['decal']);
if ($BMP['decal'] == 4) $BMP['decal'] = 0;
//3:PALETTEデータのロード
$PALETTE = array();
if ($BMP['colors'] < 16777216) {
$PALETTE = unpack('V'.$BMP['colors'], fread($f1,$BMP['colors']*4));
}
//4: イメージデータのロード
$IMG = fread($f1,$BMP['size_bitmap']);
$VIDE = chr(0);
$res = imagecreatetruecolor($BMP['width'],$BMP['height']);
$P = 0;
$Y = $BMP['height']-1;
while ($Y >= 0) {
$X=0;
while ($X < $BMP['width']) {
if ($BMP['bits_per_pixel'] == 24) $COLOR = unpack("V",substr($IMG,$P,3).$VIDE);
elseif ($BMP['bits_per_pixel'] == 16) {
$COLOR = unpack("n",substr($IMG,$P,2));
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 8) {
$COLOR = unpack("n",$VIDE.substr($IMG,$P,1));
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 4) {
$COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1));
if (($P*2)%2 == 0) $COLOR[1] = ($COLOR[1] >> 4) ;
else $COLOR[1] = ($COLOR[1] & 0x0F);
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 1) {
$COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1));
if (($P*8)%8 == 0) $COLOR[1] = $COLOR[1] >> 7;
elseif (($P*8)%8 == 1) $COLOR[1] = ($COLOR[1] & 0x40) >> 6;
elseif (($P*8)%8 == 2) $COLOR[1] = ($COLOR[1] & 0x20) >> 5;
elseif (($P*8)%8 == 3) $COLOR[1] = ($COLOR[1] & 0x10) >> 4;
elseif (($P*8)%8 == 4) $COLOR[1] = ($COLOR[1] & 0x8) >> 3;
elseif (($P*8)%8 == 5) $COLOR[1] = ($COLOR[1] & 0x4) >> 2;
elseif (($P*8)%8 == 6) $COLOR[1] = ($COLOR[1] & 0x2) >> 1;
elseif (($P*8)%8 == 7) $COLOR[1] = ($COLOR[1] & 0x1);
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
else return FALSE;
imagesetpixel($res,$X,$Y,$COLOR[1]);
$X++;
$P += $BMP['bytes_per_pixel'];
}
$Y--;
$P+=$BMP['decal'];
}
//作業終了
fclose($f1);
return $res;
}
?>
ImageCreateFromBMPを利用して、ビットマップ画像をJPEG変換して表示
先に全コード。PHPの中にHTML埋め込みました。
画像数が多かった場合に複数ページにしたかったので。
もっとも、今の表示速度で、不要かも。
<?php
print <<< _HTML_
<!DOCTYPE html>
<html lang="ja">
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html;charset="UTF-8"/>
<title>ビットマップ画像を表示</title>
</head>
<body>
<h1>ビットマップ画像を表示する</h1>
_HTML_;
$work_dir = opendir('work/');
if (isset($_GET["imageno"])) {
$imageno = $_GET["imageno"];
$image = $imageno;
}
while (false !== ($file[] = readdir($work_dir)));
closedir($work_dir);
natsort($file);
reset($file);
$reverse = array_reverse($file, true);
if (!isset($imageno)) {
$imageno = 0;
$image = 0;
}
$pageline = 4; //1ページに表示させる枚数
$imagecount = 0;
while (false !== ($bmp = each($reverse))){
if (preg_match ("|.bmp$|", $bmp[1])) {
$imagecount++;
if ($imageno == ($image+$pageline)) {
break;
}
if ($imagecount > $image) {
print "<div style='float:left;margin-right:4px;margin-bottom:8px;'>\n";
print "<table cellspacing='0' cellpadding='0' border='0'>\n";
print "<tr>\n";
include "ImageCreateFromBMP.php";
$work_dir2 = "work/";
$work_img=$work_dir2.$bmp[1];
$bmp_jpg = ImageCreateFromBMP($work_img);
if($bmp_jpg !== FALSE){
imagejpeg($bmp_jpg,"temp.jpg",50);
print "<td><img src=\"temp.jpg\"></td>\n";
imagedestroy($bmp_jpg);
}
print "</tr>\n";
print "<tr>\n";
print "<td align='left'>" . $bmp[1] . "</td>\n";
print "</tr>\n";
print "</table>\n";
print "</div>\n";
$imageno++;
}
}
}
echo '<div style="margin-top:12px;clear:both;">';
echo '<p class="txt">';
$page = $imageno+$pageline;
if ($imageno > $pageline) {
$backno = $imageno-($pageline*2);
print "<a href='displayimgs.php?imageno=" . $backno . "'>back</a>";
}
if ($imageno < $imagecount) {
print "<a href='displayimgs.php?imageno=" . $imageno . "'>next</a>";
}
echo '</div>';
echo '<a href =./index.html>トップへ戻る</a>';
echo '</body>';
echo '</html>';
?>
軽く解説
初めの方は主にヘッダの部分で、いちいちエコーするのが面倒で、
枠としてHTMLの記載にしてます。
その後、画像のあるディレクトリを開き、画像数を数え、
画像のリストを出して整形、
指定するページ数に合わせて、
ビットマップ画像を表示してます。
今回のメインになるのは、こちら。
include "ImageCreateFromBMP.php";
$work_dir2 = "work/";
$work_img=$work_dir2.$bmp[1];
$bmp_jpg = ImageCreateFromBMP($work_img);
if($bmp_jpg !== FALSE){
imagejpeg($bmp_jpg,"temp.jpg",50);
print "<td><img src=\"temp.jpg\"></td>\n";
imagedestroy($bmp_jpg);
}
print "</tr>\n";
print "<tr>\n";
print "<td align='left'>" . $bmp[1] . "</td>\n";
メインの解説
HTMLタグは無視するとして。
ImageCreateFromBMP.phpでビットマップからJPEG変換を行う為、includeしてます。
ImageCreateFromBMP()は、渡された変数を読んでくれず、改めてパス含めて見に行く為、画像のありかへのパスを指定してやります。
そして、仮に、ImageCreateFromBMPで出力されたJPG画像を、50パーセントの品質で、temp.jpgに保存し、
temp.jpgを表示、
元のJPG画像のメモリ解放をしてやる、を繰り返します。
注と疑問
header('Content-type: image/jpeg');をつけてやんなきゃ駄目(この場合はJPEGバイナリをテキストで出力、つまり文字化け)よ、と書いてあるサイトがちらほら。
そして、headerの前に余計な文字や改行は駄目です、との事なんですが。
今回の様なコードが書きたければそうも言ってられないんですが、どうすればいいでしょう??
結局、headerの記載は取りやめました(正確には、書かなくても出来た)。
imagejpeg()の疑問が2つあって、元画像,null,50とかにして、img src=元画像でいいじゃない?と思うのですが、
それは駄目で、今回の様に一度間にファイル(今回のtemp.jpg)をかましてやらないとならないらしい。よくわかりません。
また、temp.jpgだと固定されてしまうので、頭にダラーをつけて変数とかにしたいのですが、それも駄目だと。なぜなんでしょう?
問題
元々のBMPが50MBとか大きいとはいえ(これ自体は制限事項だから仕方ない)
変換に数分かかっている様では、ちょっと実用になりませんです。