I am trying to use PIL to draw a rectangle with rounded corners and a gradient fill for the color. I found a cool web site ( http://web.archive.org/web/20130306020911/http://nadiana.com/pil-tutorial-basic-advanced-drawing#Drawing_Rounded_Corners_Rectangle ) that shows how to draw a solid-color rounded rectangle and I am happy with this, but I want to be able to draw one that starts light red at the top and goes to dark red at the bottom.
My initial thought was to use the code in the website above to draw a rounded rectangle, and then overlay a second white to black rectangle over the rounded rectangle, using alpha blending. Everything that I've tried ends up blowing up in my face.
I have seen some near-miss solutions using numpy, but I am not skilled enough to commute those code fragments to a successful solution. I would be grateful if someone could show how to modify the code in the link above, implement my overlay idea, or show a completely better solution for getting a rounded rectangle with gradient fill in Python.
Cheers,
Ferris
This is a very brute force method, but it gets the job done. Code to produce the gradients was borrowed from here.
from PIL import Image, ImageDraw
def channel(i, c, size, startFill, stopFill):
"""calculate the value of a single color channel for a single pixel"""
return startFill[c] + int((i * 1.0 / size) * (stopFill[c] - startFill[c]))
def color(i, size, startFill, stopFill):
"""calculate the RGB value of a single pixel"""
return tuple([channel(i, c, size, startFill, stopFill) for c in range(3)])
def round_corner(radius):
"""Draw a round corner"""
corner = Image.new('RGBA', (radius, radius), (0, 0, 0, 0))
draw = ImageDraw.Draw(corner)
draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill="blue")
return corner
def apply_grad_to_corner(corner, gradient, backwards = False, topBottom = False):
width, height = corner.size
widthIter = range(width)
if backwards:
widthIter.reverse()
for i in xrange(height):
gradPos = 0
for j in widthIter:
if topBottom:
pos = (i,j)
else:
pos = (j,i)
pix = corner.getpixel(pos)
gradPos+=1
if pix[3] != 0:
corner.putpixel(pos,gradient[gradPos])
return corner
def round_rectangle(size, radius, startFill, stopFill, runTopBottom = False):
"""Draw a rounded rectangle"""
width, height = size
rectangle = Image.new('RGBA', size)
if runTopBottom:
si = height
else:
si = width
gradient = [ color(i, width, startFill, stopFill) for i in xrange(si) ]
if runTopBottom:
modGrad = []
for i in xrange(height):
modGrad += [gradient[i]] * width
rectangle.putdata(modGrad)
else:
rectangle.putdata(gradient*height)
origCorner = round_corner(radius)
# upper left
corner = origCorner
apply_grad_to_corner(corner,gradient,False,runTopBottom)
rectangle.paste(corner, (0, 0))
# lower left
if runTopBottom:
gradient.reverse()
backwards = True
else:
backwards = False
corner = origCorner.rotate(90)
apply_grad_to_corner(corner,gradient,backwards,runTopBottom)
rectangle.paste(corner, (0, height - radius))
# lower right
if not runTopBottom:
gradient.reverse()
corner = origCorner.rotate(180)
apply_grad_to_corner(corner,gradient,True,runTopBottom)
rectangle.paste(corner, (width - radius, height - radius))
# upper right
if runTopBottom:
gradient.reverse()
backwards = False
else:
backwards = True
corner = origCorner.rotate(270)
apply_grad_to_corner(corner,gradient,backwards,runTopBottom)
rectangle.paste(corner, (width - radius, 0))
return rectangle
img = round_rectangle((200, 200), 70, (255,0,0), (0,255,0), True)
img.save("test.png", 'PNG')
Running from left to right (runTopBottom = False):
Running from top to bottom (runTopBottom = True):
In case someone in the future is looking for a slightly more turn-key solution that can be monkey patched onto ImageDraw, I wrote the following.
Hopefully it helps.
Example:
Code:
from PIL.ImageDraw import ImageDraw
def rounded_rectangle(self: ImageDraw, xy, corner_radius, fill=None, outline=None):
upper_left_point = xy[0]
bottom_right_point = xy[1]
self.rectangle(
[
(upper_left_point[0], upper_left_point[1] + corner_radius),
(bottom_right_point[0], bottom_right_point[1] - corner_radius)
],
fill=fill,
outline=outline
)
self.rectangle(
[
(upper_left_point[0] + corner_radius, upper_left_point[1]),
(bottom_right_point[0] - corner_radius, bottom_right_point[1])
],
fill=fill,
outline=outline
)
self.pieslice([upper_left_point, (upper_left_point[0] + corner_radius * 2, upper_left_point[1] + corner_radius * 2)],
180,
270,
fill=fill,
outline=outline
)
self.pieslice([(bottom_right_point[0] - corner_radius * 2, bottom_right_point[1] - corner_radius * 2), bottom_right_point],
0,
90,
fill=fill,
outline=outline
)
self.pieslice([(upper_left_point[0], bottom_right_point[1] - corner_radius * 2), (upper_left_point[0] + corner_radius * 2, bottom_right_point[1])],
90,
180,
fill=fill,
outline=outline
)
self.pieslice([(bottom_right_point[0] - corner_radius * 2, upper_left_point[1]), (bottom_right_point[0], upper_left_point[1] + corner_radius * 2)],
270,
360,
fill=fill,
outline=outline
)
ImageDraw.rounded_rectangle = rounded_rectangle