Can I Convert RGB > HSV the simple way? - python-3.x

OK. I am creating a colour blindness simulator. I was developing a beta, where instead of a whole image, you would just insert the color's attributes in HSV/HSB.
h = input('Hue: ');
s = input('Saturation: ');
v = input('Value/Brightness: ');
But then, I need an algorithm or equation that can convert HSV/HSB into RGB. I've tried a lot of things, but most of the time I spent was looking at algorithms with operations python cannot use and trying to convert them into what python can understand. So, I just want a simple, nice, algorithm, it's fine if you don't know how it works; but please. It would also be nice if I could convert RGB > HSV, and both without any imports. Also, it doesn't matter if you have to replace HSV into HSL.

Colour implements Machado, Oliveira and Fernandes (2009) Colour Vision Deficiency (CVD) model:
H = 0 # input('Hue: ')
S = 0.95 # input('Saturation: ')
V = 0.75 # input('Value/Brightness: ')
deficiency = 'Protanomaly' # input('Deficiency (Protanomaly, Deuteranomaly, Tritanomaly)')
severity = 0.75 # input('Severity [0, 1]')
# Assuming linear H, S, V input.
HSV = [H, S, V]
RGB = colour.cctf_decoding(colour.HSV_to_RGB(HSV))
M_a = colour.blindness.matrix_cvd_Machado2009(deficiency, severity)
RGB_a = colour.utilities.vector_dot(M_a, RGB)
colour.plotting.plot_multi_colour_swatches(colour.cctf_encoding([RGB, RGB_a]));
For a colour wheel:
def colour_wheel(samples=512, clip_circle=True, method='Colour'):
xx, yy = np.meshgrid(
np.linspace(-1, 1, samples), np.linspace(-1, 1, samples))
S = np.sqrt(xx ** 2 + yy ** 2)
H = (np.arctan2(xx, yy) + np.pi) / (np.pi * 2)
HSV = colour.utilities.tstack([H, S, np.ones(H.shape)])
RGB = colour.HSV_to_RGB(HSV)
if clip_circle == True:
RGB[S > 1] = 0
if method.lower()== 'nuke':
RGB = colour.utilities.orient(RGB, 'Flip')
RGB = colour.utilities.orient(RGB, '90 CW')
return RGB
colour.plotting.plot_image(colour.cctf_encoding(
colour_wheel()));
colour.plotting.plot_image(colour.cctf_encoding(
colour.utilities.vector_dot(M_a, colour_wheel())));

Related

should I use basinhopping instead of minimize to find colors based on a small set of transforms?

