2017年12月9日土曜日

dotrace.jsの使い方・SVGにおける多色画像のRGBAビット合成的アプローチ

WEBでは画像をRGBA各8bitで扱う. なので, RGBA値をそれぞれbit値毎にpath図形化し, SVGフィルタで合成し直すことで, 理論上はあらゆるグラフィックは高々32個のpath要素(とSVGフィルタ)で表せるはずだ.

では実際にこのようなアプローチに沿って作ったSVGの描画パフォーマンスは如何なものか?
ということで, Node.jsスクリプトを書いてみた.

結果は「使い物にならないSVGを吐くポンコツスクリプト」であることが判明したが, まぁ, dotrace.jsの使い方の例としての意義はあると思い公開した.

12/12バグを見つけたので修正


※SVGでは「SVGフィルタの複雑さ」及び「各種ノード数」がグラフィックの描画パフォーマンスに大きく影響することが判っており, 今回のケースでは前者にグラフィックの複雑さを押し込んだケースに相当する. 

Node.js環境でJimpと先ほど公開したdotrace.jsとを導入した環境であれば, 下記コードを適宜保存し,

node fullcolor.js [ファイル名] > [SVGファイル名].svg
 
とすることで, 画像をSVG化出来る.



"use strict";
(async () => {
 const Dotrace = require("./dotrace.core.js");
 const Jimp = require("jimp");
 const fn = process.argv[2];
 if(!fn){return;}
 const img = await Jimp.read(fn);
 const bmp = img.bitmap;
 const data = Dotrace.normalizePixel(bmp.data);
 let paths = "";
 for(let i = 0; i<4; i++){
  for(let m = 0; m<8; m++){
   const d = data.map((val, j) => i == (j%4) ? val & (1 << m) : 0);
   const entries = Dotrace.trace(d, bmp.width, bmp.height).entries();
   const entrie = entries.next();
   paths += entrie.done 
    ? "" 
    : ((color, val) => 
     `<path transform="translate(${bmp.width * m},${bmp.height * i})" ${i != 3 ? 'fill="#'+Dotrace.formatColor(color)+'"' : 'opacity="'+ Dotrace.opacity(color) +'"'} d="${val}"/>
`)(...entrie.value);
  }
 }
 const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${bmp.width} ${bmp.height}" width="${bmp.width}" height="${bmp.height}">
<defs>
<filter id="f" x="0" y="0" width="1" height="1" primitiveUnits="objectBoundingBox" color-interpolation-filters="sRGB">
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="0"     result="b1"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.125" result="b2"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.25"  result="b3"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.375" result="b4"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.5"   result="b5"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.625" result="b6"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.75"  result="b7"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".125" height="1" dx="-.875" result="b8"/>
<feComposite in="b1" in2="b2" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b3" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b4" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b5" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b6" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b7" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b8" operator="arithmetic" k2="1" k3="1" result="rgba"/>
<feOffset in="rgba" x="0" y="0" width=".125" height=".25" dy="0"    result="r"/>
<feOffset in="rgba" x="0" y="0" width=".125" height=".25" dy="-.25" result="g"/>
<feOffset in="rgba" x="0" y="0" width=".125" height=".25" dy="-.5"  result="b"/>
<feOffset in="rgba" x="0" y="0" width=".125" height=".25" dy="-.75" result="a"/>
<feBlend mode="screen" in="r" in2="g"/>
<feBlend mode="screen"        in2="b"/>
<feComposite operator="in" in2="a"/>
</filter>
<g id="g" filter="url(#f)">
<rect width="${bmp.width*8}" height="${bmp.width*3}"/>
<rect fill="none" y="${bmp.width*3}" width="${bmp.width*8}" height="${bmp.width}"/>
${paths}</g>
</defs>
<use xlink:href="#g"/>
</svg>`;
 process.stdout.write(svg);
})();

結果は・・・とてもじゃないけれど使い物にならない. ここでは8-8-8-8でグラフィックを変換しているけれど, 実用性を鑑みると下位bitを無視するなどしてSVGフィルタ部を軽量化する必要があるだろう.

↓で改良したもの. 色ズレが気になるがそこそこ動くSVGを吐くようになった.

"use strict";
(async () => {
 const Dotrace = require("./dotrace.core.js");
 const Jimp = require("jimp");
 const fn = process.argv[2];
 if(!fn){return;}
 const img = await Jimp.read(fn);
 const bmp = img.bitmap;
 const data = Dotrace.normalizePixel(bmp.data);
 let paths = "";
 for(let i = 0; i<4; i++){
  for(let m = 0; m<4; m++){
   const d = data.map((val, j) => i == (j%4) ? val & (1 << (m+4)) : 0);
   const entries = Dotrace.trace(d, bmp.width, bmp.height).entries();
   const entrie = entries.next();
   paths += entrie.done 
    ? "" 
    : ((color, val) => 
     `<path transform="translate(${bmp.width * m},${bmp.height * i})" ${i != 3 ? 'fill="#' + Dotrace.formatColor(color) + '"' : 'opacity="' + Dotrace.opacity(color) + '"'} d="${val}"/>
`)(...entrie.value);
  }
 }
 const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${bmp.width} ${bmp.height}" width="${bmp.width}" height="${bmp.height}">
<defs>
<filter id="f" x="0" y="0" width="1" height="1" primitiveUnits="objectBoundingBox" color-interpolation-filters="sRGB">
<feOffset in="SourceGraphic" x="0" y="0" width=".25" height="1" dx="0"    result="b1"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".25" height="1" dx="-.25" result="b2"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".25" height="1" dx="-.5"  result="b3"/>
<feOffset in="SourceGraphic" x="0" y="0" width=".25" height="1" dx="-.75" result="b4"/>
<feComposite in="b1" in2="b2" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b3" operator="arithmetic" k2="1" k3="1"/>
<feComposite         in2="b4" operator="arithmetic" k2="1" k3="1" result="rgba"/>
<feOffset in="rgba" x="0" y="0" width=".25" height=".25" dy="0"    result="r"/>
<feOffset in="rgba" x="0" y="0" width=".25" height=".25" dy="-.25" result="g"/>
<feOffset in="rgba" x="0" y="0" width=".25" height=".25" dy="-.5"  result="b"/>
<feOffset in="rgba" x="0" y="0" width=".25" height=".25" dy="-.75" result="a"/>
<feBlend mode="screen" in="r" in2="g"/>
<feBlend mode="screen"        in2="b"/>
<feComposite operator="in" in2="a"/>
<feComponentTransfer>
<feFuncR type="linear" slope="1.0625"/>
<feFuncG type="linear" slope="1.0625"/>
<feFuncB type="linear" slope="1.0625"/>
<feFuncA type="linear" slope="1.0625"/>
</feComponentTransfer>
</filter>
<g id="g" filter="url(#f)">
<rect width="400%" height="300%"/>
<rect fill="none" y="300%" width="400%" height="100%"/>
${paths}</g>
</defs>
<use xlink:href="#g"/>
</svg>`;
 process.stdout.write(svg);
})();

0 件のコメント:

コメントを投稿