ほぼ備忘録目的のブログエントリです。

HTML で <canvas> を使うと画面に単純な画像だけでなく、グラフィックコンテキストを使って自由度の高い図形や関数曲線を描くことができるようになります。個人的にもよく使っています。

特筆すべきはピクセル単位での画素情報を取得することもできる点です。ここでの画素情報とは RGB 値や輝度の値といった情報です。例えば <canvas id="mycanvas"></canvas> 内に描かれている内容のピクセル単位での画素情報を動的に取得するには以下のような JavaScript コードを実装すれば可能です:
  var canvas = document.getElementById( 'mycanvas' );
  if( !canvas || !canvas.getContext ){
    return false;
  }
  var ctx = canvas.getContext( '2d' );

  var imagedata = ctx.getImageData( 0, 0, ctx.canvas.width, ctx.canvas.height );
  for( var i = 0; i < imagedata.height; i ++ ){
    for( var j = 0; j < imagedata.width; j ++ ){
      var idx = ( j + i * imagedata.width ) * 4;

      var r = imagedata.data[idx];    //. R(0-255)
      var g = imagedata.data[idx+1];  //. G(0-255)
      var b = imagedata.data[idx+2];  //. B(0-255)
      var a = imagedata.data[idx+3];  //. 輝度(0-255)

        :
    }
  }

この中で画素情報を取得する際に使っているのが getImageData() 関数です。これは canvas 内の開始点座標と幅、高さを指定し、その指定範囲内の画素情報を配列で取り出すことができます。また上述例のように1つのピクセルの情報は配列内の4つの値(R, G, B, 輝度)として格納されており、1つずつ取り出して参照することも可能です。画像を独自に減色したりする際等に必要な情報を取り出すことができて、個人的にも便利に使っています。


このように便利な canvas と getImageData() 関数ですが、この getImageData() 実行時に以下のようなエラーが発生して、画素情報を取得できないケースがありました:
'The canvas has been tainted by cross-origin data.'

ん? "Cross-Origin" ?? どういうこと???


これが発生した時の <canvas> 内は、事前に以下のような処理を行って画像が表示されていました:
  var img = new Image();
  img.src = 'https://www.xxx.com/images/abc.png';
  img.addEventListener( 'load', function(){
    var canvas = document.getElementById( 'mycanvas' );
    if( !canvas || !canvas.getContext ){
      return false;
    }
    var ctx = canvas.getContext( '2d' );
    ctx.drawImage( img, 0, 0, canvas.width, canvas.height );
  }, false );

要するに <canvas> 内には https://www.xxx.com/images/abc.png という(架空の) URL で表現される画像ファイルが表示されている状態でした。単に画像を表示するだけなら <img src="https://www.xxx.com/images/abc.png"/> みたいに記述すればいいのですが、後で前述のような画素情報を取り出す処理を実行したかったので、<img> ではなく <canvas> を使って表示していたのでした、という事情がありました。が、このケースだと getImageData() 実行時に上述のようなクロスオリジン関連のエラーが発生して情報を取得することができなくなっていました。これはいったい・・?


エラーメッセージからなんとなく想像はできたのですが、この getImageData() 関数はクロスオリジンの画像が表示されている場合は CORS の制約がかかるようです。つまりこの例であれば www.xxx.com というサーバー上にある https://www.xxx.com/images/abc.png という画像を表示しています(上のコード例では drawImage() 関数によって実際に表示されます)。この drawImage() 関数はクロスオリジンに関係なく実行することができるので、(自サーバーではない)www.xxx.com というサーバー上にある画像に対して実行してもエラーが発生することなく実行され、画像は <canvas> 上に表示されます。

しかし getImageData() 関数についてはクロスオリジン制約があるようです。したがって自サーバー上ではない URL を指定して表示された画像に対して実行すると、今回のようなエラーが発生してしまう、という制約があるようでした。


このエラーを回避しようとすると、www.xxx.com 側で CORS の制約を緩和するような設定を行うか、それが難しい場合は目的の画像をクロスオリジンにならない形の URL で指定できるようにする必要があります(そのため静的な HTML や JavaScript だけだと実現が難しいです)。具体的にはサーバーサイドの処理で、例えば GET /getimage のような REST API を用意し、この API が目的の画像バイナリと Content-Type を返すようにする、といった対応が必要になります。


現にこういう方法で回避できちゃう CORS 制約は、処理が面倒くさくなるだけであまり意味ないと思っちゃうんだけどな。。