Suppose if I have image img with contents:
[[(255, 255, 255, 255), (0, 0, 0, 255), (0, 0, 0, 255), (0, 0, 0, 255)],
[(0, 0, 0, 255), (255, 255, 255, 255), (0, 0, 0, 255), (0, 0, 0, 255)],
[(0, 0, 0, 255), (0, 0, 0, 255), (255, 255, 255, 255), (0, 0, 0, 255)],
[(0, 0, 0, 255), (0, 0, 0, 255), (0, 0, 0, 255), (255, 255, 255, 255)]]
Is there's any way I can make PIL Image from it?
I tried Image.fromarray(np.asarray(img)) and I got the following error:
TypeError: Cannot handle this data type: (1, 1, 4), <i4
How can I resolve it? Also is there's any solution without the usage of numpy module? Thanks in advance.
I think you want this (quite self explanatory from the doc):
from PIL import Image
arr = np.array(img)
PIL_image = Image.frombuffer('RGBA',(arr.shape[0],arr.shape[1]),np.uint8(arr.reshape(arr.shape[0]*arr.shape[1],arr.shape[2])),'raw','RGBA',0,1)
You need to explicitly set dtype of an array as np.uint8 to let the Image object generator know the format of input data. And I would also recommend to specify the mode because I don't know how PIL choose between RGBA and CMYK when there are 4 channels. The solution is here:
from PIL import Image
Image.fromarray(np.asarray(img, dtype=np.uint8), mode='RGBA')
Related
I need a way to generate an image so that i can decide width, length and color for each pixel, maybe through an array. how can i do that in Python?
Here is a way using Pillow and an array
from PIL import Image
# Your image as a 3d-array using RGB color values for each pixel
imageArray = [[(0, 0, 0), (0, 0, 0), (0, 0, 0)],
[(0, 0, 0), (0, 0, 0), (0, 0, 0)],
[(0, 0, 0), (0, 0, 0), (0, 0, 0)]]
# Generate image
img = Image.new("RGB", (len(imageArray[0]), len(imageArray)), "#ffffff")
for i, row in enumerate(imageArray):
for j, pixel in enumerate(row):
img.putpixel((j, i), pixel)
img.save("output.png")
Here I am again, I guess. Here is what I got so far:
from PIL import Image
from tkinter import filedialog as fd
from webcolors import rgb_to_name
from pyautogui import press, typewrite, hotkey
import time
filename = fd.askopenfilename()
im = Image.open(filename, 'r')
pix_val = list(im.getdata())
def GetColor(R, G, B):
Final = ""
named_color = rgb_to_name((R, G, B), spec='css3') # Here
return Final
for i in pix_val:
press('q')
press('t')
typewrite('.give ' + GetColor(i[0], i[1], [2]))
press('enter')
time.sleep(3)
Now the goal is to make the script automatically turn the RGB values into simple colors like Black, Red, White, Orange, Blue, Purple, Green... you know. No intense colors like firebrick. I'm sure there is probably a way to do it by just making a simple map and if the colors match near that color then it would say it is that color. Example:
XYZ = {["Red", 255, 0, 0], ["Blue", 0, 0, 255], ["Green", 0, 255, 0]...}
However, I want a simple little import that is already coded :/ Any help is much appreciated.
The problem with this is there is, in general, no good shortcut. You have to determine the distance from each pixel to each color in the palette, and keep the smallest of those distances.
Here's one that maps to the 16 colors in the HTML4 standard set:
import math
from PIL import Image
colors = [
("black", (0, 0, 0)),
("silver", (192, 192, 192)),
("gray", (128, 128, 128)),
("white", (255, 255, 255)),
("maroon", (128, 0, 0)),
("red", (255, 0, 0)),
("purple", (128, 0, 128)),
("fuchsia", (255, 0, 255)),
("green", (0, 128, 0)),
("lime", (0, 255, 0)),
("olive", (128, 128, 0)),
("yellow", (255, 255, 0)),
("navy", (0, 0, 128)),
("blue", (0, 0, 255)),
("teal", (0, 128, 128)),
("aqua", (0, 255, 255))
]
def distance(a,b):
dx = a[0]-b[0]
dy = a[1]-b[1]
dz = a[2]-b[2]
return math.sqrt(dx*dx+dy*dy+dz*dz)
def findclosest(pixel):
mn = 999999
for name,rgb in colors:
d = distance(pixel, rgb)
if d < mn:
mn = d
color = name
return color
im = Image.open("pacmanmaze.png", 'r')
pix_val = im.getdata()
for i in pix_val:
print(findclosest(i))
I have made a stripes as you can see in folowing code. I was wondering is there a simplest way to do this? or some other way that you can think of, if there are, please provide code.
from PIL import Image, ImageDraw
img = Image.new('RGB', (100, 100), (255, 255, 255))
draw = ImageDraw.Draw(img)
draw.line((100, 10, 0, 10), (0, 0, 0), 10)
draw.line((100, 30, 0, 30), (0, 0, 0), 10)
draw.line((100, 50, 0, 50), (0, 0, 0), 10)
draw.line((100, 70, 0, 70), (0, 0, 0), 10)
draw.line((100, 90, 0, 90), (0, 0, 0), 10)
img.show()
Thanks in advance!
There is no simpler way but you can use for-loop to make shorter code.
for y in range(10, 91, 20):
draw.line((100, y, 0, y), (0, 0, 0), 10)
Using Pillow 5.4.1, Python 3.6.8
Given an image image.png with 9 distinct colours, and given a data palette with 5 distinct colours, one would expect that asking pillow to reduce the image to the described palette that the resulting image would contain colours from only that palette.
However, using the im.im.convert method returns an image with colours outside the specified palette; specifically they are always greyscale images (R==B==G values)
Sample Code, outputting the unique set of colours for the original image, palette, and converted image.
from PIL import Image
im = Image.open("image.png")
# create palette from raw data
# colours: Red, Green, Blue, Black, and White (5 total)
RGBBW = [(255,0,0), (0,255,0), (0,0,255), (0,0,0), (255,255,255)]
data = sum([list(x) for x in RGBBW], [])[:256]
pimg = Image.new("P",(16,16))
pimg.putpalette(data)
# Hack
im.convert("RGB")
cim_ = im.im.convert("P", 0, pimg.im)
cim = im._new(cim_).convert("RGB")
def colors(im):
cs = []
for x in range(im.width):
for y in range(im.height):
cs.append(im.getpixel((x,y)))
return list(set(cs))
print("Original: %s" % colors(im))
print("Palette: %s" % RGBBW)
print("Convert: %s" % colors(cim))
Input image: -> <- (3x3 pixel image, all pixels unique colours)
(Larger version, for visualisation only: )
Output:
Original: [(85, 85, 85, 255), (0, 0, 255, 255), (0, 0, 0, 255), (255, 0, 0, 255), (0, 255, 255, 255), (255, 255, 255, 255), (255, 255, 0, 255), (255, 0, 255, 255), (0, 255, 0, 255)]
Palette: [(255, 0, 0), (0, 255, 0), (0, 0, 255), (0, 0, 0), (255, 255, 255)]
Convert: [(252, 252, 252), (0, 0, 255), (255, 0, 0), (0, 0, 0), (170, 170, 170), (0, 255, 0), (84, 84, 84)]
(Note that the hack to prevent dither is a workaround, pending a fix I've contributed to master (yet to be cut into a new release))
The values [(170, 170, 170), (84, 84, 84), (252, 252, 252)] appear in the converted image, but were not specified in the original palette. They all happen to be greyscale.
I think there's something in src/libImaging/Palette.c that's effecting this, but I'm not sure if this is a bug of the code, or a 'feature' of libjpeg
Turns out this issue is both user error and an unexpected initialisation issue.
The initialisation issue: As pointed out in the comments, the palette for a new image is specifically initialised as greyscale.
If we replace the entire palette with our own, then we're fine. Except, I wasn't.
data = sum([list(x) for x in RGBBW], [])[:256]
This line is logically incorrect.
The palette expects a flattened list of up to 256 triples of RGB, that is, an array of max len 768. If the array is anything less than this, then the rest of the greyscale will still be in play.
The better way to re-initialise the palette is to ensure we repeat a value as to override the greyscale.
In this case:
data = (sum([list(x) for x in RGBBW], []) + (RGBBW[-1] * (256 - len(RGBBW))))[:256*3]
That is:
data = (
sum([list(x) for x in RGBBW], []) # flatten the nested array
+ (RGBBW[-1] * (256 - len(RGBBW))) # extend with the last value, to our required length, if needed
)[:256*3] # and trim back, if needed.
This will result in the palette always being 768 length.
Using the last value from our provided array is an arbitrary choice, as is only used as a valid padding value.
This piece of code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw
imo=Image.new("RGB", (85, 64), (204, 204, 204))
pos=(10, 64)
r=8
draw=ImageDraw.Draw(imo)
draw.chord((pos[0]-r, pos[1]-r, pos[0]+r, pos[1]+r), 0, 359, (0, 0, 255), (0, 0, 0))
for pos in [(32, -1), (85, 32), (32, 64), (-1, 32), (54, 63)]:
draw.ellipse((pos[0]-r, pos[1]-r, pos[0]+r, pos[1]+r), (0, 0, 255), (0, 0, 0))
pos=(75, 65)
draw.rectangle((pos[0]-r, pos[1]-r, pos[0]+r, pos[1]+r), (0, 0, 255), (0, 0, 0))
imo.save("aa.png", "PNG")
creates this (enlarged) image:
Is there a trick to draw the circles in such a way that they are also shown fully filled at the bottom of the image?
Okay...it works fine for rectangles...but I would prefer circles.
after upgrading to python-Pillow-2.9.0-6.4.x86_64, I now get fully filled circles - even on openSUSE 13.2.