Zoom in and out an image.

Posted by Wuggly on Fri 12 Feb 2016 10:03 AM — 20 posts, 71,682 views.

USA #0
Hi again,

I've started working on a mini-window that will show an image of a map. I want to be able to zoom in and zoom out the image using the mouse. Being able to drag it around would be neat too.

I've searched around and couldn't find any information on how to accomplish something like this, besides with text maps.

Could someone point me into the right direction how I would go about doing something like this?

Here's what I've managed to piece together from various forum posts. Right now I'm just trying it with one image to see if I could get it working before getting more complicated with it. The Hotspot part of the code... is just a bad attempt for zooming in that I took out of some post reply here.

Window creates fine, and shows the map image.


function show_map ()

  map_window = GetPluginID()
  map_width = 500
  map_height = 500

  check (WindowCreate (map_window,   -- window ID
                0,   -- left
                0,   -- top
                map_width, -- width
                map_height, -- depth
                0,  -- center it 
                2,          
                0))  -- background colour

  -- load the map image
  check (WindowLoadImage (map_window, "map", GetInfo (66) .. "Maps/Woods_Map.png"))


  -- draw it
  check (WindowDrawImage (map_window, "map", 0, 0, 0, 0, 2))  -- draw it

  check (WindowAddHotspot(map_window, "map", 0, 0, map_width,map_height, "", "", "", "", "mouseup", "Zoom", 10, 0))

  -- show the window
  WindowShow (map_window, true)

end -- show_map


Assuming you would have to redraw the window every time you zoomed in or out, but being able to drag it around? How could that work?
Amended on Fri 12 Feb 2016 11:22 AM by Wuggly
USA Global Moderator #1
Ok, so you're talking about making what we call a "slippy map" in digital cartography.

To be able to drag it around, you'll need make a big hotspot that covers the entire miniwindow and then do something like this...

a) first make some variables to store the map's current display offsets call them offset_x and offset_y and set them both to 0 for now. These, combined with the dimensions of your miniwindow and your zoom ratio will determine what we'll call your view window, which is the chunk of your map that gets displayed at any moment.
b) on mousedown store the mouse position via GetInfo.
c) on mousemove get the current mouse position via GetInfo and subtract the original mouse position found during onmousedown to get the position delta (current_x-original_x, current_y-original_y). Draw your scaled image using the display offsets plus the position delta as the image location. I'd probably use WindowTransformImage for my initial prototype of this, because it doesn't require caching the scaled image.
d) on mouseup replace the saved display offsets with what you were just using as your image location (previous display offsets plus mouse position delta).

e) once you get that working, give yourself bonus points by rate-limit your attempts to redraw the map so that you don't do any of this more than like 15 times per second. More is just wildly unnecessary.
Amended on Fri 12 Feb 2016 09:59 PM by Fiendish
USA #2
Thanks for the information! That'll for sure help me get started to figuring this out.
Amended on Sat 13 Feb 2016 05:41 AM by Wuggly
Australia Forum Administrator #3
The supplied mapper module (mapper.lua) implements zooming and dragging, if that helps.
USA #4
Ok so I figured out how to keep it from positioning back up after going back to drag it again. I simply took out where it adds up the current with the delta in the MouseUp function.

function MouseUp(flags, hotspot_id)
 offset_x, offset_y = delta_xa, delta_ya
end -- function MouseUp


Now when you drag it and the image goes offscreen in the miniwindow, you can see a bunch of old copies of it.

Any idea how I would go about cleaning this up? I tried simple things like adding ReDraw into the mouseup function, but it made no difference.

Here's a screenshot below of what I mean.
http://i.imgur.com/laYztcK.png

I understand it's making a new copy of the image every time the mouse is moved when dragging it, but I can't seem to find any other way to do this with the WindowTransformImage function.
Amended on Sat 13 Feb 2016 11:36 AM by Wuggly
USA Global Moderator #5
You have to manually blank out the space where you're not drawing the image. The simplest way is to blank the whole miniwindow before drawing your image.
USA Global Moderator #6
Nick Gammon said:

The supplied mapper module (mapper.lua) implements...dragging, if that helps.

Does it? I wonder if that's a part I edited out of mine.
USA #7
Could you give me a hint how I would go about blanking it out? I have no clue where to start on something like that or what to use.
How do I blank the whole miniwindow? Wouldn't ReDraw be doing that? I'm quite lost on this.

And what about limiting the redraws like you mentioned? I have no clue about that one either.


Sorry for these newb questions, but I've never coded anything before getting mushclient and have only been at it for a month and a half now, with most of that just being simple things like triggers and aliases then slowly moving on into making plugins up to where I am now trying to make this crazy thing lol.

I don't think the mapper.lua helps any when it comes to dealing with an image.

