How do I lerp() colors on a curve? (non linear interpolation)

How do I interpolate two (or maybe even more) colors in a non-linear way along a curve? For example, instead of the mid-point being 50/50, I would want it to be 25/75, or shifted according to the control point.

I tried this, but I guess the problem is where to put the control points for each color? Changing the control points w1, w2 doesn’t seem to do anything unless I set the numbers high, and then the colors are not a smooth transition but introduce weird colors in between, because it’s not interpolating each rgb channel at the same speed.

function curveColors(col1, col2, w1, w2, t) {

    let r1 = map(col1[0], 0, 255, 0, 1);

    let g1 = map(col1[1], 0, 255, 0, 1);

    let b1 = map(col1[2], 0, 255, 0, 1);

    let r2 = map(col2[0], 0, 255, 0, 1);

    let g2 = map(col2[1], 0, 255, 0, 1);

    let b2 = map(col2[2], 0, 255, 0, 1);

    let rc1 = r1 + abs(r1 - r2) * w1;

    let rc2 = r2 - abs(r1 - r2) * w2;

    let gc1 = g1 + abs(g1 - g2) * w1;

    let gc2 = g2 - abs(g1 - g2) * w2;

    let bc1 = b1 + abs(b1 - b2) * w1;

    let bc2 = b2 - abs(b1 - b2) * w2;


    let lowR = returnLowest(col1[0],col2[0]);

    let lowG = returnLowest(col1[1],col2[1]);

    let lowB = returnLowest(col1[2],col2[2]);

    let r = constrain(map(curvePoint(rc1, r1, r2, rc2, t), 0, 1, 0, 255),lowR[0],lowR[1]);

    let g = constrain(map(curvePoint(gc1, g1, g2, gc2, t), 0, 1, 0, 255),lowG[0],lowG[1]);

    let b = constrain(map(curvePoint(bc1, b1, b2, bc2, t), 0, 1, 0, 255),lowB[0],lowB[1]);

    let finalColor = [r, g, b];

     

    return finalColor;

}

function returnLowest(a, b){

 let low;

 let high;

    if (a<=b){

     low = a;

     high = b;

 } else {low = b; high = a;}

 let both = [low, high];

 return both;

}

You can use the built in lerpColor function, like so:

lerpColor(color0, color1, amount)

where color1 and color2 are two p5 Color variables, and amount is the amount you want to lerp between them (between 0 and 1, with 0 being all color0 and 1 being all color1).

To avoid a linear interpolation, you can map your value between 0 and 1 using some other function (e.g. squaring the value to have a bias towards color1)

1 Like

I find the idea great to get the amt data for lerpColor from a function.

So I made a class where you can

  • play with a bezier curve (instead of a function) and
  • the bezier curve (its y-value) is fed into the color.

Core idea is in the function testlerpColor().

It’s a bit tricky.

But when somebody wants to know more, I can explain. I received help from the forum for this. I was mixing up curve and bezier. Bad. See reference.

This could be extended with bezierVertex() or curveVertex() I guess.

Chrisir

// Test for a new bezier Class that can do bezier and bezierpoint (2D) to set a color from bezierPoint y-value 

BezierClass mybezier; 

// ---------------------------------------------------------------------------------------------

void setup() {

  size(1500, 900);

  final int STANDARD_DISTANCE=100;

  mybezier = new  BezierClass (
    width/2-31, 33, // coordinates for the first point / anchor
    width/2-STANDARD_DISTANCE, height/2+STANDARD_DISTANCE, //  control point (cp1)
    width/2+STANDARD_DISTANCE, height/2+STANDARD_DISTANCE, //  control point 
    width/2+31, 33  // coordinates for the second point / anchor 
    );
}

