でお絵かき->DBに保存->PHPで表示->(゚Д゚)ウマー

きっかけ

canvasタグ上に描いた絵を保存しようとしたら、
意外に情報が見つからず苦労したんで、載せときます。

canvas?

まず、canvasHTML5についての仕様は、以下のサイトを見てください
http://www.html5.jp/

流れ

今回は、フリーハンドで描いた絵を保存します。

  1. canvasタグを用意
  2. お絵かき
  3. 画像情報をサーバに送り、DBに保存
  4. DBから画像情報を取り出し表示
  5. (゚Д゚)ウマー


なお、DB関係の記述は省略します。

canvasタグを用意

<html>
  <head>
    <script src="js/prototype.js" type="text/javascript"></script>
    <script src="js/canvas.js" type="text/javascript"></script>
    <style type="text/css">
      canvas
      {
        border: 1px solid #000;
      }
    </style>
    <title>CANVASでお絵かき</title>
  </head>
  <body>
    <canvas>canvasをサポートしていないブラウザです。</canvas><br>
    <canvas>canvasをサポートしていないブラウザです。</canvas><br>
    <canvas>canvasをサポートしていないブラウザです。</canvas><br>
    <canvas>canvasをサポートしていないブラウザです。</canvas><br>
  </body>
</html>

タグなんで、って書くだけですね。
タグの中身は、非対応ブラウザへのメッセージです。
代替画像を表示する方が親切かと

お絵かき

やってることは、javascriptでマウスイベントを受け取り、canvasに反映です。


canvas.js

/**
 * Canvasクラス
 * canvas要素に対して、マウスの動きに合わせて描画するメソッドを持たせる
 */
Canvas = Class.create(
{
  initialize: function(elem, num)
  {
    this.elem    = elem;
    this.context = this.elem.getContext('2d');
    this.x       = this.elem.offsetLeft;
    this.y       = this.elem.offsetTop;
    this.num     = num;   // 何番目のcanvasか区別するための番号
    this.f_draw  = false; // 描画フラグ
    this.cache   = this.moveEvent.bindAsEventListener(this);

    Event.observe(this.elem, 'mousedown', this.begin.bindAsEventListener(this));
    Event.observe(this.elem, 'mouseup',   this.end.bind(this));
    Event.observe(this.elem, 'mouseout',  this.end.bind(this));
  },

  // canvas上でマウスダウンしたら、描写開始
  begin: function(e)
  {
    this.f_draw = true;
    Event.observe(this.elem, 'mousemove', this.cache);

    // canvasの左上が(0, 0)になるように調整
    var x = Event.pointerX(e) - this.x;
    var y = Event.pointerY(e) - this.y;

    // contextに書き始めるよ、と通知
    this.context.beginPath();

    // マウスダウンした位置まで開始点を移動
    this.context.moveTo(x, y);
  },

  // マウスが動くたびに、描画を繰り返す
  moveEvent:  function(e)
  {
    if(this.f_draw)
    {
      var x = Event.pointerX(e) - this.x;
      var y = Event.pointerY(e) - this.y;

      this.context.lineTo(x, y);
      this.context.stroke();
    }
  },

  // マウスアップまたは、canvasの外へマウスが出たら
  // 描画を終了し、画像データをサーバに送る
  end: function()
  {
    if(this.f_draw)
    {
      Event.stopObserving(this.elem, 'mousemove', this.cache);

      // toDataURLメソッドは、画像の表現を含んだ data: URI をbase64形式のPNGファイルとして返す
      // 引数をimage/jpegにすれば、JPGファイルを返すことも可能
      var PNG = this.elem.toDataURL('image/png').replace(/^.*,/, '');

   // 画像情報をサーバに送る
      new Ajax.Request('save.php',
      {
        parameters:
        {
          num: this.num,
          src: PNG
        }
      });

      this.f_draw = false;
    }
  }
});

// domツリーが形成されたら、Canvasクラスを生成
Event.observe(document, 'dom:loaded', function()
{
  $$('canvas').each(function(elem, num)
  {
    new Canvas(elem, num);
  });
});

DBに保存

save.php
先ほど送られてきた情報をDBに保存するだけ。
画像情報は、バイナリではないので文字列として扱えます。

DBから画像情報を取り出し表示

PNG.php

<?php
/**
 * base64でエンコードされたPNGをPNG画像として出力する
 * このphpファイル自体がPNG形式の画像として認識される。
 */

$num = $_GET['num']; // getで取り出す番号を指定

// このファイルは、PNGですよと宣言
header("Content-Type: image/png");

// DBから画像データを取り出しデコードして表示すれば、見られます。
// 仮に、データを $PNG に入れたとすると

echo base64_decode($PNG);


PNG.php?num=0
を開くと、1つ目のcanvasに描いた絵が見られます。

(゚Д゚)ウマー1

イメージタグのsrcに、toDataURL('image/png')の戻り値を直接渡してもPNGファイルとして認識します。

var PNG = this.elem.toDataURL('image/png');
var img = new Image();
img.src = PNG;
document.body.appendChild(img);

(゚Д゚)ウマー2

canvasには、画像を読み込むdrawImageメソッドがあるのですが、
上記(゚Д゚)ウマー1で紹介した方法の画像を読み込んで、toDataURL('image/png')メソッドを呼び出すと
セキュリティエラーが起きます。


(゚Д゚)ウマー1画像のある場所とtoDataURL('image/png')メソッドを呼び出した場所が違うと判断されるみたいです。


解決法としては、一度画像として保存して同じ場所から読み込めばいいみたいです。
要は、srcに今回の例だとPNG.phpを指定すれば怒られないで済みました。

// 以前書いた絵を取得しcanvasに読み込む
var img = new Image();
img.src = 'PNG.php?num=' + this.num;
Event.observe(img, 'load', function()
{
  this.context.drawImage(img, 0, 0);
}.bind(this));