Несколько областей отсечения на полотне Fabric.js - программирование
Подтвердить что ты не робот

Несколько областей отсечения на полотне 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();

Несколько областей отсечения на полотне и полотне

где изображение одного региона отсечения появилось в другом регионе отсечения. Как я могу избежать этого или есть другой способ сделать это с помощью Fabric JS.

4b9b3361

Ответ 1

Это можно сделать с помощью Fabric, используя свойство clipTo, но вы должны "обратить" преобразования (масштабирование и вращение) в функцию clipTo.

Когда вы используете свойство clipTo в Fabric, масштабирование и вращение применяются после отсечения, что означает, что отсечение масштабируется и поворачивается вместе с изображением. Вы должны противостоять этому, применяя точное обратное преобразование в функции свойства clipTo.

Мое решение заключается в том, чтобы Fabric.Rect служил "заполнителем" для области клипа (это имеет преимущества, потому что вы можете использовать Fabric для перемещения объекта и, следовательно, области клипа.

Обратите внимание, что мое решение использует служебную библиотеку Lo-Dash, особенно для _.bind() (см. код для контекста).

Пример скрипки


Разбивка

1. Инициализируйте Fabric

Во-первых, мы хотим, чтобы наш холст, конечно:

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 свойство name, clipFor, чтобы функции clipTo могли найти то, по которому они хотят быть обрезанными:

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

Для области клипа необязательно должен быть реальный объект, но с ним проще управлять, поскольку вы можете перемещать его с помощью Fabric.

3. Функция отсечения

Мы определяем функцию, которая будет использоваться свойствами изображений clipTo отдельно, чтобы избежать дублирования кода:

Поскольку свойство angle объекта Image хранится в градусах, мы будем использовать его для преобразования в радианы.

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

findByClipName() - это удобная функция, которая использует Lo-Dash, чтобы найти свойство со свойством clipFor для объекта Image, который нужно обрезать (например, на изображении ниже, 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();
}

ПРИМЕЧАНИЕ. Ниже приведено объяснение использования this в вышеуказанной функции.

4. Объект fabric.Image с использованием clipByName()

Наконец, изображение можно создать и использовать функцию 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 = 'https://fabricjs.com/lib/pug.jpg';

Что делает _.bind()?

Обратите внимание, что ссылка заключена в функцию _.bind().

Я использую _.bind() по следующим двум причинам:

  1. Нам нужно передать ссылочный объект Image в clipByName()
  2. Свойству clipTo передается контекст холста, а не объект.

По сути, _.bind() позволяет вам создать версию функции, которая использует объект, указанный вами в качестве контекста this.

источники
  1. https://lodash.com/docs#bind
  2. https://fabricjs.com/docs/fabric.Object.html#clipTo
  3. https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/

Ответ 2

Я подправил решение @natchiketa, так как позиционирование области клипа было неправильным и все шаткое при вращении. Но сейчас все вроде бы хорошо. Проверьте эту модифицированную скрипку: https://jsfiddle.net/PromInc/ZxYCP/

Единственные реальные изменения были внесены в функцию clibByName шага 3 кода, предоставленного @natchiketa. Это обновленная функция:

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();
}

Два небольших улова, которые я нашел:

  1. Добавление обводки к объекту отсечения, кажется, отбрасывает вещи на несколько пикселей. Я попытался компенсировать позиционирование, но затем при повороте это добавило бы 2 пикселя к нижней и правой сторонам. Итак, я решил удалить его полностью.
  2. Время от времени, когда вы поворачиваете изображение, оно будет иметь интервал в 1 пиксель по случайным сторонам в отсечении.

Ответ 3

Обновите ответ @Promlnc.

Вам необходимо заменить порядок преобразований контекста, чтобы выполнить правильное отсечение.

  1. перевод
  2. масштабирование
  3. вращение

В противном случае вы получите неправильно обрезанный объект - при масштабировании без сохранения соотношения сторон (изменение только одного измерения).

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));

Посмотри это: https://jsfiddle.net/ZxYCP/185/

Правильный результат:

good

ОБНОВЛЕНИЕ 1:

Я разработал функцию обрезки по полигонам: https://jsfiddle.net/ZxYCP/198/

poly

Ответ 4

Это можно сделать гораздо проще. Fabric предоставляет метод визуализации для обрезки по контексту другого объекта.

Оформить заказ на эту скрипку. Я видел это в комментарии здесь.

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

    clippingRect.render(ctx);

    ctx.restore();
};

Ответ 5

Поскольку я проверял все скрипты выше, у них есть одна ошибка. Это когда вы будете сбрасывать значения 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();
}

Пожалуйста, проверьте обновленную скрипку

Ответ 6

В последнем обновлении 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));

Demo: https://jsfiddle.net/uimos/bntepzLL/5/

Ответ 7

Обновите ответы предыдущих парней.

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
);

Теперь мы можем без сомнения масштабировать область отсечения.