Image Warping Software

In the dark, dank, dismal, and pre-digital ages people would manually warp polaroid prints. I can see the attraction but it is one-off and rather unpredictable. We can do better now. 

The algorithm is rather simple:

for each point in the image:

       calculate the warped location of the point and put the value of the image from that point at the current location

Without much further ado

Starting from my headshot:

We can distort it to make me look like an alien

or a surrealist (I actually like this one a lot better):

(I can't for the life of me remember the name of the work that inspired this - it's a pen sketch of a famous critic)

Anyway here's the python code - which is surprisingly short.

 

#!/usr/bin/python3
#  (c) 2024 Treadco
#  read an image and a "warpmap" then output a distorted image.
#  meant to mimic the mechanical distortion of an SX70 polaroid image
#
#  warpmap defines the geometry of the distortion
#       height,cx,cy 
#   map the coordinate to a new position and put the color of that position in the coordinates.
#

import numpy as np
import sys,os
import math
from PIL import Image
from PIL import ImageChops
from PIL import ImageColor
#from pylab import * # not used
def warper( x,y, heights, centers):
    xx = x
    yy = y
    for i in range(0, len(heights)): #yes I could use a tuple of heights,.. but this is OK
        dx = xx - centers[i][0]
        dy = yy - centers[i][1]
        dr = dx*dx + dy*dy
        dr = math.sqrt(dr)
#        if dr < 0.001:
#            continue
        x += dx*heights[i]/(dr+abs(heights[i]))
        y += dy*heights[i]/(dr+abs(heights[i]))
#was        y += dy*heights[i]/(dr+1.)
    return (x,y)

def main():
    try:
      image = Image.open(sys.argv[1])
    except IndexError:
      print("Could not open the input \nUsage warp inputfile warpmap outputname")
      sys.exit()

    try:
      pushmap = open(sys.argv[2])
    except IndexError:
      print("Could not open the input \nUsage warp inputfile warpmap outputname")
      sys.exit()

    if len( sys.argv) < 4:
        outputfilename = "output.jpg"
    else:
        outputfilename = sys.argv[3]
    
    height = []
    centers = []
    for line in pushmap:
        s = line.split()
        print(s)
        height.append( float(s[0]))
        centers.append( (float(s[1]), float(s[2])) )

    iarray = np.array(image.convert("RGB"))
    nx = iarray.shape[0]
    ny = iarray.shape[1]
#
#    for i in range(0,int(centers[0][0])):
#        for j in range(0,int(centers[0][1])):
#            print( str(i)+' '+str(j)+' '+str(warper(i,j, height, centers) ) )
#    sys.exit()
#
    outarray = np.array(Image.new("RGB",(ny,nx),ImageColor.getcolor("white","RGB")))
    print( outarray.shape, iarray.shape)
    for i in range(0,nx):
        for j in range(0,ny):
            dc = warper(i,j,height,centers)
            ii = int(dc[0])
            jj = int(dc[1])
            if( ii < 0 or ii >= nx):
                continue
            if( jj < 0 or jj >= ny):
                continue
            outarray[i,j] = iarray[ii,jj]
    inew = Image.fromarray( outarray)
    inew.save(outputfilename)
main()


There's more boilerplate about opening files and syntactical sugar than lines of code to implement the algorithm. That's rare in python.