Python PIL - Rounded Polygon - python

Is it possible to dynamically draw a polygon of N-sides with rounded corners? I've seen examples done for rectangles/squares, but not for other polygons. I can easily draw the polygon, but I'm looking to achieve a rounded affect for each corner. Any help is greatly appreciated!
from PIL import Image, ImageDraw
#Triangle
inset = 40
W, H = (300,300)
# Create empty black canvas
im = Image.new('RGBA', (W, H), '#558353')
# Draw polygon
draw = ImageDraw.Draw(im)
draw.polygon([(W/2,inset), (W-inset, H-inset), (inset,H-inset)], fill = 'black')
im.show()
Output:
Desired (created in Lucid Chart):

Here's my best shot at it. The ImageDraw rasterizer isn't so good at drawing wide lines. I had to fudge the line width (with +2) to make it look a little better.
from PIL import Image, ImageDraw
import operator
def vadd(a, b):
""" Vector addition. """
return tuple(map(operator.add, a, b))
#Triangle
inset = 40
W, H = (300,300)
# Create empty black canvas
im = Image.new('RGBA', (W, H), '#558353')
# Draw polygon
draw = ImageDraw.Draw(im)
# Vertices of the polygon.
v = [
(inset, H-inset),
(W-inset, H-inset),
(W/2, inset) ]
# Radius of rounded corner.
r = 10
d = 2*r
# Outline of the polygon.
[ draw.line((v[i], v[i+1]), fill='black', width=d+2) for i in range(len(v)-1) ]
draw.line((v[-1], v[0]), fill='black', width=d+2)
# Draw a circle centered on each vertex.
for corner in v:
c = [vadd(corner, (-r, -r)), vadd(corner, (r, r))]
draw.pieslice(c, 0, 360, 'black')
# Now fill in the middle.
ImageDraw.floodfill(im, (W/2, H/2), (0, 0, 0))
im.show()

Related

How can I save 2D integer array to image?

I see lot of questions ask how to save 2D array to an image, and most of the answer is about saving image as gray scale image. But I'm trying to figure out a way to save image that can actually display each value in each cell of array. Is there a way save image that display value of array in python?
I has a quick try at this. You can play around with colours and sizes.
#!/usr/local/bin/python3
from PIL import Image, ImageFont, ImageDraw
import numpy as np
# Variables that can be edited
w, h = 8, 5 # width and height of Numpy array
cs = 100 # cell side length in pixels
# Make random array but repeatable
np.random.seed(39)
arr = np.random.randint(-1,33, (h,w), np.int)
# Generate a piece of canvas and draw text on it
canvas = Image.new('RGB', (w*cs,h*cs), color='magenta')
# Get a drawing context
draw = ImageDraw.Draw(canvas)
monospace = ImageFont.truetype("/Library/Fonts/Andale Mono.ttf", 40)
# Now write numbers onto canvas at appropriate points
for r in range(h):
draw.line([(0,r*cs),(w*cs,r*cs)], fill='white', width=1) # horizontal gridline
for c in range(w):
draw.line([(c*cs,0),(c*cs,h*cs)], fill='white', width=1) # vertical gridline
cx = cs // 2 + (c * cs) # centre of cell in x-direction
cy = cs // 2 + (r * cs) # centre of cell in y-direction
draw.text((cx, cy), f'{arr[r,c]}', anchor='mm', fill='white', font=monospace)
# Save
canvas.save('result.png')

Cutting out triangles and mixing them

