# Smooth zooming in and out on your mouse tip

**URL:** <https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117>\
**Category:** Gallery\
**Created:** [March 30, 2025, 7:33pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117 "2025-03-30T19:33:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [March 30, 2025, 7:33pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/1 "2025-03-30T19:33:03Z")

</div>

My dear friend John, a.k.a [introscopia](https://introscopia.itch.io/) has this amazing smooth zoom and pan code I really like, but he uses mostly SDL nowadays, so I asked him for help and made these two examples. One in Processing Java and the other for [Python + py5](https://py5coding). I hope you enjoy it as much as I did (I have many sketches that would benefit from this).

![out2](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/d/b/db2fb60c60f5910d471fe8b4528de92213a0e994.gif)

```pde
// A zoom strategy from John @introscopia https://introscopia.itch.io/

int zptX = 0;
int zptY = 0;
float zptScale = 1;
float zptAmount = 0;

int seed = 1;

void setup() {
  size(500, 500);
  noStroke();
}

void draw() {
  pushMatrix();
  translate(zptX, zptY);
  scale(zptScale);
  // Your drawing goes here
  background(0);
  randomSeed(seed);
  int w = 500;
  for (int i=0; i<10; i++) {
    float x= random(-w/2, w + w/2);
    float y = random(-w/2, w + w/2);
    for (int d=w*2; d>0; d=d-10) {
      fill(random(255), random(255), random(255), 50);
      circle(x, y, d);
    }
  }
  // end of the main drawing part
  popMatrix();
  // HUD (Head-Up Display, like if projected on the windscreen, unaffected by zoom & pan)
  pushStyle();
  int margin = 40;
  noFill();
  stroke(255);
  rect(margin, margin, width - margin * 2, height - margin * 2);
  fill(255);
  textSize(margin / 3);
  textAlign(LEFT, TOP);
  text(String.format("{'x': %s, 'y': %s , 'scale': %s, 'amount': %s}", zptX, zptY, zptScale, zptAmount),
       margin, height - margin / 2);
  popStyle();
}

void mouseWheel(MouseEvent e) {
  float xrd = (mouseX - zptX) / zptScale;
  float yrd = (mouseY - zptY) / zptScale;
  zptAmount -= e.getCount();
  zptScale = pow(1.1, zptAmount);
  zptX = int(mouseX - xrd * zptScale);
  zptY = int(mouseY - yrd * zptScale);
}

void mouseDragged() {
  zptX += mouseX - pmouseX;
  zptY += mouseY - pmouseY;
}

void keyPressed() {
  seed += 1;
}

```

And with py5:

```python
# A zoom strategy from John @introscopia https://introscopia.itch.io/

import py5

zpt = {'x': 0, 'y': 0, 'scale': 1, 'amount': 0} # zoom & pan transformation values
seed = 1

def setup():
    py5.size(500, 500)
    py5.no_stroke()
    
def draw():
    with py5.push_matrix():
        py5.translate(zpt['x'], zpt['y'])
        py5.scale(zpt['scale'])
        # Your drawing goes here
        py5.background(0)
        py5.random_seed(seed)
        w = 500
        for _ in range(10):
            x, y = py5.random(-w/2, w + w/2), py5.random(-w/2, w + w/2)
            for d in range(w * 2, 0, -10):
                py5.fill(py5.random(255),py5.random(255),py5.random(255), 50)
                py5.circle(x, y, d)
    # HUD (Head-Up Display, like if projected on the windscreen, unaffected by zoom & pan)
    with py5.push_style():
        margin = 40
        py5.no_fill()
        py5.stroke(255)
        py5.rect(margin, margin, py5.width - margin * 2, py5.height - margin * 2)
        py5.fill(255)
        py5.text_size(margin / 3)
        py5.text_align(py5.LEFT, py5.TOP)
        py5.text(str(zpt), margin, py5.height - margin / 2)

def mouse_wheel(e):
    xrd = (py5.mouse_x - zpt['x']) / zpt['scale']
    yrd = (py5.mouse_y - zpt['y']) / zpt['scale']
    zpt['amount'] -= e.get_count()
    zpt['scale'] = 1.1 ** zpt['amount']
    zpt['x'] = int(py5.mouse_x - xrd * zpt['scale'])
    zpt['y'] = int(py5.mouse_y - yrd * zpt['scale'])

def mouse_dragged():
    zpt['x'] += py5.mouse_x - py5.pmouse_x
    zpt['y'] += py5.mouse_y - py5.pmouse_y

def key_pressed():
    global seed
    seed += 1

py5.run_sketch()

```

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 1:22pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/2 "2025-03-31T13:22:53Z")

</div>

Random question:

What OS is that?

Windows?  
Some GNU/Linux distro?

Cool!

You probably shouldn’t use the word “interface” to avoid confusion.

`interface` has a meaning in Java (the language Processing is based upon).

> **[What Is an Interface? (The Java™ Tutorials \>        
            Learning...](https://docs.oracle.com/javase/tutorial/java/concepts/interface.html)**
>
> This beginner Java tutorial describes fundamentals of programming in the Java programming language

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 1:26pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/3 "2025-03-31T13:26:17Z")

</div>

In general, declaring variables in the `draw` method is a bad idea.

You are declaring a new variable 60x a second!

Perhaps this method could be simplified:

```auto
void mouseWheel(MouseEvent e) {
  float xrd = (mouseX - zptX) / zptScale;
  float yrd = (mouseY - zptY) / zptScale;
  zptAmount -= e.getCount();
  zptScale = pow(1.1, zptAmount);
  zptX = int(mouseX - xrd * zptScale);
  zptY = int(mouseY - yrd * zptScale);
}

```

Perhaps `int(mouseX - (mouseX - zptX) / zptScale * zptScale)` could be simplified to `int(zptX)`

NOTE: `Math.round()` is probably better than `int()`; typecasting `(int) zptX` is probably superior.

```auto
void mouseWheel(MouseEvent e) {
  final float zptScale_old = zptScale;
  //zptAmount is useless and unnecessary
  zptScale = pow(1.1, -e.getCount());
  zptX = Math.round(mouseX - (((mouseX - zptX)/zptScale_old) * zptScale));
  zptY = Math.round(mouseY - (((mouseY - zptY)/zptScale_old) * zptScale));
}

```

---

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [March 31, 2025, 1:49pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/4 "2025-03-31T13:49:32Z")

</div>

Thanks for the tips @LangdonS !

Java isn’t my thing for a long time now… good idea to not mislead people with the “interface” jargon. I have edited it out. I’m on Manjaro XFCE by the way.

You shouldn’t simplify it because zptScale changes in the middle of the operation, thus changing zptX and zptY, the zoom adjusts the pan, that’s the whole point! 😄

I could adopt your (int) casting idea but I don’t think that in the time scale of the mouse wheel events it will make any difference. It was there just because I decided it would be prettier to have `zptX` and `zptY` as ints, but they could be floats as well.

I should move `margin` out…

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 3:31pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/5 "2025-03-31T15:31:57Z")

</div>

You don’t need the variable `zptAmount`.

---

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [March 31, 2025, 4:09pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/7 "2025-03-31T16:09:06Z")

</div>

Oh it is essential, it keeps the state of the “human intuition readable zoom amount”. `zptScale` is the value used by the scale transformation, but it is calculated from `zptAmount`, which is increased or decreased by one with the scroll wheel, so it needs to be kept between scrolling events. This is the whole point of the thing, makes zooming in and out much more consistent than if you try changing the scale directly.

One could arguably reconstruct zptAmount from zptScale, or vice-versa, on the spot, but I find it much more sensible to keep both.

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 5:01pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/8 "2025-03-31T17:01:21Z")

