PIL ImageDraw.Draw() doesn't work when used in a function - python

I made a program that renders the Mandelbrot set to an image. I put the draw.point() method in a function, but it doesn't seem to actually draw on the final image, but if I put im.save() in the function it does work. The full code actually uses multiprocessing and renders the image CineBench style, which is why I can't put the im.save() in the function, or pull the draw.point() out of it. Is there some other way I can solve the problem?
from PIL import Image, ImageDraw
im = Image.new("RGB", (hor_res, vert_res), (0, 0, 0))
draw = ImageDraw.Draw(im)
def mandelbrot():
# mandelbrot code
def box_renderer(x_start: int, x_end: int, y_start: int, y_end: int):
for y in range(y_start, y_end):
for x in range(x_start, x_end):
colour = 255 - int(255*mandelbrot(x, y)/iterations)
draw.point([x, y], (0, 0, colour))
if __name__ == "__main__":
box_renderer(args)
im.save("mandelbrot.png", "PNG")
This is not the entire program, but hopefully enough to make sense

I'm not sure that your sample code is representative, because this version works fine:
from PIL import Image, ImageDraw
hor_res, vert_res = 200, 200
im = Image.new("RGB", (hor_res, vert_res), (255, 0, 0))
draw = ImageDraw.Draw(im)
def box_renderer(x_start: int, x_end: int, y_start: int, y_end: int):
for y in range(y_start, y_end):
for x in range(x_start, x_end):
colour = 128
draw.point([x, y], (0, 0, colour))
if __name__ == "__main__":
box_renderer(x_start=50,x_end=100,y_start=20,y_end=180)
im.save("mandelbrot.png", "PNG")

Related

How to display an image in Pygame using list of RGBA values of a image [duplicate]

This question already has answers here:
PIL and pygame.image
(2 answers)
Closed 1 year ago.
I am trying to make a pygame project which involves some images. In these images some are very similar and there is just a change of colour. So I came up with idea that why not to use only one image and change its corresponding colour using Python from this article.
from PIL import Image
import pygame as pg
img = Image.open("Assets/image.png")
img = img.convert("RGBA")
d = img.getdata()
new_image = []
for item in d:
if item[:3] == (0,0,0):
new_image.append((255,255,255,0))
if item[:3] == (23,186,255):
new_image.append((255,38,49,item[3]))
else:
new_image.append(item)
img.putdata(new_image)
img.save("a.png","PNG")
But in the last two lines of the above code its saving the image and I don't want that!
I want to use it in the Pygame code for displaying and then when the program exits the image is gone. So how can I use a list of RGBA values new _image to display an image in Pygame.
Any help would be appreciated.
Use pygame.image.frombuffer() to create a pygame.Surface. However you have to convert the list to a byte array. Use the ctypes module to make a byte array from a list:
flat_list = [e for c in new_image for e in c]
bute_array = (ctypes.c_ubyte * len(flat_list))(*flat_list)
surf = pg.image.frombuffer(bute_array, img.size, img.mode).convert_alpha()
See also PIL and pygame.image.
Notice that there is a bug in your algorithm. You need to use elif instead of if in the middle case:
if item[:3] == (0,0,0):
new_image.append((255,255,255,0))
#if item[:3] == (23,186,255):
elif item[:3] == (23,186,255): # <---
new_image.append((255,38,49,item[3]))
else:
new_image.append(item)
Note: if you want to change the background to a white color, you need to make the color opaque:
new_image.append((255,255,255,0))
new_image.append((255, 255, 255, 255))
Minimal example:
The left image is the test image and the right image is the result:
from PIL import Image
import pygame as pg
import ctypes
img = Image.open("Assets/image.png")
img = img.convert("RGBA")
d = img.getdata()
new_image = []
for item in d:
if item[:3] == (0, 0, 0):
new_image.append((255, 255, 255, 0))
#new_image.append((255, 255, 255, 255))
elif item[:3] == (23, 186, 255):
new_image.append((255, 38, 49, item[3]))
else:
new_image.append(item)
pg.init()
window = pg.display.set_mode(img.size)
flat_list = [e for c in new_image for e in c]
bute_array = (ctypes.c_ubyte * len(flat_list))(*flat_list)
surf = pg.image.frombuffer(bute_array, img.size, img.mode).convert_alpha()
run = True
while run:
for event in pg.event.get():
if event.type == pg.QUIT:
run = False
window.fill(0)
window.blit(surf, (0, 0))
pg.display.flip()
pg.quit()
Side note:
You don't need to load the image using PLI you can access the pixels of a pygame.Surface directly. There are different options:
with the methods get_at / set_at
with pygame.PixelArray object
with the pygame.surfarray module
I may be completely missing the point of your question, but as I understand it, you want to load an image and change some colours. I would do it like this:
#!/usr/bin/env python3
from PIL import Image
import pygame as pg
import numpy as np
# Open image and ensure RGBA mode, not palette image
img = Image.open("image.png").convert('RGBA')
# Define some colours for readability
black = [0,0,0]
white = [255,255,255]
red = [255,0,0]
blue = [0,0,255]
# Make image into Numpy array for vectorised processing
na = np.array(img)
# Make re-usable mask of black pixels then change them to white
mBlack = np.all(na[...,:3] == black, axis=-1)
na[mBlack,:3] = white
# Make re-usable mask of red pixels then change them to blue
mRed = np.all(na[...,:3] == red, axis=-1)
na[mRed,:3] = blue
pg.init()
window = pg.display.set_mode(img.size)
surf = pg.image.frombuffer(na.tobytes(), img.size, img.mode)
run = True
while run:
for event in pg.event.get():
if event.type == pg.QUIT:
run = False
window.fill(0)
window.blit(surf, (0, 0))
pg.display.flip()
pg.quit()
Which makes this image:
display like this:
So you can see the power of the masks I made, you can do things like this:
# Make any pixels that were either red or black become magenta
na[mRed|mBlack,:3] = [255,0,255]
Or:
# Make all pixels that were not black into cyan
na[~mBlack,:3] = [0,255,255]

