前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图形编辑器开发:基于 transfrom 对多个图形进行缩放

图形编辑器开发:基于 transfrom 对多个图形进行缩放

作者头像
前端西瓜哥
发布2024-05-07 16:23:29
840
发布2024-05-07 16:23:29
举报

大家好,我是前端西瓜哥。

上篇文章我们讲解如何基于 transform 缩放但个矩形,实现了 resizeRect 方法。

今天我们再来看看如何对多个图形进行缩放。

我们要实现最终效果:

这里我默认你已经看过上一篇文章,一些知识点已经理解了,否则这篇文章你可能看不大明白。

合并包围盒

我们需要计算并渲染选中多个图形的包围盒。

如果你对包围盒不熟悉,可以看看这篇简单的入门小文章: 《关于包围盒,你需要知道的那些事

计算每个图形的 AABB 包围盒,然后给它们做一个 merge。

这个包围盒,我们用 transform 的方式可以表示如下:

代码语言:javascript
复制
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 没有什么用。

代码语言:javascript
复制
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 需要应用的矩阵。

这里用到逆矩阵去求。

代码语言:javascript
复制
const scaleTf = new Matrix(...transformRect.transform).append(
  new Matrix(...startTransform).invert(),
);

使用了 pixi.js 的矩阵运算类 Matrix。

我们把这个 scaleTf 矩阵拿去 遍历每个选中图形,去左乘 transform,就能实现对每个图形缩放了。

但是,会出现我们上篇文章遇到的问题,strokeWidth 也被缩放了

修正 width 和 height

问题不大,我们再写个 recomputeTransformRect 方法修正一下。

缩放单个图形的时候,我们直接在 resizeRect 就修正了 width 和 height。

但这次因为有多个图形,它们的宽高不一样,所以要在应用 transform 后再修正。

核心思路是:确保应用 transform 后的宽高和应用前的相同

首先我们计算一下使用当前这个 transform 后的宽高。

对点 (width, 0) 应用 transform,然后再计算这个点到原点的距离,就是这个图形 transform 后的宽。高同理。

代码语言:javascript
复制
// 计算缩放后的 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:

代码语言:javascript
复制
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:

代码语言:javascript
复制
const tf = new Matrix(...rect.transform).append(scaleMatrix);

完整代码:

代码语言:javascript
复制
/**
 * 重新计算 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],
  };
};

看看效果,很完美。

结尾

我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-30,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 前端西瓜哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 合并包围盒
  • 算法实现
  • 修正 width 和 height
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com