RYB colors act like paint

Posted as an improved version (original was in Color blending code)

I wasn’t happy with the lack of deep blue. Still missing dark purple, but…

(Added, hmm, will have to try .5,0,.5 for purple again instead of 1,0,1, definitely too magenta-y)

First slider changes between RYB (Red/Yellow/Blue) color wheel, and HSV/RGB color wheel.

The meat of this, converting a RYB color range into something the LEDs can display, needs to be used in some ‘paint-y’ pattern now. It should properly mix colors (so if you put Yellow and Red together, you’d get Orange, and so on.) I’ll likely make some pattern that ‘splashes’ color up and then drips.

Added: I wasn’t sure of the real value of RYB, till I built a pattern using it, a pile of squares randomly adding up, and then cloned it to using RGB and then again to HSV, same core pattern, but I find the RYB version the most visually pleasing. I’ll share the patterns soonish for folks to compare. RYB feels bright and colorful…RGB feels more pastel (random colors generated, so less pure colors?) and HSV somehow “busier” (I am generating random H,S,V so could be due to more intensity variation). Not done with experimenting by a long shot.

Obsolete code - see below for v2
// RYB color wheel by Scruffynerf  v1.0 08-2021
// original RYB code by Dave Eddy https://github.com/bahamas10/ryb
export var wheeltype, brightness, sat
wheeltype = 0
brightness = 1
sat = 1

export function sliderWheelType(v){
  wheeltype = floor(v+.5)
}

export function sliderBrightness(v){
  brightness = v
}

export function sliderSaturation(v){
  sat = v
}

export function beforeRender(delta) {
 t1 = time(.15)
 resetTransform()
 translate(-.5, -.5)
 rotate(t1 * PI2)
}

export function render2D(index,x,y) {
  a = mod(atan2(y,x)/PI2,1)  // angle = hue
  if (wheeltype == 0) {
    rybwheel(a)
  } else {
    hsvwheel(a)
  }
}

function hsvwheel(a) {
    hsv(a, sat, brightness)
}

function rybwheel(a,r){
  // this is mashup of HSL and HSBRGB, to adjust for RYB behaviors
  // brightness (correctly) get darker towards 0, and saturation gets lighter towards 0
  v = 1-clamp((brightness/2),0,1)
  value = sat * min(v,1-v) + v
  s = value ? 2-(2*v/value) : 0
  v = value
  i = floor(a * 6)
  f = a * 6 - i
  q = v * (1 - (s * f))
  t = v * (1 - (s * (1 - f)))
  p = v * (1 - s)
  
  if (i == 0) {
    r = v; g = t; b = p
  } else if (i == 1) {
    r = q; g = v; b = p
  } else if (i == 2) {
    r = p; g = v; b = t
  } else if (i == 3) {
    r = p; g = q; b = v
  } else if (i == 4) {
    r = t; g = p; b = v
  } else if (i == 5) {
    r = v; g = p; b = q
  }
  
  ryb(r*r,g*g,b*b)
}

function ryb(R,Y,B) {
      rgb(getR(R, Y, B), getG(R, Y, B), getB(R, Y, B))
}

function cubicInt(t, A, B){
  return A + (t * t * (3 - 2 * t)) * (B - A);
}

/*  original matrix, see http://bahamas10.github.io/ryb/about.html
var MAGIC_COLORS = [
    [1,     1,     1],   // white
    [1,     1,     0],   // yellow
    [1,     0,     0],   // red
    [1,     0.5,   0],   // orange
    [0.163, 0.373, 0.6], // blue
    [0.0,   0.66,  0.2], // green
    [0.5,   0.0,   0.5], // purple
    [0.2,   0.094, 0.0]  // black
  ];
*/

/*   modified to improve LED color behavior, and also reduces math
var MAGIC_COLORS = [
    [1,     1,     1], // white
    [1,     1,     0], // yellow
    [1,     0,     0], // red
    [1,     0.5,   0], // orange
    [0,     0,     1], // blue
    [0,     1,     0], // green
    [1,     0,     1], // purple
    [0,     0,     0]  // black
  ];
*/

