Generate Color Spectrum using Python - python

I want to generate a color spectrum like that:
as a png picture. But the width and height of the picture should be adjustable. The Colors should be used as hex values like the HTML Color-Code (for example #FF0000).
I know how the scale works but i think there are already any solutions how to count up the blue to red, then counting down red etc. in a resolution that acquires the needed width of the picture.
For generating the Picture i thought about PIL:
from PIL import Image
im = Image.new("RGB", (width, height))
im.putdata(DEC_tuples)
im.save("Picture", "PNG")
Are there any existing working solutions available?

Found a solution by myself and it works pretty well, the generated image will become a new width because I won't generate float-numbers.
from PIL import Image
width = 300 # Expected Width of generated Image
height = 100 # Height of generated Image
specratio = 255*6 / width
print ("SpecRatio: " + str(specratio))
red = 255
green = 0
blue = 0
colors = []
step = round(specratio)
for u in range (0, height):
for i in range (0, 255*6+1, step):
if i > 0 and i <= 255:
blue += step
elif i > 255 and i <= 255*2:
red -= step
elif i > 255*2 and i <= 255*3:
green += step
elif i > 255*3 and i <= 255*4:
blue -= step
elif i > 255*4 and i <= 255*5:
red += step
elif i > 255*5 and i <= 255*6:
green -= step
colors.append((red, green, blue))
newwidth = int(i/step+1) # Generated Width of Image without producing Float-Numbers
print (str(colors))
im = Image.new("RGB", (newwidth, height))
im.putdata(colors)
im.save("Picture", "PNG")

Related

How can I convert an array of pixel colors into an image efficiently with Python?

I am working on a project that implements Numba and CUDA with Python 3.8. Currently, I create an array with the dimensions of the final image. Next, I generate an image with a CUDA kernel (incredibly fast). Then, I copy the pixel color into a Pillow Image (incredibly slow). My code:
for x in range(width):
for y in range(height):
if pixels[x][y] = 0:
color = [0, 0, 0]
else:
# Get color from int as tuple
color = rgb_conv(pixels[x][y]
red = int(numpy.floor(color[0]))
if red > 255:
red = 255
elif red < 0:
red = 0
green = int(numpy.floor(color[1]))
if green > 255:
green = 255
elif green < 0:
green = 0
blue = int(numpy.floor(color[2]))
if blue > 255:
blue = 255
elif blue < 0:
blue = 0
image.putpixel((x, y), (red, green, blue))
Are there any more efficient Python image libraries for this implementation? Is there a way to convert the array to an image on the GPU? Any help with direction works. Thanks!
EDIT 1: A request was made for the function rgb_conv. This is a function I found to convert a single integer into a three-wide color.
def rgb_conv(i):
color = 255 * numpy.array(colorsys.hsv_to_rgb(i / 255.0, 1.0, 0.5))
return tuple(color.astype(int))
However, I didn't particularly like the colors this function produces, so I removed it and began working with the following:
pixelArr = image.load()
for x in range(width):
for y in range(height):
color = int(numpy.floor(pixels[x][y]))
pixelArr[x, y] = (color << 21) + (color << 10) + color * 8
This adjustment doesn't do much to the running time of the code. I am looking further into a suggestion load the image from an array rather than putting each pixel into the image.
It is not efficient to place each pixel into an image with pillow. Creating an image from a numpy array is significantly faster than before. By faster, I mean the 3840x2160 image took minutes, but now takes 0.0117 seconds.
To create an array that can be converted to an image:
import numpy
pixels = numpy.zeros((height, width, 3), dtype=numpy.uint8)
Here, I run calculations on the GPU to create an image with the pixels array. To efficiently convert an array of pixels into an image:
from PIL import Image
image = Image.fromarray(pixels)

How to customise the output generated from image difference?

I have no background in image-processing. I am interested in getting the difference between these two images.
After writing the following code :
from PIL import Image
from PIL import ImageChops
im1 = Image.open("1.png")
im2 = Image.open("2.png")
diff = ImageChops.difference(im2, im1)
diff.save("diff.png")
I get this output :-
I am looking for some customisations here :
1) I want to label the differences in output in different colours. Things from the 1.png and 2.png should have a different colours.
2) background should be white.
3) I want my output to have axises and axis labels. Would it be possible somehow ?
You probably can't do this with the high-level difference method, but it's quite easy if you compare the images pixel by pixel yourself. Quick attempt:
Code:
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
im1 = Image.open("im1.jpeg").convert('1') # binary image for pixel evaluation
rgb1 = Image.open("im1.jpeg").convert('RGB') # RGB image for border copy
p1 = im1.load()
prgb1 = rgb1.load()
im2 = Image.open("im2.jpeg").convert('1') # binary image for pixel evaluation
p2 = im2.load()
width = im1.size[0]
height = im1.size[1]
imd = Image.new("RGB", im1.size)
draw = ImageDraw.Draw(imd)
dest = imd.load()
fnt = ImageFont.truetype('/System/Library/Fonts/OpenSans-Regular.ttf', 20)
for i in range(0, width):
for j in range(0, height):
# border region: just copy pixels from RGB image 1
if j < 30 or j > 538 or i < 170 or i > 650:
dest[i,j] = prgb1[i,j]
# pixel is only set in im1, make red
elif p1[i,j] == 255 and p2[i,j] == 0:
dest[i,j] = (255,0,0)
# pixel is only set in im2, make blue
elif p1[i,j] == 0 and p2[i,j] == 255:
dest[i,j] = (0,0,255)
# unchanged pixel/background: make white
else:
dest[i,j] = (255,255,255)
draw.text((700, 50),"blue", "blue", font=fnt)
draw.text((700, 20),"red", "red", font=fnt)
imd.show()
imd.save("diff.png")
This assumes that the images are the same size and have identical axes.

