Different scales or ZOOM effect, how ?!

Hi dudes, this is a question i had for a long time, and now that i had to face it (by “force”), i decided to post it here

I have a canvas big 800x640 (its just a piece of the whole frame) and i need to support different “scales”

My coordinate system is ±400 x, ± 320 y.
Instead of having a lot of zoom capabilities (thus a lot of different scales) i decided to make just 4 scales : x1, x2, x3, x4.
So for x2 for example, my canvas should represent a 1600x1280 space (± 800x, …)

I think this should be way easier than zooming with the mousewheel.

So what i was thinking is to divide everything on my shapes (what is drawn into the canvas) by the scale.

For example if my rectangle is at 120, 100, has a width of 100 and a height of 50, i should draw it as follows (considering x2 scale):

x: 60
y: 50
width: 50
height: 25

So dividing is the way to go? is that hard to make the zoom with the mousewheel and having multiple scales?

Thanks in advance for your time and patience

EDITED: im using standard j2se, im using java2D (i guess), not open gl nor any stuff like that
EDITED2: im a big noob (just if you didnt notice that)

glScale

Well, fermixx are you using an OpenGL binding or Java2D ?

sorry for not pointing that out, im using plain j2se. So its java2D what im using.

I have no idea about openGL.

first post edited

thanks for the replies

I will reply without really knowing the performance.

With Java2D, I avoid antialiasing (for performance) so I should stick to x1,x2,x3 or it will be “ugly” (when zoom out, it seems that you want to zoom in).
An easy way to do it is to :

  • keeper the same size buffer and resize everything at the end when passing to the display (just with the Gaphics.drawImage)
  • or resize the buffer and using “Graphics2D.scale(double sx, double sy)”

1 should be something like i said on my post right? (dividing)
2 does this work with passive rendering? im doing everything at drawComponent (graphics g) inside my panel.

then i do the classic Graphics2D g2d = (Graphics2D) g;

So should i use g2d.scale() with half the sizes?

i want to zoom out actually. It sounds confusing because it sounds like the zoom in a microscope. What i meant is that i need to represent twice the width (-+800 instead of ±400), not that i want to see things two times bigger.

X2 should be in this case, increase the coordinate system by two, and since the canvas is still the same, everything should decrease its size

Sorry for not being clear.

Here’s an example of using Java2D to scale an image with the mouse wheel.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JFrame;


public class ZoomFrame extends JFrame implements KeyListener, MouseWheelListener{
	BufferedImage image;
	int zoom = 1;

	public ZoomFrame() {
		try{
			image = ImageIO.read(new File("USA_Map_With_States_Names.png"));
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setFocusable(true);
		addKeyListener(this);
		addMouseWheelListener(this);
		pack();
	}
	public static void main(String[] args) {
		new ZoomFrame();
	}
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(800, 600);
	}
	public void keyReleased(KeyEvent e) {
		int key = e.getKeyCode();
		if(key == KeyEvent.VK_1)
			zoom = 1;
		if(key == KeyEvent.VK_2)
			zoom = 2;
		if(key == KeyEvent.VK_3)
			zoom = 3;
		if(key == KeyEvent.VK_4)
			zoom = 4;
		repaint();
	}

	public void mouseWheelMoved(MouseWheelEvent e) {
		zoom += e.getWheelRotation();
		repaint();
	}

	@Override
	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D)g;
		g2.setColor(Color.BLACK);
		g2.fill(getBounds());
		if(zoom == 0)
			g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
		else if(zoom < 0)
			g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), 0, 0, image.getWidth()/-zoom, image.getHeight()/-zoom, null);
		else
			g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), 0, 0, image.getWidth()*zoom, image.getHeight()*zoom, null);
	}
	public void keyPressed(KeyEvent e) {}
	public void keyTyped(KeyEvent e) {}
}

Thanks for that, i’ll try it out later.

In my case i have something like this:


//this is my custom panel which extends JPanel

@Override
public void paintComponent (Graphics g){
        super.paintComponent(g);  //did it just in case
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(skyBlue);   //background colour
        g2d.fillRect(0, 0, 800, 640);     //erasing screen

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //adding AA

        GoalArea goalArea = main.getGoalArea(); //an unique tipe of rectangle
        if (goalArea != null) goalArea.draw(g2d); 
        
        for (int i = 0; i < main.shapesList.size(); i++){ //this is an arrayList with all my shapes (rects and circles)
            Shape figure = main.shapesList.get(i);
            figure.draw(g2d);
        }

        g2d.dispose();
}

So all the shapes have their own drawing method, which consists in:


@Override
    public void draw(Graphics2D g2d){
        
        AffineTransform oldAT = g2d.getTransform(); //backupping the affine transform
                
        trans.rotate(Math.toRadians(angle), x + width/2, y - height/2); //trans is a pre-constructed affine transform
        g2d.setTransform(trans);

        Point oldCoords = new Point (x,y);
        Point newCoords = new Point();

        trans.inverseTransform(oldCoords, newCoords);
                
        int newX = (int) newCoords.getX(); int newY = (int) newCoords.getY();
        g2d.setColor (myRectangleColour);
        g2d.fillRect(newX-width/2, newY-height/2, width, height); //coordinates are the shape's center
        g2d.setTransform(oldAT);
        trans = oldAT;
        
    }

(both codes were simplified a bit to be clearer)

So, in my paintComponent, how should i do that?

is the g2d a graphics context of a back buffer image (since, i guess, swing uses double buffering) which i can retrieve and then scale?

You can just draw them scaled rather than scaling the final drawing. I see your using an AffineTransform to draw already, you can add
trans.scale(scale, scale);
to have it handle scaling for you.

