In support of some work related to color theory and linguistic relativity, I wrote some Python code to create visualizations of the RGB color space. A demo of some of the possible visualizations can be seen below (note, the animated GIF was created using GIMP):
The relevant code follows (Python 3.7):
%matplotlib inline import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from mpl_toolkits.mplot3d.art3d import Poly3DCollection import random import copy def get_color_verts(r_rng, g_rng, b_rng): """ Given a range in the RGB space, compiles list of vertices to be drawn. Used to draw the cubes representing each of the 64 defined colors. """ r_min, r_max = r_rng g_min, g_max = g_rng b_min, b_max = b_rng # In order: r+g wrt-b, r+b wrt-g, g+b wrt-r verts = [[(r_max,g_min,b_min),(r_max,g_max,b_min),(r_min,g_max,b_min),(r_min,g_min,b_min)], [(r_max,g_min,b_max),(r_max,g_max,b_max),(r_min,g_max,b_max),(r_min,g_min,b_max)], [(r_max,g_min,b_min),(r_max,g_min,b_max),(r_min,g_min,b_max),(r_min,g_min,b_min)], [(r_max,g_max,b_min),(r_max,g_max,b_max),(r_min,g_max,b_max),(r_min,g_max,b_min)], [(r_min,g_min,b_max),(r_min,g_max,b_max),(r_min,g_max,b_min),(r_min,g_min,b_min)], [(r_max,g_min,b_max),(r_max,g_max,b_max),(r_max,g_max,b_min),(r_max,g_min,b_min)] ] return verts def get_color_middle(r_rng, g_rng, b_rng): """ Given a range in the RGB space, returns the color represented by the center point of the cube. Used to draw the color of a given cube. """ r_min, r_max = r_rng g_min, g_max = g_rng b_min, b_max = b_rng return (((r_min+r_max)/2)/255.0, ((g_min+g_max)/2)/255.0, ((b_min+b_max)/2)/255.0) def get_random(min_val, max_val): return random.randint(min_val, max_val) def get_random_color(r_rng, g_rng, b_rng): """ Given a range in the RGB space, selects a random point within the cube and returns the associated color. Used to draw random points within each shown cube. """ val = [get_random(r_rng[0], r_rng[1]), get_random(g_rng[0], g_rng[1]), get_random(b_rng[0], b_rng[1])] return val def adjust_points_per_cube(orig, color_list): """ Depending on the number of colors which will be drawn, adjust the number of points shown in each. Used to balance performance. """ ret = orig if orig == 0: if not color_list or len(color_list) > 8: ret = 50 elif len(color_list) == 1: ret = 1000 elif len(color_list) <= 3: ret = 500 elif len(color_list) <= 8: ret = 300 return ret def get_filtered_color_list(all_colors, inc_colors): ret = [] if not inc_colors: ret = copy.deepcopy(all_colors) else: for c in all_colors: if c["key"] in inc_colors: ret.append(c) return ret def set_axis_limits(ax, show_full_grid, color_list): """ Calculates the limits of each axis, depending on which subset of the RGB space is being shown. """ if show_full_grid: ax.set(xlim3d = (0, 255), ylim3d = (0, 255), zlim3d = (0, 255)) else: # Get min/max for chart limits (start w/inverse) r_min, r_max, g_min, g_max, b_min, b_max = [255, 0, 255, 0, 255, 0] for c in color_list: r_min = min(r_min, c["r"][0]) g_min = min(g_min, c["g"][0]) b_min = min(b_min, c["b"][0]) r_max = max(r_max, c["r"][1]) g_max = max(g_max, c["g"][1]) b_max = max(b_max, c["b"][1]) # ax.set(xlim3d = (r_min, r_max), ylim3d = (g_min, g_max), zlim3d = (b_min, b_max)) ax.set(xlim3d = (r_min-5, r_max+5), ylim3d = (g_min-5, g_max+5), zlim3d = (b_min-5, b_max+5)) def draw_random_points(points_per_cube, color_list): for col in color_list: for i in range(points_per_cube): ci = get_random_color(col["r"], col["g"], col["b"]) area = (15)**2 ax.scatter(ci[0], ci[1], ci[2], color = [ci[0]/255.0, ci[1]/255.0, ci[2]/255.0], s = area) def draw_cubes(ax, color_list, hilite_list, cube_alpha, hilite_alpha, edge_color): for c in color_list: p = Poly3DCollection(get_color_verts(c["r"], c["g"], c["b"]), alpha = cube_alpha) p.set_color(get_color_middle(c["r"], c["g"], c["b"])) if c["key"] in hilite_list: p.set_alpha(hilite_alpha) if edge_color: p.set_edgecolor(edge_color) ax.add_collection3d(p) def set_axis_tickmarks(ax, x, y, z): ax.set_xticks(x) ax.set_yticks(y) ax.set_zticks(z) def set_axis_ticklabels(ax, x, y, z): ax.set_xticklabels(x) ax.set_yticklabels(y) ax.set_zticklabels(z) def create_color_list(): """ Creates a list of 64 "colors", by evenly dividing RGB space into 64 equal-sized cubes. This is accomplished by dividing each axis (R, G, B) into quarters. """ # NOTE: A 3-character "key" is used to identify each of the 64 cubes. Used in filtering the display. keys_ = [["B", "G", "L", "M"],["A", "E", "I", "O"],["R", "S", "T", "V"]] rng_ = [[0, 63], [64, 127], [128, 191], [192, 255]] colors = [] for idx_r, rng_r in enumerate(rng_): for idx_g, rng_g in enumerate(rng_): for idx_b, rng_b in enumerate(rng_): color = {} color["key"] = "{}{}{}".format(keys_[0][idx_r], keys_[1][idx_g], keys_[2][idx_b]) color["r"] = rng_r color["g"] = rng_g color["b"] = rng_b colors.append(color) return colors colors = create_color_list() show_boxes = True # Determines whether surfaces of each defined color cube is drawn. show_points = True # Set to True to draw random points of example shades in each selected cube. points_per_cube = 0 # Number of random shades to draw. Set to 0 to use defaults, which balances for performance. box_alpha = .1 # Alpha value for all shown colors. hilite_alpha = 1.0 # Alpha value used for colors found in hilite_cubes list. edge_color = [0, 0, 0] # Color used to draw the edges of vertices. show_full_grid = False # Setting to False will focus space on just the shown cubes. color_filter = [] # Keys of colors to be included. Set to empty [] to include all colors. hilite_cubes = [] # Keys of colors to hilight (will use hilite_alpha value). Ignored if empty []. points_per_cube = adjust_points_per_cube(points_per_cube, color_filter) x_colors = [] x_colors = get_filtered_color_list(colors, color_filter) fig = plt.figure(figsize = [14, 14]) ax = fig.gca(projection = '3d') set_axis_limits(ax, show_full_grid, x_colors) ax.set(xlabel = "RED", ylabel = "GREEN", zlabel = "BLUE") ticks_ = [32, 96, 160, 223] set_axis_tickmarks(ax, x=ticks_, y=ticks_, z=ticks_) ticklbl_ = ["1", "2", "3", "4"] set_axis_ticklabels(ax, x=ticklbl_, y=ticklbl_, z=ticklbl_) if show_points: draw_random_points(points_per_cube, x_colors) if show_boxes: draw_cubes(ax, x_colors, hilite_cubes, box_alpha, hilite_alpha, edge_color) ax.view_init(45, 45) # fig.savefig("demo-1.png", bbox_inches = "tight")
Lorem ipsum