AffineTransform: scaling a Shape from its center

25,997

Solution 1

I see what you mean when you're dealing with rectangles. The reason is because the initial calculation for the translation didn't take into account the size of the container object.

Use this instead:

tr2.translate(
    (this.getWidth()/2) - (r.getWidth()*(zoom))/2,
    (this.getHeight()/2) - (r.getHeight()*(zoom))/2
);
tr2.scale(zoom,zoom);
g.setTransform(tr2);

What this is doing is translating the rectangle to the center of the panel before scaling it. In my tests it works just fine.

Solution 2

Assuming scaling fixes the location of the top lefthand corner of the rectangle (which I think is right but it's been a long time since I've done graphics in Java), you need to translate the rectangle in the direction opposite to the scaling.

tr2.translate(
    r.getWidth()*(1-zoom)/2,
    r.getHeight()*(1-zoom)/2
);
tr2.scale(zoom,zoom);
g.setTransform(tr2);

So you move the rectangle left and up half of the change in width and height.

Solution 3

I was just working on a desktop application to crop Brittney Spear's face (D.A.Y.) The cropping rectangle had to scale around its center point:

import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;

class ResizableRectangle extends Rectangle {

 ResizableRectangle(double x, double y, double width, double height, Group group) {

  super(x, y, width, height);

  // Set scroll listener for crop selection
  group.addEventHandler(ScrollEvent.SCROLL, event -> {
   double zoomFactor = 1.10;
   double deltaY = event.getDeltaY();
   if (deltaY > 0) {
    zoomFactor = 2.0 - zoomFactor;
   }

   super.setX(getX() + (super.getWidth() * (1 - zoomFactor) / 2)); // Set new X position
   super.setWidth(getWidth() * zoomFactor); // Set new Width

   super.setY(getY() + (super.getHeight() * (1 - zoomFactor) / 2)); // Set new Y position
   super.setHeight(getHeight() * zoomFactor); // Set new Height

   event.consume();
  });
 });
}

In general, the algorithm works like this:

  • Translate rectangle x-values with: x + (width * (1 - zoomFactor) / 2)
  • Translate y-values with: y + (height * (1 - zoomFactor) / 2)
  • Set new width to: width * zoomFactor
  • Set new height to: height * zoomFactor
Share:
25,997
LabRat01010
Author by

LabRat01010

Bioinformatician Virology Genetics Biology Science Science2.0 Web2.0 Bioinformatics Genotyping Wikipedia

Updated on August 05, 2020

Comments

  • LabRat01010
    LabRat01010 almost 4 years

    I'm trying to scale a rectangle from its center using AffineTransform. I'm sure the solution is obvious but I cannot make it work ! Here is what I've tested so far...

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.awt.geom.AffineTransform;
    
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    
    public class Test extends JPanel {
        Test()
            {
            super(null);
            setOpaque(true);
            setBackground(Color.WHITE);
            setPreferredSize(new Dimension(200,200));
            }
        @Override
        protected void paintComponent(Graphics g1) {
            super.paintComponent(g1);
            Rectangle r= new Rectangle(5,5,getWidth()-10,getHeight()-10);
            double cx= r.getCenterX();
            double cy= r.getCenterY();
            Graphics2D g=(Graphics2D)g1;
            g.setColor(Color.BLACK);
            AffineTransform old= g.getTransform();
            for(double zoom=0.9; zoom>=0.5; zoom-=0.1)
                {
                AffineTransform tr2= new AffineTransform(old);
                tr2.translate(-cx, -cy);
                tr2.scale(zoom, zoom);
                tr2.translate(cx/zoom,cy/zoom);
                g.setTransform(tr2);
                g.draw(r);
                g.setTransform(old);
                }
            }
    
    
        public static void main(String[] args) {
            JOptionPane.showMessageDialog(null, new Test());
            }
        }
    

    But it doesn't work.... Any suggestion ?

  • LabRat01010
    LabRat01010 about 15 years
    your solution doesn't work but it gives me some new ideas, thanks
  • Welbog
    Welbog about 15 years
    @mmyers: what is the result you get? I don't have access to a Java IDE at work so I can't test it myself. I'm working largely from memory.
  • palantus
    palantus about 15 years
    @Welbog: With the original code, all the squares originate in the upper left corner. With your code, they get progressively shifted towards the middle, but not enough.
  • palantus
    palantus about 15 years
    Ah, got it. All you have to do is move the scale() after the translate() and it works perfectly.
  • LabRat01010
    LabRat01010 about 15 years
    Hooops, I'm sorry, I was too fast. This centered square was the most special case. When this is a rectangle anywhere on the screen, your solution doesn't work. I again tried to shift the shape from its center, scake and re-center but I still cannot get the right solution...