function getR(iR, iY, iB) {  // red
/*
      var x0 = cubicInt(iB, magic[0][0], magic[4][0]);
      var x1 = cubicInt(iB, magic[1][0], magic[5][0]);
      var x2 = cubicInt(iB, magic[2][0], magic[6][0]);
      var x3 = cubicInt(iB, magic[3][0], magic[7][0]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = cubicInt(iB, 1, 0);
  x1 = x0 // cubicInt(iB, 1, 0);
  x2 = 1  // cubicInt(iB, 1, 1);
  x3 = x0 // cubicInt(iB, 1, 0);
  y0 = x0 // cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}

function getG(iR, iY, iB) {  // green
/*
      var x0 = cubicInt(iB, magic[0][1], magic[4][1]);
      var x1 = cubicInt(iB, magic[1][1], magic[5][1]);
      var x2 = cubicInt(iB, magic[2][1], magic[6][1]);
      var x3 = cubicInt(iB, magic[3][1], magic[7][1]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = cubicInt(iB, 1, 0);
  x1 = 1 // cubicInt(iB, 1, 1);
  x2 = 0 // cubicInt(iB, 0, 0);
  x3 = cubicInt(iB, .5, 0);
  y0 = cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}

function getB(iR, iY, iB) {  // blue
/*
      var x0 = cubicInt(iB, magic[0][2], magic[4][2]);
      var x1 = cubicInt(iB, magic[1][2], magic[5][2]);
      var x2 = cubicInt(iB, magic[2][2], magic[6][2]);
      var x3 = cubicInt(iB, magic[3][2], magic[7][2]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = 1 // cubicInt(iB, 1, 1);
  x1 = 0 // cubicInt(iB, 0, 0);
  x2 = cubicInt(iB, 0, 1);
  x3 = 0 // cubicInt(iB, 0, 0); 
  y0 = cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}
1 Like

sometimes, you just need to keep looking, till you find a better answer

like this one

It literally compares the above RYB2RGB to a much simple method (math-wise, no cubics, just maxs,min,floors, etc…) that also seems to work better.

Time to reimplement yet again, and hopefully this time it’ll look right (I got blue working nicely, but purple wasn’t right, even with changes to the above)

Getting RYB working is one of those things where it seems wrong to do on RGB lights, but it really does work for some patterns to use RYB, and makes it visually ‘more what’s expected’

Wow, it’s definitely faster… 19fps for the first way, 31+fps for the new way.
And it looks good… I implemented both (plus the HSV wheel, so you can compare)
New additions: lets you drive the brightness to 100% (white) (especially to compare the cutoffs, and the most intense colors…), and stop or slow rotation to better compare the two…

// RYB (two ways) color wheel by Scruffynerf v2 08-2021
// v1 original RYB code (ryb1) by Dave Eddy https://github.com/bahamas10/ryb
// v2 uses an algo by Arah J. Leonard https://web.archive.org/web/20120302090118/http://www.insanit.net/tag/rgb-to-ryb/
// which I found in JS form at https://codepen.io/yukulele/pen/XMbrBJ

export var wheeltype, brightness, sat
wheeltype = 0
brightness = 1
sat = 1
rotation = 0

export function sliderWheelType(v){
  wheeltype = floor(v*2+.5)
}

export function sliderBrightness(v){
  brightness = v
}

export function sliderSaturation(v){
  sat = v
}

export function sliderRotate(v){
  rotation = v
}

export function beforeRender(delta) {
 t1 = time(.15)
 resetTransform()
 translate(-.5, -.5)
 rotate(t1 * PI2 * rotation)
}

export function render2D(index,x,y) {
  a = mod(atan2(y,x)/PI2,1)  // angle = hue
  if (wheeltype == 0) {
    hsvwheel(a)
  } else {
    rybwheel(a)
  }
}

function hsvwheel(a) {
    hsv(a, sat, brightness*brightness)
}

function rybwheel(a,r){
  // this is mashup of HSL and HSBRGB, to adjust for RYB behaviors
  // brightness (correctly) get darker towards 0, and saturation gets lighter towards 0
  if (wheeltype == 1) {
    // for this rgb conversion, v needs to be reversed
    v = 1-clamp((brightness),0,1)
  } else {
    // for this rgb conversion, v direction is ok
    v = clamp((brightness),0,1)
  }
  value = sat * min(v,1-v) + v
  s = value ? 2-(2*v/value) : 0
  v = value
  i = floor(a * 6)
  f = a * 6 - i
  q = v * (1 - (s * f))
  t = v * (1 - (s * (1 - f)))
  p = v * (1 - s)
  
  if (i == 0) {
    r = v; g = t; b = p
  } else if (i == 1) {
    r = q; g = v; b = p
  } else if (i == 2) {
    r = p; g = v; b = t
  } else if (i == 3) {
    r = p; g = q; b = v
  } else if (i == 4) {
    r = t; g = p; b = v
  } else if (i == 5) {
    r = v; g = p; b = q
  }
  if (wheeltype == 1) {
    ryb1(r,g,b)
  } else {
    ryb2(r,g,b)
  }
}

function ryb2(r, y, b){
	// Remove the whiteness from the color.
	var iWhite = min(min(r, y), b);
	
	r -= iWhite;
	y -= iWhite;
	b -= iWhite;

	var iMaxYellow = max(max(r, y), b);

	// Get the green out of the yellow and blue
	var g = min(y, b);
	
	y -= g;
	b -= g;

	if (b > 0 && g > 0)
	{
		b *= 2.0;
		g *= 2.0;
	}
	
	// Redistribute the remaining yellow.
	r += y;
	g += y;

	// Normalize to values.
	var iMaxGreen = max(max(r, g), b);
	
	if (iMaxGreen > 0)
	{
		var iN = iMaxYellow / iMaxGreen;
		
		r *= iN;
		g *= iN;
		b *= iN;
	}
	
	// Add the white back in.
	r += iWhite;
	g += iWhite;
	b += iWhite;
	// Save the RGB
	rgb(r*r, g*g, b*b)
}


function ryb1(R,Y,B) {
      r = getR(R, Y, B)
      g = getG(R, Y, B)
      b = getB(R, Y, B)
      rgb(r*r,g*g,b*b)
}

function cubicInt(t, A, B){
  return A + (t * t * (3 - 2 * t)) * (B - A);
}

/*  original matrix, see http://bahamas10.github.io/ryb/about.html
var MAGIC_COLORS = [
    [1,     1,     1],   // white
    [1,     1,     0],   // yellow
    [1,     0,     0],   // red
    [1,     0.5,   0],   // orange
    [0.163, 0.373, 0.6], // blue
    [0.0,   0.66,  0.2], // green
    [0.5,   0.0,   0.5], // purple
    [0.2,   0.094, 0.0]  // black
  ];
*/

/*   modified to improve LED color behavior, and also reduces math
var MAGIC_COLORS = [
    [1,     1,     1], // white
    [1,     1,     0], // yellow
    [1,     0,     0], // red
    [1,     0.5,   0], // orange
    [0,     0,     1], // blue
    [0,     1,     0], // green
    [.5,    0,    .5], // purple
    [0,     0,     0]  // black
  ];
*/

function getR(iR, iY, iB) {  // red
/*
      var x0 = cubicInt(iB, magic[0][0], magic[4][0]);
      var x1 = cubicInt(iB, magic[1][0], magic[5][0]);
      var x2 = cubicInt(iB, magic[2][0], magic[6][0]);
      var x3 = cubicInt(iB, magic[3][0], magic[7][0]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = cubicInt(iB, 1, 0);
  x1 = x0 // cubicInt(iB, 1, 0);
  x2 = cubicInt(iB, 1, .5);
  x3 = x0 // cubicInt(iB, 1, 0);
  y0 = x0 // cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}

function getG(iR, iY, iB) {  // green
/*
      var x0 = cubicInt(iB, magic[0][1], magic[4][1]);
      var x1 = cubicInt(iB, magic[1][1], magic[5][1]);
      var x2 = cubicInt(iB, magic[2][1], magic[6][1]);
      var x3 = cubicInt(iB, magic[3][1], magic[7][1]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = cubicInt(iB, 1, 0);
  x1 = 1 // cubicInt(iB, 1, 1);
  x2 = 0 // cubicInt(iB, 0, 0);
  x3 = cubicInt(iB, .5, 0);
  y0 = cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}

function getB(iR, iY, iB) {  // blue
/*
      var x0 = cubicInt(iB, magic[0][2], magic[4][2]);
      var x1 = cubicInt(iB, magic[1][2], magic[5][2]);
      var x2 = cubicInt(iB, magic[2][2], magic[6][2]);
      var x3 = cubicInt(iB, magic[3][2], magic[7][2]);
      var y0 = cubicInt(iY, x0, x1);
      var y1 = cubicInt(iY, x2, x3);
*/
  x0 = 1 // cubicInt(iB, 1, 1);
  x1 = 0 // cubicInt(iB, 0, 0);
  x2 = cubicInt(iB, 0, .5);
  x3 = 0 // cubicInt(iB, 0, 0); 
  y0 = cubicInt(iY, x0, x1);
  y1 = cubicInt(iY, x2, x3);
  return cubicInt(iR, y0, y1);
}
1 Like