BioGeek
BioGeek

Reputation: 22827

Layout images in form of a number

I have a folder with small images (Facebook profile pictures). I want to make a new mosaic-like picture where all the small pictures are laid out in the form of a number, like in this example (source).

enter image description here

Is there a software program that can do this (and that runs on Windows 7)? Otherwise I'm also open to writing a small script to do the same. I know how I can add a white border to the images with PIL/Pillow, but my searches for how to layout the images have turned up fruitless.

Can anyone point me in the right direction?

Upvotes: 4

Views: 333

Answers (1)

unutbu
unutbu

Reputation: 879371

jsheperd shows how to convert text into ASCII art. You can modify that code slightly to obtain a glyph mask -- 1 where the font is black, and 0 where there is background. We can then use PIL to randomly rotate and paste a face wherever the mask is 1.

Below I used matplotlib just to obtain an image (of Ada Lovelace) that we would all have assuming you have matplotlib installed. You can remove the matplotlib dependency and just redefine faces to be a sequence of PIL images.

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import itertools as IT
import numpy as np
import matplotlib.cbook as cbook

def text_to_pixels(text, path='arialbd.ttf', fontsize=14):
    """
    https://stackoverflow.com/a/27753869/190597 (jsheperd)
    https://stackoverflow.com/a/36386628/190597 (unutbu)
    """
    font = ImageFont.truetype(path, fontsize) 
    w, h = font.getsize(text)  
    h *= 2
    image = Image.new('L', (w, h), 1)  
    draw = ImageDraw.Draw(image)
    draw.text((0, 0), text, font=font) 
    arr = np.asarray(image)
    arr = np.where(arr, 0, 1)
    arr = arr[(arr != 0).any(axis=1)]
    return arr

def get_image():
    fn = cbook.get_sample_data("ada.png")
    face_img = Image.open(fn).convert('RGBA')
    face_img = face_img.resize((30, 40), Image.ANTIALIAS)
    # give image a white background
    img = Image.new('RGBA', size=(36, 46), color=(255, 255, 255))
    img.paste(face_img, (3, 3))
    return img

def sqdist(a, b):
    return ((a -b)**2).sum()

def pics_in_text(text, faces, img_width=600, img_height=250, path='arialbd.ttf', 
                 fontsize=20, minsep=1000):
    arr = text_to_pixels(text, path=path, fontsize=fontsize)
    yx = np.column_stack(np.where(arr)).astype(float) 
    yx /= arr.shape
    yx *= (0.75, 0.90)
    yx += 0.05
    yx *= (img_height, img_width)
    yx = yx.astype('int')
    np.random.shuffle(yx)
    keep = []
    for coord in yx:
        if all(sqdist(item, coord) > minsep for item in keep):
            keep.append(coord)
    yx = IT.cycle(keep)

    img = Image.new('RGBA', size=(img_width, img_height), color=(255, 255, 255, 255))
    seen = list()
    for face, coord in zip(faces, yx):
        deg = np.random.uniform(-45, 45)
        face = face.rotate(deg, resample=Image.BICUBIC, expand=False)
        img.paste(face, tuple(coord[::-1]), mask=face)
    return img

def get_image():
    import matplotlib.cbook as cbook
    fn = cbook.get_sample_data("ada.png")
    face_img = Image.open(fn).convert('RGBA')
    face_img = face_img.resize((30, 40), Image.ANTIALIAS)
    # give image a white background
    img = Image.new('RGBA', size=(36, 46), color=(255, 255, 255))
    img.paste(face_img, (3, 3))
    return img

num_faces = 650
faces = IT.islice(IT.cycle([get_image()]), num_faces)
img = pics_in_text('800', faces, img_width=1200, img_height=500, 
             path='/usr/share/fonts/truetype/msttcorefonts/Comic_Sans_MS.ttf', 
             fontsize=40, minsep=375)
img.save('/tmp/out.png', 'PNG')

enter image description here

min_sep is the minimum squared distance between face images. If you increase the min_sep parameter, the faces will be spaced farther apart. If you decrease min_sep then the faces may overlap more densely.

Upvotes: 1

Related Questions