Problems cropping entire white lines from .png file

What I want to do is to crop out the white lines above a given instagram print screen. I tried doing that by finding the center of the image and going up, line by line, until I found the first line entirely white. Any idea why my code is not working?
from PIL import Image
image_file = "test.png"
im = Image.open(image_file)
width, height = im.size
centerLine = height // 2
entireWhiteLine = set()
entireWhiteLine.add(im.getpixel((0, 0)))
terminateUpperCrop = 1
while terminateUpperCrop != 2 :
for i in range(centerLine, 1, -1) :
entireLine = set()
upperBorder = i - 1
for j in range(0, width, 1) :
entireLine.add((im.getpixel((i, j))))
if entireLine == im.getpixel((0,0)):
box = (0, upperBorder, width, height)
crop = im.crop((box))
crop.save('test2.png')
terminateUpperCrop = 2
Your getpixel() call is actually searching with the coordinates the wrong way around, so in effect you were scanning for the left edge. You could use the following approach. This creates a row of data containing only white pixels. If the length of the row equals your width, then you know they are all white.
from PIL import Image
image_file = "test.png"
im = Image.open(image_file)
width, height = im.size
centerLine = height // 2
white = (255, 255, 255)
for y in range(centerLine, 0, -1) :
if len([1 for x in range(width) if im.getpixel((x, y)) == white]) == width - 1:
box = (0, y, width, height)
crop = im.crop((box))
crop.save('test2.png')
break

Python code is bug or not

I try to test example all black pic it not show "mostly black" i wonder please help me
from PIL import Image
im = Image.open('im.gif')
pixels = im.getdata() # get the pixels as a flattened sequence
black_thresh = 50
nblack = 0
for pixel in pixels:
if pixel < black_thresh:
nblack += 1
n = len(pixels)
if (nblack / float(n)) > 0.5:
print("mostly black")
If the image returned as tuple containing each of the color components (e.g. RGB), not a single value. You can take the average value and compare against the threshold like this:
for pixel in pixels:
if sum(pixel) / len(pixel) < black_thresh:
nblack += 1

What's wrong with this python image blur function?

