PHPでビットマップのサムネイル表示、JPEG変換まで

###書いた範囲
PHPで、ビットマップのサムネイルを作って表示したいと。
なんですが、PHP、というかGDはビットマップに対応してませんでした。
とりあえず、JPEGに変換して表示する実験。

変換して表示できれば満足で、サムネイルにはしてません。
もとのビットマップが50MB位あると、2枚で5分30秒とか、同期処理を諦めた方がいいレベルです。
いったん、ImageMagickでの実験を行おうかと。

###ImageCreateFromBMP
そんな関数はありませんが、
http://php.net/manual/ja/function.imagecreate.php
でDHKoldさんが公開してくれてます。これを利用している方も多いみたいです。

```PHP:ImageCreateFromBMP.php
<?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:displayimgs.php
<?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の記載にしてます。

その後、画像のあるディレクトリを開き、画像数を数え、
画像のリストを出して整形、
指定するページ数に合わせて、
ビットマップ画像を表示してます。

###今回のメインになるのは、こちら。

```PHP:displayimgs_part.php
    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とか大きいとはいえ(これ自体は制限事項だから仕方ない)
変換に数分かかっている様では、ちょっと実用になりませんです。