読者です 読者をやめる 読者になる 読者になる

デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

射影変換(ホモグラフィ)について理解してみる その10

今回は、透明画像の対応について説明します。

今回、使用する透明画像はこのボール(png形式)になります。
f:id:Yaju3D:20131020023846p:plain
背景画像には、このかわいい猫ちゃんを使います。
f:id:Yaju3D:20130331175500j:plain

前回のプログラムのままでは透明画像に対応していないため、ボールの周りが黒色になってしまいます。
f:id:Yaju3D:20131020024348j:plain
これは、ImageDataをそのまま描画するputImageDataメソッドでは透過されない仕様のためです。
※前回のプログラムでは、透明度を255(不透明)固定してセットしていますが、255以下にしても透過されません。
Javascript における imageData のアルファ(透明度)について - Yahoo!知恵袋 の回答によると

imageData.data[i+3] = 50; // 青い四角形のアルファを下げる
これ↑の解釈なんですが・・・
そのアルファは、イメージの透過率ではなくて、Canvas自体の透過率みたいです。
ためしに 0 にすると、その領域で Canvas は完全に透過になります。

では、透明画像に対応するにはどうするのか?
主に3点の修正が必要となります。

  • Canvasを1枚追加し、隠しCanvasとして扱う
  • ImageData生成時に透明度を255(不透明)の固定から描画イメージの透明度をセットに変更
  • putImageDataで隠しCanvas側に一旦描画し、drawImageで表示側Canvasに転送する


各項目について説明していきます。
Canvasを1枚追加し、隠しCanvasとして扱う
下記のように、隠しCanvasを追加します。

<canvas id="ctx"></canvas>
<canvas id="ctx2" style="visibility:hidden"></canvas>

仕組みを見たい方は、style="visibility:hidden"を外すのと縦のサイズを変更するといいです。
this.height = canvas.height = window.innerHeight; → 400;

■ImageData生成時に透明度を255(不透明)の固定から描画イメージの透明度をセットに変更
setPixel(output, j, i, R, G, B, 255); としていた透明度を変更します。

function drawNearest(ctx, param, sx, sy, w, h)
    var A = pixelData.A;
    this.setPixel(output, j, i, R, G, B, A);

function drawBilinear(ctx, param, sx, sy, w, h)
    var A = rgb00.A;
    this.setPixel(output, j, i, R, G, B, A);

■putImageDataで隠しCanvas側に一旦描画し、drawImageで表示側Canvasに転送する
1.隠しCanvas(this.canvas2)のサイズも生成する画像サイズにしておきます。
2.隠しCanvasのコンテキスト(ctx2)に生成した画像データをputImageDataメソッドで描画します。その際、描画位置は左上の原点(0,0)にします。
3.イメージオブジェクトを生成して、画像のソースに隠しCanvasの画像データ(this.canvas2.toDataURL();)をセットします。
4.drawImageメソッドで表示側Canvas側の指定位置に転送することで、透明部分が反映されます。

render() {
    var ctx = this.context;
    var ctx2 = this.context2;
    var min = new Point(0, 0);
    var max = new Point(0, 0);
    var pt = [];

    // アンカー値をセット
    for(var i = 0; i < this.markers.length; i++) {
        pt.push(Anchor.getPoint("p" + (i + 1)));
        this.markers[i][0] = pt[i].x - this.offset.x;
        this.markers[i][1] = pt[i].y - this.offset.y;
    }

    // 描画用の射影変換パラメータを取得
    var inv_param = this.computeH(this.origin, this.markers, min, max);

    // 画像サイズをセット
    var w = max.x - min.x;
    var h = max.y - min.y;

    // 角度0の時に形状保存
    if(this.degrees.value == "0") {
        // 回転用に各制御点の中心点との差をセット
        this.center = new Point(w / 2, h / 2);
        this.vertexs.length = 0;
        for(var i = 0; i < this.markers.length; i++) {
            this.vertexs.push(new Point(this.markers[i][0], this.markers[i][1]));
        }
    }

    // 描画クリア
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    // 背景画像
    ctx.drawImage(this.backimage, 0, 0);

    // 隠しCanvasのサイズをセット
    this.canvas2.width = w;
    this.canvas2.height = h;

    // 画像処理
    if(this.drawType[0].checked) {
        // ニアレストネイバー
        this.drawNearest(ctx2, inv_param, 0, 0, w, h);
    } else {
        // バイリニア補間
        this.drawBilinear(ctx2, inv_param, 0, 0, w, h);
    }

    // 隠しCanvasにputImageDataで描画した画像をdrawImageで描画することで透明部分が反映される
    var newimage = new Image();
    newimage.src = this.canvas2.toDataURL();
    ctx.drawImage(newimage, this.offset.x + min.x, (this.offset.y - this.ctlHeight) + min.y);

    // 座標位置表示
    this.drawInfo(pt);
};

この変更により、下記のように透明画像が表示されるようになります。
ただし、描画コストがかなり高くなります。
f:id:Yaju3D:20131020024356j:plain

■プログラム

Typescriptで組んだソースリストは、github/Homography2に公開しました。