How to zoom on to center of the screen

So I made a zoom (and pan) function for a program I am creating, and currently it zooms on the topleft corner of the screen. I want it to zoom on the center.

NOTE: I can’t use imageMode(center), without refactoring a lot of code

zoomLevel is the zoom level. (1.0 is default, 1.1 is more zoomed in, 1.2, even more, etc.)

The pan variables are:

offsetX, offsetY

void mouseWheel(MouseEvent event) {

if(zoomLevel-event.getCount()*0.1 >= 1) zoomLevel -= event.getCount()*0.1;

}

The image that is zoomed in on and panned is displayed like so (executed in draw(), map is the original image, disp_map is what is displayed):

disp_map = map.copy();

disp_map.resize(round((map.width*ratio_x)*zoomLevel), round((map.height*ratio_y)*zoomLevel));

image(disp_map, -offsetX, -offsetY);

1 Like

try

/**
 * Load and Display 
 * mouse wheel zoom
 */

PImage img0, img;  // Declare variable "a" of type PImage
float zoom = 0.5;

void setup() {
  size(640, 360);
  img0 = loadImage("moonwalk.jpg");  // Load the image into the program
  img=img0.copy();
  img.resize((int)(zoom*img.width), (int)(zoom*img.height) );
  println("use mouseWheel and press key [z]");
}

void draw() {
  background(200, 200, 0);
  image(img, width/2-img.width/2, height/2-img.height/2);
}

void mouseWheel(MouseEvent event) {
  float e = event.getCount(); //println(e);
  if ( keyPressed && key == 'z' ) { 
    zoom += e*0.02;                  // this factor might be depending on operating system
    if ( zoom < 0.01 ) zoom = 0.01;  // limit
    println(" key z: zoom "+zoom);
    img=img0.copy();
    img.resize((int)(zoom*img.width), (int)(zoom*img.height) );
  }
}

this ( your ) way is also much more efficient as the
image(img,x,y,dx,dy);
way, if you not do it every draw loop.

1 Like

There’s a problem here: I have other elements on the screen too (that are affected by the zoom level) and I need to just have it pan to make the center into the center.

What I mean is: When you zoom in, the center of the screen is offset by an amount, and then I need to pan the screen once zoomed in to make the center the center again. Problem is, I have no idea how much to pan it, that’s what I’m asking.

1 Like

actually a good zoom to THE center ?P2D?P3D?
you do with

void draw() {
  translate( width/2,height/2 );
  scale(zoom);
  // draw here relative to new 0.0 
}

moving each element ( also a math for 2D fake perspective should exist )
is too much work.

so i think you need to invest the time to make a mini project
( like with one picture and one other object )
and show your project here ( with running code and needed files )


also study 3D options and view options like
https://processing.org/reference/libraries/#3d
http://mrfeinberg.com/peasycam/

or make your own 3D show ( i make my PanTildeZoom )

1 Like

It’s 2D. Also what I want is so that when someone zooms in, it pans the offsetX and offsetY variables instead of changing the position the image is rendered at.

Your problem is almost certainly the order of operations. You must 1) translate before 2) zooming.

For a detailed discussion of why, see:

“These operations are order dependent if you are scaling: you must center your coordinate system first, then scale from the center, then translate to your view. If you try to scale first or last then bad things happen: either scaling happens from the top left, or the view scale does not match the drawing scale and they are misaligned.”

Problems with view and frame of reference are really difficult to debug about by just posting snippets, because so many different things in the code could impact the state of the matrix stack. If you still have trouble, post a complete mini sketch – an MCVE – to get feedback.

sure this is offtopic but…
i have coded a solution to this task these days.
maybe this can help other people in future:

    //update offset values to the middle of mouse
    for(short i=0;i<coords;i++)//adapt position to recenter the scaled item and to track for pan
    {
      switch(i)
      {
        case 0:   adapt_to_mouse[i]=mouseX;  break;
        case 1:   adapt_to_mouse[i]=mouseY;  break;
        default:  adapt_to_mouse[i]=0;
      }
    }
    //Update Zoom
    if(!(mousewheel_last_updated_state==mousewheel_last_state))
    {
      //update offset values to the middle of mouse
      for(short i=0;i<coords;i++)
      {
        //set a new origin: n+          m            *                 x
        offset[i]=adapt_to_mouse[i]+(offset[i]-adapt_to_mouse[i])*get_scaling_old()/get_scaling();
      }
      mousewheel_last_updated_state=(short)mousewheel_last_state;
    }

For additional information (var names tells you possibly everything in the code) an example:

the window is 600 width-> center_x is at 300
the ‘so called’ original_x is at 200: this is where would draw the picture without scaling
the scaling factor is 5

new_x=center_x+(original_x-center_x)*scaling

-200=300+(200-300)*5

This is not the solution to your problem, however it might be useful. I always use this class, because I am lazy to rewrite the code times and times again.

public static class SimpleRectFloat {
    public float x1, y1, x2, y2;
    
    public SimpleRectFloat(float x1, float y1, float x2, float y2){
        this.set(x1, y1, x2, y2);
    }
    final static SimpleRectFloat fromCenter(float cx, float cy, float sx, float sy){
        return new SimpleRectFloat(cx - sx * 0.5f, cy - sy * 0.5f, cx + sx * 0.5f, cy + sy * 0.5f);
    }
    
