Image distortion

Clumsy attempt to use a grid mesh to distort an image.

Main (part 1 of 3)

dothidden=false

function setup()
    saveProjectInfo("Description","Distort picture using a grid mesh")
    
    print("2 finger tap --- toggle full screen")
    print("1 finger tap --- toggle control points")
    print("Move Points to distort")
    
    touches=0
    gesture=0
    
    -- pFIXBORDERS=false 
    FIXBORDERS = false -- Border points are movable
    
    pnX=1 -- n columns
    iparameter("nX",2,16,4)
    pnY=1 -- n rows
    iparameter("nY",2,16,4)
    
    pstate=0 -- cumulate distortion
    iparameter("Savestate",0,1,0)
    
    iparameter("DoWave",0,1,0) -- apply saw effect
    iparameter("HWave",0,2,0) -- horz 0, vert 1, both 2
    
    iparameter("DoReset",0,1,0) -- reset to prior saved state
    
    ImgW=0
    ImgH=0
    theMesh=mesh()
    ActivePoint=nil
    ActiveId=-1   
    CpGrid={} -- control points
    CpRect={} -- grid cells
    
    -- LOAD YOUR OWN HERE
    -- local BaseImg = readImage("Planet Cute:Icon")
    local BaseImg =  CreateMyImage(600,600)
    
    theMesh.texture=BaseImg
    ImgW=BaseImg.width
    ImgH=BaseImg.height
    
    OrigW=ImgW
    OrigH=ImgH
    
    local Tw,Th,Zom=ResizeToFit(ImgW,ImgH,WIDTH,HEIGHT)
    ImgW=Tw
    ImgH=Th
    CreateControlPoints(nX,nY)  
    CreateCells(nX,nY)
    PW=WIDTH
    PH=HEIGHT
end


function touched(touch)
    
    if touch.state == BEGAN then 
        if touches == 0 then gesture=1 end
        touches = touches +  1 
        end
        
    if (not dothidden) and touch.state==BEGAN and touches==1 and ActiveId == -1 then 
        ActivePoint=nil
        local found=false
        for i=1,nX+1 do
            if found == false then 
                for j=1,nY+1 do
                if found == false then 
                    local z = CpGrid[i][j]
                    local dd = (touch.x - z.c.x) ^ 2 + (touch.y - z.c.y) ^ 2
                    if dd < 300 then 
                        found=true
                        if CpGrid[i][j].movable then ActivePoint = CpGrid[i][j] end
                    end
                    end --endif
                end -- end j
            end -- endif
        end -- i
        if ActivePoint ~= nil then ActiveId=touch.id end   
    end
    
    if touch.state==MOVING and ActiveId==touch.id and ActivePoint ~= nil then 
        ActivePoint.c.x=touch.x
        ActivePoint.c.y=touch.y
        end
        
    if touch.state==ENDED then 
        if ActivePoint==nil and touches == 1 and gesture==1 then
            dothidden = (not dothidden)
            gesture=0
            end
         if ActivePoint == nil and touches==2 and gesture==1 then
            local tmode =displayMode()
            if tmode==STANDARD then
                displayMode(FULLSCREEN)
                else
                displayMode(STANDARD)
                end
            gesture=0
            end
            
        touches = touches - 1
        if touches==0 then gesture=0 end
        ActivePoint=nil
        ActiveId=-1
        end
        
end -- function
    
function ResetPoints()      
    for i=1,nX+1 do
        for j=1,nY+1 do
            local tobj = CpGrid[i][j]
            local tx=tobj.i.x * ImgW
            local ty=tobj.i.y * ImgH
            if tobj.movable then
                tobj.c.x=tx
                tobj.c.y=ty
                end
        end
    end
end
        
