PHPで画像の縮小+キャッシュ

4月 29th, 2008 admin

いつもはphpThumbというのを使っていて、これは十分に強力で便利なんですけど重厚すぎる感じがするのでライトな感じのものをいろいろ組み合わせて作ってみました。

やりたいこと

  • 縮小画像をPHPで自動生成したい
  • 毎回自動生成するのは嫌だからキャッシュしたい

手順

便利なライブラリを入手

Gen-X-Design | Ian Selby » PHP Thumbnailer Class v2.0
これは画像縮小用のライブラリ。ファイルを1つインクルードするだけで使える。しかもサンプルコードが超簡単。

ファイルの配置+ファイル作成

PHP Thumbnailerをダウンロードしたら下図のように配置します。.htaccessとcacheフォルダも作ります。cacheは書き込み可能にしておいてください。これらはPHP THumbnailerにキャッシュ機能を追加するために使います。
tree2.jpg

使ってみます。

./img/csnagoya.jpgってファイルを400x300に縮小して表示したいと仮定して、こんな感じでHTMLを書きます。(cacheにはこんなファイルはありませんが気にしなくて大丈夫です)

index.html

HTML:
  1. <img src="./img/thumb/cache/400x300_csnagoya.jpg" alt="" />

.htaccess

CODE:
  1. <ifmodule mod_rewrite.c>
  2. RewriteEngine on
  3. RewriteCond %{REQUEST_FILENAME} ^.*$
  4. RewriteCond %{REQUEST_FILENAME} !-f
  5. RewriteRule ^.*/([0-9]+)x([0-9]+)_(.*)(jpg|gif|pnd)$ /img/thumb/show_image.php?width=$1&height=$2&filename=../img/$3$4 [R]
  6. </ifmodule>

.htaccessをこんな感じにします。んで、次が最後です

show_image.php

PHP:
  1. $filename = 'cache/'.$_GET['width']."x".$_GET['height']."_".basename($_GET['filename']);
  2.  
  3. include_once('thumbnail.inc.php');
  4. $thumb = new Thumbnail($_GET['filename']);
  5. $thumb->resize($_GET['width'],$_GET['height']);
  6. $thumb->save($filename,100);
  7. $thumb->show();
  8. $thumb->destruct();

以上で終わりです。これで一回目のアクセスのときにcache以下に400x300_csnagoya.jpgというファイルが作られて、二回目以降のアクセスにはキャッシュを読みに行くという構成になりました。phpThumbではキャッシュを読むときもphpを読み込む必要があったりしますが、この方法ならphpを読み込まずしてキャッシュを表示できるので高速です。

注意

  • ファイル数が多いときには注意(cache内をディレクトリで分けたり)
  • cacheが勝手に消えない

参考サイト

画像もDBに格納して管理する ―扱いがめんどうなLOB(ラージオブジェクト)は使わない方法

PHPのfgetcsvでMacのやつやSJISのやつを扱うとき

4月 27th, 2008 admin

PHPにはCSVを処理してくれるfgetcsvという便利な関数があって、これを使えばダブルクォーテーションとかの処理は考えなくていいようになります。ただし

  • CRの改行コードに対応していない
  • SJISだと、いわゆる5C問題にひっかかる

という問題があるので、CSVファイルを扱うときはいきなりfgetcsvで読み込むのでなくいったんファイルを作成して、それを読み直すのがよいと思います。以下がファイルを読み込んで改行コードと文字コードを変換してfgetcsvで読み直すサンプルです。

コード

PHP:
  1. $file = file_get_contents("アップロードされたファイルのパス");
  2. $file = ereg_replace("\r\n|\r|\n","\n",mb_convert_encoding($file,"UTF-8","SJIS-win"));
  3. $fp = tmpfile();
  4. fputs($fp,$file);
  5. fseek($fp,0);
  6.  
  7. while($o = fgetcsv($fp,1024)){
  8.   //処理
  9. }

Rubyで数独(ナンプレ)を解く(本当の解決編)

4月 27th, 2008 admin

