当前位置:首页 > 科技  > 软件

图形编辑器开发:实现缩放图形

来源: 责编: 时间:2023-10-20 10:02:58 279观看
导读编辑器 github 地址:https://github.com/F-star/suika线上体验:https://blog.fstars.wang/app/suika/图形的属性图形有几个重要的基础属性,会经常被用到,我们在实现缩放图形前需要理清一下它们。x / ywidth / heightrotat

yKD28资讯网——每日最新资讯28at.com

编辑器 github 地址:yKD28资讯网——每日最新资讯28at.com

https://github.com/F-star/suikayKD28资讯网——每日最新资讯28at.com

线上体验:yKD28资讯网——每日最新资讯28at.com

https://blog.fstars.wang/app/suika/yKD28资讯网——每日最新资讯28at.com

图形的属性

图形有几个重要的基础属性,会经常被用到,我们在实现缩放图形前需要理清一下它们。yKD28资讯网——每日最新资讯28at.com

  • x / y
  • width / height
  • rotation

位置和大小

x 和 y 为图形的左上角位置,注意是旋转前的。yKD28资讯网——每日最新资讯28at.com

x、y 旋转后我们叫做 rotatedX、rotatedY,属性面板中会用到。yKD28资讯网——每日最新资讯28at.com

width 和 height 为图形的宽高,这个没什么好说的。yKD28资讯网——每日最新资讯28at.com

另外,有些图形有些特殊,它的 x、y、width、height 是要通过其他属性计算出来的,比如贝塞尔曲线。yKD28资讯网——每日最新资讯28at.com

旋转

rotation 为图形的旋转度数,通常使用 弧度单位。yKD28资讯网——每日最新资讯28at.com

因为弧度是数学计算中的常客,各种 API 都是要求提供弧度的,比如内置的 Math.sin() 方法。yKD28资讯网——每日最新资讯28at.com

你存角度自然也是可以,但不推荐,但计算时多了一层多余的单位转换,且丢失一些微小的精度。yKD28资讯网——每日最新资讯28at.com

当然 UI 层还是要展示角度,因为是面向用户的,对于数据和 UI 不统一的问题,在 UI 层做一个转换即可。yKD28资讯网——每日最新资讯28at.com

旋转度数通常要配合一个变换中心(origin),这个可以作为一个属性让用户设置。yKD28资讯网——每日最新资讯28at.com

但我更建议将 x、y、width、height 形成的 矩形的中点 作为旋转中心,这样更简单一些,减少用户的心智负担,也防止出现用户设置一些奇怪 origin 的场景。yKD28资讯网——每日最新资讯28at.com

下图中,红色矩形是蓝色矩阵顺时针旋转 45 度得到。yKD28资讯网——每日最新资讯28at.com

yKD28资讯网——每日最新资讯28at.com

旋转度数还要考虑 旋转方向、基准角度、取值范围 问题。yKD28资讯网——每日最新资讯28at.com

(因为弧度不直观,后面会用角度来描述,但数据层依旧还是用的弧度)yKD28资讯网——每日最新资讯28at.com

  • 旋转方向:设置旋转后,图形是会往顺时针方向还是逆时针方向旋转。
  • 基准角度:朝向哪里是 0 度。
  • 取值范围:通常为 [0, 360) 和 (-180, 180]。二者其实等价,只是显示有区别,后者其实只是前者减去 180 度。

通常这些编辑器自己决定就好。像我的项目,向上表示 0 度,顺时针方向为旋转方向,方向取值为 [0, 360)。yKD28资讯网——每日最新资讯28at.com

一些编辑器是支持用户自己设置的,比如 AutoCAD 可通过图形单位命令,设置旋转方向和基准角度。yKD28资讯网——每日最新资讯28at.com

图片yKD28资讯网——每日最新资讯28at.com

缩放实现思路

进入正题,对图形进行缩放。yKD28资讯网——每日最新资讯28at.com

接下来会以通过右下角(也叫东南 se 方向) 缩放控制点缩放为例进行讲解。yKD28资讯网——每日最新资讯28at.com

