Complex gradient loop

Hi there!

I am trying to do a gradient with multiple focal points, like the one you can see on this Daniel Otoole artwork.

I have found this useful code on happycoding and I am playing with it to get something more asymmetric, but I have still some questions. How could I introduce more focal points if the gradient depends on stroke, that only have three values?

void setup() {
  size(256, 256);
  noSmooth();
}

void draw() {
  for(int y = 0; y < height; y++){
    for(int x = 0; x < width; x++){
      float distanceFromTopLeft = dist(x, 200, 0, 100);
      float distanceFromTopRight = dist(50, y, width/2, 0);
      float distanceFromBottomLeft = dist(x, 100, 50, height);

      stroke(distanceFromTopLeft, distanceFromTopRight, distanceFromBottomLeft);
      point(x, y);
    }
  }
}
1 Like

Beautiful and interesting

Do you know this lerpColor() / Reference / Processing.org

maybe you can calc for every point the average color resulting from lerpColor over all focal points?

Make an arraylist of focal points: position and color

And for loop over it and sum and then divide by number

Use floats

so let’s say that they are the focal points:

size(600,800);

strokeWeight (50);

stroke(200,255,255);
point(0,0);

stroke(0,0,0);
point(0,200);

stroke(255,200,200);
point(550,400);

stroke(255,0,255);
point(50,500);

stroke(255,0,0);
point(600,800);

If now I try to make arraylist, would it be something like this?

size(600,800);

PVector point = new PVector(0, 0);
points.add(point);

}

void Draw() {
  background(255);
  
   for(int i = 0; i < height; i++) {
   PVector point = points.get(i);
   float x1 = 0;
   float y1 = 0;
   color a = color(200,255,255); 
   point(x1,y1);
  }
}

here is an example of a PVector array





PVector[] list = {
  new PVector (0, 0),
  new PVector (0, 200),
  new PVector (550, 400),
  new PVector (50, 500),
  new PVector (600, 800)
};

void setup() {
  size(600, 800);

  // PVector point = new PVector(0, 0);
  // points.add(point);
}

void draw() {
  background(255);

  for (int i = 0; i < height; i++) {

    //PVector pointMy = list[i];

    float x1 = 0;
    float y1 = 0;

    color a = color(200, 255, 255);
    point(x1, y1);
  }

  for (PVector pv : list) {
    rect(pv.x, pv.y, 6, 6);
  }

  noLoop();
}

1 Like

additionally, store colors in a parallel list





PVector[] listPV = {
  new PVector (0, 0),
  new PVector (0, 200),
  new PVector (550, 400),
  new PVector (50, 500),
  new PVector (600, 800)
};

color[] listColor = {
  color (200, 255, 255),
  color (0, 0, 0),
  color (255, 200, 200),
  color (255, 0, 255),
  color (255, 0, 0)
};

void setup() {
  size(600, 800);

  // PVector point = new PVector(0, 0);
  // points.add(point);
}

void draw() {
  background(255);

  for (int i = 0; i < height; i++) {

    //PVector pointMy = list[i];

    float x1 = 0;
    float y1 = 0;

    color a = color(200, 255, 255);
    point(x1, y1);
  }

  int i2=0;
  for (PVector pv : listPV) {
    fill(listColor[i2]);
    rect(pv.x, pv.y, 6, 6);
    i2++;
  }

  noLoop();
}

1 Like

thank you so much! So in which loop is the gradient? Here?

