Fabric.js 使用控制处理程序进行图像操作,当图像被裁剪和缩放时,下一个裁剪操作不会应用于之前的状态

问题描述 投票:0回答:1

在给定的fabricjs代码中,我正在使用图像控制处理程序开发裁剪和缩放功能。当图像第一次缩放然后裁剪时,在这种情况下它工作正常。然后我进一步缩放裁剪后的图像,然后尝试裁剪此状态,图像状态设置为原始状态。

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Image crop</title>
  <script src="https://unpkg.com/fabric@latest/dist/fabric.js"></script>
</head>
<body>
  <canvas id="canvas" width="1200" height="700"></canvas>
  <!-- <script src="app.js"></script> -->
</body>
<script>




  fabric.Object.prototype.cornerStyle = "circle";
  fabric.util.requestAnimFrame(render);
  const canvas = new fabric.Canvas("canvas");

  let aspectRatioHorizontal;
  let aspectRatioVertical;

  const UserImage = fabric.util.createClass(fabric.Image, {
    type: "userImage",
    disableCrop: false,
    clipPosition: null,
    previousTarget: null,
    cropWidth: 0,
    cropHeight: 0,
    changeWidth: 0,
    changeHeight: 0,
    rightChange: 0,
    bottomChange: 0,
    initialize(element, options) {
      options = options || {};
      options = Object.assign({
        cropHeight: this.height,
        cropWidth: this.width
      }, options);

      if (!("clipPosition" in options) || Object.values(fabric.UserImage.CLIP_POSITIONS).indexOf(options.clipPosition) === -1) {
        options.clipPosition = fabric.UserImage.CLIP_POSITIONS.CENTER_MIDDLE;
      }

      this.callSuper("initialize", element, options);

      if (!this.disableCrop) {
        this.applyCrop();
      }
    },

    getCrop(image, size, scaleX, scaleY, trim_from, corner, target) {
      let width = size.width;
      let height = size.height;

      // Calculate the crop width and height based on the provided size
      console.log('scaleX', scaleX, 'scaleY', scaleY)
      let x = this.cropX;
      let y = this.cropY;
     
      let newWidth = Math.min(width, image.width * scaleX);
      let newHeight = Math.min(height, image.height * scaleY);
      
      if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_TOP) {
        x = this.cropX;
        y = this.cropY;
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_MIDDLE) {
        x = this.cropX;
        y = this.cropY;
        if(this.previousTarget){
          x = target.cropX;
          y = target.cropY;
         }
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_BOTTOM) {
        x = 0;
        y = image.height - newHeight;
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_TOP) {
        x = this.cropX + this.cropWidth - newWidth;
        y = this.cropY;
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_MIDDLE) {
        //console.log("thisx ..................", this)
         x = (image.width * this.scaleX  - newWidth)/2;
         y = (image.height * this.scaleY - newHeight)/2;
         
        
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_BOTTOM) {
        x = this.cropX + this.cropWidth - newWidth;
        y = this.cropY + this.cropHeight - newHeight;
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_TOP) {
        x = image.width - newWidth;
        y = 0;
        
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_MIDDLE) {
        x = this.cropX + this.cropWidth - newWidth;
        y = this.cropY + this.cropHeight - newHeight;
      } else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_BOTTOM) {
        x = image.width - newWidth;
        y = image.height - newHeight;
      
      } else {
        console.error(
          new Error("Unknown clip position property - " + this.clipPosition)
        );
      }

      const scaleToFitWidth = width / this.getBoundingRect().width;
      const scaleToFitHeight = height / this.getBoundingRect().height;
      const scale = Math.max(scaleToFitWidth, scaleToFitHeight);

      newWidth *= scale;
      newHeight *= scale;
    

      return {
        cropX: x,
        cropY: y,
        cropWidth: newWidth,
        cropHeight: newHeight,
      };
    },


    applyCrop(c_position, target) {
      this.clipPosition = c_position || 'center-middle';
      
      
      let trim_from;
      if (
        c_position === fabric.UserImage.CLIP_POSITIONS.CENTER_TOP
        || c_position === fabric.UserImage.CLIP_POSITIONS.CENTER_BOTTOM
      ) {
        trim_from = 'height';
      } else if (
        c_position === fabric.UserImage.CLIP_POSITIONS.LEFT_MIDDLE
        || c_position === fabric.UserImage.CLIP_POSITIONS.RIGHT_MIDDLE
      ) {
        trim_from = 'width';
      }
      if (this.disableCrop) return;

      let boudingRect = this.getBoundingRect();


  
      const crop = this.getCrop(
        this.getOriginalSize(),
        {
          width: this.getScaledWidth(),
          height: this.getScaledHeight(),
        },
        this.scaleX,
        this.scaleY,
        trim_from,
        c_position,
        this.previousTarget
      );

      if (target && c_position === 'center-middle') {
        // Use the previous state for subsequent crops
        crop.width = target.width;
        crop.height = target.height;
        crop.cropWidth = target.cropWidth;
        crop.cropHeight = target.cropHeight;
        crop.cropX = target.cropX;
        crop.cropY = target.cropY;
        crop.scaleX = target.scaleX;
        crop.scaleY = target.scaleY;
        this.previousTarget = crop;
      } else {
        // Clear the previous state if it's not a 'center-middle' crop
        this.previousTarget = null;
      }
      
      this.set(crop);
      this.setCoords();
      console.log('this x------------', this)
      canvas.renderAll();
    },


    _render(ctx) {
      if (this.disableCrop) {
        this.cropX = 0;
        this.cropY = 0;
        this.callSuper("_render", ctx);
        return;
      }

      const width = this.width,
        height = this.height,
        cropWidth = this.cropWidth,
        cropHeight = this.cropHeight,
        cropX = this.cropX,
        cropY = this.cropY;

      ctx.save();
      ctx.drawImage(
        this._element,
        cropX,
        cropY,
        cropWidth,
        cropHeight,
        -width / 2,
        -height / 2,
        width,
        height
      );
      ctx.restore();
    },

    toObject(options) {
      options = options || [];
      return this.callSuper("toObject", [].concat(Array.from(options), ["cropWidth", "cropHeight", "disableCrop"]));
    }
  });

  UserImage.CLIP_POSITIONS = {
    LEFT_TOP: "left-top",
    LEFT_MIDDLE: "left-middle",
    LEFT_BOTTOM: "left-bottom",
    CENTER_TOP: "center-top",
    CENTER_MIDDLE: "center-middle",
    CENTER_BOTTOM: "center-bottom",
    RIGHT_TOP: "right-top",
    RIGHT_MIDDLE: "right-middle",
    RIGHT_BOTTOM: "right-bottom"
  }

  UserImage.fromObject = function (object, callback) {
    fabric.util.loadImage(object.src, function (img, error) {
      if (error) {
        callback && callback(null, error);
        return;
      }
      fabric.Image.prototype._initFilters.call(object, object.filters, function (filters) {
        object.filters = filters || [];
        fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function (resizeFilters) {
          object.resizeFilter = resizeFilters[0];
          const image = new fabric.UserImage(img, object);
          callback(image);
        });
      });
    }, null, object.crossOrigin);
  };

  fabric.UserImage = UserImage;
  fabric.UserImage.async = true;

  const controlsUtils = fabric.controlsUtils,
    scaleSkewStyleHandler = controlsUtils.scaleSkewCursorStyleHandler,
    scaleStyleHandler = controlsUtils.scaleCursorStyleHandler,
    scalingEqually = controlsUtils.scalingEqually,
    scalingYOrSkewingX = controlsUtils.scalingYOrSkewingX,
    scalingXOrSkewingY = controlsUtils.scalingXOrSkewingY,
    scaleOrSkewActionName = controlsUtils.scaleOrSkewActionName,
    scalingX = controlsUtils.scalingX,
    scalingY = controlsUtils.scalingY;

  function actionScalingOrSkewingCropHandler(eventData, transform, x, y) {
    //console.log('control utils', controlsUtils)
    const { target, corner } = transform;
    let clip_position = UserImage.CLIP_POSITIONS.CENTER;
    if (corner === 'mt') {
      clip_position = UserImage.CLIP_POSITIONS.CENTER_BOTTOM
    } else if (corner === 'mb') {
      clip_position = UserImage.CLIP_POSITIONS.CENTER_TOP
    } else if (corner === 'ml') {
      clip_position = UserImage.CLIP_POSITIONS.RIGHT_MIDDLE
    } else if (corner === 'mr') {
      clip_position = UserImage.CLIP_POSITIONS.LEFT_MIDDLE
    }

    let act;
    target.applyCrop(clip_position);
    if (corner === "mr" || corner === "ml") {
      act = scalingXOrSkewingY(eventData, transform, x, y);
    }

    if (corner === "mt" || corner === "mb") {

      act = scalingYOrSkewingX(eventData, transform, x, y);
    }

    return act;
  }

  function actionScalingEquallyCropHandler(eventData, transform, x, y) {
    const { target, corner } = transform;

    if (["tl", "tr", "bl", "br"].indexOf(corner) > -1 && eventData.shiftKey) {
      console.log("center-middle...................")
      target.applyCrop();
    }


    const currentWidth = target.width * target.scaleX;
    const currentHeight = target.height * target.scaleY;

    const newWidth = target.getScaledWidth(); // Calculate the new width based on scaling
    const scaleFactor = newWidth / currentWidth; // Calculate the scale factor

    const cropWidth = target.cropWidth / scaleFactor;
    const cropHeight = target.cropHeight / scaleFactor;

    const cropX = target.cropX + (target.cropWidth - cropWidth) / 2;
    const cropY = target.cropY + (target.cropHeight - cropHeight) / 2;


    target.set({
      width: currentWidth,
      height: currentWidth / scaleFactor,
      cropWidth,
      cropHeight,
      cropX,
      cropY,
    });

    target.applyCrop('center-middle', target);
    // Apply the updated crop
    const action = scalingEqually(eventData, transform, x, y); // Apply scaling action
    return action;
  }


  function render(shadowOffsetX, shadowOffsetY, fn) {
    const angle = this.angle;

    return function (ctx, left, top, styleOverride, fabricObject) {
      if (fabricObject.disableCrop) {
        return controlsUtils.renderCircleControl(ctx, left, top, styleOverride, fabricObject);
      }

      ctx.save();
      ctx.translate(left, top);
      ctx.rotate(fabric.util.degreesToRadians(angle + fabricObject.angle));
      ctx.shadowColor = "rgba(0, 0, 0, 0.7)";
      ctx.shadowBlur = 2;
      ctx.shadowOffsetX = shadowOffsetX;
      ctx.shadowOffsetY = shadowOffsetY;
      ctx.beginPath();
      ctx.lineWidth = 6;
      ctx.lineCap = "round";
      ctx.strokeStyle = "#FFFFFF";
      fn.call(this, ctx)
      ctx.stroke();
      ctx.restore();
    }
  }

  function renderLeftOrRight(ctx, left, top, styleOverride, fabricObject) {
    render.call(this, 0, 0, function (ctx) {
      ctx.moveTo(0, -8);
      ctx.lineTo(0, 8);
    })(ctx, left, top, styleOverride, fabricObject);
  }

  function renderTopOrBottom(ctx, left, top, styleOverride, fabricObject) {
    render.call(this, 0, 0, function (ctx) {
      ctx.moveTo(-8, 0);
      ctx.lineTo(8, 0);
    })(ctx, left, top, styleOverride, fabricObject);
  }

  const userImageControls = {
    mr: new fabric.Control({
      x: 0.5,
      y: 0,
      cursorStyleHandler: scaleSkewStyleHandler,
      getActionName: scaleOrSkewActionName,
      actionHandler: actionScalingOrSkewingCropHandler,
      render: renderLeftOrRight
    }),

    ml: new fabric.Control({
      x: -0.5,
      y: 0,
      cursorStyleHandler: scaleSkewStyleHandler,
      getActionName: scaleOrSkewActionName,
      actionHandler: actionScalingOrSkewingCropHandler,
      render: renderLeftOrRight
    }),

    mt: new fabric.Control({
      x: 0,
      y: -0.5,
      cursorStyleHandler: scaleSkewStyleHandler,
      getActionName: scaleOrSkewActionName,
      actionHandler: actionScalingOrSkewingCropHandler,
      render: renderTopOrBottom
    }),

    mb: new fabric.Control({
      x: 0,
      y: 0.5,
      cursorStyleHandler: scaleSkewStyleHandler,
      getActionName: scaleOrSkewActionName,
      actionHandler: actionScalingOrSkewingCropHandler,
      render: renderTopOrBottom
    }),

    tl: new fabric.Control({
      x: -0.5,
      y: -0.5,
      cursorStyleHandler: scaleStyleHandler,
      actionHandler: actionScalingEquallyCropHandler
    }),

    tr: new fabric.Control({
      x: 0.5,
      y: -0.5,
      cursorStyleHandler: scaleStyleHandler,
      actionHandler: actionScalingEquallyCropHandler
    }),

    bl: new fabric.Control({
      x: -0.5,
      y: 0.5,
      cursorStyleHandler: scaleStyleHandler,
      actionHandler: actionScalingEquallyCropHandler
    }),

    br: new fabric.Control({
      x: 0.5,
      y: 0.5,
      cursorStyleHandler: scaleStyleHandler,
      actionHandler: actionScalingEquallyCropHandler
    }),

    mtr: new fabric.Control({
      x: 0,
      y: -0.5,
      actionHandler: controlsUtils.rotationWithSnapping,
      cursorStyleHandler: controlsUtils.rotationStyleHandler,
      offsetY: -40,
      withConnection: true,
      actionName: 'rotate',
    })
  }

  fabric.UserImage.prototype.controls = userImageControls;


  let imgObj = new Image(), image = null;

  imgObj.crossOrigin = "anonymous"
  imgObj.src = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6JavL8o0BLx1-BXc74ndZdPkLKRCPYQGibA&usqp=CAU";
  imgObj.onload = () => {
    image = new fabric.UserImage(imgObj, { left: 10, top: 50 });
    canvas.add(image);
  }

  const stateCrop = {}