Draw interactive mandelbrot with pyglet

I have a function that calculates the Mandelbrot set in an numpy array of size (500,500).
I can draw it in matplotlib. Now i want to zoom into the image via mouse click. I want to use pyglet for that but struggle with rendering. I know this (not working) solution is inelegant, so I am happy for advice how to do this better!
PS: The zoom level is set arbitrary right now, my problem I ask for help is the refreshing.
The functions I replaced with a docstring can be found here
from pyglet.window import mouse
import pyglet
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import cm
from pyglet import image
"""
def calc_pixel_array(x_center: float = 0, y_center: float = 0,
scale: float = 1.0, width: int = 500,
height: int = 500, threshold: int = 200):
Returns numpy array with shape (500,500) with color values
def plot_pixel_array(pixel_array, cmap: str = 'gnuplot', show_plot: bool = False):
Draws pixel_array via matplotlib and saves in curent_mandelbrot.png
"""
pixel_array = calc_pixel_array()
plot_pixel_array(pixel_array)
scale = 1
i = 1
window = pyglet.window.Window(width = 500, height = 500)
image = pyglet.resource.image('curent_mandelbrot.png')
pic = image.load('curent_mandelbrot.png')
#window.event
def on_draw():
window.clear()
pic.blit(0, 0)
#window.event
def on_mouse_press(x, y, button, modifiers):
if button == mouse.LEFT:
i = np.random.randint(5)+1
pixel_array = calc_pixel_array(scale/(10*i))
plot_pixel_array(pixel_array)
window.clear()
image = pyglet.resource.image('curent_mandelbrot.png')
image.blit(0,0)
pyglet.app.run()
Hi after a lot of trying around I found this
def draw_pixel_array(self, window):
main_batch = pyglet.graphics.Batch()
shape = self.pixel_array.shape
for x in range(shape[0]):
for y in range(shape[1]):
color = int(self.pixel_array[x, y])
pyglet.graphics.draw(1, pyglet.gl.GL_POINTS,
('v2i', (x, y)),
('c3B', (color, color, 0)))
main_batch.draw()
Got it running by removing the on draw and putting everything (calculating and drawing) in the on_mouse_press function.
You will find the complete solution here. It is still very slow in drawing, any suggestions there are warmly welcome!

Python Pillow Changing RGB Value Of Pixels?

