canvas 互換機能の bugfix

uupaa.js コードリード用のエントリです。興味が無い方は読み飛ばしてください。

VML backend で ctx.drawImage(image) + 不透明度(globalAlpha) を有効にしました(条件あり)

<vml:image> は opacity 属性が機能しません。そのため、<vml:image> ではなく、<vml:shape> <v:fill opacity="..." src="..." /> </v:shape> を使うことで、不透明度を設定できるようにしました。
# opacity属性はVMLの草案にありましたが、MSDNには記載されていません。

ただし、以下の条件があります。

      • ctx.drawImage(image, dx, dy) で、Matrixが変更されていなければ不透明度が有効になります。

      • ctx.drawImage(image, dx, dy, dw, dh) や ctx.drawImage(image, sx, sy, ,,,,,) では機能しません。(左から、Silverlight, VML, Flashです)


var _IMAGE_FILL     = '<v:shape style="position:absolute;width:10px;' +
                            'height:10px;z-index:?;left:?px;top:?px"' +
                            ' filled="t" stroked="f" coordsize="100,100"' +
                            ' path="?"><v:fill type="tile" opacity="?" src="?" /></v:shape>',
    _IMAGE_SHADOW   = '<v:shape style="position:absolute;width:10px;' +
                            'height:10px;z-index:?;left:?px;top:?px"' +
                            ' filled="t" stroked="f" coordsize="100,100"' +
                            ' path="?"><v:fill color="?" opacity="?" /></v:shape>';

// CanvasRenderingContext2D.prototype.drawImage
// drawImage(image, dx, dy)
// drawImage(image, dx, dy, dw, dh)
// drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
function drawImage(image, a1, a2, a3, a4, a5, a6, a7, a8) {
    if (this.globalAlpha <= 0) {
        return;
    }
    if (this.shadowColor !== this._shadowColor) {
        this.__shadowColor = uu.color(this._shadowColor = this.shadowColor);
    }
    if (this.globalCompositeOperation !== this._mix) {
        this.__mix = _COMPOS[this._mix = this.globalCompositeOperation];
    }

    var dim = uu.img.size(image), // img actual size
        args = arguments.length, full = (args === 9),
        sx = full ? a1 : 0,
        sy = full ? a2 : 0,
        sw = full ? a3 : dim.w,
        sh = full ? a4 : dim.h,
        dx = full ? a5 : a1,
        dy = full ? a6 : a2,
        dw = full ? a7 : a3 || dim.w,
        dh = full ? a8 : a4 || dim.h,
        rv = [], fg, m,
        history, // HTMLCanvasElement context history
        frag = [], tfrag, // code fragment
        i = 0, iz, c0,
        zindex = (this.__mix ===  4) ? --this._zindex
               : (this.__mix === 10) ? (this.clear(), 0) : 0,
        renderShadow = this.__shadowColor.a && this.shadowBlur,
        sizeTrans; // 0: none size transform, 1: size transform

    if (image.src) { // HTMLImageElement

+       if (!this._matrixEffected) {
+
+           // shadow
+           if (this.__shadowColor.a && this.shadowBlur) {
+               rv.push(uu.fmt(_IMAGE_SHADOW,
+                           [zindex, dx + (this.shadowOffsetX + 1),
+                                    dy + (this.shadowOffsetY + 1),
+                            _rect(this, 0, 0, dw, dh),
+                            this.__shadowColor.hex,
+                            (this.globalAlpha / Math.sqrt(this.shadowBlur) * 0.5)]));
+           }
+
+           // no resize + no opacity
+           if (args === 3 && this.globalAlpha !== 1) {
+               rv.push(uu.fmt(_IMAGE_FILL,
+                              [zindex, dx, dy, _rect(this, 0, 0, dw, dh),
+                               this.globalAlpha, image.src]));
+           } else {
                rv.push(
                    '<v:image style="position:absolute;z-index:', zindex,
                    ';width:',      dw,
                    'px;height:',   dh,
                    'px;left:',     dx,
                    'px;top:',      dy,
                    'px" coordsize="100,100" src="', image.src,
                    '" opacity="',  this.globalAlpha, // <vml:image opacity> doesn't work.
                    '" cropleft="', sx / dim.w,
                    '" croptop="',  sy / dim.h,
                    '" cropright="',    (dim.w - sx - sw) / dim.w,
                    '" cropbottom="',   (dim.h - sy - sh) / dim.h,
                    '" />');
+           }
+       } else {

Silverlight backend で、擬似的な strokeText をサポートしました

uuCanvas.js version 2.02 では Silverlight の strokeText は fillText と同じ結果になっていました(塗りつぶしていた)。
Silverlightには、テキストパス(テキストのアウトライン)を描画する機能がなく(WPFにはあるらしい)、中抜きの文字を描画することができないのが理由です。

この制限を以下の順番で描画することにより回避し、擬似的に strokeText をサポートしました。

  • ctx.fillText() を DropShadow Effect 付きで描画する。DropShadow の影の色は ctx.strokeStyle の色を使用する
  • ctx.fillText() を ctx.xKnockoutColor で描画する。これにより、ctx.xKnockoutColor の色で塗りつぶされた輪郭を持つ文字が描画される

ctx.xKnockoutColor はstrokeText用に今回追加した独自プロパティです。デフォルトは"white"なので、白で塗りつぶされた文字が描画されます。


(上から Silverlight, VML, Flash です)


(左から Silverlight, VML, Flash です)

Flash は背景色で抜いた文字を、Silverlight は白で塗りつぶした文字を描画しています。

// CanvasRenderingContext2D.prototype.strokeText
function strokeText(text, x, y, maxWidth, fill) {
    if (fill) {
        _strokeText(this, text, x, y, maxWidth, fill);
    } else {
        var fillStyle = this.fillStyle; // save

        _strokeText(this, text, x, y, maxWidth, 0);

        this.fillStyle = this.xKnockoutColor;
        _strokeText(this, text, x, y, maxWidth, 1);

        this.fillStyle = fillStyle; // restore
    }
}
function _strokeText(ctx, text, x, y, maxWidth, fill) {
    // (snip)
    rv.push('" FontFamily="', font.rawfamily,
            '" FontSize="', font.size.toFixed(2),
            '" FontStyle="', _FONT_STYLES[font.style] || "Normal",
            '" FontWeight="', _FONT_WEIGHTS[font.weight] || "Normal",
            '">', uu.esc(text),
                _matrix('TextBlock', uu.m2d.translate(x - offX, y, ctx._matrix)));

+   if (fill) {
        rv.push((ctx.__shadowColor.a &&
                 ctx.shadowBlur) ? _dropShadow(ctx, "TextBlock", ctx.__shadowColor) : "");
+   } else {
+       rv.push(['<TextBlock.Effect><DropShadowEffect Opacity="1" Color="', ctx.__strokeStyle.hex,
+                '" BlurRadius="', 4,
+                '" Direction="', 0,
+                '" ShadowDepth="', 1,
+                '" /></TextBlock.Effect>'].join(""));
+   }
    // (snip)

}