前回で攻略したかと思われたナンプレですが、またもや間違った仮定で作っていたため解けない問題があるということがわかりました。(そんな問題があるって知らなかったんです。)
いくら「コードは間違ってても恥ずかしがらずに晒せ」と言ってもこう何度も間違えてたら流石に怒られるんじゃないかと不安になってきましたが、よく考えたら怒ってくれる人もいないので気にせず進めます。

今回はアルゴリズムをバックトラック法という方法を使うように変更しました。 バックトラック法というのはとにかく候補を順番に試していって矛盾が発生したら一つ戻るという方法です。矛盾が発生したらそれ以降の(矛盾を含んだ)パターンの探索は行わないので総当りと比べて効率がよく、どんなパターンであっても解を求めることができるのが特長です。

コード

RUBY:
  1. require 'pp'
  2. before = Time.now
  3. t = Time.now
  4. class Numpla
  5.     def initialize(sheet)
  6.         @sheet = sheet
  7.         @size = Math.sqrt(sheet.size).to_i
  8.         @side_size = Math.sqrt(@size).to_i
  9.         @possible = Array.new
  10.     end
  11.    
  12.     def solve
  13.         #初期の状態から入力候補をリストアップする。
  14.         (0...@sheet.length).each do |i|
  15.             if @sheet[i] == 0
  16.                 @possible[i] = get_possibles(get_neighbors(i))
  17.             else
  18.                 @possible[i] = []
  19.             end
  20.         end
  21.        
  22.         #探索開始
  23.         search(0)
  24.         return @sheet
  25.     end
  26.  
  27.     def search(index)
  28.         return true if index == 81
  29.        
  30.        
  31.         if @sheet[index] != 0
  32.             #0でない場合はチェックするだけ(初期から数字が割り当てられていたら)
  33.             if check(index, @sheet[index]) && search(index + 1)
  34.                 return true
  35.             end
  36.         else
  37.             #入力候補の数だけ調査する
  38.             @possible[index].each do |x|
  39.                 if check(index,x)
  40.                     @sheet[index] = x
  41.                     unless search(index + 1)
  42.                         #間違った値だった場合は取り消す
  43.                         @sheet[index] = 0
  44.                     else
  45.                         return true
  46.                     end
  47.                 end
  48.             end
  49.         end
  50.         return false
  51.     end
  52.    
  53.     #そのマスに入力できる可能性があるかどうか調べる
  54.     def check(index,x)
  55.         return  get_possibles(get_neighbors(index)).index(x)
  56.     end
  57.    
  58.     #当該マスxに関連するマスのインデックスを配列で返す
  59.     def get_neighbors(index)
  60.         get_neighbors_h(index) | get_neighbors_v(index) | get_neighbors_b(index)
  61.     end
  62.    
  63.     #水平方向のマスをリストアップ
  64.     def get_neighbors_h(index)
  65.         neighbors = Array.new
  66.         horizone = index / @size * @size
  67.         @size.times {|i|
  68.             neighbors.push(i + horizone)
  69.         }
  70.         neighbors.delete(index)
  71.         neighbors
  72.     end
  73.    
  74.     #垂直方向のマスをリストアップ
  75.     def get_neighbors_v(index)
  76.         neighbors = Array.new
  77.         vertical = index % @size
  78.         @size.times {|i|
  79.             neighbors.push(@size * i + vertical);
  80.         }
  81.         neighbors.delete(index)
  82.         neighbors
  83.     end
  84.  
  85.     #3x3のブロックのマスをリストアップ
  86.     def get_neighbors_b(x)
  87.           tf = x / @side_size * @side_size
  88.           offset = tf / @size % @side_size
  89.           fc = tf - @size * offset
  90.  
  91.           neighbors = Array.new
  92.           (0..(@side_size-1)).each do |j|
  93.             (fc+@size*j..fc+@size*j+(@side_size-1)).each do |i| neighbors.push(i) end
  94.           end
  95.           neighbors.delete(x)
  96.           neighbors
  97.     end
  98.  
  99.     #すでに確定しているマスの情報から、置ける可能性のある数字を割り出す
  100.     def get_possibles(neighbors)
  101.         possibles = Array.new
  102.         (1..@size).each{|i| possibles.push i }
  103.         neighbors.each do |i|
  104.             possibles.delete(@sheet[i])
  105.         end
  106.        
  107.         possibles
  108.     end
  109. end
  110.  
  111. sheet = Array.new
  112. sheet.concat [6,0,0,0,0,0,1,0,8]
  113. sheet.concat [0,7,0,0,0,2,3,0,0]
  114. sheet.concat [0,0,5,8,0,0,9,0,0]
  115. sheet.concat [0,4,0,9,0,0,0,0,0]
  116. sheet.concat [0,5,0,0,4,0,0,6,0]
  117. sheet.concat [0,0,1,0,5,0,0,0,4]
  118. sheet.concat [0,0,0,6,3,0,0,0,0]
  119. sheet.concat [0,2,6,0,7,9,0,3,0]
  120. sheet.concat [0,0,3,0,0,0,0,0,0]
  121.  
  122. width = 9
  123. height= 9
  124.  
  125.  
  126. foo = Numpla.new(sheet)
  127. result = foo.solve
  128.  
  129. count = 1
  130. result.each do |x|
  131.   print x
  132.   print ","
  133.   print "\n" unless count % 9> 0
  134.   count += 1
  135. end
  136.  
  137. #処理時間
  138. p Time.now - before

