QML(Qt)でCanvasを使ってみた

Qt5のリリースが待ち遠しい今日この頃ですね。
masterをビルドしてお試し中です。

今回はQt Quick2の新機能のCanvasエレメントをお試しです。
リポジトリにあるサンプルをベースに少しいじってQMLっぽい感じで動きを作ってみました。
お陰で簡単ではあったのですが、ここのプロパティやメソッドの説明はドキュメントにあるのですがサンプルが無いのがまだ残念なところです。

ざっくりとCanvasはHTML5と同等に使えます。
簡単に情報をまとめると。。。
1. Canvas エレメント配置
2. onPaintハンドラで描画処理
3. getContext('2d')でコンテキスト取得
4. save()で状態保存
5. 線の太さとか色とか回転とか設定
6. 線とか描く
7. ストロークやフィルを適用する
8. restore()で状態を戻す


/// 情報 ///
Canvas
QtQuick2::Context2DW3C Canvas 2D Context API standard準拠)
 (Canvas::getContextメソッドで取得できるオブジェクトの使い方です。)


/// 既存のHTML5アプリの移植のヒント ///
以下のことが記載されてます。
・DOM APIの呼び出しをプリパティバインディングかCanvasエレメントのメソッドへ置き換えます。
・ユーザー操作はMouseAreaエレメントで置き換えます。
・setInterval / setTimeoutメソッドはTimerエレメントかCanvas::requestAnimationFrameメソッドを使用します。
・Canvas::onPaintで描画処理を記述します。外部イベントからCanvas::requestPaintメソッドを呼び出すことで描画指示ができます。
・画像を使用するときは、Canvas::loadImageメソッドで読み込んでCanvas::onImageLoadedハンドラで描画指示をします。


/// サンプル ///
キャンバスで線をひっぱってくるくる回転して表示されるようにしてみました。
動画は5秒くらいでゆっくり表示されるようにしていますがQMLの機能で簡単にアニメーションさせれます。
(結局、わかりにくかも・・・。)


今回は高さのスケール調整で傾いてるように見せてますが、変形はContext2D::transformメソッドで行うのがいいと思います。

「onRotateChanged:requestPaint();」のあたりのイベント駆動で再描画を実行してます。QMLらしい感じですね。
Canvas::renderTarget」プロパティは、「Canvas.Image」をセットしてメモリのバッファに描画するように設定しています。OpenGLのフレームバッファに描画するように設定もできます。
ただし、「Canvas::renderStrategy」プロパティとの組み合わせでサポートされてない場合もあるので注意です。


import QtQuick 2.0

Rectangle {
    width: 300
    height: 404
    color: "#000000"
    Component.onCompleted: {
        console.debug("onCompleted")

        //回転しながら出てくる感じにアニメスタート
        canvas.scaleX = 2.0;
        canvas.scaleY = 1.0;
        canvas.rotate = Math.PI *1;
    }

    //キャラクタの絵
    Image{
        source: "meruru_bg.png"
    }

    //絵を描く
    Canvas {
        id:canvas
        anchors.fill: parent
        property string strokeStyle:"#dc9ee7"
        property string fillStyle:"red"
        property int lineWidth: 1
        property bool fill:false
        property bool stroke:true
        property real alpha:0.8
        property real scaleX :0
        property real scaleY : 0
        property real rotate : 0
        property real centerX: width / 2
        property real centerY: height * 9 / 10

        smooth:true
        renderTarget:Canvas.Image
        renderStrategy: Canvas.Immediate

        //アニメーション
        Behavior on scaleX { NumberAnimation { duration: 1800;} }
        Behavior on scaleY { NumberAnimation { duration: 1800; } }
        Behavior on rotate { NumberAnimation { duration: 1800; } }

        //各種プロパティの変更イベントで再描画させる
        onLineWidthChanged:requestPaint();
        onFillChanged:requestPaint();
        onStrokeChanged:requestPaint();
        onAlphaChanged:requestPaint();
        onScaleXChanged:requestPaint();
        onScaleYChanged:requestPaint();
        onRotateChanged:requestPaint();

        onPaint: {
            console.debug("onPaint");
            var deg = 0;
            var cx = 0;
            var cy = 0;
            var r = canvas.width / 4;

            var ctx = canvas.getContext('2d');
            //状態保存
            ctx.save();
            //描画エリアのクリア
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            //各種設定
            ctx.fillStyle = canvas.fillStyle;

            //中心をずらす
            ctx.translate(centerX, centerY);
            //拡縮する
            ctx.scale(canvas.scaleX, canvas.scaleY);
            //回転する
            ctx.rotate(canvas.rotate);

            //パス描画の開始
            ctx.beginPath();

            //メインのライン
            ctx.strokeStyle = canvas.strokeStyle;
            ctx.lineWidth = canvas.lineWidth;
            ctx.globalAlpha = canvas.alpha;
            rect(ctx, cx, cy, r, deg);
            rect(ctx, cx, cy, r, deg + Math.PI / 4);
            circle(ctx, cx, cy, r);
            circle(ctx, cx, cy, r * 1.1);
            circle(ctx, cx, cy, r * Math.sqrt(2) / 2);
            circle(ctx, cx, cy, r * Math.sqrt(2) / 2 * 0.8);

            //塗りを適用する
            if (canvas.fill)    ctx.fill();
            //ストロークを適用させる
            if (canvas.stroke)  ctx.stroke();

            //状態復帰
            ctx.restore();
        }

        //四角を描く
        function rect(ctx, cx, cy, r, deg){
            ctx.moveTo(r * Math.cos(deg) + cx, r * Math.sin(deg) + cy);
            ctx.lineTo(r * Math.cos(deg + Math.PI / 2) + cx, r * Math.sin(deg + Math.PI / 2) + cy);
            ctx.lineTo(r * Math.cos(deg + Math.PI) + cx, r * Math.sin(deg + Math.PI) + cy);
            ctx.lineTo(r * Math.cos(deg + Math.PI * 3 / 2) + cx, r * Math.sin(deg + Math.PI * 3 / 2) + cy);
            ctx.closePath();      // つながってないところをつなぐ
        }
        //円を描く
        function circle(ctx, cx, cy, r){
            ctx.moveTo(r * Math.cos(0) + cx, r * Math.sin(0) + cy);
            ctx.arc(cx, cy, r, 0, 2 * Math.PI, false);
        }
    }

    //キャラクタの絵
    Image{
        source: "meruru.png"
    }

    //情報
    Column{
        anchors.top: parent.top
        x: parent.width * 0.8

        Text{
            text: "alpha:" + canvas.alpha
            font.pixelSize: 11
            color: "#dddddd"
        }
        Text{
            text: "scaleX:" + canvas.scaleX
            font.pixelSize: 11
            color: "#dddddd"
        }
        Text{
            text: "scaleY:" + canvas.scaleY
            font.pixelSize: 11
            color: "#dddddd"
        }
        Text{
            text: "rotate:" + canvas.rotate
            font.pixelSize: 11
            color: "#dddddd"
        }
    }

}