大家好,我是前端西瓜哥。
上篇文章我们讲解如何基于 transform 缩放但个矩形,实现了 resizeRect 方法。
今天我们再来看看如何对多个图形进行缩放。
我们要实现最终效果:
这里我默认你已经看过上一篇文章,一些知识点已经理解了,否则这篇文章你可能看不大明白。
我们需要计算并渲染选中多个图形的包围盒。
如果你对包围盒不熟悉,可以看看这篇简单的入门小文章: 《关于包围盒,你需要知道的那些事》
计算每个图形的 AABB 包围盒,然后给它们做一个 merge。
这个包围盒,我们用 transform 的方式可以表示如下:
const mergedRect = mergeBox(selectItems.map(item => item.getBbox()));
const transformRect = {
width: mergedRect.width,
height: mergedRect.height,
transform: [1, 0, 0, 1, mergedRect.x, mergedRect.y]
}
接着我们需要基于前面算的这个包围盒,计算新的 tramsform 值。
这里就要用到上篇文章的 resizeRect 方法。整体思路基本是一样的,只是有一个地方要稍微改一改。
我们不要重新计算新的 width 和 height,转而把缩放效果全部放到新的 transform 上。因为我们缩放的是多个图形,算出的整体新的 width 和 height 没有什么用。
if (options.noChangeWidthAndHeight) {
// width 和 height 维持不变
scaleTf.scale(size.width / rect.width, size.height / rect.height);
newRect.width = rect.width;
newRect.height = rect.height;
} else {
newRect.width = Math.abs(size.width);
newRect.height = Math.abs(size.height);
const scaleX = Math.sign(size.width) || 1;
const scaleY = Math.sign(size.height) || 1;
scaleTf.scale(scaleX, scaleY);
}
计算出新的 transform 后,还无法直接用到每个图形上。
这里我们需要计算旧 transform 到新 transform 需要应用的矩阵。
这里用到逆矩阵去求。
const scaleTf = new Matrix(...transformRect.transform).append(
new Matrix(...startTransform).invert(),
);
使用了 pixi.js 的矩阵运算类 Matrix。
我们把这个 scaleTf 矩阵拿去 遍历每个选中图形,去左乘 transform,就能实现对每个图形缩放了。
但是,会出现我们上篇文章遇到的问题,strokeWidth 也被缩放了。
问题不大,我们再写个 recomputeTransformRect 方法修正一下。
缩放单个图形的时候,我们直接在 resizeRect 就修正了 width 和 height。
但这次因为有多个图形,它们的宽高不一样,所以要在应用 transform 后再修正。
核心思路是:确保应用 transform 后的宽高和应用前的相同。
首先我们计算一下使用当前这个 transform 后的宽高。
对点 (width, 0)
应用 transform,然后再计算这个点到原点的距离,就是这个图形 transform 后的宽。高同理。
// 计算缩放后的 width 和 height
const getTransformedSize = (rect: ITransformRect): ISize => {
// 求的是向量长度,所以 tx 和 ty 要改为 0
const tf = new Matrix(
rect.transform[0],
rect.transform[1],
rect.transform[2],
rect.transform[3],
0,
0,
);
const rightTop = tf.apply({ x: rect.width, y: 0 });
const leftBottom = tf.apply({ x: 0, y: rect.height });
const zero = { x: 0, y: 0 };
return {
width: distance(rightTop, zero),
height: distance(leftBottom, zero),
};
};
const newSize = getTransformedSize(rect);
新的 width 和 height 和旧的 width 和 height 有什么关系,乘一个矩阵的关系:
我们计算出这个 ScaleMatrix:
const scaleX = newSize.width ? rect.width / newSize.width : 1;
const scaleY = newSize.height ? rect.height / newSize.height : 1;
const scaleMatrix = new Matrix().scale(scaleX, scaleY);
把它和原来的 transform 拼起来,得到最终 transform:
const tf = new Matrix(...rect.transform).append(scaleMatrix);
完整代码:
/**
* 重新计算 width、height 和 transform
* 确保 transform 后的 size 和 transform 前的 size 相同
*/
const recomputeTransformRect = (
rect: ITransformRect,
): ITransformRect => {
const newSize = getTransformedSize(rect);
const scaleX = newSize.width ? rect.width / newSize.width : 1;
const scaleY = newSize.height ? rect.height / newSize.height : 1;
const scaleMatrix = new Matrix().scale(scaleX, scaleY);
const tf = new Matrix(...rect.transform).append(scaleMatrix);
return {
width: newSize.width,
height: newSize.height,
transform: [tf.a, tf.b, tf.c, tf.d, tf.tx, tf.ty],
};
};
看看效果,很完美。
我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。