function zoom_in ()
  if last_drawn and ROOM_SIZE < 40 then
    ROOM_SIZE = ROOM_SIZE + 2
    DISTANCE_TO_NEXT_ROOM = DISTANCE_TO_NEXT_ROOM + 2
    build_room_info ()
    draw (last_drawn)
  end -- if
end -- zoom_in


I deleted one of my posts here as it was just taking up room and irrelevant now. Here's the code so far.


function show_map ()

  map_window = GetPluginID()
  map_width, map_height = 500, 500

  check (WindowCreate (map_window,   -- window ID
                0,   -- left
                0,   -- top
                map_width, -- width
                map_height, -- depth
                0,  -- center it (ignored anyway) 
                2,          
                0))  -- background colour

  -- load the map image
  check (WindowLoadImage (map_window, "map", GetInfo (66) .. "Maps/Woods_Map.png"))


  -- draw it
if offset_x == nil and offset_y == nil then 
 offset_x, offset_y = 0, 0
else
end -- offset check
  check (WindowDrawImage (map_window, "map", offset_x, offset_y, 0, 0, 2))  -- draw it

  -- drag hotspot
check (WindowAddHotspot(map_window, "map1", 0, 0, map_width,map_height, "", "", "MouseDown", "", "MouseUp", "Drag", 10, 0))
check (WindowDragHandler (map_window, "map1", "dragmove", "", 0))

  -- show the window
  WindowShow (map_window, true)

end -- show_map


function MouseDown(flags, hotspot_id)
  original_x, original_y = GetInfo(283), GetInfo(284)
end  -- function MouseDown

function MouseUp(flags, hotspot_id)
 offset_x, offset_y = delta_xa, delta_ya
end -- function MouseUp

function dragmove(flags, hotspot_id)
 current_x, current_y = GetInfo(283), GetInfo(284)
 delta_x, delta_y = current_x-original_x, current_y-original_y
 delta_xa, delta_ya = offset_x+delta_x, offset_y+delta_y
 WindowTransformImage (map_window, "map", delta_xa, delta_ya, miniwin.image_copy, 1, 0, 0, 1) 

 Redraw ()
end  -- function dragmove
Amended on Sat 13 Feb 2016 12:43 PM by Wuggly
USA Global Moderator #8
Quote:

Could you give me a hint how I would go about blanking it out?

Draw the background color over the whole thing.

Quote:
And what about limiting the redraws like you mentioned? I have no clue about that one either.

During mousemove, check the high precision time with utils.timer() and see how much time has elapsed since the last time you redrew everything. If not enough time has elapsed, do nothing. Then do one final draw in mouseup.


Quote:
Wouldn't ReDraw be doing that? I'm quite lost on this.

I find Redraw to be actually quite useless. See this page for my thoughts:
https://github.com/fiendish/aardwolfclientpackage/wiki/Repaint-Buffer

Which corresponds to this plugin file
https://raw.githubusercontent.com/fiendish/aardwolfclientpackage/MUSHclient/MUSHclient/worlds/plugins/aard_repaint_buffer.xml
Amended on Sat 13 Feb 2016 01:53 PM by Fiendish
USA #9
I guess I just don't understand what to draw the background color with. Would I use something like WindowImageOp or WindowRectOp and just put that right before the WindowTransformImage or WindowDrawImage?

EDIT: Ok so I tried it using the RectOp putting it right before the WindowTransformImage.

WindowRectOp (map_window, 2, 500, 500, 1, 1, ColourNameToRGB("green"))


This did the trick, but, I can't seem to set the position to 0, 0. When I do that, it doesn't show at all, and with 1, 1 you can see that tiny 1 line on the top and left.. Any idea how to get it to fit perfectly?
Amended on Sat 13 Feb 2016 02:54 PM by Wuggly
USA Global Moderator #10
You have your coordinates reversed. 0,0 should come before 500,500 not after. Remember, the top left corner is 0,0 and numbers increase as you go down and right.

0,0 and lower (negative) when given for the lower right corner coordinates evaluate as "size of miniwindow plus this value". So 0,0 is the lower right corner and negative coordinates moves in from there. So when you do 500,500,0,0 you're actually doing 500,500,500,500, which is nothing. If you did 0,0,0,0 it would work as you want. And 0,0,-1,-1 would leave the bottom and right edges exposed. And so on.
Amended on Sat 13 Feb 2016 03:02 PM by Fiendish
USA #11
Thank you thank you thank you! Feels like I just became debt-free! lol.

So about the timer.. still a bit lost on that one. Would it be something like this?

if (utils.timer() < 16) then
  Redraw ()
end


Then on the mouseup just throw in a Redraw? I tried it out like that, but it wouldn't redraw until after you let go of the mouse. So I could drag it, but not see it while it was dragging, which isn't really much of an issue, it just looks and feels better when it's.. live like that.
Amended on Sat 13 Feb 2016 03:31 PM by Wuggly
USA Global Moderator #12
Something like this


