import { Point } from '../../Point'; import { CENTER } from '../../constants'; import type { FabricObject } from '../../shapes/Object/Object'; import type { TMat2D } from '../../typedefs'; import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints'; import { invertTransform, multiplyTransformMatrices, qrDecompose, } from './matrix'; /** * given an object and a transform, apply the inverse transform to the object, * this is equivalent to remove from that object that transformation, so that * added in a space with the removed transform, the object will be the same as before. * Removing from an object a transform that scale by 2 is like scaling it by 1/2. * Removing from an object a transform that rotate by 30deg is like rotating by 30deg * in the opposite direction. * This util is used to add objects inside transformed groups or nested groups. * @param {FabricObject} object the object you want to transform * @param {TMat2D} transform the destination transform */ export const removeTransformFromObject = ( object: FabricObject, transform: TMat2D, ) => { const inverted = invertTransform(transform), finalTransform = multiplyTransformMatrices( inverted, object.calcOwnMatrix(), ); applyTransformToObject(object, finalTransform); }; /** * given an object and a transform, apply the transform to the object. * this is equivalent to change the space where the object is drawn. * Adding to an object a transform that scale by 2 is like scaling it by 2. * This is used when removing an object from an active selection for example. * @param {FabricObject} object the object you want to transform * @param {Array} transform the destination transform */ export const addTransformToObject = (object: FabricObject, transform: TMat2D) => applyTransformToObject( object, multiplyTransformMatrices(transform, object.calcOwnMatrix()), ); /** * discard an object transform state and apply the one from the matrix. * @param {FabricObject} object the object you want to transform * @param {Array} transform the destination transform */ export const applyTransformToObject = ( object: FabricObject, transform: TMat2D, ) => { const { translateX, translateY, scaleX, scaleY, ...otherOptions } = qrDecompose(transform), center = new Point(translateX, translateY); object.flipX = false; object.flipY = false; Object.assign(object, otherOptions); object.set({ scaleX, scaleY }); object.setPositionByOrigin(center, CENTER, CENTER); }; /** * reset an object transform state to neutral. Top and left are not accounted for * @param {FabricObject} target object to transform */ export const resetObjectTransform = (target: FabricObject) => { target.scaleX = 1; target.scaleY = 1; target.skewX = 0; target.skewY = 0; target.flipX = false; target.flipY = false; target.rotate(0); }; /** * Extract Object transform values * @param {FabricObject} target object to read from * @return {Object} Components of transform */ export const saveObjectTransform = (target: FabricObject) => ({ scaleX: target.scaleX, scaleY: target.scaleY, skewX: target.skewX, skewY: target.skewY, angle: target.angle, left: target.left, flipX: target.flipX, flipY: target.flipY, top: target.top, }); /** * given a width and height, return the size of the bounding box * that can contains the box with width/height with applied transform. * Use to calculate the boxes around objects for controls. * @param {Number} width * @param {Number} height * @param {TMat2D} t * @returns {Point} size */ export const sizeAfterTransform = ( width: number, height: number, t: TMat2D, ) => { const dimX = width / 2, dimY = height / 2, points = [ new Point(-dimX, -dimY), new Point(dimX, -dimY), new Point(-dimX, dimY), new Point(dimX, dimY), ].map((p) => p.transform(t)), bbox = makeBoundingBoxFromPoints(points); return new Point(bbox.width, bbox.height); };