function draw()
    
    -- Resize picture for orientation or FullScreen
    if PW ~= WIDTH or PH~=HEIGHT then
       resetSize()
       PW=WIDTH
       PH=HEIGHT
       end
        
    -- change of rows columns 
    if pnX ~= nX or pnY ~= nY then
        InitGrids()
        pnX=nX
        pnY=nY
        end
    
    -- reset to last saved state    
    if DoReset==1 then
        DoReset=0
        ResetPoints()
        end
        
    -- apply saw to control points    
    if DoWave == 1 then
        Dowave=0
        local inc = 0.50
        for i=1,nX+1 do
        for j=1,nY+1 do
            
            local tobj = CpGrid[i][j]
            local tx=tobj.c.x
            local ty=tobj.c.y
            
            if HWave == 0 and nX > 3 and nY > 3 then -- horizontal
                if i%2 == 0 and j > 1 then ty = ty + inc end
            end
            if HWave == 1 and nX > 3 and nY > 3 then -- vertical
                if j%2 == 0 and i > 1 then tx = tx + inc end
            end
            if HWave == 2 and nX > 3 and nY > 3 then -- both
                if i%2 == 0 then tx = tx + inc end
                if j%2 == 0 then ty = ty + inc end
            end
            
            if tx < 0 then tx = 0 end
            if tx > WIDTH then tx = WIDTH end
            if ty < 0 then ty = 0 end
            if ty > HEIGHT then ty = HEIGHT end
            
            if tobj.movable then
                tobj.c.x=tx
                tobj.c.y=ty
                end 
            end
        end
        
        DoWave=0
        end
    
    if Savestate ~= pstate and Savestate == 1 then
        ApplyAndContinue()
        InitGrids()
        Savestate=0
        pstate=Savestate
        end
    
    background(40, 40, 40)
    fill(255,255,255,255)

    for i=1,nX do
        for j=1,nY do
            local z = CpRect[i][j]
            z:draw(theMesh)
        end
    end
    
    for i=1,nX+1 do
    for j=1,nY+1 do
    local z = CpGrid[i][j]
    z:draw()
    end
    end
    
end

Helpers (part 2 of 3)



function CreateMyImage(imgw,imgh)
    
    local myImg=image(imgw,imgh)
    
    pushStyle()
    setContext(myImg)
    fill(0,0,0,255)
    noStroke()
    
    rect(1,1,imgw,imgh)
    
    local tcol

    local samp2
    local div1 = imgw/8
    local div2 = imgh/8
    
    
    
    for i=1,8 do
        for j=1,8 do
        samp2 = (i-1) * 3 + j
        samp2=samp2%9
        
        if samp2 == 1 then tcol=color(255,0,0,255)
        elseif samp2 == 2 then tcol = color(255,255,0,255)
        elseif samp2 == 3 then tcol = color(0,255,0,255)
        elseif samp2 == 4 then tcol = color(0,255,255,255)
        elseif samp2 == 5 then tcol = color(0,0,255,255)
        elseif samp2 == 6 then tcol = color(255,0,255,255)
        elseif samp2 == 7 then tcol = color(255,255,255,255)
        elseif samp2 == 8 then tcol = color(128,128,128,255)
        else tcol = color(0,0,0,255)
        end 
        fill(tcol)
        rect((i-1)*div1+1,(j-1)*div2+1,div1,div2)
        end -- j
    end -- i
    setContext()
    popStyle()
    return myImg
end

function resetSize()
    local Tw,Th,Zom=ResizeToFit(OrigW,OrigH,WIDTH,HEIGHT)
    local oldW=ImgW
    local oldH=ImgH
    ImgW=Tw
    ImgH=Th
    -- reset points
    for i=1,nX+1 do
        for j=1,nY+1 do
            local z = CpGrid[i][j]
            z.c.x = z.c.x / oldW * ImgW
            z.c.y = z.c.y / oldH * ImgH
        end -- j
    end -- i
end

function InitGrids()
    ActivePoint=nil
    ActiveId=-1   
    CpGrid={}
    CpRect={}
    CreateControlPoints(nX,nY)  
    CreateCells(nX,nY)
end

-- Save the distort so far and resets the mesh     
function ApplyAndContinue()
    local hid=dothidden
    dothidden=true
    local minx=5000
    local maxx=-5000
    local miny=5000
    local maxy=-5000
    for i=1,nX+1 do
    for j=1,nY+1 do
        local z = CpGrid[i][j]
        if z.c.x <= minx then minx=z.c.x end
        if z.c.y <= miny then miny=z.c.y end
        if z.c.x >= maxx then maxx=z.c.x end
        if z.c.y >= maxy then maxy=z.c.y end
        end -- j
        end -- i
    -- print(minx,miny,maxx,maxy)
    local www=maxx-minx -- +1
    local hhh=maxy-miny -- +1
    local tmpimg=image(www,hhh)
    setContext(tmpimg)
    pushStyle()
    fill(40,40,40,255)
    noStroke()
    noSmooth()
    rect(0,0,www,hhh)
    popStyle()
    fill(255,255,255,255)
    for i=1,nX do
    for j=1,nY do
        local z = CpRect[i][j]
        z:draw(theMesh)
        end
        end
    setContext()
    dothidden=hid
    ImgW=www
    ImgH=hhh
    theMesh.texture=tmpimg
    OrigW=ImgW
    OrigH=ImgH
    local Tw,Th,Zom=ResizeToFit(ImgW,ImgH,WIDTH,HEIGHT)
    ImgW=Tw
    ImgH=Th
    end
             
