Blended color ramp generator

I’ve seen a lot of color ramps that employ hue and saturation shifting. Honestly, that’s a much more straightforward approach towards that end. However, I have a harder time getting that approach to work through my brain. So, I decided to implement the color blending logic to generate a “blended” color ramp. Instead of shifting hue/saturation in each color stop, a chosen color is blended by varying amounts to each stop. I’m uncertain if this approach is more effective, but I find it interesting.

Instructions

This script is intended to run within Figma through its plugin API. The fastest way to run this script is by copy-pasting within Scripter.

            
// Number of steps in the blended color ramp
const STEPS = 5;

// The starting lightness value for the ramp
const LIGHTNESS_START = .25;

// The ending lightness value for the ramp
const LIGHTNESS_END = .95;

// The amount the foreground value is blended with the background (.1 = 10%, .9 = 90%). This is the equivalent of the foreground color's opacity. In the context of this code, each of these blend stops are for the purpose of generating the color ramp.
const BLEND_AMOUNT_START = .6;
const BLEND_AMOUNT_MID = 0;
const BLEND_AMOUNT_END = .6;

// The base hue of the ramp that all colors are derived from
const BASE_HUE = 170

// The satuation for all ramp colors
const BASE_SATURATION = 100

// The midpoint lightness between start and end
const BASE_LIGHTNESS = (LIGHTNESS_START + LIGHTNESS_END)/2

// The hue shift in degrees for the foreground color which is used for blending
const BLEND_COLOR_HUE_SHIFT = 150

// An array of lightness values in the ramp
const LIGHTNESS_RAMP = easeRamp(LIGHTNESS_START, LIGHTNESS_END, STEPS, easeOutSine)

// An array of amount of blending of values in the ramp
const BLEND_RAMP = easeRampLoop(BLEND_AMOUNT_START, BLEND_AMOUNT_MID, BLEND_AMOUNT_END, STEPS, easeOutSine)

const FILL_SEED = [{
	type: "SOLID",
	visible: true,
	opacity: 1,
	blendMode: "NORMAL",
	color: {
		r: 1,
		g: 1,
		b: 1
	},
	boundVariables: {}
}]

// Shift hue 180 degrees to make complimentary HSL color
let complementaryHSL = [shiftHue(BASE_HUE, BLEND_COLOR_HUE_SHIFT), BASE_SATURATION/100, BASE_LIGHTNESS]


let baseColorValues = new Array()
let blendColorValues = new Array()

let ramp = new Array()

// Use base HSL color to formulate a ramp
for(var i = 0; i < LIGHTNESS_RAMP.length; i++) {
  baseColorValues.push([BASE_HUE/360, BASE_SATURATION/100, LIGHTNESS_RAMP[i]])
  blendColorValues.push([complementaryHSL[0], complementaryHSL[1], LIGHTNESS_RAMP[i]])
}

// Use same ramp logic to create a complementary ramp
let baseRgb, complementaryRgb
let baseColsRgb = new Array() 
let complementaryColsRgb = new Array()
for(var i = 0; i < baseColorValues.length; i++) {
  baseRgb = hslToRgb(baseColorValues[i]);
  complementaryRgb = hslToRgb(blendColorValues[i]);
  console.log(baseColorValues[i])
  baseRgb = {r: baseRgb[0]*255, g: baseRgb[1]*255, b: baseRgb[2]*255}
	baseColsRgb.push(baseRgb)
	
  complementaryRgb = {r: complementaryRgb[0]*255, g: complementaryRgb[1]*255, b: complementaryRgb[2]*255}
	complementaryColsRgb.push(complementaryRgb)
  let blend = 
	ramp.push(blendColors(complementaryRgb, baseRgb, BLEND_RAMP[i]))

}

let frame:FrameNode = figma.createFrame();
frame.name = 'Blended color ramp';

frame.layoutMode = 'VERTICAL';
frame.paddingLeft = 10;
frame.paddingRight = 10;
frame.paddingTop = 10;
frame.paddingBottom = 10;
frame.itemSpacing = 10;
frame.layoutSizingHorizontal = 'HUG';
frame.layoutSizingVertical = 'HUG';


figma.currentPage.appendChild(frame)
let rampRect, rampRect2
for(var i = 0; i < ramp.length; i++) {
  rampRect = figma.createRectangle();
	rampRect.resize(100, 25);

	let rampFill = FILL_SEED;
	rampFill[0].color.r = ramp[i].r;
	rampFill[0].color.g = ramp[i].g;
	rampFill[0].color.b = ramp[i].b;

	rampRect.fills = rampFill;
	frame.appendChild(rampRect)
}

function blendColors(foreground, background, amount) {
	return {
		r: (foreground.r/255 * amount + background.r/255 * (1 - amount)),
		g: (foreground.g/255 * amount + background.g/255 * (1 - amount)),
		b: (foreground.b/255 * amount + background.b/255 * (1 - amount))
	}
}

function easeInSine(x) {
  return 1 - Math.cos((x * Math.PI) / 2);
}

function easeOutSine(x) {
  return Math.sin((x * Math.PI) / 2);
}

function easeRamp(min, max, steps, ease) {
  let i = 0;
  let step = 1/(steps-1);
  let ramp = new Array()
  while(i < steps) {
      ramp.push(( ease(i*step)*(max-min))+min )
      i++
  }
  return ramp
}

function easeRampLoop(min, mid, max, steps, ease) {

	let rampIn = new Array();
	let rampOut = new Array();
	let midSteps
	if(steps % 2) {
		midSteps = Math.floor(steps/2);
	} else {
		midSteps = (steps/2);
	}
	
  let i = 0;
  let step = 1/(midSteps);
  let ramp = new Array()
  while(i < midSteps) {
		rampIn.push(( ease(i*step)*(mid-min))+min )
		i++
  }
	if(steps % 2) {
		rampIn.push(mid)
	}
	rampOut = rampIn.slice();
	rampOut = rampOut.reverse()

	if(steps % 2) {
		rampOut.shift();
	}
	ramp = rampIn.concat(rampOut);

  return ramp
	
}

function shiftHue(h, delta) {
	h*=360
  h+= delta;
  if (h > 360) { h -= 360; }

  return h /= 360;
}

function rgbToHsl(rgb) {
  let r = rgb[0];
  let g = rgb[1];
  let b = rgb[2];
  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if(max == min){
    h = s = 0; // achromatic
  } else{
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch(max){
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }

  return [h, s, l];
}

function hslToRgb(hsl){

  let h = hsl[0]
  let s = hsl[1]
  let l = hsl[2]
  var r, g, b;
  if(s == 0){
      r = g = b = l; 
  } else{
    var hue2rgb = function hue2rgb(p, q, t){
      if(t < 0) t += 1;
      if(t > 1) t -= 1;
      if(t < 1/6) return p + (q - p) * 6 * t;
      if(t < 1/2) return q;
      if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
      return p;
    }

    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    var p = 2 * l - q;
    r = hue2rgb(p, q, h + 1/3);
    g = hue2rgb(p, q, h);
    b = hue2rgb(p, q, h - 1/3);
  }

  return [r,g,b];
}