Opera の drawImage(SVGSvgElement) の実装には改善の余地があるみたい
先日、id:edvakf さんにヒントをいただいたので、drwaImage(SVGSvgElement) で Text API の実装を試してみました。
drwaImage(SVGSvgElement,...) では実装できないのでしょうか?
もしかして Opera だけ?
http://tc.labs.opera.com/html/canvas/svg/
テキストの表示はうまくいくのですが、dropShadow を実装しようとしたところ問題が発覚。
上が ガウスフィルター付きのSVGの描画で、
下が drawImage(SVGSvgElement) を実行したときの描画結果です。
// SVG + ガウスフィルター のコードは、こんな感じ <?xml version="1.0"?> <svg xmlns="http://www.w3.org/2000/svg"> <defs> <filter id="dropshadow"> <feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="1.2" /> <feOffset in="blur" result="offsetBlur" dx="4" dy="3"/> <feMerge> <feMergeNode in="offsetBlur"/> </feMerge> </filter> </defs> <text fill="pink" font-size="20" x="40" y="40" filter="url(#dropshadow)">abcdef</text> </svg>
drawImage を通すと画質がかなり劣化してしまうようです。
Opera9.52 〜 10α で確認しました。
もったいないから書き残す
チェックインしないつもりなので、ここにコードを書き残します。
# 最適化前なので、ちょっと不要なコードがあるけど気にしない。
// for Opera9.5 - 10.0 ( drawImage(SVGSvgElement) method ) if (uuCanvas.extendTextAPI && uu.ua.opera && (uu.ua.ver >= 9.5 && uu.ua.ver <= 10)) { uu.mix(CanvasRenderingContext2D.prototype, { textAlign: "start", textBaseling: "top", fillText: function(text, x, y, maxWidth, wire) { function svge(name) { return uudoc.createElementNS("http://www.w3.org/2000/svg", name); } /* <defs> <filter id="dropshadow"> <feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="..." /> <feOffset in="blur" result="offsetBlur" dx="..." dy="..."/> </filter> </defs> */ function filter(svg, sx, sy, sb, sc) { var e = []; svg.appendChild(e[0] = svge("defs")); e[0].appendChild(e[1] = svge("filter")); e[1].appendChild(e[2] = svge("feGaussianBlur")); e[1].appendChild(e[3] = svge("feOffset")); // e[1].appendChild(e[4] = svge("feMerge")); // e[4].appendChild(e[5] = svge("feMergeNode")); e[1].setAttribute("id", "dropshadow"); e[2].setAttribute("in", "SourceGraphic"); e[2].setAttribute("result", "blur"); e[2].setAttribute("stdDeviation", sb / 2); e[3].setAttribute("in", "blur"); e[3].setAttribute("result", "offsetBlur"); e[3].setAttribute("dx", sx); e[3].setAttribute("dy", sy); // e[5].setAttribute("in", "offsetBlur"); } var svg = svge("svg"), txt = svge("text"), txt2, align = TEXT_ALIGNS[this[TEXT_ALIGN]] || 1, metric = getTextMetric(text, this[FONT]), sc = _colorCache[this[SHADOW_COLOR]] || _addColorCache(this[SHADOW_COLOR]), offX = 0, offY = 0, blurSpace = 100; if (align !== 1) { // 1: "start" or "left" offX = (align === 2) ? metric.w / 2 : metric.w; // 2 = "center" } offY = (metric.h + metric.h / 2) / 2; // emulate textBaseLine="top" svg.setAttribute("width", metric.w + blurSpace); svg.setAttribute("height", metric.h + blurSpace); if (sc[1] && (this[SHADOW_OFFSET_X] || this[SHADOW_OFFSET_Y])) { filter(svg, this[SHADOW_OFFSET_X], this[SHADOW_OFFSET_Y], this[SHADOW_BLUR], sc); txt2 = svge("text"); txt2.setAttribute("x", 0 + blurSpace / 2); txt2.setAttribute("y", offY + blurSpace / 2); txt2.setAttribute("fill", sc[0]); txt2.setAttribute("opacity", 1); txt2.setAttribute("style", "font:" + this[FONT]); txt2.setAttribute("filter", "url(#dropshadow)"); svg.appendChild(txt2); txt2.appendChild(uudoc.createTextNode(text)); } txt.setAttribute("x", 0 + blurSpace / 2); txt.setAttribute("y", offY + blurSpace / 2); txt.setAttribute("fill", this[FILL_STYLE]); txt.setAttribute("style", "font:" + this[FONT]); svg.appendChild(txt); txt.appendChild(uudoc.createTextNode(text)); uudoc.body.appendChild(svg); this.drawImage(svg, x - offX - blurSpace / 2, y - blurSpace / 2); uudoc.body.removeChild(svg); }, strokeText: function(text, x, y, maxWidth) { this.fillText(text, x, y, maxWidth, 1); }, measureText: function(text) { var metric = getTextMetric(text, this[FONT]); return new TextMetrics(metric.w, metric.h); } }); }
おまけ
実は Firefox3 でも strokeText をサポートできるのですが、リリースするバージョンでは、機能をつぶします(fillTextと同じ結果にします)。
理由は、Geckoに実装されているAPI(mozPathText)を呼び出すと、カレントパスを変更してしまうので、他のブラウザと描画結果が異なるなど、多くの混乱が起きそうだからです。
if (uuCanvas.extendTextAPI) { if (uu.ua.gecko && uu.ua.ver === 3) { // for Firefox3.0 uu.mix(CanvasRenderingContext2D.prototype, { textAlign: "start", textBaseling: "top", fillText: function(text, x, y, maxWidth, wire) { var align = TEXT_ALIGNS[this[TEXT_ALIGN]] || 1, metric = getTextMetric(text, this[FONT]), offX = 0, offY = 0; if (align !== 1) { // 1: "start" or "left" offX = (align === 2) ? metric.w / 2 : metric.w; // 2 = "center" } offY = (metric.h + metric.h / 2) / 2; // emulate textBaseLine="top" this.save(); this.mozTextStyle = this.font; this.translate(x - offX, y + offY); if (0) { // strokeText をサポートする場合は、if (1) にする if (wire) { this.beginPath(); // reset path this.mozPathText(text); this.closePath(); this.stroke(); } else { this.mozDrawText(text); // http://d.hatena.ne.jp/uupaa/20090506/1241572019 this.fillRect(0,0,0,0); // force redraw(Firefox3 TextAPI doesn't redraw) } } else { if (wire) { this[FILL_STYLE] = this[STROKE_STYLE]; } this.mozDrawText(text); // http://d.hatena.ne.jp/uupaa/20090506/1241572019 this.fillRect(0,0,0,0); // force redraw(Firefox3 TextAPI doesn't redraw) } this.restore(); }, strokeText: function(text, x, y, maxWidth) { this.fillText(text, x, y, maxWidth, 1); }, measureText: function(text) { // return new TextMetrics(this.mozMeasureText(text), 0); var metric = getTextMetric(text, this[FONT]); return new TextMetrics(metric.w, metric.h); } });
反省会
- TextShadow は、CSS::text-shadow で描画する実装が既にあり、品質もCSS式のほうが良いので、drawImage(SVGSvgElement) 方式はお蔵入りする予定。
- 昨日見つけた Gecko の不具合や、今日見つけた Opera の問題はどちらも独自実装の部分なので、バグレポート出し辛いなぁ…
- SVG で DropShadow は http://dev.opera.com/articles/view/svg-evolution-3-applying-polish/?page=2 とかが参考になります。
- コードを書いては、ちぎっては投げ、ちぎっては捨て。
- やりきれない。