LoginSignup
2
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-04-04

書いた範囲

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

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

ImageCreateFromBMP

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

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埋め込みました。
画像数が多かった場合に複数ページにしたかったので。
もっとも、今の表示速度で、不要かも。

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の記載にしてます。

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

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

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1