void draw() {
  background(#24B41D);

  fill(0); 
  text("Bezier\n"
    +"Demo for Bezier y value to color\n"
    +"You can drag the two red control points (and the anchor points) with the mouse. \n"
    +"Move the mouse to move the white ball and the color on the bezier (mouseX->amt)", 
    14, 14);

  testbezier();
}

//-------------------------------------------------------------------------------------------------------

void testbezier () {
  mybezier.displaybezier();    // show entire bezier  
  mybezier.testbezierData();   // show circles with texts
  mybezier.moveOnbezierWithMouseX(); // OR moveOnbezier()
  mybezier.testlerpColor();   // lerpColor
  mybezier.drag();            // for dragging with the mouse
}

//-------------------------------------------------------------------------------------------------------

void mousePressed() {
  mybezier.mousePressedClass();
}

void mouseReleased() {
  mybezier.mouseReleasedClass();
}

// ===============================================================================================

class BezierClass {

  final int UNKNOWN = -1; 

  // bezier data 
  PVector anchor1, cp1, cp2, anchor2;

  // the amt for lerp 
  float amt=0;

  // indicates which point we drag (cp1, cp2 or anchor1 or anchor2)
  int drag = -1; 

  // the min and max y for the current bezier
  float minY, maxY; 

  // how strong the cp influences !!! Doesn't work good........
  float factorCP = 1; 

  // constr - Parameters: you call the constr as you would bezier command (2D version) - see reference
  BezierClass ( float f0, float f1, 
    float f2, float f3, 
    float f4, float f5, 
    float f6, float f7 ) {
    // constr 
    anchor1 = new PVector (f0, f1);  
    cp1     = new PVector (f2, f3);
    cp2     = new PVector (f4, f5);
    anchor2 = new PVector (f6, f7);

    setMinYAndMaxY();
  } // constr 

  // ---

  void setMinYAndMaxY () {
    // init 
    minY = 1000000; 
    maxY = -100000;
    // simulate bezier 
    for (int i = 0; i <= 100; i++) {
      float y = bezierPoint(anchor1.y, factorCP * cp1.y, factorCP * cp2.y, anchor2.y, 
        i/100.0);
      if (y<minY) 
        minY = y; 
      if (y>maxY) 
        maxY = y;
    }//for
  }

  // ---

  void displaybezier() {
    stroke(255, 0, 0); //RED 
    strokeWeight(1);
    noFill();
    bezier (
      anchor1.x, anchor1.y, 
      factorCP * cp1.x, factorCP * cp1.y, 
      factorCP * cp2.x, factorCP * cp2.y, 
      anchor2.x, anchor2.y
      );
  }

  // ---

  void moveOnbezier() {
    // AUTO move 
    // resulting point 
    float x, y; 
    x = bezierPoint(anchor1.x, factorCP * cp1.x, factorCP * cp2.x, anchor2.x, amt); 
    y = bezierPoint( anchor1.y, factorCP * cp1.y, factorCP * cp2.y, anchor2.y, amt);
    fill(255); // WHITE
    noStroke(); 
    ellipse(x, y, 5, 5);
    amt+=0.01;
    if (amt>=1) 
      amt=0.0;
  }

  void moveOnbezierWithMouseX() {
    // mouseX move
    float x, y; 
    // mouseX -> amt :
    amt=map(mouseX, 0, width, 
      0, 1); 
    // amt ->bezier point
    x = bezierPoint(anchor1.x, factorCP * cp1.x, factorCP * cp2.x, anchor2.x, amt); 
    y = bezierPoint( anchor1.y, factorCP * cp1.y, factorCP * cp2.y, anchor2.y, amt);

    //x = bezierPoint(factorCP * cp1.x, anchor1.x, anchor2.x, factorCP * cp2.x, amt); 
    //y = bezierPoint(factorCP * cp1.y, anchor1.y, anchor2.y, factorCP * cp2.y, amt);
    fill(255); // WHITE
    noStroke(); 
    ellipse(x, y, 5, 5);
  }

  // ---

  void testlerpColor() {
    // now it gets interesting 

    // mouseX to lerpColor 
    // mouseX -> amt : 
    float amt=map(mouseX, 0, width, 
      0, 1); 
    // amt -> bezierPoint Y 
    float y = bezierPoint( anchor1.y, factorCP * cp1.y, factorCP * cp2.y, anchor2.y, amt);  
    // y-value -> lerp color colorAmt
    float colorAmt = map ( y, minY, maxY, 
      0, 1  );
    // lerp color colorAmt -> color 
    fill( lerpColor(  color(255, 0, 0), color(0, 0, 255), colorAmt ) ); 
    rect (100, 100, 66, 66);

    fill(255);
    text ("min and max: " + minY+"  "+maxY 
      + " -> " +y 
      + " \ncolor amt = "+colorAmt, 100, 180);
  }

  // ---

  void testbezierData() {
    makeCircleWithText("cp1", cp1, color (255, 0, 0)) ;  
    makeCircleWithText("cp2", cp2, color (255, 0, 0)) ;  
    makeCircleWithText("anchor1", anchor1, color (0, 0, 255)) ;  
    makeCircleWithText("anchor2", anchor2, color (0, 0, 255)) ;
  }//method

  void makeCircleWithText(String txt, PVector pv, color col) {
    noStroke(); 
    fill(col);
    ellipse (pv.x, pv.y, 18, 18);

    fill(255);
    text(txt, 
      pv.x+15, pv.y-8);
  }

  // ---

  void mousePressedClass() {
    if (dist(mouseX, mouseY, cp1.x, cp1.y) < 33) 
      drag=0; 
    else if (dist(mouseX, mouseY, cp2.x, cp2.y) < 33) 
      drag=1;
    else if (dist(mouseX, mouseY, anchor1.x, anchor1.y) < 33) 
      drag=2;
    else if (dist(mouseX, mouseY, anchor2.x, anchor2.y) < 33) 
      drag=3;
  }//method 

  void mouseReleasedClass() {
    drag=UNKNOWN; // reset
    setMinYAndMaxY();
  }

  // ---

  void drag() {
    if (drag==UNKNOWN) 
      return; 

    switch (drag) {
    case 0:
      cp1.x=mouseX; 
      cp1.y=mouseY;
      break; 

    case 1:
      cp2.x=mouseX; 
      cp2.y=mouseY;
      break;

    case 2:
      anchor1.x=mouseX; 
      anchor1.y=mouseY;
      break;

    case 3:
      anchor2.x=mouseX; 
      anchor2.y=mouseY;
      break;

    case UNKNOWN:
      //ignore
      break;

    default:
      // Error
      break;
    }//switch

    setMinYAndMaxY();
  }//method
  //
}//class
//

and here is the idea of a sin function going into lerpColor

(by the way, since y gets bigger when you go downwards (!) on the screen, the values might be upside down. So when the sin curve is higher, its y-value is lower)

// Test to set a color from a sin function 

// ---------------------------------------------------------------------------------------------

void setup() {
  size(1500, 900);
  final int STANDARD_DISTANCE=100;
}

void draw() {
  background(#24B41D);

  // show text 
  fill(0); 
  text("\n"
    +"Demo for using sin function as amt for lerp color - move mouse left and right \n", 
    14, 14);

  // show sin
  for (int i=0; i <=360; i++) {
    float result2 = sin(radians(i));
    noStroke(); 
    fill(255);
    ellipse(map(i, 0, 360, 0, width), result2*120+width/2, 
      12, 12);
  }

  // show color rect 
  testlerpColor();
}

// -------------------------------------------------------------------------------

void testlerpColor() {
  // now it gets interesting 

  // mouseX to lerpColor 
  // mouseX -> angle : 
  float angle=map(mouseX, 0, width, 
    0, TWO_PI);
  // angle -> sin 
  float result = sin(angle); // that's the formula 
  //   sin -> amt 
  float amt = map( result, -1, 1, 0, 1); 

  // amt -> lerp color:   RED                BLUE       amt 
  fill( lerpColor(  color(255, 0, 0), color(0, 0, 255), amt ) ); 
  rect (100, 100, 66, 66);

  fill(255);
  text ("amt: "
    + amt
    + "\n" 
    + nf( amt, 1, 2), 
    100, 180);
}//func 
//


And a square function

// Test to set a color from function 

// ---------------------------------------------------------------------------------------------

void setup() {
  size(1500, 900);
  final int STANDARD_DISTANCE=100;
}

void draw() {
  background(#24B41D);

  // show text 
  fill(0); 
  text("\n"
    +"Demo for using sin function as amt for lerp color - move mouse left and right \n", 
    14, 14);

  // show sin
  for (int i=0; i <= 5; i++) {
    float result2 = i*i;
    noStroke(); 
    fill(255);
    ellipse(map(i, 0, 5, 0, width), result2*10 + 10, 
      12, 12);
  }

  // show color rect 
  testlerpColor();
}


// -------------------------------------------------------------------------------

void testlerpColor() {
  // now it gets interesting 

  // mouseX to lerpColor 
  // mouseX -> xValue : 
  float xValue=map(mouseX, 0, width, 
    0, 5);
  // xValue ->  
  float result = xValue*xValue; // that's the formula 
  // -> amt 
  float amt = map( result, 0, 25, 0, 1); 

  // amt -> lerp color:   RED                BLUE       amt 
  fill( lerpColor(  color(255, 0, 0), color(0, 0, 255), amt ) ); 
  rect (100, 100, 66, 66);

  fill(255);
  text ("amt: "
    + amt
    + "\n" 
    + nf( amt, 1, 2), 
    100, 180);
}//func 
//