</div>

> [@LangdonS](#):
>
> ```auto
> zptAmount -= e.getCount();
> zptScale = pow(1.1, zptAmount);
> 
> ```

```auto
zptAmount -= e.getCount();
zptScale = pow(1.1, zptAmount);

```

Can probably be simplified to:

`zptScale *= pow(1.1, -e.getCount())`

Then you can get rid of `zptAmount`.

Perhaps even better(uses `double` to maximize accuracy):  
`zptScale *= Math.sqrt(Math.pow(2,-e.getCount()));`

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 5:52pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/9 "2025-03-31T17:52:51Z")

</div>

Try this out:  
`zptScale = Math.sqrt(Math.pow(2, zptAmount));`  
OR:  
`zptScale = Math.pow(2, zptAmount/2d);`

This could provide “nicer” results.

2x zoom in = 200%  
2x zoom out = 50%

---

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [March 31, 2025, 6:43pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/10 "2025-03-31T18:43:14Z")

</div>

Have you tried it yourself? 😃

Answering the previous post, you really can’t get rid of `zptAmount`, because `e.getCount()` gets you `-1` or `1`, you need somewhere to keep the zoom amount state.

Also the whole point, John’s great insight, in my view, is that `pow(1.1, zptAmount)` gives a much nicer continuous “zoom experience” both inwards and outwards. But you are free to disagree and use whatever constants you like!

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [March 31, 2025, 7:42pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/11 "2025-03-31T19:42:30Z")

</div>

1.1 might give visually “nice” reults, but √2 gives numerically “nice” results try out both, see which you like.

The nice thing about √2 is the resulting zoom ratios are like **50%** , 71%, **100%** , 142%, **200%** , 283%, **400%**

Note the `*=` not `=`.

