Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Programmatic zoom on to artwork

Help!

I’ve been batting may head off this for days (maybe weeks)

I’m trying to zoom in on a sprite in specified areas (imagine comixology’s Comic reader app) where I specify a rect and the program zooms that artwork up big until that area is isolated alone. (for bonus points i’d Like to mask off areas outside the zoom area). I’ll have a bunch of these focus points, and the art will tween-zoom between them.

I seem to be struggling with figuring out how to translate and zoom in on the right area On the artwork.

Can supply code, but I feel like it’s actually simple, but conceptually, there’s something i’m Missing.

The problem, I think, is the origin for the zoom needs to be at the centre of the focus point of the art, but that won’t necessarily be at zero zero.

Say I have a sprite that’s 500 x 1000

And I have three focus points (measuring from left corner as an origin)

0,0,500,1000 (the full page, no zoom, no need to translate)
0,500,500,500 (half of the top page, no zoom likely, need to translate to the centre of focus)
0,300,100,100 (big zoom in on the 100x100 area - zoom is simply width of display / focus area)

I suspect I need to translate to the zoom centre, then offset the image to display the focus area as a centre.

But I welcome any thoughts!

(can supply code, but it’s not doing what I want so not sure if it’s useful!)

Thanks for any suggestions!

Comments

  • Posts: 424

    I have a little rectangle class that can handle such transformations. The code for that might help you work out what you want to do. You can find it on github.

    In setup:

    frame = Rectangle({lowerleft=vec2(0,300), size=vec2(100,100)})
    

    In draw:

    frame:TransformFromScreen()
    

    now all drawing commands should take place inside frame.

  • dave1707dave1707 Mod
    Posts: 7,468

    @pjholden This might give you an idea. Tap on a section of the screen to zoom, tap again to un-zoom.

    displayMode(FULLSCREEN)
    
    function setup()
        img=readImage("Cargo Bot:Startup Screen")
    end
    
    function draw()
        background(40, 40, 50)
        if zoom then
            sprite(img1,WIDTH/2,HEIGHT/2,WIDTH*2,WIDTH*2)
        else
            sprite(img,WIDTH/2,HEIGHT/2)
        end   
    end
    
    function touched(t)
        if t.state==BEGAN then
            zoom=not zoom
            img1=img:copy(t.x//1-100,t.y//1-100,200,200)
        end
    end
    
  • em2em2
    Posts: 194

    Translate to middle of screen, scale, then translate again to negative position coordinate:

    translate(WIDTH/2,HEIGHT/2)
    scale(3)
    translate(-pos.x,-pos.y)
    spriteMode(CORNER)
    sprite("Cargo Bot:Startup Screen",0,0,WIDTH)
    
  • Ah, thank you em2! This combined with finally figuring out the maths required (I knew it would be simple) gives me exactly what I’m after (thanks for everyone else’s input too, good stuff to know.

    The code looks like this

    self.focusOffsetX = (self.w/2) - (self.focusX+ (self.focusW /2) )
    
        self.focusOffsetY = (self.h/2) - (self.focusY+ (self.focusH / 2))
    
        self.zoom = math.min( WIDTH/ self.focusW, HEIGHT/self.focusH) -- limit our zoom to the width of the page.
        pushMatrix()
    
        x = x or WIDTH /2 
        y = y or HEIGHT / 2
        -- translate to the screen co-ordinates to display
        translate(x,y)
        -- zoom in on the image using the calculated zoom
    
        scale(self.zoom)
    
        -- now translate the image is centred on the part we want to display
    
        translate(self.focusOffsetX, self.focusOffsetY)
    
          -- now offset the focus of the image
        spriteMode(CENTER)    
        sprite(self.src,0,0) 
       pushMatrix() -- return to our old translates 
    
    

    Now just have to figure out how to maximise the screen (I think the math.min does the job here, except this is animated, and when animating from a horizontal to a vertical orientation it gets a bit ugly because the calculations change, there’s probably a nicer way to do it...)

  • em2em2
    edited October 2017 Posts: 194

    @pjholden no problem. I spent hours racking my brains for the solution and I want to spare others the trouble.
    For a bounding box defined with a bottom-left corner min and a top-right corner max (pardon my confusing naming conventions):

    local center = vec2(max.x+min.x,max.y+min.y) * 0.5 -- average to find center, for translation
    local cameraX,cameraY = WIDTH/2, HEIGHT/2 -- center the zoom
    local width = max.x-min.x
    local height = max.y-min.y
    local zoom = math.min(WIDTH/width,HEIGHT/height) -- choose whichever is smaller, scale to fit width or scale to fit height
    
    pushMatrix()
    translate(cameraX,cameraY)
    scale(zoom)
    translate(center.x,center.y)
    -- Draw your image here
    popMatrix()
    
  • Here’s where I’ve gotten so far. It occurred to me that the problem I was having with the zoom is that it was calculating the zoom on the fly, whereas what I should have been doing was figuring out my start zoom and end zoom and tweeting between those points, and now I have a buttery smooth zoom between panels on a page.

    PageClass = class()
    
    FocusClass = class()
    
    function FocusClass:init(x,y,w,h, bg)
        -- you can accept and set parameters here
        self.x = x
        self.y = y
        self.h = h
        self.w = w
        self.bg = bg or color(255,0) -- default to no background colour ...
    
    end
    
    
    VERTICAL = 1
    HORIZONTAL = 2
    
    
    
    function PageClass:addPanel(x,y,w,h)
        self.panelCount = self.panelCount + 1
        self.panels[self.panelCount] = FocusClass(x,y,w,h)
    end
    
    
    function PageClass:boxOut(x,y,w,h, bg)
        -- draw a box around x,y,w,h
        bg = bg or color(255, 0, 0, 0)
        pushStyle()
        stroke(0,0)
        strokeWidth(0)
        -- break it up into four chunks
    
    
    
    
        popStyle()
    
    end
    function PageClass:init(src)
        -- parameter src = the source image to add...
        self.src = src
    
        -- default focus on the entire page
    
        self.focusX = 0
        self.focusY = 0
    
        self.w, self.h = spriteSize(self.src)
    
        self.focusW = self.w
        self.focusH = self.h 
    
        self.focusHorizontal = self.w > self.h
    
        self.focusZoom = 1
    
        self.viewPortX = 0
        self.viewPortY = 0
    
        self.viewPortW = WIDTH
        self.viewPortH = HEIGHT
    
        self.panels = {}
        self.panelCount = 0
    
    
        self.animating = false
        self.currentPanel = 1
    
    
    
        -- create the first panel to cover the whole page
    
        self:addPanel(0,0,self.w,self.h)
    
    
    end
    
    function PageClass:panelMove(n)
    
        -- known bug, if we travel outside the limits of the panel count (-4, or panelcount+10 say
        -- then we loop back to panelcount (if -4) or 1 (if panelcount+10)
    
        if self.animating then
            -- stop animating and skip to the next panel.
            tween.stop(self.animating)
            self.animating = false
        end
    
        self.currentPanel = n
    
        if self.currentPanel < 1 then 
            self.currentPanel = self.panelCount
        end
        if self.currentPanel > self.panelCount then
            self.currentPanel = 1
        end
    
        -- now do animation
        -- fix the orientation for any animation
    
        self.focusHorizontal = self.panels[self.currentPanel].w > self.panels[self.currentPanel].h
    
        local zoom
    
        if self.focusHorizontal then
            zoom = WIDTH / self.panels[self.currentPanel].w
        else
            zoom = HEIGHT / self.panels[self.currentPanel].h
        end    
    
    
    
        self.animating = tween( 1, self, 
            { focusX = self.panels[self.currentPanel].x, 
              focusY = self.panels[self.currentPanel].y,
            focusW = self.panels[self.currentPanel].w,
             focusH = self.panels[self.currentPanel].h,
            focusZoom = zoom },
            tween.easing.In,
            function()
            self.animating = false
            end
        )
    
    end
    
    function PageClass:nextPanel()
        self:panelMove(self.currentPanel + 1) -- next
    end
    
    function PageClass:previousPanel()
        self:panelMove(self.currentPanel -1) -- move previous
    end
    
    
    
    function PageClass:deleteCurrentPanel()
    
    
    end
    
    function PageClass:draw(x,y)
    
        -- calculate from centre point
        self.focusOffsetX = (self.w/2) - (self.focusX+ (self.focusW /2) )
    
        self.focusOffsetY = (self.h/2) - (self.focusY+ (self.focusH / 2))
    
    
    
        pushMatrix()
    
        x = x or WIDTH /2 
        y = y or HEIGHT / 2
        -- translate to the screen co-ordinates to display
        translate(x,y)
        -- zoom in on the image using the calculated zoom
    
        scale(self.focusZoom)
    
        -- now translate the image is centred on the part we want to display
    
        translate(self.focusOffsetX, self.focusOffsetY)
    
            -- now offset the focus of the image
        spriteMode(CENTER)    
        sprite(self.src,0,0) 
    
        --Now we draw the panels in for bug hunting...
    
        translate(-1*self.w/2, -1*self.h/2)
    
        pushStyle()
        stroke(255, 247, 0, 255)
        fill(255, 0, 0, 42)
        rect(self.focusX,self.focusY,self.focusW,self.focusH)
    
        for i,v in pairs(self.panels) do
            stroke(255, 0, 0, 255)
            fill(255, 0, 0, 0)
            strokeWidth(5)
            rect(v.x, v.y, v.w, v.h)
    
            fontSize(60)
            fill(0, 0, 0, 255)
    
            text(i, v.x+v.w/2, v.y+v.h/2)
    
        end
        popMatrix()
    
    
        popMatrix()
    end
    
    function PageClass:touched(touch)
        -- Codea does not automatically call this method
    end
    

    My plan is to add in loading/editing of pages/panels, so I can create animated comics (simple frame to frame animation much like comixology does)

  • Here’s a link to what this looks like https://youtu.be/F6VSBkNZQNo
    (the page is one I drew for 2000ad/Judge Dredd)

    I had to eyeball the positions of all the panels, want to get something that lets me draw the boxes on the page and use those.

    Discovered tween doesn’t work on colour, ideally those background images should be changing from white to transparent to black depending on what looks best for each panel) i think it’s an easy fix.

    There’s a little thing I’m not quite able to figure out, I want a seemless border around the art. But there seems to be a bit of a problem for me getting it entirely seemlessly ... the code looks like this

    function PageClass:boxOut(x,y,w,h, bg)
        -- draw a box around x,y,w,h
        bg = bg or color(255, 255, 255, 0) — should probably just exit here without drawing anything if the big isn’t specified...
        pushStyle()
        stroke(0,0)
        strokeWidth(0)
        fill(bg)
        -- break it up into 8 boxes around the image...
        -- box A top ...
         rect(0, y+h, x, y) -- a
         rect(x, y+h, w, y) -- b
         rect(x+w, y+h, x, y) -- c
    
        rect(0,y,x,h) --d
        rect(x+w, y, self.w-x-w,h) --e (force to the edge of the art -self.w - )
    
        rect(0,0,x,y) -- f
        rect(x,0,w,y) -- g
        rect(x+w, 0, x,y)
    
        popStyle()
    
    end
    

    Maybe there’s a better way to do that?

    Thanks for looking!

  • em2em2
    Posts: 194

    @pjholden

    what I should have been doing was figuring out my start zoom and end zoom and tweeting between those points

    I didn't know you could "tweet" between points :p

    Discovered tween doesn’t work on colour, ideally those background images should be changing from white to transparent to black depending on what looks best for each panel) i think it’s an easy fix.

    Tween does work on color. Just set the color as the target:

    self.c = color(255)
    tween(1, -- run for one second
        self.c, -- set the color as the target
        {a = 0}, -- tween it to a transparent white
        tween.easing.cubicInOut, -- cubic easing for smoothness
        function() -- callback function
            self.c = color(0,0) -- Change it to black; nobody notices because it is transparent. This is done for consistent and smoother transitions. You won't see any grey in between.
            -- Tween again
            tween(1, self.c, {a = 255}, tween.easing.cubicInOut)
        end
    )
    

    Rects side-by side are not seamless unless drawn with noSmooth. Alternatively, the faster option would be to use mesh with addRect.

  • dave1707dave1707 Mod
    Posts: 7,468

    @pjholden I was looking at your video of the comics page zooming to different panels and I created this similar to what was in the video. The center coordinates and panel sizes are just estimates because I didn’t want to spend a lot of time on this. Since I didn’t have any comics, I just created blank panels. Tap the screen once to start the tween. Maybe you can use something from this.

    displayMode(FULLSCREEN)
    supportedOrientations(PORTRAIT_ANY)
    
    function setup()
        rectMode(CENTER)
        pos=0    
        tab={   {x=WIDTH/2+780,y=HEIGHT/2-950,s=WIDTH*3},
                {x=WIDTH/2+20,y=HEIGHT/2-950,s=WIDTH*3},
                {x=WIDTH/2-750,y=HEIGHT/2-950,s=WIDTH*3},
                {x=WIDTH/2+400,y=HEIGHT/2-100,s=WIDTH*2},    
                {x=WIDTH/2-350,y=HEIGHT/2-100,s=WIDTH*2},    
                {x=WIDTH/2+780,y=HEIGHT/2+860,s=WIDTH*3},
                {x=WIDTH/2+20,y=HEIGHT/2+860,s=WIDTH*3},
                {x=WIDTH/2-750,y=HEIGHT/2+860,s=WIDTH*3},    
                {x=WIDTH/2,y=HEIGHT/2,s=WIDTH} }
    
        circ={ix=WIDTH/2,iy=HEIGHT/2,s=WIDTH}    
        img=image(WIDTH,HEIGHT)
        setContext(img)
        background(255)
        stroke(0)
        strokeWidth(5)
        fill(255,0,0)
        rect(125,225,225,340)
        rect(375,225,225,340)
        rect(625,225,225,340)    
        rect(190,525,350,200)
        rect(565,525,350,200)
        rect(125,825,225,340)
        rect(375,825,225,340)
        rect(625,825,225,340)
        setContext()
        fill(0, 44, 255, 255)
    end
    
    function draw()
        background(255)
        sprite(img,circ.ix,circ.iy,circ.s)
        text("Tap the screen once to start",WIDTH/2,HEIGHT-15)
    end
    
    function touched(t)
        if t.state==BEGAN then 
            next()       
        end
    end
    
    function next()
        pos=pos+1
        if pos<=#tab then
            tween.delay(1,next1)
        end
    end
    
    function next1()
        t2=tween(.5,circ,{ix=tab[pos].x,iy=tab[pos].y,s=tab[pos].s},{loop=tween.loop.once},next)
    end
    
Sign In or Register to comment.