for (int i = 0; i < height; i++) {

or here?

for (PVector pv : listPV) {

for each i (or rather

    for (int x = 0; x < width; x++) {   
        for (int y = 0; y < height; y++) {

)
check the dist to all focal points using for (PVector pv : listPV) {

1 Like

here is an example

(which is just made up and not accurate)





PVector[] listPV = {
  new PVector (0, 0),
  new PVector (0, 200),
  new PVector (550, 400),
  new PVector (50, 500),
  new PVector (600, 800)
};

color[] listColor = {
  color (200, 255, 255),
  color (0, 0, 0),
  color (255, 200, 200),
  color (255, 0, 255),
  color (255, 0, 0)
};

void setup() {
  size(600, 800);

  // PVector point = new PVector(0, 0);
  // points.add(point);
}

void draw() {
  background(255);

  for (int i = 0; i < height; i++) {

    //PVector pointMy = list[i];

    float x1 = 20;
    float y1 = 0;

    color a = getCol (x1, i); // color(200, 255, 255);
    fill(a);
    noStroke();
    rect(x1, i, 5, 5);
  }

  int i2=0;
  for (PVector pv : listPV) {
    fill(listColor[i2]);
    rect(pv.x, pv.y, 6, 6);
    i2++;
  }

  noLoop();
}

color getCol(float x1, float y1) {

  color c1=0;
  int i2=0;

  for (PVector pv : listPV) {
    float f1 = listPV[i2].dist(new PVector(x1, y1));
    float amt = map(f1, 0, 600, 1, 0) ;
    c1 = lerpColor ( c1, listColor[i2], amt) ;
    i2++;
  }

  return c1;
}

1 Like

thank you so much again, but defintety this is to complex for me. I ask myself if I could find a template like this or this for processing

Or would it be easier to blur a hard edge drawing like they mention here?

i don’t know, can’t help you

probably should be easy

  • to take for each point A of the canvas
  • the colors of each of the 5 rects B
  • (each with a different weight/score, depending how far B is from A)
  • and add / average the colors (with that score) to find the color for A.

But I didn’t achieve it.

Here I tried without lerpColor but with calculating the average for red, green and blue values


PVector[] listPV = {
  new PVector (0, 0),
  new PVector (0, 200),
  new PVector (550, 400),
  new PVector (50, 500),
  new PVector (600-24, 800-24)
};

color[] listColor = {
  color (200, 255, 255),
  color (0, 0, 0),
  color (255, 200, 200),
  color (255, 0, 255),
  color (255, 0, 0)
};

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

void setup() {
  size(600, 800);
}

void draw() {
  background(255);

  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      color a = getCol (x, y); // color(200, 255, 255);

      noFill();
      stroke(a);
      point(x, y);
    }
  }

  int i2=0;
  for (PVector pv : listPV) {
    fill(listColor[i2]);
    stroke(0);
    rect(pv.x, pv.y, 6, 6);
    noFill();
    rect(pv.x, pv.y, 6, 16);
    rect(pv.x, pv.y, 26, 26);

    i2++;
  }

  noLoop();
}

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

color getCol(float x1, float y1) {

  color c1=0;
  float red=0, green=0, blue=0;
  int i2=0;
  c1=color(random(256));
  // c1=0;

  for (PVector pv : listPV) {
    float distMy;
    // distMy = pv.dist(new PVector(x1, y1));
    distMy = dist(pv.x, pv.y, x1, y1);
    float amt = map(distMy, 0, 444, 255, 0) ;

    //  c1 = lerpColor ( c1, listColor[i2], amt) ;
    c1=listColor[i2];

    red   += red(c1)*amt;
    green += green(c1)*amt;
    blue  += blue(c1)*amt;

    i2++;
  }//for

  // i2--;

  // take average
  red/=(float)i2;
  green/=(float)i2;
  blue/=(float)i2;
  // println(red, green, blue);

  return color(red, green, blue);
  // return   c1;
}

1 Like

new version

looks better, still not perfect

you can DRAG the focal points now


PVector[] listPV = {
  new PVector (0, 0),
  new PVector (0, 200),
  new PVector (550, 400),
  new PVector (50, 500),
  new PVector (600-24, 800-24)
};

color[] listColor = {
  color (100, 255, 255),
  color (255, 0, 0),
  color (255, 200, 200),
  color (255, 0, 255),
  color (255, 0, 0)
};

// mouse dragging
boolean hold=false;
int hold_i=-1;

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

void setup() {
  size(600, 800);
}

void draw() {
  background(255);

  noFill();
  color c1;
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      c1 = getCol (x, y); // color(200, 255, 255);
      stroke(c1);
      point(x, y);
    }
  }

  // optional: show all focal points
  showFocalPoints();

  // mouse dragging
  if (hold&&hold_i!=-1) {
    listPV[hold_i].x=mouseX;
    listPV[hold_i].y=mouseY;
  }
  //noLoop();
}

void mousePressed() {
  // mouse dragging
  hold=true;
  int i2=0;
  for (PVector pv : listPV) {
    if (dist(mouseX, mouseY, pv.x, pv.y)<21) {
      hold_i=i2;
      return;
    }
    i2++;
  }//for
}

void mouseReleased() {
  // mouse dragging
  hold=false; // reset
  hold_i=-1;
}

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

color getCol(float x1, float y1) {

  color c1=0;
  float red=0, green=0, blue=0;
  int i2a=0;
  c1=color(random(256));
  // c1=0;

  ///for (PVector pv : listPV) {
  for ( int i2=0; i2<listPV.length-1; i2++ ) {

    // we look at one focal point and the next

    PVector pv1 = listPV[i2];
    c1  = listColor[i2];
    float distMy1 = dist(pv1.x, pv1.y, x1, y1);

    PVector pv2 = listPV[i2+1];
    color c2  = listColor[i2+1];
    float distMy2 =  dist(pv2.x, pv2.y, x1, y1);

    // 0.5
    // println(distMy1 / distMy2);
    /// exit();

    float amt =  distMy1 / distMy2    ;
    amt=map(amt, 0, 3, 0, 1);

    color c3 = lerpColor (c1, c2, amt);

    // ----

    // sum it
    red   += red(c3);
    green += green(c3);
    blue  += blue(c3);

    i2a=i2;
  }//for

  // take average
  //red    /= (float)i2a;
  //green  /= (float)i2a;
  //blue   /= (float)i2a;
  // println(red, green, blue);

  // OR just map the sums to 0..256
  float maxRange1 = 1220;
  red = map(red, 0, maxRange1, 0, 256);
  green = map(green, 0, maxRange1, 0, 256);
  blue = map(blue, 0, maxRange1, 0, 256);

  return color(red, green, blue);
}

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

// NOT IN USE 
color getColOld(float x1, float y1) {

  color c1=0;
  float red=0, green=0, blue=0;
  int i2=0;
  c1=color(random(256));
  // c1=0;

  for (PVector pv : listPV) {
    float distMy;
    // distMy = pv.dist(new PVector(x1, y1));
    distMy = dist(pv.x, pv.y, x1, y1);
    float amt = map(distMy, 0, 444, 255, 0) ;

    //  c1 = lerpColor ( c1, listColor[i2], amt) ;
    c1=listColor[i2];

    red   += red(c1)*amt;
    green += green(c1)*amt;
    blue  += blue(c1)*amt;

    i2++;
  }//for

  // i2--;

  // take average
  red/=(float)i2;
  green/=(float)i2;
  blue/=(float)i2;
  // println(red, green, blue);

  return color(red, green, blue);
  // return   c1;
}

void showFocalPoints() {

  int i2=0;
  for (PVector pv : listPV) {
    fill(listColor[i2]);
    stroke(0);
    rect(pv.x, pv.y, 6, 6);
    noFill();
    rect(pv.x, pv.y, 6, 16);
    rect(pv.x, pv.y, 26, 26);

    i2++;
  }
}


Remarks

  • of course you can / should join the 2 parallel ArrayLists by creating a class FocalPoint and say
    ArrayList<FocalPoint> listFocalPoints = new ArrayList();

the function getCol I have problems with; it’s the core question, how can I

  • for each pixel A in the canvas
  • calc the lerpColor between (in this approach) 2 focal points B1 and B2 (with colors c1 and c2)
  • with an amt that is between 0 and 1 and calculated so that it reflects the distances between A-B1 and A-B2.

So when A-B1 is 600 and A-B2 is 300,

  • A is closer to B2,
  • the amt value between colors c1 and c2 should be around 0.6 I guess (making the color c2 belonging to B2 dominant over c1).
  • But what is the exact formula?

And when I have a couple of pairs B1/B2, B2/B3…calculated, how can I calc the average color?

1 Like

Here is a version that computes the gradient in a shader using a Gaussian function for the color spread.

GradientShader-2023-09-27-18-28-53

Press Enter to generate a new set of random points and colors. Space toggles showing the control points and text. The + key saves an image. The right and left arrows change the number of control points. Up and down arrows change the strength of the Gaussian. Up arrow narrows the Gaussian which intensifies the individual color around each control point. Increase it enough and you get a voronoi diagram of the points. If you decrease it enough, everything blurs to the average color of all of the points. By default, the Gaussian is scaled based on the number of points – for more points, you want a tighter curve so each color shows more distinctly.

GradientShader-2023-09-27-18-29-16

I pass the control points to the shader in a Nx2 texture. The top row is the positions with x in AR and y in GB, each scaled from 0 to 65535 across the image. The bottom row is the control point color.

PImage inputs;
PGraphics grad;
PShader gradShader;

int N = 4;
float strength = 1.;
boolean bShowPoints = true;
boolean bSaveFrame = false;

void setup() {
  size( 800, 600, P3D );
  //fullScreen( P3D, SPAN );
  colorMode( HSB, 1, 1, 1, 1 );
  grad = createGraphics( width, height, P3D );
  gradShader = new PShader( this, gradVert, gradFrag );
  initControlPoints();
  gradShader.set( "strength", strength );
  noLoop();
}

void initControlPoints() {
  inputs = createImage( N, 2, ARGB );
  inputs.loadPixels();
  for( int i=0; i<N; i++ ) {
    int x = (int)random(65536);
    int y = (int)random(65536);
    color c = color( random(1), random(0.25, 1), random(0.25, 1) );
    inputs.pixels[ i ] = (x << 16) | y;
    inputs.pixels[ i+N ] = c;
  }
  inputs.updatePixels();
  gradShader.set( "inputs", inputs );
  gradShader.set( "N", N );
}

void draw() {
  gradShader.set( "bShowPoints", bShowPoints );
  grad.beginDraw();
  grad.noStroke();
  grad.shader( gradShader );
  grad.rect( 0, 0, grad.width, grad.height );
  grad.resetShader();
  grad.endDraw();
  image( grad, 0, 0 );
  if( bShowPoints ) 
    text( str(N)+"    "+str(strength), 8, 16 );
  if( bSaveFrame ) {
    String fileName = getClass().getSimpleName() + "-" +
      nf(year(),4) + "-" + nf(month(),2) + "-" + nf(day(),2) + "-" +
      nf(hour(),2) + "-" + nf(minute(),2) + "-" + nf(second(),2) + ".png";
    save( fileName );
    println( "saved " + fileName );
    bSaveFrame = false;
  }
}

void keyPressed() {
  if( keyCode == ENTER ) initControlPoints();
  else if( keyCode == DOWN ) { 
    strength /= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == UP ) { 
    strength *= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == LEFT ) { N = max(N-1, 1);  initControlPoints(); }
  else if( keyCode == RIGHT ) { N++;  initControlPoints(); }
  else if( key == ' ' ) bShowPoints = !bShowPoints;
  else if( key == '+' ) bSaveFrame = true;
  redraw();
}


String[] gradVert = {"""
#version 330
uniform mat4 transformMatrix;
in vec4 position;
void main() {
  gl_Position = transformMatrix * position;
}
"""};


String[] gradFrag = {"""
#version 330
precision highp float;
uniform vec2 resolution;
uniform sampler2D inputs;
uniform int N;
uniform bool bShowPoints;
uniform float strength;
out vec4 fragColor;

void main() {
  float aspect = resolution.x/resolution.y;
  vec2 uv = gl_FragCoord.xy / resolution.y;
  vec3 col = vec3(0.);
  float totw = 0.;
  for( int i=0; i<N; i++ ) {
    vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
    float x = (posData.a * 256 + posData.r)*aspect;
    float y = posData.g * 256 + posData.b;
    vec2 pos = vec2( x, y ) / 65536.0;
    vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
    float d = length( uv - pos );
    float w = exp( -d*d*N*strength );
    if( w > 0 ) {
      col += c * w;
      totw += w;
    }
  }
  if( totw > 0. ) col /= totw;

  if( bShowPoints ) {
    for( int i=0; i<N; i++ ) {
      vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
      float x = (posData.a * 256 + posData.r)*aspect;
      float y = posData.g * 256 + posData.b;
      vec2 pos = vec2( x, y ) / 65536.0;
      vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
      float d = length( uv - pos );
      if( d < 0.01 ) col = vec3(1.);
      if( d < 0.007 ) col = c;
    }
  }

  fragColor = vec4( col, 1. );
}
"""};
3 Likes

This is pretty good. Only downside is the color-mixing is done in RGB space which leads to rather greyish colour as colours are mixed.

I tried with the oklab code below and it didn’t seem to help much. Still get the banding with the occasional grays – it just seems to move them around a bit. Let me know if you can improve it.

Use o to toggle oklab vs rgb.

PImage inputs;
PGraphics grad;
PShader gradShader;

int N = 4;
float strength = 1.;
boolean bShowPoints = true;
boolean bSaveFrame = false;
boolean bOklab = true;

void setup() {
  size( 800, 600, P3D );
  //fullScreen( P3D, SPAN );
  colorMode( HSB, 1, 1, 1, 1 );
  grad = createGraphics( width, height, P3D );
  gradShader = new PShader( this, gradVert, gradFrag );
  initControlPoints();
  gradShader.set( "strength", strength );
  gradShader.set( "bOklab", bOklab );
  noLoop();
}

void initControlPoints() {
  inputs = createImage( N, 2, ARGB );
  inputs.loadPixels();
  for( int i=0; i<N; i++ ) {
    int x = (int)random(65536);
    int y = (int)random(65536);
    color c = color( random(1), random(0.25, 1), random(0.25, 1) );
    inputs.pixels[ i ] = (x << 16) | y;
    inputs.pixels[ i+N ] = c;
  }
  inputs.updatePixels();
  gradShader.set( "inputs", inputs );
  gradShader.set( "N", N );
}

void draw() {
  gradShader.set( "bShowPoints", bShowPoints );
  grad.beginDraw();
  grad.noStroke();
  grad.shader( gradShader );
  grad.rect( 0, 0, grad.width, grad.height );
  grad.resetShader();
  grad.endDraw();
  image( grad, 0, 0 );
  if( bShowPoints ) {
    text( str(N)+"    "+str(strength), 8, 16 );
    text( bOklab ? "oklab" : "rgb", 8, 32 );
  }
  if( bSaveFrame ) {
    String fileName = getClass().getSimpleName() + "-" +
      nf(year(),4) + "-" + nf(month(),2) + "-" + nf(day(),2) + "-" +
      nf(hour(),2) + "-" + nf(minute(),2) + "-" + nf(second(),2) + ".png";
    save( fileName );
    println( "saved " + fileName );
    bSaveFrame = false;
  }
}

void keyPressed() {
  if( keyCode == ENTER ) initControlPoints();
  else if( keyCode == DOWN ) { 
    strength /= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == UP ) { 
    strength *= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == LEFT ) { N = max(N-1, 1);  initControlPoints(); }
  else if( keyCode == RIGHT ) { N++;  initControlPoints(); }
  else if( key == ' ' ) bShowPoints = !bShowPoints;
  else if( key == '+' ) bSaveFrame = true;
  else if( key == 'o' ) { 
    bOklab = !bOklab;
    gradShader.set( "bOklab", bOklab );
  }
  redraw();
}


String[] gradVert = {"""
#version 330
uniform mat4 transformMatrix;
in vec4 position;
void main() {
  gl_Position = transformMatrix * position;
}
"""};


String[] gradFrag = {"""
#version 330
precision highp float;
uniform vec2 resolution;
uniform sampler2D inputs;
uniform int N;
uniform bool bShowPoints;
uniform bool bOklab;
uniform float strength;
out vec4 fragColor;

// oklab code from https://www.shadertoy.com/view/ttcyRS by IQ
    // https://bottosson.github.io/posts/oklab
    const mat3 kCONEtoLMS = mat3(                
         0.4121656120,  0.2118591070,  0.0883097947,
         0.5362752080,  0.6807189584,  0.2818474174,
         0.0514575653,  0.1074065790,  0.6302613616);
    const mat3 kLMStoCONE = mat3(
         4.0767245293, -1.2681437731, -0.0041119885,
        -3.3072168827,  2.6093323231, -0.7034763098,
         0.2307590544, -0.3411344290,  1.7068625689);

void main() {
  float aspect = resolution.x/resolution.y;
  vec2 uv = gl_FragCoord.xy / resolution.y;
  vec3 col = vec3(0.);
  float totw = 0.;

  for( int i=0; i<N; i++ ) {
    vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
    float x = (posData.a * 256 + posData.r)*aspect;
    float y = posData.g * 256 + posData.b;
    vec2 pos = vec2( x, y ) / 65536.0;
    vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
    c = pow( c, vec3(2.2) );  // un-gamma correct
    if( bOklab ) c = pow( kCONEtoLMS*c, vec3( 1./3. ) );
    float d = length( uv - pos );
    float w = exp( -d*d*N*strength );
    if( w > 0 ) {
      col += c * w;
      totw += w;
    }
  }
  if( totw > 0. ) col /= totw;
  if( bOklab ) col = kLMStoCONE*(col*col*col);
  col = pow( col, vec3( 1./2.2 ) );  // gamma correct

  if( bShowPoints ) {
    for( int i=0; i<N; i++ ) {
      vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
      float x = (posData.a * 256 + posData.r)*aspect;
      float y = posData.g * 256 + posData.b;
      vec2 pos = vec2( x, y ) / 65536.0;
      vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
      float d = length( uv - pos );
      if( d < 0.01 ) col = vec3(1.);
      if( d < 0.007 ) col = c;
    }
  }

  fragColor = vec4( col, 1. );
}
"""};

The worst gray-banding seems to happen from red to blue. Maybe we just can’t interpolate nicely across the horseshoe.

GradientShader-2023-09-28-07-52-14

2 Likes

well, I can hardly believe it, it is just what I was looking for! Voronoi diagram it is just a great idea for this. How could I limit the number of control points? I see

int N = 4;

and

for( int i=0; i<N; i++ ) {

but if I change int N = 6 I can not make just 6 control points in the loop. Could you please help me?

1 Like

If I change to int N = 6;, I get 6 points.

What do you get that is different? What are you wanting to get instead?

1 Like

yes, you are right, I made a mistake. Again, it is more amazing I can imagine

1 Like