How do I generate circular thumbnails with PIL?

32,876

Solution 1

The easiest way to do it is by using masks. Create a black and white mask with any shape you want. And use putalpha to put that shape as an alpha layer:

from PIL import Image, ImageOps

mask = Image.open('mask.png').convert('L')
im = Image.open('image.png')

output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5))
output.putalpha(mask)

output.save('output.png')

Here is the mask I used:

alt text


If you want the thumbnail size to be variable you can use ImageDraw and draw the mask:

from PIL import Image, ImageOps, ImageDraw

size = (128, 128)
mask = Image.new('L', size, 0)
draw = ImageDraw.Draw(mask) 
draw.ellipse((0, 0) + size, fill=255)

im = Image.open('image.jpg')

output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5))
output.putalpha(mask)

output.save('output.png')

If you want the output in GIF then you need to use the paste function instead of putalpha:

from PIL import Image, ImageOps, ImageDraw

size = (128, 128)
mask = Image.new('L', size, 255)
draw = ImageDraw.Draw(mask)
draw.ellipse((0, 0) + size, fill=0)

im = Image.open('image.jpg')

output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5))
output.paste(0, mask=mask)
output.convert('P', palette=Image.ADAPTIVE)

output.save('output.gif', transparency=0)

Note that I did the following changes:

  • The mask is now inverted. The white was replaced with black and vice versa.
  • I'm converting into 'P' with an 'adaptive' palette. Otherwise, PIL will only use web-safe colors and the result will look bad.
  • I'm adding transparency info to the image.

Please note: There is a big issue with this approach. If the GIF image contained black parts, all of them will become transparent as well. You can work around this by choosing another color for the transparency. I would strongly advise you to use PNG format for this. But if you can't then that is the best you could do.

Solution 2

I would like to add to the already accepted answer a solution to antialias the resulting circle, the trick is to produce a bigger mask and then scale it down using an ANTIALIAS filter: here is the code

from PIL import Image, ImageOps, ImageDraw

im = Image.open('image.jpg')
bigsize = (im.size[0] * 3, im.size[1] * 3)
mask = Image.new('L', bigsize, 0)
draw = ImageDraw.Draw(mask) 
draw.ellipse((0, 0) + bigsize, fill=255)
mask = mask.resize(im.size, Image.ANTIALIAS)
im.putalpha(mask)

this produces a far better result in my opinion.

Solution 3

Slight modification on @DRC's solution to also support images which already have transparency. He sets the alpha channel to 0 (invisible) outside the circle and to 255 inside (opaque), so I use darker which takes the min of the mask and the original alpha channel (which can be anywhere betwen 0-255) :-)

from PIL import Image, ImageChops, ImageDraw

def crop_to_circle(im):
    bigsize = (im.size[0] * 3, im.size[1] * 3)
    mask = Image.new('L', bigsize, 0)
    ImageDraw.Draw(mask).ellipse((0, 0) + bigsize, fill=255)
    mask = mask.resize(im.size, Image.ANTIALIAS)
    mask = ImageChops.darker(mask, im.split()[-1])
    im.putalpha(mask)

im = Image.open('0.png').convert('RGBA')
crop_to_circle(im)
im.save('cropped.png')

Solution 4

Thank you very much. I was looking for hours and your idea does the trick.

Together with this other script from there. PIL round edges and add border it works perfectly for me.

from PIL import Image
from PIL import ImageDraw, ImageChops

def add_corners( im, rad=100):
    circle = Image.new('L', (rad * 2, rad * 2), 0)
    draw = ImageDraw.Draw(circle)
    draw.ellipse((0, 0, rad * 2, rad * 2), fill=255)
    alpha = Image.new('L', im.size, "white")
    w, h = im.size
    alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0))
    alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad))
    alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0))
    alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad))

    alpha = ImageChops.darker(alpha, im.split()[-1])

    im.putalpha(alpha)
    return im

im = Image.open ('AceOfSpades.png').convert('RGBA')

im = add_corners (im, 24)

im.show()
im.save("perfect.png")

Name this image AceOfSpades.png for testing

Share:
32,876

Related videos on Youtube

ohnoes
Author by

ohnoes

i make apis beg for timeout

Updated on October 03, 2020

Comments

  • ohnoes
    ohnoes over 3 years

    How do I generate circular image thumbnails using PIL? The space outside the circle should be transparent.

    Snippets would be highly appreciated, thank you in advance.

  • ohnoes
    ohnoes about 15 years
    Great! And how about outputting gif instead of png? I guess gif doesn't handle alpha, does it?
  • Nadia Alramli
    Nadia Alramli about 15 years
    Gif doesn't support alpha channel transparency. It only support setting one color as transparent. However, I made the necessary adjustment to output the result in gif.
  • zach
    zach over 10 years
    great answer. while i can apply this technique easily to saved images I ran into an issue applying the mask within python. any chance you could take a look at this question: stackoverflow.com/questions/20150552/…
  • ademarizu
    ademarizu about 8 years
    Dude, the ANTIALIAS made a huge difference! Thanks! =D
  • Artur Barseghyan
    Artur Barseghyan over 6 years
    This worked for me so well. No need to design a mask.
  • MeLight
    MeLight almost 6 years
    Best tip ever!!
  • Seabass77
    Seabass77 over 5 years
    For anyone looking for a mask, search 'circle mask png' on google images,a white circle on a black background.
  • xjcl
    xjcl over 4 years
    This does not work if the image already has transparency inside the circle
  • Daniel Chin
    Daniel Chin about 2 years
    For later versions of PIL, use keyword argument resample=Image.ANTIALIAS.
  • AcidResin
    AcidResin about 2 years
    Or just use Image.paste() and add a mask