Here's an interesting bit of Python code I hacked together – it's a script that takes an image and warps it so that it is tileable (making it suitable for a repeating backgound or a texture in a game).

A fractal

A Mandlebrot fractal

If you use it on a photograph, it will come out looking like a fair-ground mirror. But it works well when applied to a pattern, or something more abstract, such as the fractal image on the left.

The code is public domain – use it for whatever the heck you want!

Example Output

Update: Here's another, more interesting example, The original is here.

The Code

import Image
from math import *

def maketilable(src_path, dst_path):
    src = Image.open(src_path)
    src = src.convert('RGB')
    src_w, src_h = src.size

    dst = Image.new('RGB', (src_w, src_h))
    w, h = dst.size

    def warp(p, l, dl):
        i = float(p) / l
        i = sin(i*pi*2 + pi)
        i = i / 2.0 + .5
        return abs(i * dl)

    warpx = [warp(x, w-1, src_w-1) for x in range(w)]
    warpy = [warp(y, h-1, src_h-1) for y in range(h)]

    get = src.load()
    put = dst.load()

    def getpixel(x, y):

        frac_x = x - floor(x)
        frac_y = y - floor(y)

        x1 = (x+1)%src_w
        y1 = (y+1)%src_h

        a = get[x, y]
        b = get[x1, y]
        c = get[x, y1]
        d = get[x1, y1]

        area_d = frac_x * frac_y
        area_c = (1.-frac_x) * frac_y
        area_b = frac_x * (1. - frac_y)
        area_a = (1.-frac_x) * (1. - frac_y)

        a = [n*area_a for n in a]
        b = [n*area_b for n in b]
        c = [n*area_c for n in c]
        d = [n*area_d for n in d]

        return tuple(int(sum(s)) for s in zip(a,b,c,d))


    old_status_msg = None
    status_msg = ''
    for y in xrange(h):

        status_msg = '%2d%% complete' % ((float(y) / h)*100.0)
        if status_msg != old_status_msg:
            print status_msg
        old_status_msg = status_msg

        for x in xrange(w):
            put[x, y] = getpixel(warpx[x], warpy[y])

    dst.save(dst_path)


if __name__ == "__main__":

    import sys
    try:
        src_path = sys.argv[1]
        dst_path = sys.argv[2]
    except IndexError:
        print "<source image path>, <destination image path>"
    else:
        maketilable(src_path, dst_path)
This blog post was posted to It's All Geek to Me on Saturday July 18th, 2009 at 6:06PM
 

3 Responses to "Making tileable images with Python"

  • Pauli Virtanen
    July 18th, 2009, 9:37 p.m.

    Smells like a job for Numpy (http://www.scipy.org [scipy.org]):

    import Image
    import numpy as np

    def maketilable(src_path, dst_path, w=None, h=None):
    src_img = Image.open(src_path)
    src_img = src_img.convert('RGB')
    src = np.asarray(src_img)

    src_w, src_h, ncolors = src.shape

    if w is None:
    w = src_w
    if h is None:
    h = src_h

    def warp(p, l, dl):
    i = p * 1.0 / l
    i = np.sin(i*np.pi*2 + np.pi)
    i = i / 2.0 + .5
    return abs(i * dl)

    xp = warp(np.arange(w), w-1, src_w-1)[:,np.newaxis]
    yp = warp(np.arange(h), h-1, src_h-1)[np.newaxis,:]

    x = xp.astype(int)
    y = yp.astype(int)

    x1 = (x + 1) % src_w
    y1 = (y + 1) % src_h

    a = src[x, y]
    b = src[x1, y]
    c = src[x, y1]
    d = src[x1, y1]

    frac_x = (xp - np.floor(xp))[...,np.newaxis]
    frac_y = (yp - np.floor(yp))[...,np.newaxis]

    d *= frac_x * frac_y
    c *= (1.-frac_x) * frac_y
    b *= frac_x * (1. - frac_y)
    a *= (1.-frac_x) * (1. - frac_y)

    dst = a + b + c + d
    dst_img = Image.fromarray(dst, 'RGB')
    dst_img.save(dst_path)

    if __name__ == "__main__":
    import sys
    try:
    src_path = sys.argv[1]
    dst_path = sys.argv[2]
    except IndexError:
    print "<source image path>, <destination image path>"
    else:
    maketilable(src_path, dst_path)
  • July 18th, 2009, 9:49 p.m.

    Pauli, Nice! I think it is a job for Numpy.

  • Pauli Virtanen
    July 18th, 2009, 10:11 p.m.

    Once more, this time demonstrating the use of scipy.ndimage for the interpolation:

    import Image
    import numpy as np
    import scipy.ndimage

    def maketilable(src_path, dst_path, w=None, h=None):
    src_img = Image.open(src_path)
    src_img = src_img.convert('RGB')
    src = np.asarray(src_img)

    src_w, src_h, ncolors = src.shape

    if w is None:
    w = src_w
    if h is None:
    h = src_h

    def warp(p, l, dl):
    i = p * 1.0 / l
    i = np.sin(i*np.pi*2 + np.pi)
    i = i / 2.0 + .5
    return abs(i * dl)

    x = warp(np.arange(w), w-1, src_w-1)
    y = warp(np.arange(h), h-1, src_h-1)

    coords = np.broadcast_arrays(*np.ix_(x, y))

    dst = np.empty((w, h, 3), src.dtype)
    for j in xrange(3):
    dst[:,:,j] = \
    scipy.ndimage.map_coordinates(src[:,:,j],
    coords,
    mode='wrap',
    order=3)

    dst_img = Image.fromarray(dst, 'RGB')
    dst_img.save(dst_path)

    if __name__ == "__main__":
    import sys
    try:
    src_path = sys.argv[1]
    dst_path = sys.argv[2]
    except IndexError:
    print "<source image path>, <destination image path>"
    else:
    maketilable(src_path, dst_path)

Leave a Comment

You can use bbcode in the comment: e.g. [b]This is bold[/b], [url]http://www.willmcgugan.com[/url], [code python]import this[/code]
Preview Posting...
Previewing comment, please wait a moment...

My Tweets

Will McGugan

My name is Will McGugan. I am an unabashed geek, an author, a hacker and a Python expert – amongst other things!

Search for Posts
Possibly related posts
Tags
Popular Tags
 
Archives
2013
 
Recent Comments
Nice one. How can change link and text for every image.
Beautiful. Just beautiful. Thank you.
- Tyler Troy on Going sub-pixel with PyGame
Sorry for the double comment my browser is very slow.
Hi Will I get the following error when i try to run simpleopengl.py. Traceback (most recent call last): File firstopengl.py, ...
Hi Will I get the following error when i try to run simpleopengl.py. Traceback (most recent call last): File firstopengl.py, ...
 
© 2008 Will McGugan.

A technoblog blog, design by Will McGugan