function CreateControlPoints(nX,nY)  
    
    CpRect={}
    CpGrid={}
    
    for i=1,nX+1 do
        local stari = (i-1) / nX * ImgW
        if stari > ImgW then stari = ImgW end
        CpGrid[i]={}
        for j=1,nY+1 do
            local starj =  (j-1) / nY * ImgH
            if starj > ImgH then starj = ImgH end
            CpGrid[i][j]=ControlPoint(stari,starj,stari/ImgW,starj/ImgH)
            if FIXBORDERS then
                if i==1 or i==nX+1 or j==1 or j==nY+1 then
                    CpGrid[i][j].movable=false
                end
            end
        end
    end
end   

function CreateCells(nX,nY)   
      CpRect={}
      for i=1,nX do
        CpRect[i]={}
        for j=1,nY do
            local tl = CpGrid[i][j]
            local tr = CpGrid[i+1][j]
            local bl = CpGrid[i][j+1]
            local br = CpGrid[i+1][j+1]
            CpRect[i][j]=ControlRect(tl,tr,bl,br)
        end
    end
end
   
function ResizeToFit(cxDib,cyDib,ScrW,ScrH)
    
        local neww= ScrW
        local newh =ScrH
        local ratio = cxDib / cyDib
        
        newh = neww  / ratio
        
        if(newh > ScrH) then 
            ratio = ScrH / newh
            newh = ScrH
            neww = neww * ratio
            if(neww > ScrW) then neww=ScrW end
        end
        local zoomw= neww/ cxDib
        return neww,newh,zoomw
end

-- thepoints : array of vec2 (closed shape, dup the first point)
-- test      : vec2 -- point to be tested
function PointInPoly(thepoints,test)

  local nvert = #thepoints
  local c = 0
  
  local j=nvert
  local z0
  local z1
  
  for i=1,nvert do
  
    z0 = thepoints[i]
    z1 = thepoints[j]
    
    if ( ((   z0.y > test.y) ~= ( z1.y > test.y)) and
     (test.x <  (z1.x-z0.x) * (test.y-z0.y) / (z1.y-z0.y) + z0.x) ) then
       if c == true then 
             c = false
       else
          c = true
       end
    end 

    j=i
    end -- loop
    
    return c
    
end


-- Dist from point p to line p1,p2 (segment)
-- Returns distance and closest point on the line
function DistFromSegment(p,p1,p2)

    local dx = p2.x - p1.x
    local dy = p2.y - p1.y
    local Nearpt = p1
    local dist=0
    
    if dx == 0 and dy == 0 then
        -- It's a point not a line segment.
        dx = p.x - p1.x
        dy = p.y - p1.y
        Nearpt = p1
        dist = math.sqrt(dx * dx + dy * dy)
        return dist,Nearpt
    end

    -- Calculate the t that minimizes the distance.
    local t = ((p.x - p1.x) * dx + (p.y - p1.y) * dy) / (dx * dx + dy * dy)

    if t < 0 then
        dx = p.x - p1.x
        dy = p.y - p1.y
        Nearpt = p1
    elseif t > 1 then
        dx = p.x - p2.x
        dy = p.y - p2.y
        Nearpt = p2
    else
        Nearpt = vec2( p1.x + t * dx, p1.y * t * dy)
        near_y = Y1 + t * dy
        dx = p.x - Nearpt.x
        dy = p.y - Nearpt.y
    end

    dist = math.sqrt(dx * dx + dy * dy)
    return dist,Nearpt
end

function pointToLineDistance(A, B, P)
    local normalLength = math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))
    local distance=0
    if normalLength > 0.000001 then  
        distance = math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / normalLength
    else
        distance = math.sqrt((P.x-A.x)*(P.x-A.x)+(P.y-A.y)*(P.y-A.y))
    end
     return distance
end

Control Points (part 3 of 3)

ControlPoint = class()

function ControlPoint:init(x,y,x1,y1)
    self.c=vec2(x,y) -- in pixels
    self.i=vec2(x1,y1) -- corresp image location .. in pct of width and height
    self.movable=true
end

function ControlPoint:draw() 
    if not dothidden then
    pushStyle()
    strokeWidth(2)
    stroke(209, 63, 63, 255)  
    fill(194, 154, 60, 255)
    ellipse(self.c.x,self.c.y, 10, 10)
    popStyle()
    end
    
