signal-desktop/ts/mediaEditor/MediaEditorFabricCropRect.ts
2021-11-30 20:14:25 -06:00

196 lines
4.7 KiB
TypeScript

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { fabric } from 'fabric';
import { clamp } from 'lodash';
export class MediaEditorFabricCropRect extends fabric.Rect {
static PADDING = 4;
constructor(options?: fabric.IRectOptions) {
super({
fill: undefined,
lockScalingFlip: true,
...(options || {}),
});
this.on('modified', this.containBounds.bind(this));
}
private containBounds() {
if (!this.canvas) {
return;
}
const zoom = this.canvas.getZoom() || 1;
const { left, top, height, width } = this.getBoundingRect();
const canvasHeight = this.canvas.getHeight();
const canvasWidth = this.canvas.getWidth();
if (height > canvasHeight || width > canvasWidth) {
this.canvas.discardActiveObject();
} else {
this.set(
'left',
clamp(
left / zoom,
MediaEditorFabricCropRect.PADDING / zoom,
(canvasWidth - width - MediaEditorFabricCropRect.PADDING) / zoom
)
);
this.set(
'top',
clamp(
top / zoom,
MediaEditorFabricCropRect.PADDING / zoom,
(canvasHeight - height - MediaEditorFabricCropRect.PADDING) / zoom
)
);
}
this.setCoords();
}
override render(ctx: CanvasRenderingContext2D): void {
super.render(ctx);
const bounds = this.getBoundingRect();
const zoom = this.canvas?.getZoom() || 1;
const canvasWidth = (this.canvas?.getWidth() || 0) / zoom;
const canvasHeight = (this.canvas?.getHeight() || 0) / zoom;
const height = bounds.height / zoom;
const left = bounds.left / zoom;
const top = bounds.top / zoom;
const width = bounds.width / zoom;
ctx.save();
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
// top
ctx.fillRect(0, 0, canvasWidth, top);
// left
ctx.fillRect(0, top, left, height);
// bottom
ctx.fillRect(0, height + top, canvasWidth, canvasHeight - top);
// right
ctx.fillRect(left + width, top, canvasWidth - left, height);
ctx.restore();
}
}
MediaEditorFabricCropRect.prototype.controls = {
tl: new fabric.Control({
x: -0.5,
y: -0.5,
actionHandler: fabric.controlsUtils.scalingEqually,
render: (
ctx: CanvasRenderingContext2D,
left: number,
top: number,
_,
rect: fabric.Object
) => {
const WIDTH = getMinSize(rect.width);
ctx.save();
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(left - 2, top + WIDTH);
ctx.lineTo(left - 2, top - 2);
ctx.lineTo(left + WIDTH, top - 2);
ctx.stroke();
ctx.restore();
},
}),
tr: new fabric.Control({
x: 0.5,
y: -0.5,
actionHandler: fabric.controlsUtils.scalingEqually,
render: (
ctx: CanvasRenderingContext2D,
left: number,
top: number,
_,
rect: fabric.Object
) => {
const WIDTH = getMinSize(rect.width);
ctx.save();
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(left + 2, top + WIDTH);
ctx.lineTo(left + 2, top - 2);
ctx.lineTo(left - WIDTH, top - 2);
ctx.stroke();
ctx.restore();
},
}),
bl: new fabric.Control({
x: -0.5,
y: 0.5,
actionHandler: fabric.controlsUtils.scalingEqually,
render: (
ctx: CanvasRenderingContext2D,
left: number,
top: number,
_,
rect: fabric.Object
) => {
const WIDTH = getMinSize(rect.width);
ctx.save();
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(left - 2, top - WIDTH);
ctx.lineTo(left - 2, top + 2);
ctx.lineTo(left + WIDTH, top + 2);
ctx.stroke();
ctx.restore();
},
}),
br: new fabric.Control({
x: 0.5,
y: 0.5,
actionHandler: fabric.controlsUtils.scalingEqually,
render: (
ctx: CanvasRenderingContext2D,
left: number,
top: number,
_,
rect: fabric.Object
) => {
const WIDTH = getMinSize(rect.width);
ctx.save();
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(left + 2, top - WIDTH);
ctx.lineTo(left + 2, top + 2);
ctx.lineTo(left - WIDTH, top + 2);
ctx.stroke();
ctx.restore();
},
}),
};
MediaEditorFabricCropRect.prototype.excludeFromExport = true;
MediaEditorFabricCropRect.prototype.borderColor = '#ffffff';
MediaEditorFabricCropRect.prototype.cornerColor = '#ffffff';
function getMinSize(width: number | undefined): number {
return Math.min(width || 24, 24);
}