EDIT: Thanks to Howard, I've corrected the code here and it seems to be working now.
EDIT2: I've updated the code to include a vertical blur as originally intended. Resulting sample output with various settings: Blur comparison images.jpg
Another reference for blur operations (Java): Blurring for Beginners
original post:
I'm trying to learn about basic image processing and duplicate this simple Blur method (the second function BlurHorizontal under "Reusing results") in python. I know there are already blur functions in PIL, but I want to try out the basic pixel operations myself.
This function should take a source image, then average RGB pixel values based on a certain radius and write the processed image to a new file. My problem is that I'm getting a lot of pixels with completely wrong averaged values (for example, bright green lines instead of red in certain areas).
With a blur radius of 2, the averaging method adds up the RGB values for the 5 pixels centered on the input pixel. It uses a "sliding window" to keep a running total, subtracting the outgoing pixel (left side) and adding the new incoming pixel (right side of window). Blur method explained here
Sample: Blur test image output.jpg
Any ideas where I've gone wrong? I'm not sure why some parts of the image blur cleanly while other areas are filled with colors completely unrelated to the surrounding areas.
Thanks for your help.
FIXED WORKING Code (Thanks Howard)
import Image, numpy, ImageFilter
img = Image.open('testimage.jpg')
imgArr = numpy.asarray(img) # readonly
# blur radius in pixels
radius = 2
# blur window length in pixels
windowLen = radius*2+1
# columns (x) image width in pixels
imgWidth = imgArr.shape[1]
# rows (y) image height in pixels
imgHeight = imgArr.shape[0]
#simple box/window blur
def doblur(imgArr):
# create array for processed image based on input image dimensions
imgB = numpy.zeros((imgHeight,imgWidth,3),numpy.uint8)
imgC = numpy.zeros((imgHeight,imgWidth,3),numpy.uint8)
# blur horizontal row by row
for ro in range(imgHeight):
# RGB color values
totalR = 0
totalG = 0
totalB = 0
# calculate blurred value of first pixel in each row
for rads in range(-radius, radius+1):
if (rads) >= 0 and (rads) <= imgWidth-1:
totalR += imgArr[ro,rads][0]/windowLen
totalG += imgArr[ro,rads][1]/windowLen
totalB += imgArr[ro,rads][2]/windowLen
imgB[ro,0] = [totalR,totalG,totalB]
# calculate blurred value of the rest of the row based on
# unweighted average of surrounding pixels within blur radius
# using sliding window totals (add incoming, subtract outgoing pixels)
for co in range(1,imgWidth):
if (co-radius-1) >= 0:
totalR -= imgArr[ro,co-radius-1][0]/windowLen
totalG -= imgArr[ro,co-radius-1][1]/windowLen
totalB -= imgArr[ro,co-radius-1][2]/windowLen
if (co+radius) <= imgWidth-1:
totalR += imgArr[ro,co+radius][0]/windowLen
totalG += imgArr[ro,co+radius][1]/windowLen
totalB += imgArr[ro,co+radius][2]/windowLen
# put average color value into imgB pixel
imgB[ro,co] = [totalR,totalG,totalB]
# blur vertical
for co in range(imgWidth):
totalR = 0
totalG = 0
totalB = 0
for rads in range(-radius, radius+1):
if (rads) >= 0 and (rads) <= imgHeight-1:
totalR += imgB[rads,co][0]/windowLen
totalG += imgB[rads,co][1]/windowLen
totalB += imgB[rads,co][2]/windowLen
imgC[0,co] = [totalR,totalG,totalB]
for ro in range(1,imgHeight):
if (ro-radius-1) >= 0:
totalR -= imgB[ro-radius-1,co][0]/windowLen
totalG -= imgB[ro-radius-1,co][1]/windowLen
totalB -= imgB[ro-radius-1,co][2]/windowLen
if (ro+radius) <= imgHeight-1:
totalR += imgB[ro+radius,co][0]/windowLen
totalG += imgB[ro+radius,co][1]/windowLen
totalB += imgB[ro+radius,co][2]/windowLen
imgC[ro,co] = [totalR,totalG,totalB]
return imgC
# number of times to run blur operation
blurPasses = 3
# temporary image array for multiple passes
imgTmp = imgArr
for k in range(blurPasses):
imgTmp = doblur(imgTmp)
print "pass #",k,"done."
imgOut = Image.fromarray(numpy.uint8(imgTmp))
imgOut.save('testimage-processed.png', 'PNG')
I suppose you have an issue with the line
for rads in range(-radius, radius):
which runs to radius-1 only (range excludes last). Add one to the second range argument.
Update: There is another small isue within the line
if (co-radius-1) > 0:
which should be
if (co-radius-1) >= 0:
I modified/refactored your code just a bit, and thought I'd share. I needed something to do a custom blur that would: 1) work on a data array, and 2) only wrap horizontally and not vertically. As the TODO notes, I'm thinking of further refactoring so it can do partial pixel blends (i.e. 0.5). Hope this helps someone:
def blur_image(image_data, blur_horizontal=True, blur_vertical=True, height=256, width=256, radius=1):
#TODO: Modify to support partial pixel blending
# blur window length in pixels
blur_window = radius*2+1
out_image_data = image_data
# blur horizontal row by row, and wrap around edges
if blur_horizontal:
for row in range(height):
for column in range(0, width):
total_red = 0
total_green = 0
total_blue = 0
for rads in range(-radius, radius+1):
pixel = (row*width) + ((column+rads) % width)
total_red += image_data[pixel][0]/blur_window
total_green += image_data[pixel][1]/blur_window
total_blue += image_data[pixel][2]/blur_window
out_image_data[row*width + column] = (total_red, total_green, total_blue, 255)
image_data = out_image_data
# blur vertical, but no wrapping
if blur_vertical:
for column in range(width):
for row in range(0, height):
total_red = 0
total_green = 0
total_blue = 0
blur_window = 0
for rads in range(-radius, radius+1):
if rads in range(0, height):
blur_window += 1
for rads in range(-radius, radius+1):
row_mod = row+rads
if row_mod in range(0, height):
pixel = (row_mod*width) + column
total_red += image_data[pixel][0]/blur_window
total_green += image_data[pixel][1]/blur_window
total_blue += image_data[pixel][2]/blur_window
out_image_data[row*width + column] = (total_red, total_green, total_blue, 255)
image_data = out_image_data
return image_data
You can use it when you've already got an image that's in an array of RGBA pixels, then run:
image_data = blur_image(image_data, height=height, width=width, radius=2)
im = Image.new('RGB', (width, height))
im.putdata(image_data)

Categories