web-dev-qa-db-ja.com

Fabric.jsキャンバス上の複数のクリッピング領域

Photo Collage Maker を作成するために、オブジェクトベースのクリッピング機能を備えたファブリックjsを使用します。この機能は素晴らしいですが、そのクリッピング領域内の画像は拡大縮小、移動、回転できません。固定位置のクリッピング領域が必要で、ユーザーが望むように画像を固定クリッピング領域内に配置できます。

私はグーグルで検索し、非常に近い解決策を見つけました。

var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(10,10,150,150);
ctx.rect(180,10,200,200);
ctx.closePath();
ctx.stroke();
ctx.clip();

ファブリックjsキャンバス上の複数のクリッピング領域

あるクリッピング領域の画像が別のクリッピング領域に表示されている場合。どうすればこれを回避できますか、またはファブリックjsを使用してこれを達成する別の方法があります。

18
ep4f

これは、FabricでclipToプロパティを使用して実行できますが、clipTo関数で変換(スケールと回転)を「逆」にする必要があります。

FabricでclipToプロパティを使用すると、スケーリングと回転がクリッピングの後に適用されます。つまり、クリッピングは画像に合わせてスケーリングおよび回転されます。 clipToプロパティ関数の変換の正確なreverseを適用することにより、これに対抗する必要があります。

私の解決策は、_Fabric.Rect_をクリップ領域の「プレースホルダー」として機能させることです(これには、Fabricを使用してオブジェクトを移動し、クリップ領域を移動できるため、利点があります。

私のソリューションでは、特に _.bind() の場合に Lo-Dash ユーティリティライブラリを使用していることに注意してください(コンテキストについてはコードを参照してください)。

フィドルの例


壊す

1.ファブリックを初期化します

もちろん、最初にキャンバスが必要です。

_var canvas = new fabric.Canvas('c');
_

2.クリップ領域

_var clipRect1 = new fabric.Rect({
    originX: 'left',
    originY: 'top',
    left: 180,
    top: 10,
    width: 200,
    height: 200,
    fill: 'none',
    stroke: 'black',
    strokeWidth: 2,
    selectable: false
});
_

これらのRectオブジェクトに名前プロパティclipForを付けると、clipTo関数は、クリップするオブジェクトを見つけることができます。

_clipRect1.set({
    clipFor: 'pug'
});
canvas.add(clipRect1);
_

クリップ領域の実際のオブジェクトとなるhaveはありませんが、移動できるため、管理が容易になります。ファブリックを使用します。

3.クリッピング機能

コードの重複を避けるために、画像のclipToプロパティで個別に使用される関数を定義します。

Imageオブジェクトのangleプロパティは度で格納されるため、これを使用してラジアンに変換します。

_function degToRad(degrees) {
    return degrees * (Math.PI / 180);
}
_

findByClipName()は便利な関数で、 Lo-Dash を使用して、クリップするImageオブジェクトのclipForプロパティを持つを検索します(たとえば、下の画像では、name _'pug'_)になります:

_function findByClipName(name) {
    return _(canvas.getObjects()).where({
            clipFor: name
        }).first()
}
_

そして、これは仕事をする部分です:

_var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);
    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}
_

