Paint everything outside of polygons in a Pillow image in palette mode? - python

I'm creating a heatmap using Pillow in Python. After painting the heatmap, I want to blank out everything outside a certain area. The area in question is described by a handful of polygons, each containing a couple of thousands of points.
I know how to paint everything inside the polygons (see below), but how do I paint everything outside the polygons? The below examples paints the polygons blue, while it keeps the color of the background. I want to paint the bakground blue, while keeping the color of the polygons.
from PIL import Image, ImageDraw
# This image painted red represents the heatmap.
# In reality, it's not a solid color but the result of some calculations.
image = Image.new("P", (500, 500), 1)
image.putpalette([
0, 0, 255,
255, 0, 0
])
draw = ImageDraw.Draw(image)
# Actual polygon is much more complex.
polygons = [[(100, 100), (100, 400), (350, 50)], [(150, 450), (400, 300), (450, 50)]]
# This is the opposite of what I want. It paints everything in the polygons blue.
# I want everything outside of them blue.
for polygon in polygons:
draw.polygon(polygon, fill=0, outline=None, width=0)
# Look at the result.
image.show()
The approach in this quesiton might be useful, but I'm not sure how to adapt it since I am not interested in transparency, and I am working with palette ("P") images.

I think the secret here is to work out how to draw either:
the polygons, or
everything that is not the polygons
in black on a white background. Once you have that as a "mask", you can use it as the parameter to paste() to control where any new colour or image is pasted onto your existing one.

Related

How can I change the brightness of an image in pygame?

I need a bright version of some of my images in pygame. However, I don't want to make a bright version of every single one. Can I change this through pygame?
I've found a similar queation here (How can you edit the brightness of images in PyGame?), but I don't know how to multiply the color of an image. Can someone explain how I can do that?
If you want to brighten an image, then I recommend to add a constant color to the surface. This can be achieved by .fill(), wby the use of the special parameter BLEND_RGB_ADD. If the fill color is black (0, 0, 0) then the image won't change at all. If the fill color is white (255, 255, 255), then the entire image will become white. e.g.:
image = pygame.image.load(my_imagename)
brighten = 128
image.fill((brighten, brighten, brighten), special_flags=pygame.BLEND_RGB_ADD)
[...] I want the image to be more transparent.
If you want to increase the transparency of the image, then yo can "blend" the image with a transparent color, by the use of the special flag BLEND_RGBA_MULT . Of course you've to ensure that the image format provides an alpha channel (e.g. .convert_alpha())
image = pygame.image.load(my_imagename).convert_alpha()
transparency = 128
image.fill((255, 255, 255, transparency), special_flags=pygame.BLEND_RGBA_MULT)

HTML5 canvas source-atop alternative in PIL

I cannot find the right function to add color overlay on another image when transparent areas should remain untouched.
For example on source image there is a semi-transparent white circle and around it everything is fully transparent. When I apply the operation with red color, I'd like to get white/red circle with fully transparent area around it.
On HTML5 canvas, it is achieved by:
layerContext.globalCompositeOperation = "source-atop";
layerContext.fillStyle = color;
layerContext.fillRect(0, 0, w, h);
I tried alpha_composite, blend and composite functions, but maybe misused them.
EDIT: here is what I get using:
overlay = Image.new('RGB', im1.size, (255, 0, 0))
im1.paste(overlay, mask=im1)
Canvas:
PIL:
PIL result is a little bit lighter, but color is the same. Any ideas?

Is there a way to change the Python Imaging Library (PIL)'s default fill color?