    final public SimpleRectFloat set(float x1, float y1, float x2, float y2){
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        return this;
    }
    final public SimpleRectFloat set(SimpleRectFloat rect){
        if(rect == null) return this;
        this.x1 = rect.x1;
        this.y1 = rect.y1;
        this.x2 = rect.x2;
        this.y2 = rect.y2;
        return this;
    }
    final public SimpleRectFloat setCentered(float x, float y, float w, float h){
        return this.set(x - w * 0.5f, y - h * 0.5f, x + w * 0.5f, y + h * 0.5f);
    }
    final public SimpleRectFloat setCenter(float x, float y){
        return this.setCentered(x, y,  this.getWidth(), this.getHeight());
    }
    final public SimpleRectFloat setSizeFromCenter(float w, float h){
        return this.setCentered(this.getCenterX(), this.getCenterY(), w, h);
    }
    final public SimpleRectFloat copy(){
        return new SimpleRectFloat(this.x1, this.y1, this.x2, this.y2);
    }

    final public float getLeft(){
        return (float)Math.min(this.x1, this.x2);
    }
    final public float getRight(){
        return (float)Math.max(this.x1, this.x2);
    }
    final public float getTop(){
        return (float)Math.min(this.y1, this.y2);
    }
    final public float getBottom(){
        return (float)Math.max(this.y1, this.y2);
    }

    final public float getWidth(){
        return Math.abs(this.x1 - this.x2);
    }
    final public float getHeight(){
        return Math.abs(this.y1 - this.y2);
    }
    final public PVector getSize(){
        return new PVector(this.getWidth(), this.getHeight());
    }
    final public float getCenterX(){
        return (this.x1 + this.x2) * 0.5f;
    }
    final public float getCenterY(){
        return (this.y1 + this.y2) * 0.5f;
    }
    final public PVector getCenter(){
        return new PVector(this.getCenterX(), this.getCenterY());
    }

    final public PVector map(float x, float y, SimpleRectFloat rect){
        if(rect == null) throw new NullPointerException();
        return new PVector(
            this.getLeft() + (x - rect.getLeft()) / rect.getWidth() * this.getWidth(),
            this.getTop() + (y - rect.getTop()) / rect.getHeight() * this.getHeight()
        );
    }
    final public PVector map(PVector p, SimpleRectFloat rect){
        if(p == null || rect == null) throw new NullPointerException();
        return new PVector(
            this.getLeft() + (p.x - rect.getLeft()) / rect.getWidth() * this.getWidth(),
            this.getTop() + (p.y - rect.getTop()) / rect.getHeight() * this.getHeight()
        );
    }
    final public boolean isPointInside(float x, float y){
        return x >= this.getLeft() && x <= this.getRight() && y >= this.getTop() &&y <= this.getBottom();
    }
    final public boolean isPointInside(PVector p){
        if(p == null) return false;
        return this.isPointInside((float)p.x, (float)p.y);
    }
    
    final public SimpleRectFloat scaleCenter(float scale){
        float cx = this.getCenterX();
        float cy = this.getCenterY();
        float sx = this.getWidth() * 0.5f * scale;
        float sy = this.getHeight() * 0.5f * scale;
        
        return this.set(cx - sx, cy - sy, cx + sx, cy + sy);
    }

    final public SimpleRectFloat createToFitAtCenter(float width, float height, float scale){
        if(width < 0) width = - width;
        if(height < 0) height = - height;

        float upscale = (float)Math.min(this.getWidth() / width, this.getHeight() / height);

        return SimpleRectFloat.fromCenter(this.getCenterX(), this.getCenterY(), width, height).scaleCenter(upscale * scale);
    }
    final public SimpleRectFloat fitInside(SimpleRectFloat rect, float scale){
        if(rect == null) return this;
        
        float upscale = (float)Math.min(rect.getWidth() / this.getWidth(), rect.getHeight() / this.getHeight());

        return this.setCenter(rect.getCenterX(), rect.getCenterY()).scaleCenter(upscale * scale);
    }

    @Override final public String toString(){
        return "[" + this.x1 + ", " + this.y1 + ", " + this.x2 + ", " + this.y2 + "]";
    }
}

And here is an example code that maybe resembles yours (though I’m not confident it does).

SimpleRectFloat rect;
PImage image;
float zoomLevel = 1;

void setup(){
  size(640, 480);
  image = loadImage("path");
  rect = SimpleRectFloat.createToFitAtCenter(image.width, image.height, 1);
}

void draw(){
  background(23);

  //Snap rect to center                     set size to image size * zoomLevel
  rect.setCenter(width * 0.5, height * 0.5).setSizeFromCenter(image.width * zoomLevel, image.height * zoomLevel);
  
  //Draw image
  imageMode(CORNERS);
  image(image, rect.x1, rect.y1, rect.x2, rect.y2);
  imageMode(CENTER);
  image(image, rect.getCenterX(), rect.getCenterY(), rect.getWidth(), rect.getHeight());
  imageMode(CORNER);
  image(image, rect.getLeft(), rect.getTop(), rect.getWidth(), rect.getHeight());
}

void mouseWheel(MouseEvent e){
  if(e.getCount() > 0) zoomLevel *= 1.25;
  else zoomLevel *= 0.8;
}