Fill PIL ImageDraw Line partially - python

I'm trying to fill a line by a different color increasingly like a progress bar. This is the image:
It was created with this code
from PIL import Image, ImageDraw
image = Image.new("RGBA", (300, 300), color="black")
draw = ImageDraw.Draw(image)
width = 7
image_w, image_h = image.size
coord_a = image_w / 2, width
coord_b = width, image_h / 2
coord_c = image_w / 2, image_h - width
coord_d = image_w - width, image_h / 2
draw.line([coord_a, coord_b, coord_c, coord_d, coord_a], fill="red", width=width, joint="curve")
image.show()
image.save("test.png")
I'm trying to fill it with different color like this:
Should I just fill each line separately and combine them all?

Interesting question! You could have lots of fun thinking up ways to do this.
As you suggest, you could draw the rhombus as four separate lines. You would have to calculate the point where the red and blue portion met using sin/cos but that's not too hard.
You could draw it much more simply as the four sides of a square with its sides initially horizontal and vertical, then rotate it 45 degrees into place when you are finished drawing. I think I would go for this option.
You could draw a single long horizontal red line, and then overdraw the correct percentage in blue. Then cut it into four pieces, rotate and paste onto the black square background.
You could get the coordinates of all the points on the rhombus using scikit-image draw.polygon_perimeter() as documented here. Then colour the first however many percent blue and the remainder in red. You could make the lines thicker using morphological dilation.

Related

Pygame Rotation size issues

I am trying to rotate an image so it is facing the mouse at all times and I am noticing that the image size is changing. I am trying to troubleshoot but am having no luck. I would love some advice.
Here is what I am using:
ang = 360 - math.atan2(mousey - 540, mousex - 960) * 180 / math.pi
rotcircle = pygame.transform.scale(pygame.transform.rotate(redcircle,ang), [100, 100])
rect = rotcircle.get_rect(center=(960,540))
screen.blit(rotcircle,rect)
Just think about it. If you rotated an image 45 degrees, for example, the surface would naturally have to be larger in order to accommodate the corners, which would otherwise stick out of the original surface's bounds. So, if you rotate the image, it's going to be larger, just as the documentation says:
Unless rotating by 90 degree increments, the image will be padded larger to hold the new size. If the image has pixel alphas, the padded area will be transparent. Otherwise pygame will pick a color that matches the Surface colorkey or the topleft pixel value.
You're explicitly forcing the surface to be 100x100, meaning that the closer the image gets to being rotated by 45 degrees (again, for example), the smaller it'll appear. The easy solution is to stop resizing the image, or use another surface that's large enough to hold the rotated image and then blit the rotated image onto it.

How to draw character with gradient colors using PIL?

I have the function that generates character images from a font file using PIL. For the current example, it generates a white background image and a red character text. What I want now is that instead of pure red or any other color I can generate a gradient color. Is this possible with my current code? I have seen this post but it didn't help me.
Edit 1:
Currently, I am generating English alphabet images from font files using PIL. The fonts variable in my code has N number of ".ttf" files. lets suppose N=3 all in different styles e.g. style1, style2, style3. My current code will always generate these N different styles with fixed white background and fixed red character color. As shown in the below figure.
Instead of red color for the characters, I would like to apply gradients for each style. i.e. all characters in style1 font images should have the same gradient, style 2 font style should have a different gradient from style1 characters but should be the same for all of its characters and so on. As shown below (styles are different from the above images. Its just for demonstration of what I want).
My code so far:
fonts = glob.glob(os.path.join(fonts_dir, '*.ttf'))
for font in fonts:
image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), color='white')
font = ImageFont.truetype(font, 150)
drawing = ImageDraw.Draw(image)
w, h = drawing.textsize(character, font=font)
drawing.text(
((IMAGE_WIDTH-w)/2, (IMAGE_HEIGHT-h)/2),
character,
fill='red',
font=font
)
image.save(file_path, 'PNG')
One fairly easy way of doing it is to draw the text in white on a black background and then use that as the alpha/transparency channel over a background with a gradient.
Here's a background gradient:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFont
w, h = 400, 150
image = Image.open('gradient.jpg').rotate(90).resize((w,h))
font = ImageFont.truetype('/System/Library/Fonts/MarkerFelt.ttc', 80)
# Create new alpha channel - solid black
alpha = Image.new('L', (w,h))
draw = ImageDraw.Draw(alpha)
draw.text((20,10),'Some Text',fill='white',font=font)
alpha.save('alpha.png')
# Use text cutout as alpha channel for gradient image
image.putalpha(alpha)
image.save('result.png')
The alpha.png looks like this:
And the result.png looks like this:
Note that the area around the text is transparent. but you can easily paste it onto a white or black background. So, say you wanted the background yellow, add the following to the bottom of the code above:
solid = Image.new('RGB', (w,h), 'yellow')
solid.paste(image,image)
solid.save('result2.png')

