Simple pinch zoom

Here is a listing to allow you to pinch-zoom images or display groups. Please let me know if you have questions or need it pulling apart…

Matt.
[lua]–[[
These are support functions which I use everywhere. They could easily be in a library file.
]]–

– calculates the hypoteneuse, ie: the distance between two points.
– Input args format: a, b … { x, y }
function getLength( a, b )
local width, height = b.x-a.x, b.y-a.y
return math.sqrt(width*width + height*height)
end

– calculates the angle of a point from the 0,0. (When using atan2 the 0 degrees angle is actually pointing east.)
– Input args: pt … { x, y }
function AngleOfPoint( pt )
local x, y = pt.x, pt.y
local radian = math.atan2(y,x)
local angle = radian*180/math.pi
if angle < 0 then angle = 360 + angle end
return angle
end

– calculates the difference between two angles, but this accepts two points, not their angles
– Input args: pointA, pointB … { x, y }
– Input args: clockwise … true|false
function AngleDiff( pointA, pointB, clockwise )
local angleA, angleB = AngleOfPoint( pointA ), AngleOfPoint( pointB )

if angleA == angleB then
return 0
end

if clockwise then
if angleA > angleB then
return angleA - angleB
else
return 360 - (angleB - angleA)
end
else
if angleA > angleB then
return angleB + (360 - angleA)
else
return angleB - angleA
end
end

end

–[[
This creates a multi-touch tracking object and adds it to a list of multi-touch tracking objects
internal to the image being manipulated by the touches.
]]–
function addTouch( img, event )

–[[Construction]]–
local touch = display.newCircle( event.x, event.y, 50 )
touch:setFillColor( 255,0,0,150 )

– this data is required to be able to calculate the motion of the touches.
– basically, if you don’t use this function to manage your multiple touch points, you need to
– manage this data yourself, so that the objects passed into the ‘makePinchZoom’ functions have it.
– the ‘makePinchZoom’ functions don’t call the other args or functions added by this function.
touch.xStart, touch.yStart = event.x, event.y
touch.xPrev, touch.yPrev = event.x, event.y
touch.x, touch.y = event.x, event.y

touch.parentImg = img
touch.id = event.id

– adds the list in which to keep the multi-touch tracking objects
if (touch.parentImg.touchList == nil) then
local list = {}

function list:indexOf( touch )
for i=1, #list do
if (list[i] == touch) then
return i
end
end
return 0
end

touch.parentImg.touchList = list
end

