Scaling (zoom) and moving the object and rendering area relative to the mouse cursor.

Task

There are certain objects that need to be scaled, for example, a rectangle that should grow/shrink depending on mouse wheel rotation, while scaling should be done relative to a specified point, such as the mouse cursor position.

The complexity arises when the relative point changes: the mouse is moved - a new point.

Moving and scaling an object

  1. Move the object space in such a way that the scaling point is at the origin (0,0).
  2. Perform the actual transformation.
  3. Move the object space back - perform exactly the same transformation but in reverse, without considering the scaling factor.

Visual explanation

Zoom in relative to a point (4, 6)

Zoom in relative to a point (3, 5)

Implementation in Java

public class PointScale extends JPanel {
    
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		final Graphics2D g2d = (Graphics2D) g;
        
		final AffineTransform transform = new AffineTransform();
        
		// Perform a shift of a point (0, 0)
		transform.translate(point.x, point.y);
        
		// Perform a zoom
		transform.scale(zoom, zoom);
        
		// Restore the position to its original state (0, 0) -> point
		transform.translate(-point.x, -point.y);
        
		// Apply all transformations
		g2d.setTransform(transform);
	}
}

Moving and scaling the rendering area

To perform a relative transformation, it's necessary to first execute the initial one, and then on top of it, the new one.

Since in this task we are changing the sizes and positions not of specific objects, but of the entire field, it's necessary to transform the field each time it's redrawn, as it resets to its original state every time.

Find the difference between the shift of the coordinate system of the previous state and the position of the cursor relative to the scaled coordinate system of the previous state.

prevX - newX / prevScale = prevXScaled / prevScale - newX / prevScale = (prevXScaled - newX) / prevScale

Add the new offset in the newly scaled coordinate system:

deltaTranslate + newTranslate = deltaTranslate + newX / newScale = (prevXScaled - newX) / prevScale + newX / newScale

Visual explanation

The principle of calculating transformations:

Original image:

Scaling:

Moving:

Calculation example:

X - Coordinates in the original system
X' - Coordinates in the scaled system

Implementation in Java

private AffineTransform prevTransform = new AffineTransform();

private void applyTransformation(Graphics2D g2d) {
	final Point mousePosition = getMousePosition(); // method of Component class
	if (null != mousePosition) {
		final int x = mousePosition.x, y = mousePosition.y;
		g2d.scale(zoom, zoom);
        // Translate the current coordinates back to the previous coordinate system and perform the previous shift.
		g2d.translate(
        	// translateX(Y) - это уже масштабированное значение X(Y): translateX = prevX * prevScaleX
			(prevTransform.getTranslateX() - x) / prevTransform.getScaleX() + x / zoom,
			(prevTransform.getTranslateY() - y) / prevTransform.getScaleY() + y / zoom);
		if (isDragging) g2d.translate(dragX / zoom, dragY / zoom); // dragX - это dX, delta X, изменение координаты при перетаскивании
		dragX = 0;
		dragY = 0;
	} else {
		g2d.setTransform(prevTransform);
	}
	prevTransform = g2d.getTransform();
}

Sources could be downloaded from Github: https://github.com/asilichenko/mouse-move-scale

No comments:

Post a Comment

Why BQ28Z610 function Current() returns 0 mA

Fixing 0 mA Current Readings on the BQ28Z610 Device Custom driver for the BQ28Z610 device was connected directly via I2C. It is p...