I'm trying to change the pixels on my picture to darker green than I have already made it, I am trying to add + rgb(0,50,0) to it but I can't seem to be able to do so, could you help? I have put my code in below, and freljord2.png is now just a full green image using getcolor(green, "RGBA")
im = Image.open('freljord2.png')
#one_pixel = im.getpixel((0, 0))
#one_pixel[1] = 0;
#im.save('freljord3.png')
(0, 0, 0, 0)
for x in range(0):
for y in range(0):
im.putpixel((x, y), (210, 210, 210))
for x in range(560):
for y in range(557):
print("hi")
hello = ImageColor.get(00B200)
im.putpixel((x, y), )
im.getpixel((0, 0))
(210, 210, 210, 255)
im.getpixel((0, 50))
(169, 169, 169, 255)
im.save('freljord2.png')
You mention that you want to make your image more dark green, but if you add 50 to each pixel value of an image, you only make it brighter (and greener). What you want to do is to place a green transparent overlay over your existing image. You should create a new image for that with the same size as your original with the color you want to add, and an alpha value, which indicates how transparent it is. Next, you need to paste that image over your original image using a mask.
The following code example should do the trick. The result is shown below. You can play around a little bit with the values to tailor to your needs.
'''
Created on Oct 23, 2016
#author: physicalattraction
'''
import os.path
from PIL import Image
def get_img_dir() -> str:
'''
Return the full path to the image directory
:return: string
'''
pkg_dir = os.path.dirname(__file__)
img_dir = os.path.join(pkg_dir, '..', '..', 'img')
return img_dir
def open_img(img_name: str) -> Image:
'''
Open the given file form the image directory
:param img_name: Name including extension of the image
:return: Image object
'''
img_dir = get_img_dir()
full_img_path = os.path.join(img_dir, img_name)
return Image.open(full_img_path)
def save_img(img: Image, img_name: str):
'''
Save the given image to the image directory
:param img: Image object
:param img_name: Name including the extension of the image
'''
img_dir = get_img_dir()
full_img_path = os.path.join(img_dir, img_name)
img.save(full_img_path)
def overlay(img: Image, overlay_color: tuple):
'''
Place an overlay over an existing image
:param img: Image opened with PIL.Image
:param overlay_color: four-tuple with color to add to your image
'''
assert len(overlay_color) == 4, 'Overlay color shall be a 4-tuple'
img_overlay = Image.new(size=img.size, color=overlay_color, mode='RGBA')
img.paste(img_overlay, None, mask=img_overlay)
color_string = '_'.join([str(c) for c in overlay_color])
filename = 'amsterdam_{color}.jpg'.format(color=color_string)
save_img(img, filename)
if __name__ == '__main__':
ams = open_img('amsterdam.jpg')
green = (0, 50, 0, 128)
overlay(ams, green)
Original image:
Darker green image:

how to create a french flag in python?

I'm trying to create a french flag without using most of python's built in functions but for some reason my loop won't work... can anyone tell me why?
from PIL import Image
def french():
flag= Image.new('RGB', (300, 200))
pixels=flag.getdata()
pixs= list(pixels)
r=0
w=100
b=200
while True:
for x in range(r,(r+100)):
pixs[x]= (255,0,0)
flag.putdata(pixs)
r=+300
for x in range(w,(w+100)):
pixs[x]= (255,255,255)
flag.putdata(pixs)
w=+300
for x in range(b,(b+100)):
pixs[x]=(0,0,255)
flag.putdata(pixs)
b=+300
return (r>60000 or w>60000 or b>60000)
flag.save('frenchflag.png')
french()
Instead of
# on first pass through the loop, exit the function and return False
return (r>60000 or w>60000 or b>60000)
I think you mean
# when we are at the end of the pixel buffer, leave the loop
if r >= 60000:
break
Pixel-poking is a horribly inefficient way to create an image; use PIL.ImageDraw instead:
from PIL import Image, ImageDraw
RED = (255, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
def french(fname, width=300, height=200):
flag = Image.new("RGB", (width, height))
p, q = width//3, 2*width//3 # bar edge coordinates
draw = ImageDraw.Draw(flag)
draw.rectangle((0, 0, p, height), RED)
draw.rectangle((p, 0, q, height), WHITE)
draw.rectangle((q, 0, width, height), BLUE)
flag.save(fname)
french("frenchflag.png")
which produces

The image does not show up when I run this Python code in Ubuntu

Is there something wrong with this code? It doesn't say there are any errors but the 'img' variable never shows at the end.
import Image
import ImageDraw
def main():
b = 4
size = 25
fig_width = size
fig_height = size
white_rgb = (255, 255, 255)
black_rgb = (0, 0, 0)
img = Image.new("RGB", (fig_width, fig_height), white_rgb)
draw = ImageDraw.Draw(img)
for i in range(0, size, 5):
for j in range(0, size, 5):
if i % 2 == j % 2:
draw.rectangle([(j, i), (j + b, i + b)], black_rgb)
img.show()
main()
PIL will try to use the ImageMagick display utility or xv to display images. If it cannot find either, it will fail silently. Try installing the imagemagick Ubuntu package and then run the script again.

Categories

Resources