Open5

Fabric.js

midorinotanukimidorinotanuki

https://github.com/fabricjs/fabric.js/wiki/When-to-use-Fabric

Fabricを使うとき

Fabricが得意とすること

  • オブジェクトの操作(オブジェクトは「単純な図形、画像、ベクトル図形、テキスト」のいずれか
  • 操作は「移動、回転、サイズ変更」のいずれか
  • 画像フィルタ(組み込みのフィルタは少なく、自分で定義するのも簡単)
  • キャンバス上に多数のオブジェクトを表示する(パフォーマンス的に)
  • リッチテキストを表示する(複数行、テキスト配置、テキスト装飾、背景、シャドウ、アウトライン)
  • SVG を解析してキャンバス上に表示する SVG や、Fabric がサポートするコンテンツ(単純な図形、ベクトル図形、画像、テキスト)のいずれかを、Node.js 経由でサーバー上にレンダリングする。
  • js 「単純なシェイプ、ベクターシェイプ、画像、テキスト」のアニメート
  • 非透明コンテンツ(バウンディングボックス経由だけでなく)によるオブジェクトのグラブ
  • 自由な描画(いくつかのブラシが内蔵されており、独自のブラシを定義するのも簡単です)

Fabricが苦手とすること

  • 自明でない衝突検出(たとえば、ピクセル単位やカーソル単位での衝突検出。 スプライトベースのアニメーション(スプライトを抽象化することは可能ですが、ボックスの外には何もありません)
  • 3Dオブジェクトの変換(three.jsのような3Dライブラリを使用するのがベストです。
midorinotanukimidorinotanuki

Create a custom filter that swap colors

画像を処理するカスタムフィルターを作成する方法

Fabric.jsには新しいフィルターを作成するための基本ファイルがあります。今回はこのフィルターを「SwapColor」と呼ぶことに決めました。まず、この基本ファイルをコピーして、すべての「MyFilter」を「SwapColor」に置き換えます。

次に、本格的なコードを作成します。

このフィルターのアイデアは、画像内の色を取り、その色が完全に一致する場合に別の色と交換することです。これには、JavaScript(JS)部分とWebGL部分の両方でコードを書く必要があります。

前提条件

標準の1パラメータフィルターは「mainParameter」引数を使って自身を保存・復元できますが、このフィルターは2つのパラメータが必要です。したがって、「mainParameter」への参照を削除し、いくつかの情報を追加します。

フィルターはソースカラーと宛先カラーを処理する必要があるので、空の基本ファイルに以下の情報を追加します。

/**
 * SwapColor colorSource、CSSカラー
 * @param {String} colorSource
 * @default
 */
colorSource: 'rgb(255, 0, 0)',

/**
 * SwapColor colorDestination、CSSカラー
 * @param {String} colorDestination
 * @default
 */
colorDestination: 'rgb(0, 255, 0)',

さらに、フィルターが自身を保存し、復元できるようにします。

/**
 * インスタンスのオブジェクト表現を返します
 * @return {Object} インスタンスのオブジェクト表現
 */
toObject: function() {
  return fabric.util.object.extend(this.callSuper('toObject'), {
    colorSource: this.colorSource,
    colorDestination: this.colorDestination,
  });
}

次に、フィルターを2DCanvasとWebGLの両方で動作させる必要があります。

2DCanvasでのピクセル交換
フィルタークラス内で、applyTo2dが標準のWebGLサポートなしの関数です。このコードはブラウザで実行する場合はあまり必要ありませんが、Nodeでも使用する場合には便利です。ただし、Fabric.jsは両方のモードをサポートしているため、フィルターを書く場合は両方のモードに対応した方が良いです。

ロジックはシンプルです。各ピクセルについて、色が一致すれば宛先の色と交換します。色が一致するかどうかを判断するために、fabric.Colorを使ってCSSカラー文字列を解析し、最初の3つのコンポーネントを比較します。

/**
 * SwapColor操作を画像のピクセルを表すUint8ClampedArrayに適用します。
 *
 * @param {Object} options
 * @param {ImageData} options.imageData フィルタリングされるUint8ClampedArray。
 */
applyTo2d: function(options) {
  var imageData = options.imageData,
      data = imageData.data, i, len = data.length,
      // fabric.Colorを使って、サポートされているカラー文字列からr,g,b値を取得します
      source = new fabric.Color(this.colorSource).getSource(),
      destination = new fabric.Color(this.colorDestination).getSource();
  for (i = 0; i < len; i += 4) {
    if (data[i] === source[0] && data[i + 1] === source[1] && data[i + 2] === source[2]) {
      data[i] = destination[0];
      data[i + 1] = destination[1];
      data[i + 2] = destination[2];
    }
  }
},

これで、2DCanvasでの部分は完了です。

WebGL部分
WebGLに慣れていれば、この部分はとても簡単に見えるでしょうが、そうでない場合はいくつかの概念を理解する必要があります。ここでは、Fabric.jsがWebGLの基本機能を再利用可能なパーツにラップしているコードを紹介します。

WebGLシェーダーでの変数設定
まず、WebGLシェーダー内で変数を設定するためのコードを用意します。これには、getUniformLocationsとsendUniformDataという2つの関数が関わります。getUniformLocationsはシェーダー内での変数の呼び出し方を定義し、sendUniformDataはその変数にデータをバインドします。sendUniformDataを書くのはかなり難しい部分で、どの型の変数を使用するかを理解している必要があります。ここでは、色の文字列を0から1の範囲の4つの数値の配列に変換する必要があり、そのためにgl.uniform4fv関数を使用します。

/**
 * このフィルターのシェーダー用WebGLユニフォームロケーションを返します。
 *
 * @param {WebGLRenderingContext} gl フィルターのシェーダーをコンパイルするために使用されるGLキャンバスコンテキスト。
 * @param {WebGLShaderProgram} program このフィルターのコンパイル済みシェーダープログラム。
 */
getUniformLocations: function(gl, program) {
  return {
    uColorSource: gl.getUniformLocation(program, 'colorSource'),
    uColorDestination: gl.getUniformLocation(program, 'colorDestination'),
  };
},

/**
 * このフィルターからシェーダープログラムのユニフォームにデータを送信します。
 *
 * @param {WebGLRenderingContext} gl フィルターのシェーダーをコンパイルするために使用されるGLキャンバスコンテキスト。
 * @param {Object} uniformLocations ユニフォーム名からWebGLUniformLocationオブジェクトへのマップ
 */
sendUniformData: function(gl, uniformLocations) {
  var source = new fabric.Color(this.colorSource).getSource(),
      destination = new fabric.Color(this.colorDestination).getSource();
  // WebGLでは色のコンポーネントは0から1の範囲でなければなりません
  source[0] /= 255;
  source[1] /= 255;
  source[2] /= 255;
  destination[0] /= 255;
  destination[1] /= 255;
  destination[2] /= 255;
  gl.uniform4fv(uniformLocations.uColorSource, source);
  gl.uniform4fv(uniformLocations.uColorDestination, destination);
},

WebGLシェーダーのソースコード
次に、WebGLシェーダーのソースコードが必要です。これはフィルターの処理を定義します。

/**
 * SwapColorプログラムのフラグメントシェーダーソース
 */
fragmentSource: 'precision highp float;\n' +
  'uniform sampler2D uTexture;\n' +
  'uniform vec4 colorSource;\n' +
  'uniform vec4 colorDestination;\n' +
  'varying vec2 vTexCoord;\n' +
  'void main() {\n' +
    'vec4 color = texture2D(uTexture, vTexCoord);\n' +
    'vec3 delta = abs(colorSource.rgb - color.rgb);\n' +
    'gl_FragColor = length(delta) < 0.02 ? colorDestination.rgba : color;\n' +
  '}',

このコードでは、シェーダーが画像の色と指定された色との違いを計算し、その違いが小さい場合には指定された色に置き換えています。

実行してみよう
これで基本的なコードの説明は終了です。実際に動作を確認するためには、さらにスクロールして続きの情報を見てみてください。

midorinotanukimidorinotanuki

Class: Image

Members type
lockMovementY Boolean true`の場合、オブジェクトの水平方向の動きはロックされる。
lockMovementY Boolean true`の場合、オブジェクトの垂直方向の動きはロックされる。
selectable Boolean false`に設定すると、オブジェクトを選択して変更することができなくなります(ポイント・クリック・ベースでもグループ・ベースでも選択可能です)。 しかし、イベントは発生します。