html5 の canvas + localStorage で画像保存

こんにちは。麺処まつば副店長です。

1ヶ月以上ぶりの更新です。
い…いや…、べ…別に遊んでいたわけではありませんよ…?
(店長からの視線に怯えながら)

さて。今日は HTML5 の WebStorage について少し調べてみましたので
それについてまとめてみます。

WebStorage には sessionStorage と localStorage の2つがあるようです。
sessionStorage は読んで字のごとく…そのセッションのみで有効で、
localStorageは、消さない限り残ってしまうものです。

両方とも key-value で値を保持するんだそうで。
副店長、お恥ずかしながら「それって、cookie と何が違うの?」って思ったのですが

・cookie より容量が大きい
・cookie みたいに毎回サーバに送信とかしない
・cookie と違って有効期限とかない(localStorageの場合)

とかいう違いがあるようです。
詳しくはこちらへどうぞ。→http://www.htmq.com/webstorage/

WebStorage はこんな感じで操作できるようです。

メソッド・属性 処理
length 中身のデータのサイズをゲット
key(n) n番目の key をゲット
getItem(key) key をもとに value をゲット
setItem(key, value) key と value で値をセット
removeItem(key) key をもとに データ削除
clear() 中身根こそぎ消す


それでは、今回は localStorage を使って行こうと思います。
先日作成した画像ドラッグドロップで画像保存するやつに少々付け足して
(→http://d.hatena.ne.jp/noodles_mtb/20110903/1315036154
canvasにドラッグドロップされた画像を localStorage に保存して、
さらに localStorage から画像を呼び出して canvas に表示する、
というのをやってみます。

作り方はこんなカンジです。
1.views/index.haml
2.public/javascripts/dragdrop.js
3.app.rb(コレは今回あんまり関係ないです)

views/index.haml

まずは見た目ですね。(悪いクセです)

  1 !!! XML
  2 !!!
  3 
  4 %html{:lang => "ja"}
  5   %head
  6     %meta{ :content=>"text/html", :charset=>"utf-8" }
  7     %title 画像
  8     %link{ :rel => "stylesheet", :href =>"/style.css" }
  9     %script{ :src => "javascript/jquery-1.6.2.min.js" }
 10     %script{ :src => "javascript/dragdrop.js" }
 11   %body
 12     #main
 13       %select#ls_select
 14         %option{value: ''} localStorage 
 15       %input{:type => "button",
 16              :id=>"clear_ls_button", :value => "localStorageをクリア"}
 17       %input{:type => "button",
 18              :id=>"save_ls_button", :value => "localStorageに保存",
 19              :style => "visibility: hidden;"}
 20       %br      
 21       %canvas{ :id=> "img_canvas",
 22                :width => "500", :height => "500",
 23                :ondragover => "onDragOver(event)", :ondrop => "onDrop(event)",
 24                :style => "border: medium dotted #666;"}

dragDrop で画像を受けるための canvas (21〜24行目)の他に、
localStorage に保存するためのボタン(17〜19行目)、
localStorage の中の画像選択するプルダウン(13〜14行目)、
localStorage の中身をお掃除するためのボタン(15〜16行目)
を配置しています。

public/javascripts/dragdrop.js

次は、今回の肝です。
canvas に dragDrop された画像をサーバに保存しつつ
localStorage に追加したり、取り出したり、削除したりする部分を作ります。

  1 var canvas;
  2 var context;
  3 
  4 
  5 $(function(){
  6   // canvas 初期化
  7   canvas = $("#img_canvas").get(0);
  8   context = canvas.getContext("2d");
  9   // プルダウンにローカルストレージの中身をセット
 10   for(var i=0 ; i<localStorage.length ; i++){
 11     $("#ls_select").append($('<option>').attr({value: i+1}).text("image"+String(i+1)));
 12   }
 13   // localStorageのプルダウンの値が変わったときの動作。
 14   // canvasに画像を反映する
 15   $("#ls_select").change(function(){
 16     if($(this).val().length > 0){
 17       // canvasにLocalStorageの中身を表示
 18       var img = document.createElement('img');
 19       img.src = localStorage.getItem($(this).val());
 20       img.onload = function() {
 21         canvas.width = img.width;
 22         canvas.height = img.height;
 23         context.drawImage(img, 0, 0);
 24       }
 25     }
 26   });
 27   // localStorage保存ボタンを押したときの動作。
 28   // localStorageに画像を保存して、プルダウン更新
 29   $("#save_ls_button").click(function() {
 30     canvas_data = canvas.toDataURL();
 31     key = localStorage.length+1
 32     localStorage.setItem(key, canvas_data);
 33     $("#ls_select").append($('<option>').attr({value: key}).text("image"+key));
 34     $("#ls_select").val("");
 35     alert("ローカルストレージに保存したよ。今"+(localStorage.length)+"個のデータがあるよ。");
 36   });
 37 
 38   // localStorageクリアボタンを押したときの動作。
 39   // localStorageをクリアして、プルダウン更新
 40   $("#clear_ls_button").click(function() {
 41     var l_length = localStorage.length;
 42     if(l_length > 0){
 43       localStorage.clear();
 44       $("#ls_select").children().remove();
 45       $("#ls_select").append($('<option>').attr({value: ""}).text("localStorage"));
 46       alert("ローカルストレージを削除したよ。" + l_length + "個のデータを削除したよ");
 47     }else{
 48       alert("ローカルストレージの中身、今空だよ。");
 49     }
 50   });
 51 
 52 });
 53 
 54 function onDrop(event){
 55   // drop されたファイルの取得
 56   var f = event.dataTransfer.files[0];
 57 
 58   // drop されたファイルが画像ファイルの場合の処理
 59   if(/^image/.test(f.type)){
 60     // 画像ファイルを読み込むための準備 
 61     var img = document.createElement('img');
 62     var fr = new FileReader();
 63 
 64     fr.onload = function(){
 65       img.src = fr.result;
 66       img.onload = function() {
 67         // jquery ajax を使ってPOST
 68         new $.ajax({
 69           url: "/post_img",
 70           type: "POST",
 71           data: {file: img.src},
 72           success: function(){
 73                      // canvas に画像描画
 74                      canvas.width = img.width;
 75                      canvas.height = img.height;
 76                      context.drawImage(img, 0, 0);
 77                      $("#save_ls_button").css("visibility","visible");
 78                    },
 79           error: function(){ alert("ごめん失敗した。");},
 80           complete: function(){ alert("保存した!"); }
 81         });
 82       }
 83     }
 84     fr.readAsDataURL(f);
 85   }
 86 
 87   // ブラウザがファイル自体を表示するのを防止
 88   event.preventDefault();
 89 } // end funciton [openDrop]
 90 
 91 function onDragOver(event){
 92   event.preventDefault();
 93 } // end function [onDragOver]

54〜93行目までは、canvas に dragDrop された場合の処理です。
今回さほどいじってません。今回追加したのは1〜52行目です。
1〜2行目で、使用する変数の宣言。5〜52行目で初期化やイベント登録などしています。

6〜8行目
canvas の初期化

  6   // canvas 初期化
  7   canvas = $("#img_canvas").get(0);
  8   context = canvas.getContext("2d");


10〜12行目
ローカルストレージに保存されたデータをプルダウンにセット
ローカルストレージの中身のサイズは「localStorage.length」で取れます。

 10   for(var i=0 ; i<localStorage.length ; i++){
 11     $("#ls_select").append($('<option>').attr({value: i+1}).text("image"+String(i+1)));
 12   }


13〜26行目
上記プルダウンの値が変更された場合のイベントを登録しています。
該当するデータをローカルストレージから引っ張ってきて canvas にセットしています。
19行目の「img.src = localStorage.getItem($(this).val());」で
ローカルストレージの該当データを引っ張り出しています。
20行目で「img.onload」を使っていますが、これは
こういう理由です(→http://www.html5.jp/canvas/how6.html

 13   // localStorageのプルダウンの値が変わったときの動作。
 14   // canvasに画像を反映する
 15   $("#ls_select").change(function(){
 16     if($(this).val().length > 0){
 17       // canvasにLocalStorageの中身を表示
 18       var img = document.createElement('img');
 19       img.src = localStorage.getItem($(this).val());
 20       img.onload = function() {
 21         canvas.width = img.width;
 22         canvas.height = img.height;
 23         context.drawImage(img, 0, 0);
 24       }
 25     }
 26   });


27〜36行目
ローカルストレージに保存するためのボタンが押された時のイベントを登録しています。
30行目の「canvas_data = canvas.toDataURL();」で canvas の画像を取得し
32行目の「localStorage.setItem(key, canvas_data);」でローカルストレージに保存しています。
で、ローカルストレージに保存しただけだと、
再読み込みしない限りプルダウンで画像が選べないので
プルダウンに選択肢(option)を追加して、さらに未選択状態にしています。

 27   // localStorage保存ボタンを押したときの動作。
 28   // localStorageに画像を保存して、プルダウン更新
 29   $("#save_ls_button").click(function() {
 30     canvas_data = canvas.toDataURL();
 31     key = localStorage.length+1
 32     localStorage.setItem(key, canvas_data);
 33     $("#ls_select").append($('<option>').attr({value: key}).text("image"+key));
 34     $("#ls_select").val("");
 35     alert("ローカルストレージに保存したよ。今"+(localStorage.length)+"個のデータがあるよ。");
 36   });


38〜52行目
ローカルストレージの中身をクリアするボタンが押された時のイベントを登録しています。
43行目の「localStorage.clear();」で、お掃除。
これだけだと、プルダウンに消されたデータが残ってしまうので
こちらもお掃除しています。

 38   // localStorageクリアボタンを押したときの動作。
 39   // localStorageをクリアして、プルダウン更新
 40   $("#clear_ls_button").click(function() {
 41     var l_length = localStorage.length;
 42     if(l_length > 0){
 43       localStorage.clear();
 44       $("#ls_select").children().remove();
 45       $("#ls_select").append($('<option>').attr({value: ""}).text("localStorage"));
 46       alert("ローカルストレージを削除したよ。" + l_length + "個のデータを削除したよ");
 47     }else{
 48       alert("ローカルストレージの中身、今空だよ。");
 49     }
 50   });
 51 
 52 });