まとめ

13行目付近で初期の配置から候補を絞って、それを探索に使っているのだけど、この問題の場合でいうとこれをすると約6秒、やらないと約12秒とかなり計算時間に差が出た。今後はこの調子で計算時間を縮めたい。ちなみにこの問題での再帰の回数は14764回でした、がんばってるねー!

Rubyで数独(ナンプレ)を解く(解決編)

4月 26th, 2008 admin

※このコードでは解けない問題があることがわかったのでコードを書き直しました。
新しいコードはこちら「Rubyで数独(ナンプレ)を解く(本当の解決編)

前回はそもそもナンプレを1度しかやったことがないこともあってルールを間違えており解けませんでした。
今回やっと解決編としてナンプレを解くコードを掲載します。

アルゴリズム

(1)全マスについて候補を求め、候補リスト(配列)を作成
(2)全マスについて、そのマスに関係するマスの候補リストから置ける数字を調べる
(3)(2)をマス数回行う < -このへんが頭悪い感じが出ているので改良候補

コード

RUBY:
  1. class Numpla
  2.     def initialize(sheet)
  3.         @sheet = sheet
  4.         @size = Math.sqrt(sheet.size).to_i
  5.         @side_size = Math.sqrt(@size).to_i
  6.         @possible = Array.new
  7.     end
  8.    
  9.     def resolve
  10.     81.times{
  11.             (0...@sheet.length).each do |i|
  12.                 if @sheet[i] == 0
  13.                     @possible[i] = get_possibles(get_neighbors(i))
  14.                 else
  15.                     @possible[i] = []
  16.                 end
  17.             end
  18.            
  19.             (0...@sheet.length).each do |i|
  20.                 if @possible[i].size> 0
  21.                     if @possible[i].size == 1
  22.                         @sheet[i] = @possible[i].pop
  23.                     else
  24.                         @sheet[i] = get_answer(i,get_neighbors(i))
  25.                     end
  26.                 end
  27.             end
  28.             }
  29.             @sheet
  30.     end
  31.    
  32.     def get_answer(index,neighbors)
  33.         answer = @possible[index]
  34.         possibles = Array.new
  35.         neighbors.each do |i|
  36.             possibles |= @possible[i]
  37.         end
  38.         possibles.each do |v|
  39.             answer.delete(v)
  40.         end
  41.      result = (answer.size == 1) ? answer.pop : 0;
  42.      return result
  43.     end
  44.    
  45.     def get_neighbors(index)
  46.         get_neighbors_h(index) | get_neighbors_v(index) | get_neighbors_b(index)
  47.     end
  48.    
  49.     def get_neighbors_h(index)
  50.         neighbors = Array.new
  51.         horizone = index / @size * @size
  52.         @size.times {|i|
  53.             neighbors.push(i + horizone)
  54.         }
  55.         neighbors.delete(index)
  56.         neighbors
  57.     end
  58.    
  59.     def get_neighbors_v(index)
  60.         neighbors = Array.new
  61.         vertical = index % @size
  62.         @size.times {|i|
  63.             neighbors.push(@size * i + vertical);
  64.         }
  65.         neighbors.delete(index)
  66.         neighbors
  67.     end
  68.  
  69.  
  70.     def get_neighbors_b(x)
  71.           tf = x / @side_size * @side_size
  72.           offset = tf / @size % @side_size
  73.           fc = tf - @size * offset
  74.  
  75.           neighbors = Array.new
  76.           (0..(@side_size-1)).each do |j|
  77.             (fc+@size*j..fc+@size*j+(@side_size-1)).each do |i| neighbors.push(i) end
  78.           end
  79.  
  80.           neighbors
  81.     end
  82.  
  83.     def get_possibles(neighbors)
  84.         possibles = Array.new
  85.         (1..@size).each{|i| possibles.push i }
  86.         neighbors.each do |i|
  87.             possibles.delete(@sheet[i])
  88.         end
  89.        
  90.         possibles
  91.     end
  92. end
  93.  
  94. sheet = Array.new
  95. sheet.concat [3,7,0,6,2,0,0,0,1]
  96. sheet.concat [0,0,8,0,5,0,0,6,9]
  97. sheet.concat [4,0,0,0,7,0,0,8,0]
  98. sheet.concat [0,6,0,0,0,4,0,3,8]
  99. sheet.concat [8,0,0,0,3,0,0,0,6]
  100. sheet.concat [5,2,0,0,8,6,0,9,0]
  101. sheet.concat [0,4,0,0,1,0,0,0,3]
  102. sheet.concat [9,3,0,0,6,0,8,0,0]
  103. sheet.concat [6,0,0,0,4,2,0,7,5]
  104. width = 9
  105. height= 9
  106.  
  107.  
  108. foo = Numpla.new(sheet)
  109.  
  110. result = foo.resolve
  111.  
  112. count = 1
  113. result.each do |x|
  114.   print x
  115.   print ","
  116.   print "\n" unless count % 9> 0
  117.   count += 1
  118. end