---

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [March 31, 2025, 8:48pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/12 "2025-03-31T20:48:59Z")

</div>

> [@LangdonS](#):
>
> Note the `*=` not `=`.

Oh! I see… but then, just because you want to remove a variable, you loose what you described yourself as a “numerically nice” state variable! I’ll stand behind John’s choices: nice zoom experience plus a state variable with a very intuitive value. 😆

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [April 1, 2025, 2:10pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/13 "2025-04-01T14:10:24Z")

</div>

Try out the `√2` and see what you think.

The problem with `1.1` is the resulting scaling ratios are kinda weird: 91%, 100%, 110%, 121%

---

<div class="post-metadata">

**Author:** ![scudly](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/scudly/32/5597_2.png) [@scudly](https://discourse.processing.org/u/scudly)\
**Post date:** [April 1, 2025, 3:50pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/14 "2025-04-01T15:50:46Z")

</div>

√2 is too fast. If you want smooth and rounding to nice values use 1.09051 which is just over 2 ^ 1/8. Every 8 tics of the mouse wheel will give you an even power of 2 scaling.

@villares If I tell you that the view scaling is 1x or 2x or 10x, I think you’d have a pretty clear idea what that means. If I say that zptAmount is 1 or 4 or 7, is that going to have some intuitive meaning to anyone? Stick with `zptScale *= pow( 1.09051, -e.getCount() );` and I suspect people would find the `zptScale` value much more meaningful.

---

<div class="post-metadata">

**Author:** ![villares](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/villares/32/3166_2.png) [@villares](https://discourse.processing.org/u/villares)\
**Post date:** [April 1, 2025, 7:52pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/15 "2025-04-01T19:52:36Z")

</div>

Cool! That’s a nice constant!

---

<div class="post-metadata">

**Author:** ![neilcsmith](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/neilcsmith/32/144_2.png) [@neilcsmith](https://discourse.processing.org/u/neilcsmith)\
**Post date:** [April 2, 2025, 12:51pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/16 "2025-04-02T12:51:17Z")

</div>

Interesting thread!

> [@LangdonS](#):
>
> In general, declaring variables in the `draw` method is a bad idea.
> 
> You are declaring a new variable 60x a second!

Not sure what the point of this was, but seriously don’t worry about it, and concentrate on working legible code. Anyway, it’s _only_ 60x a second - that is nothing! Audio code would run at 48,000x a second, and I still wouldn’t worry about it.

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [April 2, 2025, 10:46pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/17 "2025-04-02T22:46:04Z")

</div>

Well then why not some other root of two, like the cube root or 4th root or something? `2 ^ 1/8` is the 8th root, BTW.

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [April 2, 2025, 10:52pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/18 "2025-04-02T22:52:15Z")

</div>

> [@LangdonS](#):
>
> 2 ^ 1/8

According to BEDMAS, that actually means `Math.pow(2, 1)/8`  
I think you mean 2 ^ (1/8)

`Math.pow(2, 1f/8f)`

---

<div class="post-metadata">

**Author:** ![scudly](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/scudly/32/5597_2.png) [@scudly](https://discourse.processing.org/u/scudly)\
**Post date:** [April 2, 2025, 11:21pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/19 "2025-04-02T23:21:20Z")

</div>

The zoom base in the original code is `1.1`. `2` is approximately equal to `1.1` raised to `1/7.27254`. I chose to round it to `8`. Using `3` or `4` would also hit 2x exactly, but would zoom in and out more quickly while giving you fewer zoom levels to stop at along the way. Probably the cleaner way to write the zoom would be as

`zptScale *= pow( 2, -e.getCount() / 8.0 );`

since that leaves the `2` and the `8` more explicit and avoids the curious looking constant.

---

<div class="post-metadata">

**Author:** ![scudly](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/scudly/32/5597_2.png) [@scudly](https://discourse.processing.org/u/scudly)\
**Post date:** [April 3, 2025, 12:18am UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/20 "2025-04-03T00:18:11Z")

</div>

In case you want to add rotation as well, I posted this in the distant past: [https://www.reddit.com/r/processing/comments/aecgdp/using\_a\_mousebased\_ui\_to\_explore\_truchet\_tiles/](https://www.reddit.com/r/processing/comments/aecgdp/using_a_mousebased_ui_to_explore_truchet_tiles/)

---

<div class="post-metadata">

**Author:** ![LangdonS](https://avatars.discourse-cdn.com/v4/letter/l/ebca7d/32.png) [@LangdonS](https://discourse.processing.org/u/LangdonS)\
**Post date:** [May 7, 2025, 4:25pm UTC](https://discourse.processing.org/t/smooth-zooming-in-and-out-on-your-mouse-tip/46117/21 "2025-05-07T16:25:10Z")

</div>

12th root of 2 might be good too.