//this is my custom panel which extends JPanel

@Override
public void paintComponent (Graphics g){
        super.paintComponent(g);  //did it just in case
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(skyBlue);   //background colour
        g2d.fillRect(0, 0, 800, 640);     //erasing screen

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //adding AA

        AffineTransform oldAT = g2d.getTransform(); //backupping the affine transform
        g2d.scale(zoom,zoom);

        GoalArea goalArea = main.getGoalArea(); //an unique tipe of rectangle
        if (goalArea != null) goalArea.draw(g2d); 
        
        for (int i = 0; i < main.shapesList.size(); i++){ //this is an arrayList with all my shapes (rects and circles)
            Shape figure = main.shapesList.get(i);
            figure.draw(g2d);
        }

        g2d.setTransform(oldAT);
        g2d.dispose();
}

? ;D

You can simplify the object drawing method :


@Override
    public void draw(Graphics2D g2d)
{
        AffineTransform oldAT = g2d.getTransform(); //backupping the affine transform
                
        g2d.translate(x,y);
        g2d.rotate(Math.toRadians(angle));
        g2d.setColor (myRectangleColour);
        g2d.fillRect(-width/2, -height/2, width, height);

        g2d.setTransform(oldAT);        
    }

Thanks for that. Thats really simplified ! and it worked really well.
Had no idea i could apply rotations and stuff straight into the g2d, thought i had to use an affine transform as my only choice.

Tryed the paintcomponent code with the g2d.scale. It worked really well, but the problem is that everything stays at the same position (coordinates). Their size is reduced but they are not repositioned with what should be a new (bigger) coordinate system.

What i mean is that the point (800,640) is still (800,640) instead of (1600,1080). I think im going to need to do the width/zoom, x/zoom, etc thing

For example, something that was on the top left corner, after zooming out, the object should appear closer to the center, but its still on the top left corner

Hmmm, I don’t see what you mean… may be you speech about mouse coordinate on the screen ? If so, yes you will need to transform the screen coordinate to the drawing coordinate.

Yes, the zoom is a scaling from the (0,0) point. You will have to add a translation to zoom from another point.
For you example, your (0,0) point can be at the center of the screen and you start by doing a translation of (width/2,height/2).


   Point2D.Float mouseScreen = new Point2D.Float();
   Point2D.Float mouseDrawing = new Point2D.Float();
   AffineTransform drawingTransform = null;
 
    ...
 
   AffineTransform oldAT = g2d.getTransform(); //backupping the affine transform
   g2d.translate(width/2,height/2);
   g2d.scale(zoom,zoom);
   drawingTransform = g2d.getTransform();
   g2d.invert();
   
   ...

   g2d.setTransform(oldAT);
   g2d.dispose();

   ...

  mouseScreen.x = mouse_x;
  mouseScreen.y = mouse_y;
  drawingTransform.transform(mouseScreen ,mouseDrawing );
   

it’s easy to do with this tools i made
my skype is ishailin and msn is ailin8@163.com
i can send the tools to you

public class gCanvas extends Canvas{
Image img;
Zoom zoom = new Zoom();
gCanvas()
{
try {
img = Image.createImage("/menu160.png");
zoom.setImage(img, W, H);
} catch (IOException e) {
e.printStackTrace();
}
}

int W=50,H=70;
protected void paint(Graphics g) {
	zoom.draw(g,this.getWidth()>>1, this.getHeight()>>1, 3);
	g.drawImage(img, this.getWidth()>>1, this.getHeight()>>1, 3);
}
protected void keyPressed(int key)
{
	zoom.setSize(W+=5,H+=5);
	repaint();
}

}

//-------------------------------------------------------------------------------------------------------------

public class Zoom {

        private AbstractImage globe;
        private AbstractImage lastRenderedImage;
        private ImageTransformationResize resizer;
        private int oldW;
        private int oldH;


        public void setImage(Image img, int w, int h) {

/* 19*/ try {
/* 19*/ Dispose();
/* 20*/ globe = new SmartImage(img);
/* 21*/ resizer = new ImageTransformationResize();
/* 22*/ setSize(w, h);
}
/* 24*/ catch (Exception ex) {
/* 26*/ ex.printStackTrace();
}
}

        public void Dispose() {

/* 32*/ globe = null;
/* 33*/ lastRenderedImage = null;
/* 34*/ resizer = null;
}

        public void setSize(int w, int h) {

/* 39*/ if (w <= 0 || h <= 0) {
/* 41*/ return;
}
/* 43*/ if (w == oldW && h == oldH) {
/* 45*/ return;
} else {
/* 49*/ oldW = w;
/* 50*/ oldH = h;
/* 52*/ lastRenderedImage = null;
/* 53*/ lastRenderedImage = new SmartImage(w, h);
/* 54*/ resizer.setTargetDimensions(w, h);
/* 55*/ resizer.process(globe, lastRenderedImage);
/* 56*/ return;
}
}

        public void draw(Graphics g, int x, int y, int anchor) {

/* 60*/ lastRenderedImage.drawOnGraphics(g, x, y, anchor);
}
}

This made me laugh because it was so short, succinct, and exactly right. :slight_smile:

NB:
when zooming it is better to do zoom*=1.1 or zoom*=0.9 (rather than zoom+=something and zoom-=something), it give a more linear result

also for mousewhell you should handle multiple ticks something like :

public void mouseWheelMoved(MouseWheelEvent e) 
{	
    	int wheel=e.getWheelRotation();
    	if(wheel<0)
    	{
    		this.zoom*=Math.pow(1.2,-wheel);	
    	}
    	else
    	{
    	
    		this.zoom*=Math.pow(0.8,wheel);		
    	}
}