13

I'm trying to make a 3x3 Grid by sequence images but can't seem to get it right. The images are in folder, named from 0 - 8 (total 9 images), the output of the final one image grid of 3x3 should as follow

image0 image1 image2
image3 image4 image5
image6 image7 image8 

I was trying to follow How do you merge images into a canvas using PIL/Pillow? but couldn't get it work correctly.

There are no need to change anything in the image, just merge them and make a 3x3 Grid

Community
  • 1
  • 1
Hoyo
  • 1,044
  • 3
  • 13
  • 23

2 Answers2

24

To make a grid of arbitrary shape (cols*img_height, rows*img_width) out of rows*cols images:

def image_grid(imgs, rows, cols):
    assert len(imgs) == rows*cols

    w, h = imgs[0].size
    grid = Image.new('RGB', size=(cols*w, rows*h))
    grid_w, grid_h = grid.size
    
    for i, img in enumerate(imgs):
        grid.paste(img, box=(i%cols*w, i//cols*h))
    return grid

In your case, assuming imgs is a list of PIL images:

grid = image_grid(imgs, rows=3, cols=3)
Ivan
  • 34,531
  • 8
  • 55
  • 100
  • Apologies, it's actually something to do with how I'm feeding in the images: I'd written `image, *images = [PIL.Image.open(file) for file in sorted(glob.glob(images_in))]`, but later on only passed in `images` to `image_grid()`. Solved it with `images.insert(0, image)`. Feel free to correct me - I'm aware it's not ideal (but it works!). – Dbercules Jan 27 '21 at 21:43
  • 1
    I'm guessing you unpacked the *list* to use `image` alone? As an alternative to `images.insert(0, image)`, you could pass `[image, *images]` to `image_grid` :) – Ivan Jan 27 '21 at 21:54
  • Awesome, makes me feel much more professional - though I'm not sure I understand why. Would you mind granting me an explanation please? – Dbercules Jan 27 '21 at 21:59
  • 1
    `image, *images = my_list` will unpack the *list* into two variables: `image` will hold the first element (*i.e.* `my_list[0]`) and `images` the rest (*i.e.* `my_list[1:]`), the single asterisk is used for [iterable unpacking](https://docs.python.org/3/reference/expressions.html#expression-lists). Remember `images` will be a *list* (which can be empty if `my_list` only has a single element). Additionally, you can unpack a *list* into... another *list*: `[*images]` which is basically just `images`. The cool thing is that you can define your *list* with additional elements (as you would ... – Ivan Jan 27 '21 at 22:47
  • 2
    ...with literals instead of variables). Here I added `image`: `[image, *images]`. So the resulting *list* contains `image` as the first element and the content of images (**unpacked**) as the following elements of the *list*. The best way to get to know this kind of behavior is to try it out yourself: define `x = [1,2,3]`, unpack as `a, *rest = x`, what will `rest` be? Can you guess what `[*rest, a]` will be? Now, for something a little bit more challenging: `a, *(what, _) = x`, what will `what` be? What about `c` in `a, *(b,*c) = [1,2,3,4]`? Good luck ;) – Ivan Jan 27 '21 at 22:47
8

Here's an example how this can be done (consider image is one of your images):

    img_w, img_h = image.size
    background = Image.new('RGBA',(1300, 1300), (255, 255, 255, 255))
    bg_w, bg_h = background.size
    offset = (10,(((bg_h - img_h)) / 2)-370)
    background.paste(image1,offset)

Adjust the offset, width and height to fit your requirements.

dmitryro
  • 3,463
  • 2
  • 20
  • 28