local last_time

function MouseDown(flags, hotspot_id)
  last_time = utils.timer()
  ...


function dragmove(flags, hotspot_id, force_update)
  local new_time = utils.timer()
  if new_time - last_time < 0.1 and not force_update then return end
  last_time = new_time
  ...

function MouseUp(flags, hotspot_id)
  dragmove(nil, nil, true)
  ...



I also almost never directly use Redraw or Repaint when I care about the interface actually following the user. The first has too much flexibility to do absolutely nothing until "later", and the second stops the world for long enough that it might cause the next iterations of processing to fall behind.
Amended on Sat 13 Feb 2016 03:47 PM by Fiendish
USA #13
Ok, edited, had to wait 8 minutes.

local last_time

function MouseDown(flags, hotspot_id)
  original_x, original_y = GetInfo(283), GetInfo(284)
  last_time = utils.timer()
end  -- function MouseDown

function dragmove(flags, hotspot_id, force_update)
  local new_time = utils.timer()
  if new_time - last_time < 0.1 and not force_update then return end
  last_time = new_time
 current_x, current_y = GetInfo(283), GetInfo(284)
 delta_x, delta_y = current_x-original_x, current_y-original_y
 delta_xa, delta_ya = offset_x+delta_x, offset_y+delta_y
WindowRectOp (map_window, 2, 0, 0, 0, 0, ColourNameToRGB("green"))
 WindowTransformImage (map_window, "map", delta_xa, delta_ya, miniwin.image_copy, 1, 0, 0, 1) 
  Redraw ()
end  -- function dragmove

function MouseUp(flags, hotspot_id)
 offset_x, offset_y = delta_xa, delta_ya
dragmove(nil, nil, true)

end -- function MouseUp


This is working, but it's funky. When I let go of the mouse depending on which way I was dragging, the map image jumps that direction a little bit than where I left off. I'm guessing I need to add something to the delta_xa and ya in the mouseup part? It didn't do this before adding in the timer.
Amended on Sat 13 Feb 2016 04:12 PM by Wuggly
USA Global Moderator #14
First, it looks like that Redraw in MouseUp does nothing since you always call it right after WindowTransformImage anyway.
Second, maybe that 15 needs to be 0.1 or something. Let me check my notes.

Yeah it does. It's a measure of seconds, not milliseconds, sorry. So make it 0.1 or 0.09 or 0.08 or something on that order. I'll update the above post to reflect this.
Amended on Sat 13 Feb 2016 03:56 PM by Fiendish
USA Global Moderator #15
Quote:
This is working, but it's funky. When I let go of the mouse depending on which way I was dragging, the map image jumps that direction a little bit than where I left off. I'm guessing I need to add something to the delta_xa and ya?

Save the final offset in mouseup after calling that last dragmove instead of before?
USA Global Moderator #16
You can also just not try to do the rate-limiting stuff right now until you find that you actually experience a performance problem while dragging.
USA #17
Thanks Fiendish, you've been very helpful. I'm sure I'll be back with more questions once I try to start with the zooming stuff.

=)
USA #18
Although it was much frustration, I managed to work in zoom in/out.

Please let me know if I should start a new topic for this next question or not. It's going to be part of this same script so I figured I'd just ask it in here to keep my topics down and have one place for everything regarding this script.

Anyways, what I need now is scroll bars. I took Nick's hyperlink code and adapted it to make a big list of all the different maps that can be viewed.
reference: http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=12868

However, there are almost a hundred maps, so the map list miniwindow became very long cutting off half of all the different map links, so now I need to work in a scrollbar.

I've been looking around at how others did it, and like most stuff here, I haven't quite been able to wrap my mind around it just yet.

Would anyone be willing to write up a little scrollbar tutorial, like how Fiendish explained about making a slippy map in the second reply of this thread?

(feel free to ignore this part, basically just me thinking out loud here)
Things it needs to be able to move when scrolling I suppose would be the WindowTexts and WindowAddHotspots. I get confused quite easily when it comes to the math stuff, so that's why I need some sort of tutorial for it. If you look over in the hyperlink thread down on in the post where Nick's code is, I'm wondering how scroll would affect it since it draws it using ipairs. MakeHyperlink (k, v).
The map list miniwindow is only as wide as the longest hyperlink, and only as long as the list is, so I also need to figure a way to manipulate the code some more to make it a set size so there is room for the scrollbars. I'm also wondering how this would work since the list is so big. After it draws, would I be able to set it so it shows the list starting from the top, being able to scroll down to see the rest?
Amended on Sun 14 Feb 2016 03:22 PM by Wuggly
USA Global Moderator #19
I've actually explained how to make scroll bars in another thread already. Link here: http://www.gammon.com.au/forum/?id=10728&reply=82#reply82