Graphics editor development: zoom to fit the canvas

Hello everyone, I am the front-end watermelon brother.

We have implemented the canvas scaling function before, this article will talk about how to make the content zoom to fit the canvas size (Zoom to fit).

Let's see the effect.

insert image description here

The animation demo in this article is from the graphic design tool I am developing:

https://github.com/F-star/suika

Online experience:

https://blog.fstars.wang/app/suika/

Scale to fit canvas

This involves the conversion of scene coordinates and view coordinates, and introduces the concept of zoom and viewport.

If you don't understand them, please read this article of mine:

" Graphics editor development: zooming the canvas with the cursor as the center "

general idea:

  1. Calculate the large bounding box bbox (AABB bounding box, without rotation) that wraps all graphics;
  2. Computes the new zoom ratio newZoom. Need to judge whether to scale based on the width of the bbox or based on the height;
  3. The last is to calculate viewport.x and viewport.y, so that the content is just in the middle of the viewport.

The most important thing is to calculate the zoom ratio, whether it is based on the width or height of the bbox, and divide it by the width or height of the viewport .

This belongs to the contain strategy in the filling strategy .

For more filling strategies, see my article:

" Five schemes for displaying pictures in containers: contain, cover, fill, none, scale-down "

We need to compare the aspect ratio of the bbox with the aspect ratio of the viewport.

const viewportRatio = vw / vh;
const bboxRatio = bbox.width / bbox.height;
if (viewportRatio > bboxRatio) {
    
    
  // 基于 bbox 的高进行缩放
  newZoom = vh / bbox.height;
} else {
    
    
  // 基于宽
  newZoom = vw / bbox.width;
}

Then there is a simple algorithm for centering the small rectangle vertically and horizontally under the large rectangle . The following is to invert the position of the large rectangle through the small rectangle.

 const newViewportX =
    composedBBox.x - (viewport.width / newZoom - composedBBox.width) / 2;

  const newViewportY =
    composedBBox.y - (viewport.height / newZoom - composedBBox.height) / 2;

This algorithm can be seen in the article I wrote:

" Graphics Editor: Filling Algorithms Needed to Draw Graphics "

Full code:

function zoomToFix() {
    
    
  //(1)计算所有图形的大包围盒
  const bbox = getRectsBBox(graphs.map((item) => item.getBBox()));

  //(2)计算 newZoom
  const vh = viewport.height; // 这里可以加个边距
  const vw = viewport.width;
  const viewportRatio = vw / vh;
  const bboxRatio = bbox.width / bbox.height;
  if (viewportRatio > bboxRatio) {
    
    
    // basic height scale
    newZoom = vh / bbox.height;
  } else {
    
    
    newZoom = vw / bbox.width;
  }

  //(3)计算视口 x 和 y 值
  const newViewportX =
    composedBBox.x - (viewport.width / newZoom - composedBBox.width) / 2;
  const newViewportY =
    composedBBox.y - (viewport.height / newZoom - composedBBox.height) / 2;

  //(4)更新视口对象
  this.setZoom(newZoom);
  this.setViewport({
    
    
    x: newViewportX,
    y: newViewportY,
  });
}

plus margin

Sometimes we want to give a margin, just like the animation below.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-TzmOz1yu-1687424462936)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/% E9%80%82%E5%BA%94%E7%94%BB%E5%B8%83%E5%8A%A0%E8%BE%B9%E8%B7%9D.gif)]

A margin of 50px is added, so that the content is no longer close to the edge of the viewport, and the control point of the selected graphic image will not run out of the viewport.

The idea is to subtract the padding from the original basis for the vw and vh used to calculate newZoom, and then calculate.

It should be noted that the original viewport.x and viewport.y should still be used when calculating the centering later.

Calculate the zoom ratio, the object is the width and height of the viewport minus padding; calculate the position, the object is the original viewport width and height .

To implement the code, just change the second step of the above code.

//(2)计算 newZoom
const padding = 50;
const vh = viewport.height - padding * 2; // 注意考虑 vh 或 vw 是负数的情况
const vw = viewport.width - padding * 2;

The selected graphics fit the canvas

Just make all graphics adapt to the canvas as before, and replace the bbox with the selected graphics.

const bbox = getRectsBBox(selectGraphs.map((item) => item.getBBox()));

end

The essence of most zoom functions is to calculate the new zoom and viewport x, y.

Basically, you can't escape the contain filling strategy and the center alignment algorithm. If you understand them, the zoom function will basically be fine.

I am the front-end watermelon brother, welcome to follow me and learn to develop a graphic design tool.

Guess you like

Origin blog.csdn.net/fe_watermelon/article/details/131342529