出力

result.jpg

まとめ

コードを書いていてとても楽しかった。仕事ではWebアプリを書くことが多いのですが、Webアプリではアルゴリズムを自分で考えるということがあまりないんだなというのを改めて思い知らされました。ショッピングサイトでない限り掛け算や割り算はおろか足し算引き算もしなかったりしますからね。

とりあえず問題を解けるようになったので、まずは他のナンプレの解法をみて比較したり改良してもっとRubyっぽくしてみたいと思います。

AS3でWebカメラの映像をJPEGで保存

4月 25th, 2008 admin

Webカメラ楽しいー!とかって5年遅れぐらいですかね。

デモ

※白黒画像ですがカメラを許可して画面をクリックすると画像が保存されます。
動作サンプルはこちら

成果物

ステージをクリックするとWebカメラに表示されている内容が白黒でサーバ上に保存されます。
パーマリンクとかつけてあげると普通に便利なサイトが出来上がりそうです。
sample.jpg

コード

ACTIONSCRIPT:
  1. package
  2. {
  3.     import flash.display.Bitmap;
  4.     import flash.display.BitmapData;
  5.     import flash.display.Sprite;
  6.     import flash.events.Event;
  7.     import flash.events.EventDispatcher;
  8.     import flash.events.MouseEvent;
  9.     import flash.geom.Point;
  10.     import flash.geom.Rectangle;
  11.     import flash.media.Camera;
  12.     import flash.media.Video;
  13.     import flash.net.*;
  14.     import flash.text.TextField;
  15.     import flash.utils.ByteArray;
  16.    
  17.     import com.adobe.images.JPGEncoder;
  18.    
  19.     public class Main extends flash.display.Sprite
  20.     {
  21.         private var camera_width:int = 200;
  22.         private var camera_height:int = 150;
  23.         private var video:Video = new Video(camera_width, camera_height);
  24.         private var tfView:TextField;
  25.        
  26.         private var urlRequest:URLRequest;
  27.         private var urlLoader:URLLoader; 
  28.         private var fileReceiver:String = "http://www.jamboree.jp/tmp/cam2jpeg/receive.php";
  29.    
  30.         public function Main():void
  31.         {
  32.            
  33.             tfView = addTextField(220, 0, 200, 150);
  34.             tfView.appendText("start\n");
  35.             var camera:Camera = Camera.getCamera();
  36.             if (camera == null) {
  37.                 //error
  38.                 tfView.appendText("cant get camera!\n");
  39.                 return;
  40.             }
  41.             video.attachCamera(camera);
  42.             addChild(video);
  43.             video.x = 0;
  44.             video.y = 0;
  45.             stage.addEventListener(MouseEvent.CLICK, onClick);
  46.         }
  47.        
  48.         private function onClick(event:MouseEvent):void {
  49.             tfView.appendText("click!\n");
  50.             var s:BitmapData = new BitmapData(camera_width, camera_height)
  51.             s.draw(video);
  52.            
  53.             var d:BitmapData = new BitmapData(camera_width, camera_height)
  54.            
  55.             var r:Rectangle = new Rectangle(0, 0, camera_width, camera_height);
  56.             d.fillRect(r, 0xFFFFFFFF);
  57.            
  58.             d.threshold(s, r, new Point(0, 0), "<=", 90, 0xFF000000, 255, false);
  59.            
  60.             var img:Bitmap = new Bitmap(d);
  61.             addChild(img);
  62.             img.x = 0;
  63.             img.y160;
  64.            
  65.             //送信
  66.             var jpgEncoder:JPGEncoder = new JPGEncoder(80);
  67.             var byteArr:ByteArray = jpgEncoder.encode(d);
  68.             urlRequest = new URLRequest(fileReceiver);
  69.             urlLoader = new URLLoader();
  70.             urlRequest.contentType = "application/octet-stream";
  71.             urlRequest.method = URLRequestMethod.POST;
  72.             urlRequest.data = byteArr;
  73.             urlLoader.load(urlRequest);
  74.             urlLoader.addEventListener(Event.COMPLETE,onUpload)
  75.         }
  76.        
  77.         private function onUpload(event:Event):void {
  78.             tfView.appendText("uploaded!\n");
  79.         }
  80.        
  81.         private function addTextField(x:Number, y:Number, w:Number, h:Number):TextField {
  82.             var textField:TextField;
  83.             textField = new TextField();
  84.             addChild(textField);
  85.             textField.x         = x;
  86.             textField.y         = y;
  87.             textField.width     = w;
  88.             textField.height    = h;
  89.             textField.text      ="";
  90.             textField.selectable=true;
  91.             textField.border    =true;
  92.            
  93.             return textField;
  94.         }
  95.     }
  96. }

receive.php

PHP:
  1. <?php
  2. $fileName = time()."-".round(rand(1,10000)).".jpg";
  3. $fp = fopen("data/".$fileName, 'wb');
  4. fwrite($fp, $GLOBALS['HTTP_RAW_POST_DATA']);
  5. fclose($fp);
  6. chmod("data/".$fileName,0777);
  7. ?>

解説

as3corelibのJPGEncoderクラスというのをBitmapからJpegへの変換に使用しているのでhttp://code.google.com/p/as3corelib/source/checkoutからチェックアウトして下図の様に配置しておいてください。
tree.gif

まとめ

勉強会とかもみんなWebCamでバシバシとったら面白いんじゃないかと思った。いまどきは動画もいけるんだけどやっぱ写真の面白さはまた別にありますよね。

参考サイト

Flashから画像ファイルを保存する - FICC LABS