[〜#〜] note [〜#〜]でのthisの使用と説明については、以下を参照してください。上記の関数。

4. clipByName()を使用した_fabric.Image_オブジェクト

最後に、イメージをインスタンス化して、次のようにclipByName関数を使用するように作成できます。

_var pugImg = new Image();
pugImg.onload = function (img) {    
    var pug = new fabric.Image(pugImg, {
        angle: 45,
        width: 500,
        height: 500,
        left: 230,
        top: 170,
        scaleX: 0.3,
        scaleY: 0.3,
        clipName: 'pug',
        clipTo: function(ctx) { 
            return _.bind(clipByName, pug)(ctx) 
        }
    });
    canvas.add(pug);
};
pugImg.src = 'http://fabricjs.com/lib/pug.jpg';
_

_.bind()は何をしますか?

参照は _.bind() 関数でラップされていることに注意してください。

次の2つの理由で_.bind()を使用しています。

  1. 参照ImageオブジェクトをclipByName()に渡す必要があります
  2. clipToプロパティには、オブジェクトではなく、キャンバスコンテキストが渡されます。

基本的に、_.bind()を使用すると、thisコンテキストとして指定したオブジェクトを使用する関数のバージョンを作成できます。

  1. http://lodash.com/docs#bind
  2. http://fabricjs.com/docs/fabric.Object.html#clipTo
  3. http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
30
natchiketa

クリップ領域の配置が正しく配置されておらず、回転するとすべてが不安定になったため、@ natchiketaによってソリューションを微調整しました。しかし、今はすべてが良いようです。この変更されたフィドルをチェックしてください: http://jsfiddle.net/PromInc/ZxYCP/

実際の変更は、@ natchiketaによって提供されたコードのステップ3のclibByName関数でのみ行われました。これは更新された関数です:

var clipByName = function (ctx) {
    this.setCoords();

    var clipRect = findByClipName(this.clipName);

    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();

    var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
    var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
    var ctxWidth = clipRect.width - clipRect.strokeWidth + 1;
    var ctxHeight = clipRect.height - clipRect.strokeWidth + 1;

    ctx.translate( ctxLeft, ctxTop );

    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();

    ctx.rect(
        clipRect.left - this.oCoords.tl.x,
        clipRect.top - this.oCoords.tl.y,
        ctxWidth,
        ctxHeight
    );
    ctx.closePath();
    ctx.restore();
}

私が見つけた2つのマイナーなキャッチ:

  1. クリッピングオブジェクトにストロークを追加すると、数ピクセルずれてしまうようです。位置を補正しようとしましたが、回転すると、下側と右側に2ピクセル追加されます。だから私はそれを完全に削除することを選びました。
  2. たまに画像を回転させると、クリッピングのランダムな辺に1pxの間隔ができてしまいます。
10
PromInc

Promlncの回答に更新します。

適切なクリッピングを実行するには、コンテキスト変換の順序を置き換える必要があります。

  1. 翻訳
  2. スケーリング
  3. 回転

そうしないと、アスペクト比を維持せずに(1つの寸法のみを変更して)拡大縮小すると、オブジェクトが誤ってクリップされてしまいます。

bad

コード(69-72):

ctx.translate( ctxLeft, ctxTop );

ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);

置換先:

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate(degToRad(this.angle * -1));

これを参照してください: http://jsfiddle.net/ZxYCP/185/

適切な結果:

good

更新1:

ポリゴンごとにクリップする機能を開発しました。 http://jsfiddle.net/ZxYCP/198/

poly

6
l00k

これははるかに簡単に行うことができます。ファブリックは、別のオブジェクトコンテキストでクリップするための render メソッドを提供します。

これをチェックアウト フィドル 。コメントでこれを見ました ここ

        obj.clipTo = function(ctx) {
            ctx.save();
            ctx.setTransform(1, 0, 0, 1, 0, 0);

            clippingRect.render(ctx);

            ctx.restore();
        };
3
Kerem Demirer

上記のすべてのフィドルをテストしたところ、バグが1つあります。 X値とY値を一緒に反転すると、境界のクリッピングが間違ってしまいます。また、画像を正しい位置に配置するためのすべての計算を実行しないようにするには、originX = 'center'およびoriginY = 'center'を指定する必要があります。

これは@natchiketaからの元のコードへのクリッピング関数の更新です

var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);

    //logic for correct scaling
    if (this.getFlipY() && !this.getFlipX()){
      ctx.scale(scaleXTo1, -scaleYTo1);
    } else if (this.getFlipX() && !this.getFlipY()){
      ctx.scale(-scaleXTo1, scaleYTo1);
    } else if (this.getFlipX() && this.getFlipY()){
      ctx.scale(-scaleXTo1, -scaleYTo1);
    } else {
        ctx.scale(scaleXTo1, scaleYTo1);
    }
    //IMPORTANT!!! do rotation after scaling
    ctx.rotate(degToRad(this.angle * -1));
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}

更新を確認してください フィドル

2
Observer

fabric 1.6.0-rc.1の最新の更新では、Shiftキーを押しながら中央の軸をドラッグすることで画像を歪めることができます。

クリッピング領域が同じになるようにスキューを反転する方法に問題があります。次のコードを元に戻そうとしましたが、機能しませんでした。

var skewXReverse = - this.skewX;
var skewYReverse = - this.skewY;

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.transform(1, skewXReverse, skewYReverse, 1, 0, 0);
ctx.rotate(degToRad(this.angle * -1));

デモ: https://jsfiddle.net/uimos/bntepzLL/5/

1
Jowin Yip

以前の人の答えに更新します。

ctx.rect(
    clipRect.oCoords.tl.x - this.oCoords.tl.x - clipRect.strokeWidth,
    clipRect.oCoords.tl.y - this.oCoords.tl.y - clipRect.strokeWidth,
    clipRect.oCoords.tr.x - clipRect.oCoords.tl.x,
    clipRect.oCoords.bl.y - clipRect.oCoords.tl.y
);

これで、間違いなくクリッピング領域を拡大縮小できます。

0
Newbie