</script>
</html>

我尝试在图像缩放到 applyCrop 函数时传递目标对象,以便下次操作时它应该是状态。

javascript canvas html5-canvas fabricjs
1个回答
0
投票

我实现了一个裁剪工具,通过原生dom而不是fabric.js实现。您可以尝试一下,看看是否满足您的需求。 https://github.com/zxf9397/image-cropper

npm i icropper
import { fabric } from 'fabric';
import { FabricCropListener } from 'icropper';

const cropButton = document.querySelector('#crop-btn') as HTMLButtonElement;
const confirmButton = document.querySelector('#confirm-btn') as HTMLButtonElement;
const cancelButton = document.querySelector('#cancel-btn') as HTMLButtonElement;

(async () => {
  const canvas = document.querySelector('#canvas') as HTMLCanvasElement;
  canvas.width = 600;
  canvas.height = 600;
  canvas.style.setProperty('border', `${2}px solid`);
  const fabricCanvas = new fabric.Canvas(canvas);

  function fabricImageFromURL(url: string, imgOptions?: fabric.IImageOptions) {
    return new Promise<fabric.Image>((resolve, reject) => {
      try {
        fabric.Image.fromURL(url, resolve, { ...imgOptions, crossOrigin: 'anonymous' });
      } catch (error) {
        reject(error);
      }
    });
  }

  const image = (await fabricImageFromURL('/gallery/pic.png', {
    left: 100,
    top: 100,
    scaleX: -0.6,
    scaleY: -0.5,
    angle: 0,
    flipX: true,
    flipY: true,
  })) as Required<fabric.Image>;

  fabricCanvas.add(image).renderAll();

  const listener = new FabricCropListener(fabricCanvas, { containerOffsetX: 604, containerOffsetY: 2 });

  cropButton.addEventListener('click', listener.crop.bind(listener));
  confirmButton.addEventListener('click', listener.confirm.bind(listener));
  cancelButton.addEventListener('click', listener.cancel.bind(listener));
})();
© www.soinside.com 2019 - 2024. All rights reserved.