I'm currently using Python Imaging Library's (PIL) transform with EXTENT function to allow users to do some basic editing of images i.e. simple zoom in / zoom out, x and y-axis offsets, set background color, rotation etc
And so one problem is that they are able to zoom out or offset enough so that parts of the final output image goes beyond the bounds of the source image. When this happens, PIL fills this portion with a black color
Does anybody know if there's a way to set a custom fill color rather than the black default or has any suggestions on ways to get around this? Much appreciated
I was originally thinking of pre-pasting an alpha layer to where the bounds are exceeded but this seems to get complicated quite quickly..
[Edit] Maybe this will help. So, do something like (don't pay too much attention to the exact integers ) ...
image2 = image1.transform((600, 400), Image.EXTENT, (0, 0, 1200, 800))
draw = ImageDraw.Draw(image2)
# Fill in rectangle below the real image
draw.rectangle( (0, 50, 1200, 600), fill=(255, 150, 0))
# Fill in rectangle to the right of the real image
draw.rectangle( (100, 0, 800, 50), fill=(150, 255, 0))
del draw
image2.save(SAVE_NAME)

Drawn surface transparency in pygame?

I'm writing a predator-prey simulation using python and pygame for the graphical representation. I'm making it so you can actually "interact" with a creature (kill it, select it and follow it arround the world, etc). Right now, when you click a creature, a thick circle(made of various anti-aliased circles from the gfxdraw class) sorrounds it, meaning that you have succesfully selected it.
My goal is to make that circle transparent, but according to the documentation you can't set an alpha value for drawn surface. I have seen solutions for rectangles (by creating a separate semitransparent surface, blitting it, and then drawing the rectangle on it), but not for a semi-filled circle.
What do you suggest? Thank's :)
Have a look at the following example code:
import pygame
pygame.init()
screen = pygame.display.set_mode((300, 300))
ck = (127, 33, 33)
size = 25
while True:
if pygame.event.get(pygame.MOUSEBUTTONDOWN):
s = pygame.Surface((50, 50))
# first, "erase" the surface by filling it with a color and
# setting this color as colorkey, so the surface is empty
s.fill(ck)
s.set_colorkey(ck)
pygame.draw.circle(s, (255, 0, 0), (size, size), size, 2)
# after drawing the circle, we can set the
# alpha value (transparency) of the surface
s.set_alpha(75)
x, y = pygame.mouse.get_pos()
screen.blit(s, (x-size, y-size))
pygame.event.poll()
pygame.display.flip()

PyGame: translucent sprites with per pixel alpha

Is it possible to display PyGame surfaces with controllable alpha? I would like to take a surface with its own per pixel alpha and display it with variable level of translucency without affecting the surface data and keep the transparency intact i.e. the objects on the surface would keep their shapes but their "contents" becoming more or less translucent.
In other words I want to combine per-pixel alpha from the source image with per-surface alpha calculated at the runtime.
The Pygame documentation says that you can't combine surface alpha with per-pixel alpha, but if you're using Pygame 1.8.1 or newer, you can work around this by using the special_flags parameter to .blit().
Here's how I did it:
# Load an image with per-pixel alpha
s = pygame.image.load('spam.png')
s = s.convert_alpha()
# Simulate s.set_alpha(alpha)
alpha_img = pygame.Surface(s.get_rect().size, pygame.SRCALPHA)
alpha_img.fill((255, 255, 255, alpha))
s.blit(alpha_img, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
The way this works is to create a second surface the same size as the first, and blit it on to the first using RGBA multiplication. By having the R, G and B components of the second image equal to 255, the multiplication won't affect the colour of the image, but it will scale the alpha channel by the given alpha factor.
Note that the method above differs from calling set_alpha() in that set_alpha() can be reversed by calling set_alpha(255). If you use the method above it will result in the pixel alphas of every pixel being changed, so the process cannot be straightforwardly reversed.
Yes you can. The documentation of the Surface class explains how to do it. It boils down to two cases only:
Either you set a flag during the creation of the Surface object:
s = pygame.Surface((16, 16), flags=pygame.SRCALPHA)
Or you give an alpha channel to a surface that doesn't have one yet:
s = pygame.image.load('spam.png')
s.convert_alpha()
The documentation of the pygame.image module says that applying convert_alpha is necessary for PNGs with transparency.
If you want you can modify the alpha value of each pixel with the modules draw and surfarray. When specifying a color, use then a tuple of four integers: (red, green, blue, alpha). The alpha parameter ranges from 0 (totally transparent) to 255 (totally opaque).
import pygame
pygame.init()
screen = pygame.display.set_mode((320, 200))
screen.fill((200, 100, 200))
s = pygame.Surface((64, 64), flags=pygame.SRCALPHA)
for i in range(64):
pygame.draw.line(s, (0, 0, 0, i*4), (0, i), (64, i))
screen.blit(s, (50, 30))
pygame.display.flip()
After checking both PyGame and SDL documentations I came to conclusion that what I asked wasn't doable in PyGame using standard functions.
SDL docs state that per-pixel alpha and per-surface alpha cannot be combined with the former always taking the precedence. The only way to achieve the effect I want is by writing a code which updates per-pixel alpha values of the source surface before the blit.
If your input image has per-pixel alpha enabled, but is only using a single bit for transparency (e.g., .png sprites with a transparent background or text), you can convert the transparent background to a colorkey-alpha background pretty easily, and then use set_alpha() to blend the whole texture onto whatever.
This is a quick and dirty helper function I used for fading text effects and other things:
def convert_to_colorkey_alpha(surf, colorkey=pygame.color.Color("magenta")):
newsurf = surface.Surface(surf.get_size())
newsurf.fill(colorkey)
newsurf.blit(surf, (0, 0))
newsurf.set_colorkey(colorkey)
return newsurf
A couple of other tricks potentially solve this problem might be:
Doing per-pixel editing to modify the alpha channel before blitting
Blitting a white or black-filled, semi-transparent surface with special_flags=BLEND_RGBA_MULT or BLEND_RGBA_SUB onto your source surface.

Categories