app.rb

以前作った時から全く手を入れていませんが、ないと動かないので…。
今回は関係ないので、サーバ保存しない版を作っても良かったんですが
副店長はモノグs………(ごほんごほん)
全然いじっていないので、今回は説明省きますね。
なにこれ?って方はこちら見ていただけると幸いです。
(→http://d.hatena.ne.jp/noodles_mtb/20110903/1315036154

  1 #coding:utf-8
  2 
  3 require 'rubygems'
  4 require 'sinatra'
  5 require 'haml'
  6 
  7 configure :production do
  8 end
  9 
 10 get '/' do
 11   set :haml, :format => :html5
 12   haml :index
 13 end # end [get /]
 14 
 15 post '/post_img' do
 16   # 画像ファイルの取得
 17   img_array = params[:file].split(/\s*,\s*/)
 18 
 19   # 拡張子
 20   ext = ".dat"
 21   case img_array[0]
 22   when "data:image/jpeg;base64"
 23     ext = ".jpg"
 24   when "data:image/gif;base64"
 25     ext = ".gif"
 26   when "data:image/png;base64"
 27     ext = ".png"
 28   end
 29 
 30   # 画像ファイル保存
 31   f = File.open("tmp/image#{ext}",'w')
 32   f.puts img_array[1].unpack('m')[0]
 33   f.close
 34   return ""
 35 end
 36 
 37 get '/style.css' do
 38   content_type 'text/css', :charset => 'utf-8'
 39   sass :style
 40 end


それでは、実際に動かしてみましょう。

初期表示してみたところ


ドラッグドロップしてみた所。


ローカルストレージに保存してみたところ


プルダウンから画像を選んでみたところ



おぉぉー。何とか動いているぽいですねぇー。
次回は、さらにこれに手を加えて
副店長の本領発揮、変なものをつくろうと思います。
# …多分…。
# 「できあがったものがこちらです。」的に
# アプリ公開するだけかもしれませんが…