29

I have 4 directories with images for an animation. I would like to take the set of images and generate a single image with the 4 images arranged into a 2x2 grid for each frame of the animation.

My code so far is:

import Image

fluid64 = "Fluid64_half_size/00"
fluid128 = "Fluid128_half_size/00"
fluid512 = "Fluid512_half_size/00" 
fluid1024 = "Fluid1024_half_size/00" 

out_image = "Fluid_all/00"

for pic in range(1, 26):
    blank_image = Image.open("blank.jpg")

    if pic < 10:
        image_num = "0"+str(pic)
    else:
        image_num = str(pic)

    image64 = Image.open(fluid64+image_num+".jpg")
    image128 = Image.open(fluid128+image_num+".jpg")
    image512 = Image.open(fluid512+image_num+".jpg")
    image1024 = Image.open(fluid1024+image_num+".jpg")
    out = out_image + image_num + ".jpg"

    blank_image.paste(image64, (0,0)).paste(fluid128, (400,0)).paste(fluid512, (0,300)).paste(fluid1024, (400,300)).save(out)

Not sure why it's not working. I'm getting the error:

Traceback (most recent call last):
  File "C:\Users\Casey\Desktop\Image_composite.py", line 24, in <module>
    blank_image.paste(image64, (0,0)).paste(fluid128, (400,0)).paste(fluid512, (
ste(fluid1024, (400,300)).save(out)
AttributeError: 'NoneType' object has no attribute 'paste'
shell returned 1

Any help would be awesome. Thanks!

Nope
  • 34,682
  • 42
  • 94
  • 119

5 Answers5

42

The only problem there is that "paste" does not return an image object - it rather modifies the "blank" image inplace.

So, when the second paste is called (the one that uses the fuild128 image), it tries to be applied on "None" - which is the return value of the first image.

If that is the only problem you are having, just make one paste call per line, like this:

blank_image.paste(image64, (0,0))
blank_image.paste(fluid128, (400,0))
blank_image.paste(fluid512, (0,300))
blank_image.paste(fluid1024, (400,300))
blank_image.save(out)

Although it looks likely you'd need to scale each image so that their format match as well. And your code for the "image_num" variable is unecessary. Python is really good with strings - just do something like this:

image64 = Image.open(fluid64 + "%02d.jpg" % pic)
jsbueno
  • 99,910
  • 10
  • 151
  • 209
12

You may want to be using something along the lines of :

blank_image = Image.new("RGB", (800, 600))

This will create a new area in memory in which you can generate your image. You should then be able to paste you images into that.

Then you'll need to save it out again later on with:

blank_image.save("blank.jpg")
David Hewitt
  • 829
  • 4
  • 13
  • 1
    This is not what is "wrong" in the code - although he woudl also benefit from it. – jsbueno Dec 31 '10 at 01:50
  • Yeah, I noticed what was really "wrong" afterwards. I looked at the trace and it looked like blank_image hadn't been defined properly. So I just made assumptions. It is fairly late here after all :) – David Hewitt Dec 31 '10 at 01:55
  • 1
    My use case was to create a new image with a dynamic size, concatenating segments together into the new blank image, so this was very helpful. +1 – Clint Powell Sep 29 '14 at 18:01
4

Read the error message:

AttributeError: 'NoneType' object has no attribute 'paste'

This means you tried to call .paste on something that was of type NoneType, i.e. on the None object.

Image.paste returns None. You can't "chain" together calls like that except when the functions are specifically designed to support it, and Image.paste is not. (Support for this sort of thing is accomplished by having the function return self. You get an error that talks about NoneType because the function is written not to return anything, and everything in Python returns None by default if nothing else is returned explicitly.) This is considered Pythonic: methods either return a new value, or modify self and return None. Thus, so-called "fluent interfaces" are not used when the functions have side effects - Pythonistas consider that harmful. Returning None is a warning that the function has side effects. :)

Just do four separate .paste calls.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
2

Tiling figures in a 2-by-2 grid would be easy to achieve with the append_images function defined in this reply https://stackoverflow.com/a/46623632/8738113

For example:

img1 = append_images([image64, image128], direction='horizontal')
img2 = append_images([image512, image1024], direction='horizontal')
final = append_images([img1, img2], direction='vertical')
final.save("Fluid_all/00.jpg")
teekarna
  • 1,004
  • 1
  • 10
  • 13
1

Unlike PIL APIs copy, crop, resize or rotate which return an Image object, paste returns None which prevents chained method calls. Not so convenient API design.

michaelliu
  • 1,667
  • 2
  • 13
  • 13