How can I implement image crop and clipPath with fabricjs

141 views Asked by At

`I want to make a trim and clip path functionality with fabric js, basically the same thing happens to me as in this case https://stackoverflow.com/questions/75260621/image-cropping-and-masking-using-fabricjs/76327881 I would greatly appreciate any help

The problem is that when reducing the image that contains the clipPath to a small size the element exceeds the limits, if I move the image the clipPath adapts again, but the problem is in the resizing.

import { extension } from '../utils';
import { controlPositionIcons } from './drawControls';

export enum ControlPositions {
  TOP_LEFT = 'tl',
  TOP = 't',
  TOP_RIGHT = 'tr',
  RIGHT = 'r',
  BOTTOM_RIGHT = 'br',
  BOTTOM = 'b',
  BOTTOM_LEFT = 'bl',
  LEFT = 'l',
}
export default extension('image.controls', (fabric) => {
  fabric.util.object.extend(fabric.Image.prototype, {
    type: 'image',
    _editingMode: false,
    __editingImage: null,
    cornerLengthEditing: 5,
    cornerStrokeColorEditing: 'black',
    cornerSizeEditing: 2,
    cropType: 'triangle',
    initialize: function (element, options) {
      options || (options = {});
      this.filters = [];
      this.cacheKey = 'texture' + fabric.Object.__uid++;
      this.registerEditingEvents();
      this.callSuper('initialize', options);
      this._initElement(element, options);
    },
    getElement: function () {
      return this._element || {};
    },
    applyCrop: function () {
      if (this.clipPath instanceof fabric.Object) {
        this.clipPath = null;
      }

      if (this.cropType === 'circle') {
        this.clipPath = new fabric.Ellipse({
          rx: this.width / 2,
          ry: this.height / 2,
          left: -this.width / 2,
          top: -this.height / 2,
        });
      }
      if (this.cropType === 'triangle') {
        this.clipPath = new fabric.Triangle({
          width: this.width,
          height: this.height,
          left: -this.width / 2,
          top: -this.height / 2,
          minScaleLimit: 0,
        });
      }

      return this;
    },
    registerEditingEvents: function () {
      this.on('mousedblclick', () => {
        if (!this._editingMode) {
          return this.enterEditingMode();
        } else {
          this.exitEditingMode();
        }
      });
      this.on('deselected', () => {
        this.exitEditingMode();
      });
    },
    enterEditingMode: function () {
      if (this.selectable && this.canvas) {
        this._editingMode = true;
        this.__editingImage = fabric.util.object.clone(this);
        this.__editingImage.clipPath = null;
        const element = this.__editingImage.getElement();
        const { top = 0, left = 0, cropX = 0, cropY = 0, scaleX = 1, scaleY = 1 } =      this.__editingImage;
        this.__editingImage.set({
          top: top - cropY * scaleY,
          left: left - cropX * scaleX,
          height: element.height,
          width: element.width,
          cropX: 0,
          cropY: 0,
          opacity: 0.6,
          selectable: true,
          evented: false,
          excludeFromExport: true,
        });
        this.canvas.add(this.__editingImage);
        this.controls = this.__editingControls();
        this.applyCrop();
        this.canvas.requestRenderAll();
        this.on('moving', this.__editingOnMoving);
      }
    },
    exitEditingMode: function () {
      if (this.selectable && this.canvas) {
        this._editingMode = false;
        if (this.__editingImage) {
          this.canvas.remove(this.__editingImage);
          this.__editingImage = null;
        }
        this.off('moving', this.__editingOnMoving);
        this.controls = fabric.Object.prototype.controls;
        this.fire('exit:editing', { target: this });
        this.canvas.requestRenderAll();
      }
    },
    __editingControls: function () {
      const controls = Object.values(ControlPositions);
      return controls.map(this.__createEditingControl.bind(this));
    },
    __createEditingControl: function (position) {
      const cursor = position.replace('t', 's').replace('l', 'e').replace('b', 'n').replace('r', 'w');

      return new fabric.Control({
        cursorStyle: cursor + '-resize',
        actionName: `edit_${this.type}`,
        render: controlPositionIcons[position],
        positionHandler: this.__editingControlPositionHandler.bind(this, position),
        actionHandler: this.__editingActionHandlerWrapper(position),
      });
    },
    __editingActionHandlerWrapper: function (position) {
      return (_event, _transform, x, y) => {
        this.__editingSetCrop(position, x, y, true);
        return true;
      };
    },
    __editingOnMoving: function (event) {
      if (this._editingMode && event.pointer) {
        this.set('dirty', true);
        this.__editingSetCrop(ControlPositions.TOP_LEFT, this.left, this.top);
      }
    },
    __editingSetCrop: function (position, x, y, resize = false) {
      if (this.__editingImage) {
        const { top = 0, left = 0, width = 0, height = 0, scaleX = 1, scaleY = 1 } = this.__editingImage;
        if (position.includes('t')) {
          const maxTop = top + height * scaleY - (resize ? 0 : this.getScaledHeight());
          const minTop = Math.min(y, maxTop, this.top + this.getScaledHeight());
          this.top = Math.max(minTop, top);
          const cropY = Math.min((Math.min(Math.max(y, top), this.top) - top) / scaleY, height);
          if (resize) {
            this.height = Math.max(0, Math.min(this.height + (this.cropY - cropY), height));
          }
          this.cropY = cropY;
        } else if (position.includes('b') && resize) {
          const minHeight = Math.min((y - top) / scaleY - this.cropY, height - this.cropY);
          this.height = Math.max(0, minHeight);
        }
        if (position.includes('l')) {
          const maxLeft = left + width * scaleX - (resize ? 0 : this.getScaledWidth());
          const minLeft = Math.min(x, maxLeft, this.left + this.getScaledWidth());
          this.left = Math.max(minLeft, left);
          const cropX = Math.min((Math.min(Math.max(x, left), this.left) - left) / scaleX, width);
          if (resize) {
            this.width = Math.max(0, Math.min(this.width + (this.cropX - cropX), width));
          }
          this.cropX = cropX;
        } else if (position.includes('r') && resize) {
          const minWidth = Math.min((x - left) / scaleX - this.cropX, width - this.cropX);
          this.width = Math.max(0, minWidth);
        }
        this.applyCrop();
      }
    },
    __editingControlPositionHandler: function (position) {
      const xMultiplier = position.includes('l') ? -1 : position.length > 1 || position === 'r' ? 1 : 0;
      const yMultiplier = position.includes('t') ? -1 : position.length > 1 || position === 'b' ? 1 : 0;
      const x = (this.width / 2) * xMultiplier;
      const y = (this.height / 2) * yMultiplier;

      return fabric.util.transformPoint(
        new fabric.Point(x, y),
        fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, this.calcTransformMatrix())
      );
    },
    __renderEditingControl: function (position, ctx, left, top) {
      ctx.save();
      ctx.strokeStyle = this.cornerStrokeColorEditing;
      ctx.lineWidth = this.cornerSizeEditing;
      ctx.translate(left, top);
      if (this.angle) {
        ctx.rotate(fabric.util.degreesToRadians(this.angle));
      }
      ctx.beginPath();
      const x = position.includes('l') ? -ctx.lineWidth : position.includes('r') ? ctx.lineWidth : 0;
      const y = position.includes('t') ? -ctx.lineWidth : position.includes('b') ? ctx.lineWidth : 0;
      if (position === 'b' || position === 't') {
        ctx.moveTo(x - this.cornerLengthEditing / 2, y);
        ctx.lineTo(x + this.cornerLengthEditing, y);
      } else if (position === 'r' || position === 'l') {
        ctx.moveTo(x, y - this.cornerLengthEditing / 2);
        ctx.lineTo(x, y + this.cornerLengthEditing);
      } else {
        if (position.includes('b')) {
          ctx.moveTo(x, y - this.cornerLengthEditing);
        } else if (position.includes('t')) {
          ctx.moveTo(x, y + this.cornerLengthEditing);
        }
        ctx.lineTo(x, y);
        if (position.includes('r')) {
          ctx.lineTo(x - this.cornerLengthEditing, y);
        } else if (position.includes('l')) {
          ctx.lineTo(x + this.cornerLengthEditing, y);
        }
      }
      ctx.stroke();
      ctx.restore();`your text`
    },
  });
});
0

There are 0 answers