end

ControlRect=class()

function ControlRect:init(tl,tr,bl,br)
    self.tl= tl
    self.tr= tr
    self.bl= bl
    self.br= br
    -- rectangle always
    local cx = (tl.i.x + tr.i.x + bl.i.x + br.i.x) / 4
    local cy = (tl.i.y + tr.i.y + bl.i.y + br.i.y) / 4
    self.centeri = vec2(cx,cy)
    
end

function ControlRect:draw(theMesh)
    
    -- create 4 triangles by adjusting center point

    local verts={}
    local tverts={}
    
    local cx = (self.tl.c.x + self.tr.c.x + self.bl.c.x + self.br.c.x) / 4
    local cy = (self.tl.c.y + self.tr.c.y + self.bl.c.y + self.br.c.y) / 4
    local centerc = vec2(cx,cy)
    
    -- Centroid : shape is not necessarily a rectangle 
    local sarea=0
    local ax=0
    local ay=0
    
    local a1 =  (self.tl.c.x * self.tr.c.y) - (self.tl.c.y * self.tr.c.x) 
    sarea = sarea + a1
    ax = ax +   (self.tl.c.x   + self.tr.c.x) * a1
    ay = ay +   (self.tl.c.y   + self.tr.c.y) * a1
    
    a1 =  (self.tr.c.x * self.br.c.y) - (self.tr.c.y * self.br.c.x) 
    sarea = sarea + a1
    ax = ax +   (self.tr.c.x   + self.br.c.x) * a1
    ay = ay +   (self.tr.c.y   + self.br.c.y) * a1
    
    a1 =  (self.br.c.x * self.bl.c.y) - (self.br.c.y * self.bl.c.x) 
    sarea = sarea + a1
    ax = ax +   (self.br.c.x   + self.bl.c.x) * a1
    ay = ay +   (self.br.c.y   + self.bl.c.y) * a1
    
    a1 =  (self.bl.c.x * self.tl.c.y) - (self.bl.c.y * self.tl.c.x) 
    sarea = sarea + a1
    ax = ax +   (self.bl.c.x   + self.tl.c.x) * a1
    ay = ay +   (self.bl.c.y   + self.tl.c.y) * a1
    sarea = sarea * 0.50
    
    if sarea ~= 0 then
       ax = ax / ( 6 * sarea)
       ay = ay / ( 6 * sarea)
       centerc=vec2(ax,ay)
       end
    
    --right
    table.insert(verts,self.tr.c)
    table.insert(tverts,self.tr.i)
    table.insert(verts,self.br.c)
    table.insert(tverts,self.br.i)
    table.insert(verts,centerc)
    table.insert(tverts,self.centeri)

    
    -- Bottom
    table.insert(verts,self.tr.c)
    table.insert(tverts,self.tr.i)
    table.insert(verts,centerc)
    table.insert(tverts,self.centeri)
    table.insert(verts,self.tl.c)
    table.insert(tverts,self.tl.i)
    
    --left
    table.insert(verts,centerc)
    table.insert(tverts,self.centeri)
    table.insert(verts,self.bl.c)
    table.insert(tverts,self.bl.i)
    table.insert(verts,self.tl.c)
    table.insert(tverts,self.tl.i)
    
    --top
    table.insert(verts,self.br.c)
    table.insert(tverts,self.br.i)
    table.insert(verts,self.bl.c)
    table.insert(tverts,self.bl.i)
    table.insert(verts,centerc)
    table.insert(tverts,self.centeri)
    theMesh.vertices = verts
    theMesh.texCoords = tverts
    pushStyle()
    fill(255,255,255,255) -- otherwise applies tint to image
    theMesh:draw()
    popStyle()
    -- self:drawCenters(centerc) -- for debug
    end
    
function ControlRect:drawCenters(centerc) 
    pushStyle()
    strokeWidth(2)
    stroke(209, 63, 63, 255)  
    noFill()
    line(self.tl.c.x,self.tl.c.y,self.tr.c.x,self.tr.c.y)
    line(self.tr.c.x,self.tr.c.y,self.br.c.x,self.br.c.y)
    line(self.br.c.x,self.br.c.y,self.bl.c.x,self.bl.c.y)
    line(self.bl.c.x,self.bl.c.y,self.tl.c.x,self.tl.c.y)
    
    fill(0,255,255,255)
    strokeWidth(2)
    stroke(209, 63, 63, 255)  
    ellipse(centerc.x,centerc.y, 10, 10)
    popStyle()
end

This is actually pretty cool! Worth continuing. Good job!