I am busy with something witch is not so familiar to me. Trying to design a small logo to be displayed on a Oled display sh1106, 128x64
I am using PIL and Luma libraries.
I am battling to position a small arc in the right position.
that small little arc in the yellow circle should be positioned where the arrow is pointing.
This is the code I am using for the arc:
shape = [(32, 32), (36,36)]
draw.arc(shape, start = 160, end = 20, fill ="white")
As soon as I change any of the parameters in those two lines above, the shape, size of the arc changes. Even the position changes, but I assume is because of the size change. Thats not I won't.
Is there any other parameter which I am missing to position the arc in the right place?
Hopefully this little example will show you how it works. The bbox specifies the top-left and bottom-right corners of the bounding box that encloses the arc. I have drawn in the bounding box of each arc in the same colour:
#!/usr/bin/env python3
from PIL import Image, ImageDraw
# Create black image and get drawing context
im = Image.new("RGB", (200, 100))
draw = ImageDraw.Draw(im)
# Draw white arc with bbox
bbox = [(10, 20), (80, 60)]
draw.arc(bbox, start=180, end=360, fill='white')
draw.rectangle(bbox, outline='white')
# Draw red arc with bbox
bbox = [(60, 30), (190, 90)]
draw.arc(bbox, start=180, end=360, fill='red')
draw.rectangle(bbox, outline='red')
# Save result
im.save('result.png')
Related
I am using Pygame and have an image. I can clip a rectangle from it:
image = pygame.transform.scale(pygame.image.load('example.png'), (32, 32))
handle_surface = image.copy()
handle_surface.set_clip(pygame.Rect(0, 0, 32, 16))
clipped_image = surface.subsurface(handle_surface.get_clip())
I have tried to use subsurface by passing a Surface:
handle_surface = image.copy()
hole = pygame.Surface((32, 32))
pygame.draw.circle(hole, (255, 255, 255), (0, 0), 32)
handle_surface.set_clip(hole)
image = surface.subsurface(handle_surface.get_clip())
surf = image.copy()
But I get the error:
ValueError: invalid rectstyle object
This error is because despite its name, subsurface expects a Rect, not a Surface. Is there a way to clip another shape from this image and have collidepoint work correctly?
You cannot use pygame.Surface.subsurface because a Surface is always rectangular and cannot have a circular shape. pygame.Rect.collidepoint detects if a point is inside a rectangular area and therefore cannot help you either.
Collision detection between a circle and a point can be calculated using the distance between the pointer and the center of the circle. Calculate the square of the Euclidean distance (dx*dx + dy*dy) from the point to the center of the circle. Check that the square of the distance is less than the square of the radius. In the following code the coordinates of the point are (px, py) and the circle is defined by its center (cx, cy) and its radius (radius).
dx = px - cx
dy = py - cy
if dx*dx + dy*dy <= radius*radius:
print('hit')
An alternative solution could be PyGame collision with masks. Also pygame.sprite.collide_circle could help, but then you would have to create a pygame.sprite.Sprite object for the point with radius 1, which seems to overcomplicate the problem.
If you want to clip a circular area from a pygame.Surface, see:
how to make circular surface in PyGame
How do I focus light or how do I only draw certain circular parts of the window in pygame?
How do I display a large black rectangle with a moveable transparent circle in pygame?
Can I use an image on a moving object within Pygame as opposed to to a color?
Short instruction:
Create a rectangular partial area from the image (only if the circle does not fill the whole image). However, the image must have an alpha channel. If it does not have one, this can be achieved with convert_alpha.
Create a transparent (pygame.SRCALPHA) mask with the size of the image
Draw a white opaque circle on the mask (I use pygame.draw.ellipse here, because it is easier to set the dimension)
Blend the circular mask with the image using the blend mode pygame.BLEND_RGBA_MIN. (see pygame.Surface.blit)
sub_image = image.subsurface(pygame.Rect(x, y, w, h)).convert_alpha()
mask_image = pygame.Surface(sub_image.get_size(), pygame.SRCALPHA)
pygame.draw.ellipse(mask_image, (255, 255, 255, 255), sub_image.get_rect())
sub_image.blit(mask_image, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
I've got a number of ordered points that form a polygon and I've got an image.
Now I want to create a new image.
Every point inside the polygon should be part of the new image.
Every point outside the polygon should be transparent.
I am using PIL. Has anyone a theoretical approach or even a code example how to solve this problem?
Demo code to create a polygon image with transparent background.
from random import randint
from PIL import Image, ImageDraw
width, height = 200, 200
line_color, line_width = 'blue', 5
polygon = [(randint(0, width-1), randint(0, height-1),) for i in range(20)]
polygon.append(polygon[0])
im = Image.new("RGBA", (width, height), (255, 255, 255, 0))
draw = ImageDraw.Draw(im)
draw.line(polygon, fill=line_color, width=line_width) # join="curve"
# draw.polygon(polygon, fill=line_color) # no width option
im.show()
im.save("D:/polygon.png", format="PNG")
I'm kinda new with the PIL was wondering why my circle is not perfect. Is there a fix for this? Thanks.
here's my code:
avatar_image = avatar_image.resize((128, 128))
avatar_size = (avatar_image.size[0] * 3, avatar_image.size[1] * 3)
circle_image = Image.new('L', avatar_size, 0)
circle_draw = ImageDraw.Draw(circle_image)
circle_draw.ellipse((0, 0) + avatar_size, fill=255)
mask = circle_image.resize(avatar_image.size, Image.ANTIALIAS)
avatar_image.putalpha(mask)
final = ImageOps.fit(avatar_image, mask.size, centering=(0.5, 0.5))
final.putalpha(mask)
final.show()
Draw Circle: right side of the circle looks off
Circle with Picture:
You have an off-by-one error, commonly caused by a confusion between size and position which is the case here too.
image.new takes a width and height in number of pixels.
circle_draw.ellipse takes a start and end position, which is based on a 0-indexed grid.
To get a full circle you need to make the circle one pixel smaller than it is now to fit inside circle_image
I am trying to draw a narrow filled arc in python--the height is small and the width is very wide. I want it to startX at 250, startY at 550, i want the width to be 245, the height to be 15... and then I am stuck.
I have tried a few different things for the start/angle but it doesn't look right:
0/90.
I just want it to be a straight horizontal line with a small arc attached to make a semi-circle.
Here is an example of what I tried:
addArcFilled(pic, 250, 550,245,15, 0, 90, blue)
#knells Probably this functions does exactly what you need: http://pillow.readthedocs.io/en/3.1.x/reference/ImageDraw.html#PIL.ImageDraw.PIL.ImageDraw.Draw.arc
IL.ImageDraw.Draw.arc(xy, start, end, fill=None)
Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box.
PIL.ImageDraw.Draw.chord(xy, start, end, fill=None, outline=None)
Same as arc(), but connects the end points with a straight line.
Sample code below:
from PIL import Image, ImageDraw
# get an image
im = Image.open('test.png').convert('RGBA')
# create draw context and draw arc
draw = ImageDraw.Draw(im)
draw.chord([10,10, 200, 200], 0,45, fill=0)
del draw
im.save('test_out.png', "PNG")
im.show()
I am trying to draw a circle on an image, using Python. I tried this using PIL but I would like to specify a linewidth. Currently, PIL draws a circle but the border is too thin.
Here is what I have done.
For a test image: I created a 1632 X 1200 image in MS Paint and filled it green. I called it test_1.jpg. Here is the input file:
from PIL import Image, ImageDraw
im = Image.open('test_1.jpg')
width, height = im.size
eX, eY = 816,816 #Size of Bounding Box for ellipse
bbox = (width/2 - eX/2, height/2 - eY/2, width/2 + eX/2, height/2 + eY/2)
draw = ImageDraw.Draw(im)
bbox_L = []
for j in range(0,5):
bbox_L.append([element+j for element in bbox])
draw.ellipse(tuple(bbox_L[j]), outline ='white')
im.show()
Basically, I tried to draw multiple circles that would be centered at the same spot but with a different radius. My thinking was that this would create the effect of a thicker line.
However, this is producing the output shown in the attached file below:
Problem: As you can see, the bottom-left and top-right are too thin. Also, there are gaps between the various circles (see top left and bottom right).
The circle has a varying thickness. I am looking a circle with a uniform thickness.
Question:
Is there a way to do draw a circle in Python, on an image like test_1.jpg, using PIL, NumPy, etc. and to specify line thickness?
I had the same problem, and decided to write a helper function, similar to yours. This function draws two concentric ellipses in black and white on a mask layer, and the intended outline colour is stamped onto the original image through the mask. To get smoother results (antialias), the ellipses and mask is drawn in higher resolution.
Output with and without antialias
The white ellipse is 20 pixels wide, and the black ellipse is 0.5 pixels wide.
Code
from PIL import Image, ImageDraw
def draw_ellipse(image, bounds, width=1, outline='white', antialias=4):
"""Improved ellipse drawing function, based on PIL.ImageDraw."""
# Use a single channel image (mode='L') as mask.
# The size of the mask can be increased relative to the imput image
# to get smoother looking results.
mask = Image.new(
size=[int(dim * antialias) for dim in image.size],
mode='L', color='black')
draw = ImageDraw.Draw(mask)
# draw outer shape in white (color) and inner shape in black (transparent)
for offset, fill in (width/-2.0, 'white'), (width/2.0, 'black'):
left, top = [(value + offset) * antialias for value in bounds[:2]]
right, bottom = [(value - offset) * antialias for value in bounds[2:]]
draw.ellipse([left, top, right, bottom], fill=fill)
# downsample the mask using PIL.Image.LANCZOS
# (a high-quality downsampling filter).
mask = mask.resize(image.size, Image.LANCZOS)
# paste outline color to input image through the mask
image.paste(outline, mask=mask)
# green background image
image = Image.new(mode='RGB', size=(700, 300), color='green')
ellipse_box = [50, 50, 300, 250]
# draw a thick white ellipse and a thin black ellipse
draw_ellipse(image, ellipse_box, width=20)
# draw a thin black line, using higher antialias to preserve finer detail
draw_ellipse(image, ellipse_box, outline='black', width=.5, antialias=8)
# Lets try without antialiasing
ellipse_box[0] += 350
ellipse_box[2] += 350
draw_ellipse(image, ellipse_box, width=20, antialias=1)
draw_ellipse(image, ellipse_box, outline='black', width=1, antialias=1)
image.show()
I've only tested this code in python 3.4, but I think it should work with 2.7 without major modification.
Simple (but not nice) solution is to draw two circles (the smaller one with color of background):
outline = 10 # line thickness
draw.ellipse((x1-outline, y1-outline, x2+outline, y2+outline), fill=outline_color)
draw.ellipse((x1, y1, x2, y2), fill=background_color)
From version 5.3.0 onwards, released on 18 Oct 2018, Pillow has supported width for ImageDraw.ellipse. I doubt many people are using PIL nowadays.
I don't think there's a way to specify ellipse thickness, but you probably can draw lines at each pixel where ellipse pass, with the argument width=...
NB: I'm foreign, so sorry if my english is wrong.
You can use the Image.core.draw method like this:
zero_array = np.zeros((224,224))
im = Image.fromarray(np.uint8(zero_array))
draw = ImageDraw.Draw(im)
dr_im = Image.core.draw(im.getdata(), 0)
dr_im.draw_rectangle((22,33, 150,100),220,2)
dr_im.draw_rectangle((22,33, 150,100),125,0)
#draw.rectangle((22,33, 150,100), fill=220,outline = 125)
print(np.array(im)[33][23])
im.show()