Несколько областей отсечения на холсте 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 canvas
где изображение одного региона отсечения появляется в другом регионе отсечения. Как я могу избежать этого или есть другой способ сделать это с помощью ткани JS.
7 ответов
Это можно сделать с помощью Fabric, используя clipTo
свойство, но вы должны "обратить" преобразования (масштаб и вращение) в clipTo
функция.
Когда вы используете clipTo
Свойство в Fabric, масштабирование и поворот применяются после отсечения, что означает, что отсечение масштабируется и поворачивается вместе с изображением. Вы должны противостоять этому, применяя точную обратную трансформацию в clipTo
функция собственности.
Мое решение предполагает наличие Fabric.Rect
служит "заполнителем" для области клипа (это имеет преимущества, поскольку вы можете использовать Fabric для перемещения объекта и, таким образом, области клипа.
Обратите внимание, что мое решение использует служебную библиотеку Lo-Dash, особенно для _.bind()
(см. код для контекста).
Пример скрипки
Сломать
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);
Для области клипа не обязательно должен быть реальный объект, но он облегчает управление, поскольку вы можете перемещать его с помощью 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 = 'http://fabricjs.com/lib/pug.jpg';
Что значит _.bind()
делать?
Обратите внимание, что ссылка обернута в _.bind()
функция.
я использую _.bind()
по следующим двум причинам:
- Нам нужно передать ссылку
Image
ВозражатьclipByName()
-
clipTo
Свойство передается контексту холста, а не объекту.
В принципе, _.bind()
позволяет создать версию функции, которая использует объект, который вы указали в качестве this
контекст.
Я подправил решение @natchiketa, так как позиционирование области клипа было неправильным и все шаткое при повороте. Но сейчас все вроде бы хорошо. Проверьте эту измененную скрипку: http://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();
}
Два небольших улова я нашел:
- Добавление обводки к объекту отсечения, кажется, отбрасывает вещи на несколько пикселей. Я пытался компенсировать позиционирование, но затем при повороте это добавило бы 2 пикселя к нижней и правой сторонам. Поэтому я решил удалить его полностью.
- Время от времени, когда вы поворачиваете изображение, оно будет иметь интервал в 1 пиксель по случайным сторонам в отсечении.
Обновление до ответа Promlnc.
Вам необходимо изменить порядок преобразований контекста, чтобы выполнить правильное отсечение.
- перевод
- пересчет
- вращение
В противном случае вы получите неправильно обрезанный объект - при масштабировании без сохранения соотношения сторон (изменение только одного измерения).
Код (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/
Правильный результат:
ОБНОВЛЕНИЕ 1:
Я разработал функцию обрезки по полигонам. http://jsfiddle.net/ZxYCP/198/
Это можно сделать гораздо проще. Fabric предоставляет метод рендеринга для обрезки по другому контексту объектов.
Оформить эту скрипку. Я видел это в комментарии здесь.
obj.clipTo = function(ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
clippingRect.render(ctx);
ctx.restore();
};
Поскольку я проверял все скрипты выше, у них есть одна ошибка. Это когда вы будете сбрасывать значения 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();
}
Пожалуйста, проверьте обновленную скрипку
В последнем обновлении 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));
Обновление ответов предыдущих парней.
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
);
Теперь мы можем масштабировать область отсечения без сомнения.