question up front
def shade_func(color, offset):
return tuple([int(c * (1 - offset)) for c in color])
def tint_func(color, offset):
return tuple([int(c + (255 - c) * offset) for c in color])
def tone_func(color, offset):
return tuple([int(c * (1 - offset) + 128 * offset) for c in color])
given an objective over a collection of colors that returns the least distance to a target color, how do I ensure that basinhopping isn't better than minimization in scikit learn?
I was thinking that, for any one color, there will be up to 4 moments in a v-shaped curve, and so only one minimum. if the value with offset zero is itself a minimum, maybe it could be 5. Am I wrong? In any case each is a single optimum, so if we are only searching one color at a time, no reason to use basinhopping.
If we instead use basinhopping to scan all colors at once (we can scale the two different dimensions, in fact this is where the idea of a preprocessor function first came from), it scans them, but does not do such a compelling just of scanning all colors. Some colors it only tries once. I think it might completely skip some colors with large enough sets.
details
I was inspired by way artyclick shows colors and allows searching for them. If you look at an individual color, for example mauve you'll notice that it prominently displays the shades, tints, and tones of the color, rather like an artist might like. If you ask it for the name of a color, it will use a hidden unordered list of about a thousand color names, and some javascript to find the nearest colorname to the color you chose. In fact it will also show alternatives.
I noticed that quite often a shade, tint or tone of an alternative (or even the best match) was often a better match than the color it provided. For those who don't know about shade, tint and tone, there's a nice write up at Dunn Edward's Paints. It looks like shade and tint are the same but with signs reversed, if doing this on tuples representing colors. For tone it is different, a negative value would I think saturate the result.
I felt like there must be authoritative (or at least well sourced) colorname sources it could be using.
In terms of the results, since I want any color or its shade/tint/tone, I want a result like this:
{'color': '#aabbcc',
'offset': {'type': 'tint', 'value': 0.31060384614807254}}
So I can return the actual color name from the color, plus the type of color transform to get there and the amount of you have to go.
For distance of colors, there is a great algorithm that is meant to model human perception that I am using, called CIEDE 2000. Frankly, I'm just using a snippet I found that implements this, it could be wrong.
So now I want to take in two colors, compare their shade, tint, and tone to a target color, and return the one with the least distance. After I am done, I can reconstruct if it was a shade, tint or tone transform from the result just by running all three once and choosing the best fit. With that structure, I can iterate over every color, and that should do it. I use optimization because I don't want to hard code what offsets it should consider (though I am reconsidering this choice now!).
because I want to consider negatives for tone but not for shade/tint, my objective will have to transform that. I have to include two values to optimize, since the objection function will need to know what color to transform (or else the result will give me know way of knowing which color to use the offset with).
so my call should look something like the following:
result = min(minimize(objective, (i,0), bounds=[(i, i), (-1, 1)]) for i in range(len(colors)))
offset_type = resolve_offset_type(result)
with that in mind, I implemented this solution, over the past couple of days.
current solution
from scipy.optimize import minimize
import numpy as np
import math
def clamp(low, x, high):
return max(low, min(x, high))
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def rgb_to_hex(rgb):
return '#{:02x}{:02x}{:02x}'.format(*rgb)
def rgb_to_lab(color):
# Convert RGB to XYZ color space
R = color[0] / 255.0
G = color[1] / 255.0
B = color[2] / 255.0
R = ((R + 0.055) / 1.055) ** 2.4 if R > 0.04045 else R / 12.92
G = ((G + 0.055) / 1.055) ** 2.4 if G > 0.04045 else G / 12.92
B = ((B + 0.055) / 1.055) ** 2.4 if B > 0.04045 else B / 12.92
X = R * 0.4124 + G * 0.3576 + B * 0.1805
Y = R * 0.2126 + G * 0.7152 + B * 0.0722
Z = R * 0.0193 + G * 0.1192 + B * 0.9505
return (X,Y,Z)
def shade_func(color, offset):
return tuple([int(c * (1 - offset)) for c in color])
def tint_func(color, offset):
return tuple([int(c + (255 - c) * offset) for c in color])
def tone_func(color, offset):
return tuple([int(c * (1 - offset) + 128 * offset) for c in color])
class ColorNameFinder:
def __init__(self, colors, distance=None):
if distance is None:
distance = ColorNameFinder.ciede2000
self.distance = distance
self.colors = [hex_to_rgb(color) for color in colors]
#classmethod
def euclidean(self, left, right):
return (left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2 + (left[2] - right[2]) ** 2
#classmethod
def ciede2000(self, color1, color2):
# Convert color to LAB color space
lab1 = rgb_to_lab(color1)
lab2 = rgb_to_lab(color2)
# Compute CIE 2000 color difference
C1 = math.sqrt(lab1[1] ** 2 + lab1[2] ** 2)
C2 = math.sqrt(lab2[1] ** 2 + lab2[2] ** 2)
a1 = math.atan2(lab1[2], lab1[1])
a2 = math.atan2(lab2[2], lab2[1])
dL = lab2[0] - lab1[0]
dC = C2 - C1
dA = a2 - a1
dH = 2 * math.sqrt(C1 * C2) * math.sin(dA / 2)
L = 1
C = 1
H = 1
LK = 1
LC = math.sqrt(math.pow(C1, 7) / (math.pow(C1, 7) + math.pow(25, 7)))
LH = math.sqrt(lab1[0] ** 2 + lab1[1] ** 2)
CB = math.sqrt(lab2[1] ** 2 + lab2[2] ** 2)
CH = math.sqrt(C2 ** 2 + dH ** 2)
SH = 1 + 0.015 * CH * LC
SL = 1 + 0.015 * LH * LC
SC = 1 + 0.015 * CB * LC
T = 0.0
if (a1 >= a2 and a1 - a2 <= math.pi) or (a2 >= a1 and a2 - a1 > math.pi):
T = 1
else:
T = 0
dE = math.sqrt((dL / L) ** 2 + (dC / C) ** 2 + (dH / H) ** 2 + T * (dC / SC) ** 2)
return dE
def __factory_objective(self, target, preprocessor=lambda x: x):
def fn(x):
print(x, preprocessor(x))
x = preprocessor(x)
color = self.colors[x[0]]
offset = x[1]
bound_offset = abs(offset)
offsets = [
shade_func(color, bound_offset),
tint_func(color, bound_offset),
tone_func(color, offset)]
least_error = min([(right, self.distance(target, right)) \
for right in offsets], key = lambda x: x[1])[1]
return least_error
return fn
def __resolve_offset_type(self, sample, target, offset):
bound_offset = abs(offset)
shade = shade_func(sample, bound_offset)
tint = tint_func(sample, bound_offset)
tone = tone_func(sample, offset)
lookup = {}
lookup[shade] = "shade"
lookup[tint] = "tint"
lookup[tone] = "tone"
offsets = [shade, tint, tone]
least_error = min([(right, self.distance(target, right)) for right in offsets], key = lambda x: x[1])[0]
return lookup[least_error]
def nearest_color(self, target):
target = hex_to_rgb(target)
preprocessor=lambda x: (int(x[0]), x[1])
result = min(\
[minimize( self.__factory_objective(target, preprocessor=preprocessor),
(i, 0),
bounds=[(i, i), (-1, 1)],
method='Powell') \
for i, color in enumerate(self.colors)], key=lambda x: x.fun)
color_index = int(result.x[0])
nearest_color = self.colors[color_index]
offset = preprocessor(result.x)[1]
offset_type = self.__resolve_offset_type(nearest_color, target, offset)
return {
"color": rgb_to_hex(nearest_color),
"offset": {
"type": offset_type,
"value": offset if offset_type == 'tone' else abs(offset)
}
}
let's demonstrate this with mauve. We'll define a target that is similar to a shade of mauve, include mauve in a list of colors, and ideally we'll get mauve back from our test.
colors = ['#E0B0FF', '#FF0000', '#000000', '#0000FF']
target = '#DFAEFE'
agent = ColorNameFinder(colors)
agent.nearest_color(target)
we do get mauve back:
{'color': '#e0b0ff',
'offset': {'type': 'shade', 'value': 0.0031060384614807254}}
the distance is 0.004991238317138219
agent.distance(hex_to_rgb(target), shade_func(hex_to_rgb(colors[0]), 0.0031060384614807254))
why use Powell's method?
in this arrangement, it is simply the best. No other method that uses bounds did a good job of scanning positives and negatives, and I had mixed results using the preprocessor to scale the values back to negative with bounds of (0,2).
I do notice that in the sample test, a range between about 0.003 and 0.0008 seems to produce the same distance, and that the values my approach considers includes a large number of these. is there a more efficient solution?
If I'm wrong, please let me know.
correctness of the color transformations
what is adding a negative amount of white? (in the case of a tint) I was thinking it is like adding a positive amount of black -- ie a shade, with signs reversed.
my implementation is not correct:
agent.distance(hex_to_rgb(target), shade_func(hex_to_rgb(colors[0]), 0.1)) - agent.distance(hex_to_rgb(target), tint_func(hex_to_rgb(colors[0]), -0.1))
produces 0.3239904390784106 instead of 0.
I'll probably be fixing that soon

Linearly evolutive color map

I am trying to create a colormap that should linearly vary according to a "w" value, from white-red to white-purple.
So...
For w = 1, the minimum value's color (0 for example) would be white and the maximum value's color (+ inf) would be red.
For w = 10 (example), the minimum value's color (0 for example) would be white and the maximum value's color (+ inf) would be orange.
For w = 30 (example), the minimum value's color (0 for example) would be white and the maximum value's color (+ inf) would be yellow.
and so on, until...
For w = 100 (example), the minimum value's color (0 for example) would be white and the maximum value's color (+ inf) would be purple.
I used this website to generate the image : https://g.co/kgs/utJPmw
I can get the first (w = 1) color map by using this code, but no idea on how to make it vary according to what I would like to :
import matplotlib.cm as cm
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
color_map_1 = cm.get_cmap('Reds', 256)
newcolors_1 = color_map_1(np.linspace(0, 1, 256))
color_map_1 = ListedColormap(newcolors_1)
Any idea to do such a thing in python would be so much welcome,
Thank you guys
I finally found the solution. Maybe this is not the cleanest way, but it works very well for what I want to do. The colormaps I create can vary from white-red to white-purple (color spectrum). 765 variations are possible here, but by adding some small changes to the code, it could vary much more or less, depending on what you want.
In the following code : using the create_custom_colormap function, you get as an output cmap and color_map. cmap is the matrix containing the (r,g,b) values. color_map is the object that can be used in matplotlib (imshow) as an actual colormap, on any image.
Using the following code, define the function we will need for this job:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
def create_image():
'''
Create some random image on which we will apply the colormap. Any other image could replace this one, with or without extent.
'''
dx, dy = 0.015, 0.05
x = np.arange(-4.0, 4.0, dx)
y = np.arange(-4.0, 4.0, dy)
X, Y = np.meshgrid(x, y)
extent = np.min(x), np.max(x), np.min(y), np.max(y)
def z_fun(x, y):
return (1 - x / 2 + x**5 + y**6) * np.exp(-(x**2 + y**2))
Z2 = z_fun(X, Y)
return(extent, Z2)
def create_cmap(**kwargs):
'''
Create a color matrix and a color map using 3 lists of r (red), g (green) and b (blue) values.
Parameters:
- r (list of floats): red value, between 0 and 1
- g (list of floats): green value, between 0 and 1
- b (list of floats): blue value, between 0 and 1
Returns:
- color_matrix (numpy 2D array): contains all the rgb values for a given colormap
- color_map (matplotlib object): the color_matrix transformed into an object that matplotlib can use on figures
'''
color_matrix = np.empty([256,3])
color_matrix.fill(0)
color_matrix[:,0] = kwargs["r"]
color_matrix[:,1] = kwargs["g"]
color_matrix[:,2] = kwargs["b"]
color_map = ListedColormap(color_matrix)
return(color_matrix, color_map)
def standardize_timeseries_between(timeseries, borne_inf = 0, borne_sup = 1):
'''
For lisibility reasons, I defined r,g,b values between 0 and 255. But the matplotlib ListedColormap function expects values between 0 and 1.
Parameters:
timeseries (list of floats): can be one color vector in our case (either r, g o r b)
borne_inf (int): The minimum value in our timeseries will be replaced by this value
borne_sup (int): The maximum value in our timeseries will be replaced by this value
'''
timeseries_standardized = []
for i in range(len(timeseries)):
a = (borne_sup - borne_inf) / (max(timeseries) - min(timeseries))
b = borne_inf - a * min(timeseries)
timeseries_standardized.append(a * timeseries[i] + b)
timeseries_standardized = np.array(timeseries_standardized)
return(timeseries_standardized)
def create_custom_colormap(weight):
'''
This function is at the heart of the process. It takes only one < weight > parameter, that you can chose.
- For weight between 0 and 255, the colormaps that are created will vary between white-red (min-max) to white-yellow (min-max).
- For weight between 256 and 510, the colormaps that are created will vary between white-green (min-max) to white-cyan (min-max).
- For weight between 511 and 765, the colormaps that are created will vary between white-blue (min-max) to white-purple (min-max).
'''
if weight <= 255:
### 0>w<255
r = np.repeat(1, 256)
g = np.arange(0, 256, 1)
g = standardize_timeseries_between(g, weight/256, 1)
g = g[::-1]
b = np.arange(0, 256, 1)
b = standardize_timeseries_between(b, 1/256, 1)
b = b[::-1]
if weight > 255 and weight <= 255*2:
weight = weight - 255
### 255>w<510
g = np.repeat(1, 256)
r = np.arange(0, 256, 1)
r = standardize_timeseries_between(r, 1/256, 1)
r = r[::-1]
b = np.arange(0, 256, 1)
b = standardize_timeseries_between(b, weight/256, 1)
b = b[::-1]
if weight > 255*2 and weight <= 255*3:
weight = weight - 255*2
### 510>w<765
b = np.repeat(1, 256)
r = np.arange(0, 256, 1)
r = standardize_timeseries_between(r, weight/256, 1)
r = r[::-1]
g = np.arange(0, 256, 1)
g = standardize_timeseries_between(g, 1/256, 1)
g = g[::-1]
cmap, color_map = create_cmap(r=r, g=g, b=b)
return(cmap, color_map)
Use the function create_custom_colormap to get the colormap you want, by giving as argument to the function a value between 0 and 765 (see 5 examples in the figure below):
### Let us create some image (any other could be used).
extent, Z2 = create_image()
### Now create a color map, using the w value you want 0 = white-red, 765 = white-purple.
cmap, color_map = create_custom_colormap(weight=750)
### Plot the result
plt.imshow(Z2, cmap =color_map, alpha=0.7,
interpolation ='bilinear', extent=extent)
plt.colorbar()

How to generate two sets of distinct points on a sphere in julia language?

I need to apply the PCA at different points of a spherical cap, but I don’t know how to build these sets of different points, I need at least 2 sets.
Here is a picture with the idea of what I need.
Spherical Cap
If I correctly understand, here is how I would do in R.
library(uniformly)
library(pracma)
library(rgl)
# sample points on a spherical cap
points_on_cap1 <- runif_on_sphericalCap(300, r = 2, h = 0.5)
# convert to spherical coordinates
sphcoords1 <- cart2sph(points_on_cap1)
# sample points on a spherical cap
points_on_cap2 <- runif_on_sphericalCap(300, r = 2, h = 0.5)
# rotate them, because this is the same spherical cap as before
points_on_cap2 <- rotate3d(points_on_cap2, 3*pi/4, 1, 1, 1)
# convert to spherical coordinates
sphcoords2 <- cart2sph(points_on_cap2)
# 3D plot
spheres3d(0, 0, 0, radius = 2, alpha = 0.5, color = "yellow")
points3d(points_on_cap1, color = "blue")
points3d(points_on_cap2, color = "red")
# 2D plot (of the spherical coordinates)
plot(
sphcoords1[, 1:2], xlim = c(-pi, pi), ylim = c(-pi/2, pi/2),
pch = 19, col = "blue"
)
points(sphcoords2[, 1:2], pch = 19, col = "red")
Do I understand?
Here is the function runif_on_sphericalCap:
function(n, r = 1, h){
stopifnot(h > 0, h < 2*r)
xy <- runif_in_sphere(n, 2L, 1)
k <- h * apply(xy, 1L, crossprod)
s <- sqrt(h * (2*r - k))
cbind(s*xy, r-k)
}
It always samples on a spherical cap with symmetry axis joining the center of the sphere to the North pole. That is why I do a rotation, to get another spherical cap.
Say me if I understand and I'll try to help you to convert the code to Julia.
EDIT: Julia code
using Random, Distributions, LinearAlgebra
function runif_in_sphere(n::I, d::I, r::R) where {I<:Integer, R<:Number}
G = Normal()
sims = rand(G, n, d)
norms = map(norm, eachrow(sims))
u = rand(n) .^ (1/d)
return r .* u .* broadcast(*, 1 ./ norms, sims)
end
function runif_on_sphericalCap(n::I, r::Number, h::Number) where {I<:Integer}
if h <= 0 || h >= 2*r
error("")
end
xy = runif_in_sphere(n, 2, 1.0)
k = h .* map(x -> dot(x,x), eachrow(xy))
s = sqrt.(h .* (2*r .- k))
return hcat(broadcast(*, s, xy), r .- k)
end

Filling pixels under or above some function

Seems like a simple problem, but I just cant wrap my head around it.
I have a config file in which I declare a few functions. It looks like this:
"bandDefinitions" : [
{
"0": ["x^2 + 2*x + 5 - y", "ABOVE"]
},
{
"0": ["sin(6*x) - y", "UNDER"]
},
{
"0": ["tan(x) - y", "ABOVE"]
}
]
These functions should generate 3 images. Every image should be filled depending on solution of equations, and provided position (Under or Above). I need to move the coordinate system to the center of the image, so I'm adding -y into the equation. Part of image which should be filled should be colored white, and the other part should be colored black.
To explain what I mean, I'm providing images for quadratic and sin functions.
What I'm doing is solve the equation for x in [-W/2, W/2] and store the solutions into the array, like this:
#Generates X axis dots and solves an expression which defines a band
#Coordinate system is moved to the center of the image
def __solveKernelDefinition(self, f):
xAxis = range(-kernelSize, kernelSize)
dots = []
for x in xAxis:
sol = f(x, kernelSize/2)
dots.append(sol)
print(dots)
return dots
I'm testing if some pixel should be colored white like this:
def shouldPixelGetNoise(y, x, i, currentBand):
shouldGetNoise = True
for bandKey in currentBand.bandDefinition.keys():
if shouldGetNoise:
pixelSol = currentBand.bandDefinition[bandKey][2](x, y)
renderPos = currentBand.bandDefinition[bandKey][1]
bandSol = currentBand.bandDefinition[bandKey][0]
shouldGetNoise = shouldGetNoise and pixelSol <= bandSol[i] if renderPos == Position.UNDER else pixelSol >= bandSol[i]
else:
break
return shouldGetNoise
def kernelNoise(kernelSize, num_octaves, persistence, currentBand, dimensions=2):
simplex = SimplexNoise(num_octaves, persistence, dimensions)
data = []
for i in range(kernelSize):
data.append([])
i1 = i - int(kernelSize / 2)
for j in range(kernelSize):
j1 = j - int(kernelSize / 2)
if(shouldPixelGetNoise(i1, j1, i, currentBand)):
noise = normalize(simplex.fractal(i, j, hgrid=kernelSize))
data[i].append(noise * 255)
else:
data[i].append(0)
I'm only getting good output for convex quadratic functions. If I try to combine them, I get a black image. Sin just doesn't work at all. I see that this bruteforce approach won't lead me anywhere, so I was wondering what algorithm should I use to generate these kinds of images?
As far as I understood, you want to plot your functions and fill up above or under of these functions. You might easily do this by creating a grid (i.e. a 2D Cartesian coordinate system) in numpy, and define your functions on the grid.
import numpy as np
import matplotlib.pyplot as plt
max_ax = 100
resolution_x = max_ax/5
resolution_y = max_ax/20
y,x = np.ogrid[-max_ax:max_ax+1, -max_ax:max_ax+1]
y,x = y/resolution_y, x/resolution_x
func1 = x**2 + 2*x + 5 <= -y
resolution_x = max_ax
resolution_y = max_ax
y,x = np.ogrid[-max_ax:max_ax+1, -max_ax:max_ax+1]
y,x = y/resolution_y, x/resolution_x
func2 = np.sin(6*x) <= y
func3 = np.tan(x) <= -y
fig,ax = plt.subplots(1,3)
ax[0].set_title('f(x)=x**2 + 2*x + 5')
ax[0].imshow(func1,cmap='gray')
ax[1].set_title('f(x)=sin(6*x)')
ax[1].imshow(func2,cmap='gray')
ax[2].set_title('f(x)=tan(x)')
ax[2].imshow(func3,cmap='gray')
plt.show()
Is this what you are looking for?
Edit: I adjusted the limits of x- and y-axes. Because, for example, sin(x) does not make much sense outside of the range [-1,1].

Selecting colors that are furthest apart

I'm working on a project that requires me to select "unique" colors for each item. At times there could be upwards of 400 items. Is there some way out there of selecting the 400 colors that differ the most? Is it as simple as just changing the RGB values by a fixed increment?
You could come up with an equal distribution of 400 colours by incrementing red, green and blue in turn by 34.
That is:
You know you have three colour channels: red, green and blue
You need 400 distinct combinations of R, G and B
So on each channel the number of increments you need is the cube root of 400, i.e. about 7.36
To span the range 0..255 with 7.36 increments, each increment must be about 255/7.36, i.e. about 34
Probably HSL or HSV would be a better representations than RGB for this task.
You may find that changing the hue gives better variability perception to the eye, so adjust your increments in a way that for every X units changed in S and L you change Y (with Y < X) units of hue, and adjust X and Y so you cover the spectrum with your desired amount of samples.
Here is my final code. Hopefully it helps someone down the road.
from PIL import Image, ImageDraw
import math, colorsys, os.path
# number of color circles needed
qty = 400
# the lowest value (V in HSV) can go
vmin = 30
# calculate how much to increment value by
vrange = 100 - vmin
if (qty >= 72):
vdiff = math.floor(vrange / (qty / 72))
else:
vdiff = 0
# set options
sizes = [16, 24, 32]
border_color = '000000'
border_size = 3
# initialize variables
hval = 0
sval = 50
vval = vmin
count = 0
while count < qty:
im = Image.new('RGBA', (100, 100), (0, 0, 0, 0))
draw = ImageDraw.Draw(im)
draw.ellipse((5, 5, 95, 95), fill='#'+border_color)
r, g, b = colorsys.hsv_to_rgb(hval/360.0, sval/100.0, vval/100.0)
r = int(r*255)
g = int(g*255)
b = int(b*255)
draw.ellipse((5+border_size, 5+border_size, 95-border_size, 95-border_size), fill=(r, g, b))
del draw
hexval = '%02x%02x%02x' % (r, g, b)
for size in sizes:
result = im.resize((size, size), Image.ANTIALIAS)
result.save(str(qty)+'/'+hexval+'_'+str(size)+'.png', 'PNG')
if hval + 10 < 360:
hval += 10
else:
if sval == 50:
hval = 0
sval = 100
else:
hval = 0
sval = 50
vval += vdiff
count += 1
Hey I came across this problem a few times in my projects where I wanted to display, say, clusters of points. I found that the best way to go was to use the colormaps from matplotlib (https://matplotlib.org/stable/tutorials/colors/colormaps.html) and
colors = plt.get_cmap("hsv")[np.linspace(0, 1, n_colors)]
this will output rgba colors so you can get the rgb with just
rgb = colors[:,:3]

Resources