how to make my code identify the difference between 2 circles (2 circles one filled with white and one with black) using python and pillow?

I have 2 images,
1- White circle with black stroke
2- Black circle with black stroke
I want to compare both images and identify that both have the same circle but with different filling
I should only use python & pillow
I have already tried several methods like Edge Detection, but whenever I try to reform the picture for edge detection the new image appear as empty
from PIL import Image, ImageDraw
import numpy as np
from math import sqrt
# Load image:
input_image = Image.open("input.png")
input_pixels = input_image.load()
width, height = input_image.width, input_image.height
# Create output image
output_image = Image.new("RGB", input_image.size)
draw = ImageDraw.Draw(output_image)
# Convert to grayscale
intensity = np.zeros((width, height))
for x in range(width):
for y in range(height):
intensity[x, y] = sum(input_pixels[x, y]) / 3
# Compute convolution between intensity and kernels
for x in range(1, input_image.width - 1):
for y in range(1, input_image.height - 1):
magx = intensity[x + 1, y] - intensity[x - 1, y]
magy = intensity[x, y + 1] - intensity[x, y - 1]
# Draw in black and white the magnitude
color = int(sqrt(magx**2 + magy**2))
draw.point((x, y), (color, color, color))
output_image.save("edge.png")
expected result that the both pictures will be greyscaled with only the circle edges marked in white
actual result empty black image (as if it couldnt see the edges)
Well, If all you want is Edge Detection in an image, then you can try using Sobel Operator or its equivalents.
from PIL import Image, ImageFilter
image = Image.open(r"Circle.png").convert("RGB")
image = image.filter(ImageFilter.FIND_EDGES)
image.save(r"ED_Circle.png")
The above code takes in an input image, converts it into RGB mode (certain images have P mode, which doesn't allows edge detection, therefore converting to RGB). Then finds edges in it via image.filter(ImageFilter.FIND_EDGES).
Sample Input Image (Black border with black circle):-
Output after processing through python program:-
Sample Image 2 (white circle with black border):-
Output after processing through python program:-
In the above sample, both the input images were of the same size and the circles in them were also of the same dimensions, the only difference between the two was that, one had a white circle inside a black border, and the other had a black circle inside black border.
Since the circles were of same dimensions, passing them through the edge detection process gave us same results.
NOTE:-
In the question, you wanted circle edges in white, and the rest of
part in greyscale. Which isn't the best choice for edge detection.
White and Black are inverse of each other, therefore edges could be
easily identified if the sample space of the image consists of these
two colors. Even then, if you want greyscale instead of black, then you can simple change each black pixel of the image to a grey one, or something that meets your needs
The results of above edge detection are same because the size of the
border is negligible. If the border is wider (a stroke), then when
the process is done on a white circle with black border, the edge
detection will create more then one white border. You can get through
that problem, by making the program ignore the inner edges and only
taking into account the outermost ones.

Is there another way to fill the area outside a rotated image with white color? 'fillcolor' does not work with older versions of Python

I want to rotate a black and white image. I am trying to use the rotate function as follows:
image.rotate(angle, fillcolor=255)
I am required to older versions of Python and Pillow, and they do not support the 'fillcolor' argument. I cannot upgrade to the newer versions due to certain restrictions and cannot use any external libraries.
Is there another way to fill the area outside the rotated image with white color using Pillow?
Rotated image has black color in the area outside the rotated part. I want to fill it with white color.
Original : Original image
Rotated :Rotated image
You can try Interpolating the Original Image, with the cropped one via Image.composite() to get rid of the black bars/borders.
from PIL import Image
img = Image.open(r"Image_Path").convert("RGBA")
angle = 30
img = img.rotate(angle)
new_img = Image.new('RGBA', img.size, 'white')
Alpha_Image = Image.composite(img, new_img, img)
Alpha_Image = Alpha_Image.convert(img.mode)
Alpha_Image.show()
The above code takes in an Image, converts it into mode RGBA (Alpha is required for this process), and then rotates the Image by 30 degrees. After that It creates a empty Image object of mode RGBA of the same dimensions as the original image, with each pixel having a default value of 255 each channel (i.e Pure white for RGB, and Full Opacity in the context of Alpha/Transparency). Then Interpolates the original image with this empty one using the mask of original Image (we are using the transparency mask of the first image). This results in the Desired images, where black bars/edges are replaced by white. In the end we convert the image color space to the original one.
ORIGINAL IMAGE:-
IMAGE AFTER ROTATING 30 DEGREES:-
An awkward option that has always worked for me, seeing as with my tools I always get a light gray "border" around the rotated image that interferes with filling:
add a border on the non-rotated image and use the fill color with that border.
The bordering operation is lossless and filling will be exact (and easy).
rotate the bordered image. The seam will now also be correct (but not exact unless you
rotate by 45° or 90°).
calculate the size of the rotated border using trigonometry. The result will not be exact (i.e. "131.12 pixel"). Usually you can do this in reverse, starting with an exact border on the rotated image and calculating the border you need to add, and adjust the border width so that the nonrotated border is exact. Example: with a rotated border of 170 pixels you get a nonrotated border of 140.3394 pixels. So you use a 510 pixel rotated border, resulting in the need to add a 421.018 pixel nonrotated border. This is close enough to 421 pixels that it is acceptable.
remove the rotated border.
This also helps avoiding some artefacts near the cut parts of the image that fall off the rotated image.
It has the drawback that you end up with a more massive rotation, with higher memory expenditure and computation time, especially if you use larger borders to increase precision.
Edit: As no external libraries are allowed, I would suggest cropping the rectangle you want and pasting it onto the original image, this could be done with magic numbers (of the rectangle's coordinates), this works for me (you might will need to tweek a little)
im = Image.open("mFul4.png")
rotated = im.rotate(105)
box = (55, 65,200,210)
d = rotated.crop(box=box)
im.paste(d, box=box)
im.save("ex.bmp" )
and the output
Edit2: This is the ugliest way, but it works, you might need to tweak the magic numbers a bit to have it more precise, I was working on your given image, so couldn't tell when i'm overdoing it. It produces the same output
from PIL import Image
im = Image.open("mFul4.png")
angle=105
cos = 0.240959049 # -cos(angle)
d = im.rotate(angle)
pix = d.load()
tri_x = 120
for i in range(4): # 4 triangles
for j in range(tri_x, -1, -1):
for k in range(int((tri_x-j)*cos)+1, -1, -1):
x,y =( j, k )if i <1 else (d.size[0]-j-1, d.size[1]-k-1)
if i in [2,3]:
y, x = (d.size[0] - j-2 , k) if i <3 else (j, d.size[1] - k)
pix[x,y] = (255, 255, 255, 255)
d.show()

how to fill a part of a circle using PIL?

I'm trying to use PIL for a task but the result is very dirty.
What I'm doing is trying to fill a part of a piece of a circle, as you can see on the image.
Here is my code:
def gen_image(values):
side = 568
margin = 47
image = Image.open(settings.MEDIA_ROOT + "/i/promo_circle.jpg")
draw = ImageDraw.Draw(image)
draw.ellipse((margin, margin, side-margin, side-margin), outline="white")
center = side/2
r = side/2 - margin
cnt = len(values)
for n in xrange(cnt):
angle = n*(360.0/cnt) - 90
next_angle = (n+1)*(360.0/cnt) - 90
nr = (r * values[n] / 5)
max_r = r
min_r = nr
for cr in xrange(min_r*10, max_r*10):
cr = cr/10.0
draw.arc((side/2-cr, side/2-cr, side/2+cr, side/2+cr), angle, next_angle, fill="white")
return image
It's been a while since I used PIL, but in various other graphics libraries, there's often an aliasing problem when drawing arcs.
Have you tried enabling anti-aliasing or drawing with thicker lines?
[Edit] Having a quick look over the PIL library, I think you're right about line width etc.
Sounds like the easiest thing to do here is to build up a polygon which covers each area. So a pair of points at each end and then a load round the middle to stop the edges looking jagged. Does that make sense?
Instead of erasing with white, consider drawing a mask of just the areas you want to show. Here's an example of this for a circular mask.
How do I generate circular thumbnails with PIL?
You draw a circle on a separate image and then remove the slice of the circle you don't want by creating a triangle over that area and removing it from the image (make it transparent). Then you copy this circle segment into the image where you want it.
If you want a partial ring, draw a circle and then draw a smaller circle to cut from the first, then use radial lines to make a triangle to remove the parts you don't need in the same way.

Categories