touch.list = touch.parentImg.touchList
touch.list[#touch.list +1] = touch

–[[External functions]]–

function touch:isFirst()
if (touch.list[1] == touch) then
return true
else
return false
end
end

function touch:indexOf()
return touch.list:indexOf( touch )
end

–[[Internal functions]]–

– basically makes a record of the original event position
function touch:began( event )
– should never be called, this is here for convenience, but should never be called
touch.xStart, touch.yStart = event.x, event.y
end

– moves the touch object and fires the image’s touchMoved event listener
function touch:moved( event )
if (touch.parentImg.touchMoved ~= nil) then
touch.xPrev, touch.yPrev = touch.x, touch.y
touch.x, touch.y = event.x, event.y
touch.parentImg:touchMoved( touch )
end
end

– removes the touch object
function touch:endedCancelled( event )
display.getCurrentStage():setFocus( touch, nil )
table.remove( touch.list, touch:indexOf() )
touch:removeSelf()
end

– just calls other functions to deal with the events, to make the code cleaner
function touch:touch( event )
if (event.phase == “began”) then
touch:began( event )
elseif (event.phase == “moved”) then
touch:moved( event )
elseif (event.phase == “ended” or event.phase == “cancelled”) then
touch:endedCancelled( event )
end
end

–[[Completion]]–

touch:addEventListener( “touch”, touch )
display.getCurrentStage():setFocus( touch, event.id )

return touch – for convenience, but not really used
end


–[[
Adds functions to the image to allow it to be pinch-zoom-able
In short: this contains the functions that do the magic.
If you want to rip the magic out, just take these functions, remove the ‘img:’ from their name,
and make sure you are managing the x, y, and xPrev, yPrev values to be passed to these functions.
]]–
function makePinchZoom( img )
– sets the reference point to be the mid-point between the touches, relative to the img x,y
function img:setReference( touchA, touchB )
img.xPrevReference = img.xReference
img.yPrevReference = img.yReference

– get touch mid-point relative to image
local tax = img.x-(img.x - touchA.x)
local tay = img.y-(img.y - touchA.y)
local tbx = img.x-(img.x - touchB.x)
local tby = img.x-(img.y - touchB.y)

– set the position (relative to the image’s 0,0) around which scaling and rotation is performed
– see: http://developer.anscamobile.com/content/display-objects#object.xReference
img.xReference = (tbx-tax)/2
img.yReference = (tby-tay)/2
end

– sets the reference back to it’s original value, to avoid screwing with other code which may have used it
– this will usually be 0,0
– set: http://developer.anscamobile.com/content/display-objects#object.xReference
function img:unsetReference()
img.xReference = img.xPrevReference
img.yReference = img.yPrevReference

img.xPrevReference = nil
img.yPrevReference = nil
end

– moves the image relative to the amount the mid-point between the touches has moved
function img:doMove( touchA, touchB )
local x = ((touchA.x - touchA.xPrev) + (touchB.x - touchB.xPrev)) / 2
local y = ((touchA.y - touchA.yPrev) + (touchB.y - touchB.yPrev)) / 2

img.x = img.x + x
img.y = img.y + y
end

– rotates the image relative to how much the touch points have moved relative to each other
function img:doRotate( touchA, touchB )
local prev = AngleOfPoint( { x=touchB.xPrev-touchA.xPrev, y=touchB.yPrev-touchA.yPrev } )
local current = AngleOfPoint( { x=touchB.x-touchA.x, y=touchB.y-touchA.y } )

img.rotation = img.rotation + (current - prev)
end

– scales the images relative to the previous and current distance between the two touch points
function img:doScale( touchA, touchB )
local prevLen = getLength( {x=touchA.xPrev, y=touchA.yPrev}, {x=touchB.xPrev, y=touchB.yPrev} )
local currentLen = getLength( {x=touchA.x, y=touchA.y}, {x=touchB.x, y=touchB.y} )

local scale = currentLen / prevLen

img.xScale = img.xScale * scale
img.yScale = img.yScale * scale
end

–[[
This is called by the addTouch functions above.
if you don’t want to have my code managing the multi-touch points, you will have to track those
multi-touch events yourself and make sure you can pass the data objects into these functions.
all that this function really does is make sure there are at least 2 multi-touch points known in
the system and passes them in. If you want to do that yourself, just pass in objects with the data:
{ x, y, xPrev, yPrev }
It is important to call the setReference and unsetReference functions because they manipulate the
x|yReference values. see: http://developer.anscamobile.com/content/display-objects#object.xReference
]]–
function img:touchMoved( touch )
if (#img.touchList == 1) then
img.x, img.y = img.x+(touch.x-touch.xPrev), img.y+(touch.y-touch.yPrev)
elseif (#img.touchList == 2) then
– this is the section of code you need to call yourself if you decide to extract this code
– just make sure you always pass in: { x, y, xPrev, yPrev } for each arg
img:setReference( img.touchList[1], img.touchList[2] )
img:doMove( img.touchList[1], img.touchList[2] )
img:doRotate( img.touchList[1], img.touchList[2] )
img:doScale( img.touchList[1], img.touchList[2] )
img:unsetReference()
end
end
end


– I don’t really have a good image, but my test code always a large crate png!
– My listing (above) adds a lot of functions and internal values to the img object to allow it to be manipulated
– by the pinch zoom code. But this is easily extracted from the ‘makePinchZoom’ function and used in your own way.
local img = display.newImage( “crate.png” )

– handling of the touches on the image is managed by this function until the addTouch objects take over
– if you want to manage the multi-touch events yourself, go ahead and do it - just make sure you pass
– { x, y, xPrev, yPrev } objects into the img:touchMoved event listener when getting this code to do the pinch-zoom.
– if you track multi-touches on an image object but pass a display group into the addTouch function it should work fine.
function img:touch( event )
if (event.phase == “began”) then
addTouch( img, event ) – adds a multitouch tracking object to maintain start, previous and pinchzoom data
end
end

– this is really just here to let me simulate multiple touch events in the simulator
– don’t worry, this listing isn’t really that complicated.
function img:tap( event )
if (event.numTaps == 2) then
addTouch( img, event )
end
end

– just adds a bunch of functions to the display object (in this case an image) to allow easier pinch-zoom manipulation
– but you can just rip that stuff out and put it in more general code or an external library, quite easily
makePinchZoom( img )

– guess what these do
img:addEventListener( “touch”, img )
img:addEventListener( “tap”, img ) – ok, this isn’t even needed in the device, it just helps me add simulated multi-touch points[/lua]
[import]uid: 8271 topic_id: 4184 reply_id: 304184[/import]

If you want the full app (main.lua, crate.png) here it is: https://files.me.com/horacebury/gfzt8k

Matt [import]uid: 8271 topic_id: 4184 reply_id: 12992[/import]

Thanks for posting. I was just looking for something like this for a strategic game that includes moving the view of a map file that’s larger than the phone screen. [import]uid: 1560 topic_id: 4184 reply_id: 13023[/import]

Excellent, I thought there would be a fair number of people looking for this.

I should point out that this should be at the top of the listing:

[lua]system.activate( “multitouch” )[/lua]

Matt.

Ps: I’ve not had a chance to build and deploy to a device yet. [import]uid: 8271 topic_id: 4184 reply_id: 13025[/import]

Amazing. Thanks so much! Is it in the Code Exchange already? [import]uid: 8192 topic_id: 4184 reply_id: 13071[/import]

Glad you like it. I’ve not submitted it - was sort of waiting for a positive reaction before doing that.

Matt [import]uid: 8271 topic_id: 4184 reply_id: 13074[/import]

Right. This is annoying. Now I’ve had a chance to deploy to a device, I’ve seen that the rotation seems to be a little off. My intention was simply to make the image keep in place with the touch points, but this is not currently happening, though it is much closer (for reasons I can’t determine) on the simulator. I will keep working on this and so far I relatively pleased with it, but it’s not perfect (and that’s the aim!) so please let me know if you have any joy or other input on this…

I’ll post here when I have more to tell you.

Matt. [import]uid: 8271 topic_id: 4184 reply_id: 13168[/import]

Sweet! I haven’t checked this out yet but very thankful someone is working on it!!!

-Mark [import]uid: 9189 topic_id: 4184 reply_id: 13286[/import]

@ horacebury. So I am trying to understand how to implement this. If I add this as a module and want to pinch or zoom a picture (actually a sprite), do I just call the pinchzoom function? Or do I have to set the img to my sprite? [import]uid: 8192 topic_id: 4184 reply_id: 14199[/import]

@amigoni I’m not sure what you mean by “set the img to my sprite”, but all you should need to do is call the makePinchZoom function and attach the touch handler via addTouch. If you want to call the makepinchzoom on your own you can just handle the touch events yourself, but you’d need to make sure you’re passing in the right data (including xPrev and yPrev.) If you mean that you’re not sure which object to have as the touch listener, that’s really up to you - as long as you pass the image/sprite object that you want pinch-zoomed to the pinchzoom function.
Have you got a code sample of what you’re doing that I could modify for you?

matt [import]uid: 8271 topic_id: 4184 reply_id: 14272[/import]

I think I understand. so just use the object name as the argument
[import]uid: 8192 topic_id: 4184 reply_id: 14275[/import]

I can’t wait to dig into this code when I have time this weekend, it looks really useful. [import]uid: 12108 topic_id: 4184 reply_id: 14284[/import]

Thanks, guys. As I said in a previous post, it’s not perfect, so if you see where I’m going wrong please post what you find. I believe my logic is correct, just not the implementation. Matt [import]uid: 8271 topic_id: 4184 reply_id: 14339[/import]

V nice !

C [import]uid: 24 topic_id: 4184 reply_id: 14343[/import]

@horacebury, thanks a lot for your code!

My need is a little less sophisticated: a simple zoom, like in Photos application delivered with iphone:
with “two fingers gesture” or a “dobble tap”, I want a zoom and then, with one finger I want to drag/move the zoomed picture .
I try to merge the two source code examples in Ressources section, “PinchZoomGesture” & “Drag me”, but without success.
Perhaps solution is in your code… Can someone point me on a right direction or with an example of code.
Thanks in advance.
[import]uid: 8970 topic_id: 4184 reply_id: 14463[/import]

You can use my code for that - just comment out the line:

[lua]img:doRotate…[/lua] [import]uid: 8271 topic_id: 4184 reply_id: 14465[/import]

Thanks, super, it’s ok! (and I also forgot to add system.activate( “multitouch” !).

Now, I try to maximise the zoom factor (eg. max 3), but my code doesn’t work. I added a test in your doScale function

function img:doScale( touchA, touchB )

local prevLen = getLength( {x=touchA.xPrev, y=touchA.yPrev}, {x=touchB.xPrev, y=touchB.yPrev} )
local currentLen = getLength( {x=touchA.x, y=touchA.y}, {x=touchB.x, y=touchB.y} )
local scale = currentLen / prevLen

–ADDED
if scale > 3 then
img.xScale = img.xScale * 3
img.yScale = img.yScale * 3
else
img.xScale = img.xScale * scale
img.yScale = img.yScale * scale
end

end
Help welcomed, thanks
[import]uid: 8970 topic_id: 4184 reply_id: 14470[/import]

What exactly doesn’t work? Without knowing what’s broken or anything about your code its hard to help.

I would start by modifying the listing I provided (by first removing the doRotate) and work from there. In fact, its been so long since I worked with the listing I’d have to…

m [import]uid: 8271 topic_id: 4184 reply_id: 14471[/import]

Yes, I work with your code and have removed doRotate. It’s ok.
The next step I try to do is to limit the scale/zoom factor (eg. maximum 3) with pinch gesture… [import]uid: 8970 topic_id: 4184 reply_id: 14473[/import]

Hello, I have a question about your code, I’m trying to set a limit on how far the xScale and yScale can go, like a xScaleMax = 0.5 and xScaleMin = 1.0 so the img doesn’t zoom in too far and the user can’t zoom out very far.

how would I implement that into your code?

Thanks and I look forward to your reply. [import]uid: 14940 topic_id: 4184 reply_id: 43556[/import]