yKD28资讯网——每日最新资讯28at.com

交互逻辑:yKD28资讯网——每日最新资讯28at.com

选择工具下,当光标落在右下角的缩放控制点上时,光标会变成缩放样式(这个不是本文核心,不讲)。yKD28资讯网——每日最新资讯28at.com

此时按下鼠标,然后进行拖拽,即可对图形以左上角为缩放中心,进行缩放。yKD28资讯网——每日最新资讯28at.com

实现思路:更新 width 和 height,然后确定参照点,修正 x  和 y。yKD28资讯网——每日最新资讯28at.com

按下鼠标时,我们要把当前图形的 x、y、width、height、rotation 记录下来。之后的缩放是基于这个初始状态进行的。yKD28资讯网——每日最新资讯28at.com

const mousedown = (e) => {  // ...    // 缩放前图形的属性,之后我们会直接更新图形属性,导致原来的属性丢失,所以要记录下这个快照。  prevElement = {    x: item.x,    y: item.y,    width: item.width,    height: item.height,    rotation: item.rotation ?? 0,  }}

拖拽时,调用我们将要实现的 movePoint 方法,去更新这个图形。yKD28资讯网——每日最新资讯28at.com

const drag = (e) = {  // ...    selectElement.movePoint(    'se', // 缩放控制点类型:右下(或东南)    lastPoint, // 当前光标位置(基于场景坐标系)    prevElement, // 缩放前的属性快照  );}

下面就是核心方法 movePoint 的实现逻辑了。yKD28资讯网——每日最新资讯28at.com

更新 width 和 height

首先是更新矩形宽高。yKD28资讯网——每日最新资讯28at.com

因为有一个旋转,所以算法不会这么直观。yKD28资讯网——每日最新资讯28at.com

我们要意识到这里有一个变换。看到的图形,是做过变换(基于矩形中心旋转)之后的,但我们需要修改的 width、height、x、y 则是旋转前的。yKD28资讯网——每日最新资讯28at.com

所以我们需要把光标位置给旋转回来,然后再减去 x 和 y 去得到真正的 width 和 height。yKD28资讯网——每日最新资讯28at.com

yKD28资讯网——每日最新资讯28at.com

看看代码yKD28资讯网——每日最新资讯28at.com

class Graph {  // ...  // 根据缩放点更新图形  movePoint(type, newPos, oldBox) {    // 1. 计算 width 和 height    // 计算缩放中心(也就是矩形的中点)    const cx = oldBox.x + oldBox.width / 2;    const cy = oldBox.y + oldBox.height / 2;    // 计算反向旋转的光标位置    const { x: posX, y: poxY } = transformRotate(      newPos.x,      newPos.y,      -(oldBox.rotation || 0), // 注意这里是负数      cx,      cy    );        let width = 0;    let height = 0;    if (type === 'se') {      // 参照点为左上角(x 和 y)      // 新的宽高自然就是光标位置减去 x、y      width = posX - oldBox.x;      height = poxY - oldBox.y;    }    // 其他控制点的逻辑暂且省略...        // 2. 计算 x 和 y    // ...  }}

看看只更新宽高的效果。yKD28资讯网——每日最新资讯28at.com

yKD28资讯网——每日最新资讯28at.com

可以看到是有问题的,因为修改宽高后,矩形的中心点也发生了变化,导致缩放中心错误。所以我们要修正一下 x 和 y。yKD28资讯网——每日最新资讯28at.com

修正 x 和 y

接着我们就要修正 x 和 y 的值。yKD28资讯网——每日最新资讯28at.com

重点就一句话:缩放前的参考点和缩放后的参考点的位置要保持一致。这个参考点其实就是图形缩放过程中的缩放中心。yKD28资讯网——每日最新资讯28at.com

对于右下角缩放控制点,它的缩放中心就是左上角,即 x 和 y 经过旋转的位置。yKD28资讯网——每日最新资讯28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox) {    // 1. 计算 width 和 height    // ...        // 2. 计算 x 和 y    // 设置参照点,不同缩放类型的参照点不同    let prevOriginX = 0;    let prevOriginY = 0;    let originX = 0;    let originY = 0;    if (type === "se") {      prevOriginX = oldBox.x;      prevOriginY = oldBox.y;      originX = oldBox.x;      originY = oldBox.y;    }    // 其他缩放类型暂且省略    // 缩放前的参考点位置    const { x: prevRotatedOriginX, y: prevRotatedOriginY } = transformRotate(      prevOriginX,      prevOriginY,      oldBox.rotation || 0,      cx,      cy    );    // 缩放后的参考点位置    const { x: rotatedOriginX, y: rotatedOriginY } = transformRotate(      originX,      originY,      oldBox.rotation || 0,      oldBox.x + width / 2, // 旋转中心是新的      oldBox.y + height / 2    );    // 计算新旧两个参考点的差值,对 x、y 进行补正    const dx = rotatedOriginX - prevRotatedOriginX;    const dy = rotatedOriginY - prevRotatedOriginY;    const x = oldBox.x - dx;    const y = oldBox.y - dy;  }}

width 和 height 可能为负数,这里要做一个标准化,然后赋值给图形属性即可。yKD28资讯网——每日最新资讯28at.com

this.setAttrs(  normalizeRect({    x,    y,    width,    height,  }),);

其他缩放控制点

对于其他类型缩放控制点,比如左上、右上、左下缩放控制点,它们的大框架是一样的,只是 width 和 height 计算方式不同,以及参考点不同。yKD28资讯网——每日最新资讯28at.com

不同类型下 width 和 height 的设置:yKD28资讯网——每日最新资讯28at.com

let width = 0;let height = 0;if (type === 'se') { // 右下  width = posX - oldBox.x;  height = poxY - oldBox.y;} else if (type === 'ne') { // 右上  width = posX - oldBox.x;  height = oldBox.y + oldBox.height - poxY;} else if (type === 'nw') {  width = oldBox.x + oldBox.width - posX;  height = oldBox.y + oldBox.height - poxY;} else if (type === 'sw') {  width = oldBox.x + oldBox.width - posX;  height = poxY - oldBox.y;}

新旧参考点设置:yKD28资讯网——每日最新资讯28at.com

let prevOriginX = 0;let prevOriginY = 0;let originX = 0;let originY = 0;if (type === 'se') {  prevOriginX = oldBox.x; // 右下缩放点,参考点为左上角  prevOriginY = oldBox.y;  originX = oldBox.x;  originY = oldBox.y;} else if (type === 'ne') { // 右上缩放点,参考点为左下角  prevOriginX = oldBox.x;  prevOriginY = oldBox.y + oldBox.height;  originX = oldBox.x;  originY = oldBox.y + height;} else if (type === 'nw') {  prevOriginX = oldBox.x + oldBox.width;  prevOriginY = oldBox.y + oldBox.height;  originX = oldBox.x + width;  originY = oldBox.y + height;} else if (type === 'sw') {  prevOriginX = oldBox.x + oldBox.width;  prevOriginY = oldBox.y;  originX = oldBox.x + width;  originY = oldBox.y;}

暂时没实现正北、正南、正西、正东的逻辑,逻辑大差不差。yKD28资讯网——每日最新资讯28at.com

锁定缩放比

按住 shift 可以锁定缩放比。yKD28资讯网——每日最新资讯28at.com

做法是对比新旧图形宽高比,将 width 和 height 其中一个进行修正即可。注意正负号。yKD28资讯网——每日最新资讯28at.com

方法需要多传一个 keepRatio 的参数:yKD28资讯网——每日最新资讯28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox, keepRatio = false) {    // 1. 计算 width 和 height    // ...        if (keepRatio) {      const ratio = oldBox.width / oldBox.height;      const newRatio = Math.abs(width / height);      if (newRatio > ratio) {        height = (Math.sign(height) * Math.abs(width)) / ratio;      } else {        width = Math.sign(width) * Math.abs(height) * ratio;      }    }        // 2. 计算 x 和 y    // ...  }}

貌似没考虑除数 height 为 0 的情况..yKD28资讯网——每日最新资讯28at.com

优化点

本文的实现是考虑的是比较简单的缩放图形场景,一些更复杂的场景并未实现。yKD28资讯网——每日最新资讯28at.com

缩放还有另一种策略,就是会产生 反向颠倒 的缩放。要实现这个效果,需要引入缩放属性,复杂度会提升很多。yKD28资讯网——每日最新资讯28at.com

另外就是选中多个图形,然后缩放的场景我没实现。这种场景下,通常是要锁定宽高比的。yKD28资讯网——每日最新资讯28at.com

否则就会出现图形的斜切效果,这个如果要实现,我们还要引入斜切属性,复杂度再一次提升。yKD28资讯网——每日最新资讯28at.com

下面是 Figma 的效果,真是让人头扁。yKD28资讯网——每日最新资讯28at.com

yKD28资讯网——每日最新资讯28at.com

按住 Alt 实现图形中心缩放也没做,这个比较简单,有空再做。yKD28资讯网——每日最新资讯28at.com

读者如果看懂我这篇文章,心里应该有思路的:width、height 的计算要加入图形中点参数,参照点设置为图形中点。yKD28资讯网——每日最新资讯28at.com

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-14344-0.html图形编辑器开发:实现缩放图形

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 深入理解 Netty FastThreadLocal

下一篇: 23种软件设计模式综述

标签:
  • 热门焦点
  • 官方承诺:K60至尊版将会首批升级MIUI 15

    官方承诺:K60至尊版将会首批升级MIUI 15

    全新的MIUI 15今天也有了消息,在官宣了K60至尊版将会搭载天玑9200+处理器和独显芯片X7的同时,Redmi给出了官方承诺,K60至尊重大更新首批升级,会首批推送MIUI 15。也就是说虽然
  • 天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    这几年除了手机作为智能生活终端最主要的核心之外,第二个可以成为中心点的产品是什么?——是智能音箱。 手机在执行命令的时候有两种操作方式,手和智能语音助手,而智能音箱只
  • 6月安卓手机好评榜:魅族20 Pro蝉联冠军

    6月安卓手机好评榜:魅族20 Pro蝉联冠军

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年6月1日至6月30日,仅限国内市场。第一名:魅族20 Pro好评率:95%5月份的时候魅族20 Pro就是
  • 线程通讯的三种方法!通俗易懂

    线程通讯的三种方法!通俗易懂

    线程通信是指多个线程之间通过某种机制进行协调和交互,例如,线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中,线程等待和通知的实现手段有以下几种方式:Object 类下
  • 十个简单但很有用的Python装饰器

    十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 猿辅导与新东方的两种“归途”

    猿辅导与新东方的两种“归途”

    作者|卓心月 出品|零态LT(ID:LingTai_LT)如何成为一家伟大企业?答案一定是对“势”的把握,这其中最关键的当属对企业战略的制定,且能够站在未来看现在,即使这其中的
  • 阿里瓴羊One推出背后,零售企业迎数字化新解

    阿里瓴羊One推出背后,零售企业迎数字化新解

    作者:刘旷近年来随着数字经济的高速发展,各式各样的SaaS应用服务更是层出不穷,但本质上SaaS大多局限于单一业务流层面,对用户核心关切的增长问题等则没有提供更好的解法。在Saa
  • iQOO 11S或7月上市:搭载“鸡血版”骁龙8Gen2 史上最强5G Soc

    iQOO 11S或7月上市:搭载“鸡血版”骁龙8Gen2 史上最强5G Soc

    去年底,iQOO推出了“电竞旗舰”iQOO 11系列,作为一款性能强机,iQOO 11不仅全球首发2K 144Hz E6全感屏,搭载了第二代骁龙8平台及144Hz电竞屏,同时在快充
  • OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    日前OPPO官方宣布,将于7月25日14:30举办新品发布会,届时全新的OPPO K11将正式与大家见面,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼
Top
Baidu
map