Hi could you please give me any tips on how to cut 4 triangles out of an image and mix them together, so that they look like this:
I want to use a python in order to achieve this.
Starting with this image (dog.jpg):
You could do something like this:
#!/usr/bin/env python3
from PIL import Image, ImageDraw
# Open image, generate a copy and rotate copy 180 degrees
im1 = Image.open('dog.jpg')
im2 = im1.copy().rotate(180)
# DEBUG: im2.save('im2.png')
# Get dimensions of image
w, h = im1.size
# Create an alpha layer same size as our image filled with black
alpha = Image.new('L',(w,h))
# Draw 2 white (i.e. transparent) triangles on the alpha layer
draw = ImageDraw.Draw(alpha)
draw.polygon([(0, 0), (w, 0), (w/2, h/2)], fill = (255))
draw.polygon([(0, h), (w, h), (w/2, h/2)], fill = (255))
# DEBUG: alpha.save('alpha.png')
# Composite rotated image over initial image while respecting alpha
result = Image.composite(im1, im2, alpha)
result.save('result.png')
The intermediate steps (commented with #DEBUG: in the code), look like this:
im2.png
and alpha.png:

Given pixel label, draw a bounding box in python

Using cityscapes dataset I would like to draw a bounding box around an pedestrian and fill this with noise (salt and pepper).
Which has the following annotations
"objects": [
{
"instanceId": 24000,
"bbox": [
1580,
277,
150,
366
],
"bboxVis": [
1594,
279,
126,
364
],
"label": "pedestrian"
},
How I go about drawing a bounding box around the pedestrian? Or what's the best practice?
Below an example of what I am trying to achieve.
Note: I resized the original (1024x2048) for viewing purposes.
Update: Tips or suggestions are very much welcome!
Update #2 Added example of what I am trying to achieve. So there are two things here. First, drawing the rectangle bounding box and 2) filling in up with noise. Hope this clears things up.
Are you asking:
A. how to find the coordinates for the bounding boxes?
or
B. are you asking how to draw a rectangle in an image with python?
A. For every pedestrian, get the highest and lowest pixel values for each axis (x_min, x_max, y_min, y_max) and use the as the boundary values for the bounding box.
B. You can use openCV:
import cv2
image = cv2.imread('the path to your image')
cv2.rectangle(image,(x_min,y_min),(x_max,y_max),(0,255,0),2) # add rectangle to image
You can achieve a salt-and-pepper bounding box like in the image if you crop the area and apply the salt ans pepper function from the link above (I just hardcoded the area but you can read it it from the label):
salt-and-peper function is taken from here
import cv2
import numpy as np
import time
def noisy(image):
row, col, ch = image.shape
s_vs_p = 0.5
amount = 0.5
out = image
# Salt mode
num_salt = np.ceil(amount * image.size * s_vs_p)
coords = [np.random.randint(0, i - 1, int(num_salt))
for i in image.shape]
out[coords] = 1
# Pepper mode
num_pepper = np.ceil(amount * image.size * (1. - s_vs_p))
coords = [np.random.randint(0, i - 1, int(num_pepper))
for i in image.shape]
out[coords] = 0
return out
im = cv2.imread('test.jpg', cv2.IMREAD_COLOR)
x = 1580
y = 277
h = 366
w = 150
crop_img = im[y:y+h, x:x+w]
noisy(crop_img)
cv2.rectangle(im, (x,y), (x+w, y+h), (0,0,0), 2) #change (0,0,0) to whatever color you want
cv2.imwrite('exp.jpg', im)
Bounding_box_pedestrian

Python PIL: How to draw an ellipse in the middle of an image?

I seem to be having some trouble getting this code to work:
import Image, ImageDraw
im = Image.open("1.jpg")
draw = ImageDraw.Draw(im)
draw.ellipse((60, 60, 40, 40), fill=128)
del draw
im.save('output.png')
im.show()
This should draw an ellipse at (60,60) which is 40 by 40 pixels. The image returns nothing.
This code works fine however:
draw.ellipse ((0,0,40,40), fill=128)
It just seems that when i change the first 2 co-ords (for where the ellipse should be placed) it won't work if they are larger than the size of the ellipse to be drawn. For example:
draw.ellipse ((5,5,15,15), fill=128)
Works, but only shows part of the rect. Whereas
draw.ellipse ((5,5,3,3), fill=128)
shows nothing at all.
This happens when drawing a rectangle too.
The bounding box is a 4-tuple (x0, y0, x1, y1) where (x0, y0) is the top-left bound of the box and (x1, y1) is the lower-right bound of the box.
To draw an ellipse to the center of the image, you need to define how large you want your ellipse's bounding box to be (variables eX and eY in my code snippet below).
With that said, below is a code snippet that draws an ellipse to the center of an image:
from PIL import Image, ImageDraw
im = Image.open("1.jpg")
x, y = im.size
eX, eY = 30, 60 #Size of Bounding Box for ellipse
bbox = (x/2 - eX/2, y/2 - eY/2, x/2 + eX/2, y/2 + eY/2)
draw = ImageDraw.Draw(im)
draw.ellipse(bbox, fill=128)
del draw
im.save("output.png")
im.show()
This yields the following result (1.jpg on left, output.png on right):
The ellipse function draws an ellipse within a bounding box. So you need to use draw.ellipse((40,40,60,60)) or other coordinates where the top left is smaller than the bottom right.

How do I draw text at an angle using python's PIL?

Using Python I want to be able to draw text at different angles using PIL.
For example, imagine you were drawing the number around the face of a clock. The number 3 would appear as expected whereas 12 would we drawn rotated counter-clockwise 90 degrees.
Therefore, I need to be able to draw many different strings at many different angles.
Draw text into a temporary blank image, rotate that, then paste that onto the original image. You could wrap up the steps in a function. Good luck figuring out the exact coordinates to use - my cold-fogged brain isn't up to it right now.
This demo writes yellow text on a slant over an image:
# Demo to add rotated text to an image using PIL
import Image
import ImageFont, ImageDraw, ImageOps
im=Image.open("stormy100.jpg")
f = ImageFont.load_default()
txt=Image.new('L', (500,50))
d = ImageDraw.Draw(txt)
d.text( (0, 0), "Someplace Near Boulder", font=f, fill=255)
w=txt.rotate(17.5, expand=1)
im.paste( ImageOps.colorize(w, (0,0,0), (255,255,84)), (242,60), w)
It's also usefull to know our text's size in pixels before we create an Image object. I used such code when drawing graphs. Then I got no problems e.g. with alignment of data labels (the image is exactly as big as the text).
(...)
img_main = Image.new("RGB", (200, 200))
font = ImageFont.load_default()
# Text to be rotated...
rotate_text = u'This text should be rotated.'
# Image for text to be rotated
img_txt = Image.new('L', font.getsize(rotate_text))
draw_txt = ImageDraw.Draw(img_txt)
draw_txt.text((0,0), rotate_text, font=font, fill=255)
t = img_value_axis.rotate(90, expand=1)
The rest of joining the two images together is already described on this page.
When you rotate by an "unregular" angle, you have to improve this code a little bit. It actually works for 90, 180, 270...
Here is a working version, inspired by the answer, but it works without opening or saving images.
The two images have colored background and alpha channel different from zero to show what's going on. Changing the two alpha channels from 92 to 0 will make them completely transparent.
from PIL import Image, ImageFont, ImageDraw
text = 'TEST'
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', 50)
width, height = font.getsize(text)
image1 = Image.new('RGBA', (200, 150), (0, 128, 0, 92))
draw1 = ImageDraw.Draw(image1)
draw1.text((0, 0), text=text, font=font, fill=(255, 128, 0))
image2 = Image.new('RGBA', (width, height), (0, 0, 128, 92))
draw2 = ImageDraw.Draw(image2)
draw2.text((0, 0), text=text, font=font, fill=(0, 255, 128))
image2 = image2.rotate(30, expand=1)
px, py = 10, 10
sx, sy = image2.size
image1.paste(image2, (px, py, px + sx, py + sy), image2)
image1.show()
The previous answers draw into a new image, rotate it, and draw it back into the source image. This leaves text artifacts. We don't want that.
Here is a version that instead crops the area of the source image that will be drawn onto, rotates it, draws into that, and rotates it back. This means that we draw onto the final surface immediately, without having to resort to masks.
def draw_text_90_into (text: str, into, at):
# Measure the text area
font = ImageFont.truetype (r'C:\Windows\Fonts\Arial.ttf', 16)
wi, hi = font.getsize (text)
# Copy the relevant area from the source image
img = into.crop ((at[0], at[1], at[0] + hi, at[1] + wi))
# Rotate it backwards
img = img.rotate (270, expand = 1)
# Print into the rotated area
d = ImageDraw.Draw (img)
d.text ((0, 0), text, font = font, fill = (0, 0, 0))
# Rotate it forward again
img = img.rotate (90, expand = 1)
# Insert it back into the source image
# Note that we don't need a mask
into.paste (img, at)
Supporting other angles, colors etc is trivial to add.
Here's a fuller example of watermarking diagonally. Handles arbitrary image ratios, sizes and text lengths by calculating the angle of the diagonal and font size.
from PIL import Image, ImageFont, ImageDraw
import math
# sample dimensions
pdf_width = 1000
pdf_height = 1500
#text_to_be_rotated = 'Harry Moreno'
text_to_be_rotated = 'Harry Moreno (morenoh149#gmail.com)'
message_length = len(text_to_be_rotated)
# load font (tweak ratio based on your particular font)
FONT_RATIO = 1.5
DIAGONAL_PERCENTAGE = .5
diagonal_length = int(math.sqrt((pdf_width**2) + (pdf_height**2)))
diagonal_to_use = diagonal_length * DIAGONAL_PERCENTAGE
font_size = int(diagonal_to_use / (message_length / FONT_RATIO))
font = ImageFont.truetype(r'./venv/lib/python3.7/site-packages/reportlab/fonts/Vera.ttf', font_size)
#font = ImageFont.load_default() # fallback
# target
image = Image.new('RGBA', (pdf_width, pdf_height), (0, 128, 0, 92))
# watermark
opacity = int(256 * .5)
mark_width, mark_height = font.getsize(text_to_be_rotated)
watermark = Image.new('RGBA', (mark_width, mark_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)
draw.text((0, 0), text=text_to_be_rotated, font=font, fill=(0, 0, 0, opacity))
angle = math.degrees(math.atan(pdf_height/pdf_width))
watermark = watermark.rotate(angle, expand=1)
# merge
wx, wy = watermark.size
px = int((pdf_width - wx)/2)
py = int((pdf_height - wy)/2)
image.paste(watermark, (px, py, px + wx, py + wy), watermark)
image.show()
Here it is in a colab https://colab.research.google.com/drive/1ERl7PiX6xKy5H9EEMulBKPgglF6euCNA?usp=sharing you should provide an example image to the colab.
I'm not saying this is going to be easy, or that this solution will necessarily be perfect for you, but look at the documentation here:
http://effbot.org/imagingbook/pil-index.htm
and especially pay attention to the Image, ImageDraw, and ImageFont modules.
Here's an example to help you out:
import Image
im = Image.new("RGB", (100, 100))
import ImageDraw
draw = ImageDraw.Draw(im)
draw.text((50, 50), "hey")
im.rotate(45).show()
To do what you really want you may need to make a bunch of separate correctly rotated text images and then compose them all together with some more fancy manipulation. And after all that it still may not look great. I'm not sure how antialiasing and such is handled for instance, but it might not be good. Good luck, and if anyone has an easier way, I'd be interested to know as well.
If you a using aggdraw, you can use settransform() to rotate the text. It's a bit undocumented, since effbot.org is offline.
# Matrix operations
def translate(x, y):
return np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])
def rotate(angle):
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
def draw_text(image, text, font, x, y, angle):
"""Draw text at x,y and rotated angle radians on the given PIL image"""
m = np.matmul(translate(x, y), rotate(angle))
transform = [m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]]
draw = aggdraw.Draw(image)
draw.settransform(transform)
draw.text((tx, ty), text, font)
draw.settransform()
draw.flush()

Categories