Per-vertex color gradient fill

I’m trying to color each vertex of a rectangle differently to create a nice, smooth gradient fill inside. But when I run the sketch below, it looks like the shapes are broken into triangles and I see a clear band - is there any way I can make all gradients to connect smoother? Maybe I should look into GLSL shaders? Any pointers would be appreciated.

void setup() {
  size(400, 400, P2D);
}

void draw() {
  noStroke();
  beginShape();
  fill(255, 0, 0);
  vertex(0, 0);
  fill(0, 255, 0);
  vertex(width, 0);
  fill(0, 0, 255);
  vertex(width, height);
  fill(255, 255, 0);
  vertex(0, height);
  endShape();
}
1 Like

I think the problem is that you defined the vertices in the wrong order, which leads to a bad interpretation of the polygon. If I run your sketch I get this output image, which shows the clear green band:

When you switch the second and last vertex (to counter-clockwise order), then it seems to be more equally distributed:

image

The code for that would be like this:

void setup() {
  size(400, 400, P2D);
}

void draw() {
  noStroke();
  beginShape();
  fill(255, 0, 0);
  vertex(0, 0);
  fill(0, 255, 0);
  vertex(0, height);
  fill(0, 0, 255);
  vertex(width, height);
  fill(255, 255, 0);
  vertex(width, 0);
  endShape();
}
3 Likes

Even simpler solution: You could also leave your code as it is and tell processing which polygon mode for the shape it should use.

beginShape(QUAD);

3 Likes

Wow, I learned something new today! Thank you very much, @cansik !

1 Like

I am trying @cansik 's suggestion and it works to some degree. It seems to depend on what colors are being used. Some colors bands are more pronounced:

void setup() {
  size(400, 400, P2D);
  frameRate(4);
}

void draw() {
  noStroke();

  beginShape();
  fill(random(255), random(255), random(255));
  vertex(0, 0);
  fill(random(255), random(255), random(255));
  vertex(0, width);
  fill(random(255), random(255), random(255));
  vertex(width, height);
  fill(random(255), random(255), random(255));
  vertex(width, 0);
  endShape();
}

I am wondering whether there’s a way to interpolate between the two points on the diagonal.

1 Like

Well, perhaps create finer control by building a square out of more vertices? – you don’t have to use only 4. Intermediate points can be created dynamically with PVector.lerp. Intermediate colors can be created with lerpColor. The space and color ratios don’t need to be the same, of course…

thanks, @jeremydouglass
I ditched the idea of using only 4 vertices, and instead implemented bilinear interpolation for all the pixels, and now it’s all looking good.

Below is my crude attempt at implementing the algorithm. If anyone has a better suggestion, I’d appreciate it.

/* 
 bilinear interpolation. needs colors for each corner
 0 | 2
 --+--
 1 | 3
 */
color[] interpolateBilinear(int w, int h, color[] corners) {
  color[] arr = new color[w * h];
  for (int x = 0; x < w; x++) {
    float xinc = (float) x/w;
    color t = lerpColor(corners[0], corners[2], xinc);
    color b = lerpColor(corners[1], corners[3], xinc);
    for (int y = 0; y < h; y++) {
      float yinc = (float) y/h;
      color m = lerpColor(t, b, yinc);
      arr[x + y*w] = m;
    }
  }
  return arr;
}
3 Likes

Lerping every color is too slow for me. I needed gradients too, here is my solution (P2D / P3D):


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


void draw() {
  background(0);
  
  gradient(color(255, 0, 0), color(0, 255, 0), NW);
  rect(10, 10, 100, 100);
  
  noGradient();
  rect(10, 10 + 100, 100, 100);

  gradient(color(255, 0, 0), color(0, 255, 0), color(0, 0, 255), color(255, 255, 0));
  rect(10 + 100, 10, 100, 100);
  
  
  
}


final static int N = UP;
final static int E = RIGHT;
final static int S = DOWN;
final static int W = LEFT;
final static int NE = 5;
final static int SE = 6;
final static int NW = 7;
final static int SW = 8;

boolean gradient = false;
int[] gradient_colors = new int[4]; // lt, rt, rb, lb


void gradient(int color_1, int color_2, int direction) {
  gradient = true;
  int LT = 0;
  int RT = 1;
  int RB = 2;
  int LB = 3;

  switch(direction) {
  case RIGHT: 
    {
      gradient_colors[LT] = color_1;
      gradient_colors[RT] = color_2;
      gradient_colors[RB] = color_2;
      gradient_colors[LB] = color_1;
      break;
    }
  case UP: 
    {
      gradient_colors[LT] = color_2;
      gradient_colors[RT] = color_2;
      gradient_colors[RB] = color_1;
      gradient_colors[LB] = color_1;
      break;
    }
  case LEFT: 
    {
      gradient_colors[LT] = color_2;
      gradient_colors[RT] = color_1;
      gradient_colors[RB] = color_1;
      gradient_colors[LB] = color_2;
      break;
    }
  case DOWN: 
    {
      gradient_colors[LT] = color_1;
      gradient_colors[RT] = color_1;
      gradient_colors[RB] = color_2;
      gradient_colors[LB] = color_2;
      break;
    }
  case NE: 
    {
      int color_lerp = lerpColor(color_1, color_2, 0.5f);
      gradient_colors[LT] = color_lerp;
      gradient_colors[RT] = color_2;
      gradient_colors[RB] = color_lerp;
      gradient_colors[LB] = color_1;
      break;
    }
    case SE: 
    {
      int color_lerp = lerpColor(color_1, color_2, 0.5f);
      gradient_colors[LT] = color_1;
      gradient_colors[RT] = color_lerp;
      gradient_colors[RB] = color_2;
      gradient_colors[LB] = color_lerp;
      break;
    }
    case SW: 
    {
      int color_lerp = lerpColor(color_1, color_2, 0.5f);
      gradient_colors[LT] = color_lerp;
      gradient_colors[RT] = color_1;
      gradient_colors[RB] = color_lerp;
      gradient_colors[LB] = color_2;
      break;
    }
    case NW: 
    {
      int color_lerp = lerpColor(color_1, color_2, 0.5f);
      gradient_colors[LT] = color_2;
      gradient_colors[RT] = color_lerp;
      gradient_colors[RB] = color_1;
      gradient_colors[LB] = color_lerp;
      break;
    }
    
  }
}


void gradient(int color_1, int color_2, int color_3, int color_4) {
  
  gradient = true;
  gradient_colors[0] = color_1;
  gradient_colors[1] = color_2;
  gradient_colors[2] = color_3;
  gradient_colors[3] = color_4;
}


void noGradient() {
  gradient = false;
}



void rect(float a, float b, float c, float d) {
  if (!gradient) {
    g.rect(a, b, c, d);
  } else {
    gradient_rect(a, b, c, d);
  }
}



void gradient_rect(float a, float b, float c, float d) {
  
  int bk_fill = g.fillColor;
  
  float hradius, vradius;
  switch (g.rectMode) {
  case CORNERS:
    break;
  case CORNER:
    c += a; 
    d += b;
    break;
  case RADIUS:
    hradius = c;
    vradius = d;
    c = a + hradius;
    d = b + vradius;
    a -= hradius;
    b -= vradius;
    break;
  case CENTER:
    hradius = c / 2.0f;
    vradius = d / 2.0f;
    c = a + hradius;
    d = b + vradius;
    a -= hradius;
    b -= vradius;
  }

  if (a > c) {
    float temp = a; 
    a = c; 
    c = temp;
  }

  if (b > d) {
    float temp = b; 
    b = d; 
    d = temp;
  }

  float x1 = a;
  float y1 = b;
  float x2 = c;
  float y2 = d;


  beginShape(QUADS);

  fill(gradient_colors[0]); 
  vertex(x1, y1);

  fill(gradient_colors[1]);
  vertex(x2, y1);

  fill(gradient_colors[2]);
  vertex(x2, y2);

  fill(gradient_colors[3]); 
  vertex(x1, y2);

  endShape();
  
  g.fillColor = bk_fill;
}

1 Like

Hi, i came here to try to figure out a solution to re-paint a shape per vertex, without having to explicitly re-enter the vertexes. Don’t think this is possible according to what i have read so far.

I read these comments and i think you might be happy with this library i put together after reading a great tutorial about color gradients in Processing: https://luisferreira.space/projects/BetterGradients/

2 Likes

As a general Java performance rule avoid LinkedList completely.

Even more so when accessing it by index, especially inside loops:


B/c on each index access, the whole LinkedList container needs to be traversed until the index is found:

1 Like

@LuisFerreira thanks for sharing this library. Great documentation as well. Have you submitted it for the PDE Contributions Manager yet?

Please consider posting this announcement to the forum Libraries and/or gallery.

1 Like

I heard a similar comment just yesterday, after spending the last years using it. I will slowly refactor my projects to use ArrayList instead.

Thanks :slight_smile:

1 Like

Thank Jeremy. I haven’t made it official because i want to use the library a bit more, find out which interfaces are missing that could be handy, and be confident that it works without bugs. Same thing for a couple of other libraries i developed myself and i am using at the moment.

When i feel that these are stable and easy to use i will make an official release. In the meantime i am happy if people use them and report back any strange behaviour:

As for the reason that brought me to this topic, i wanted to paint a shape per pixel, after the shape is defined or even loaded from an OBJ file. The function setFill(color) is probably know by most people, but i only found out about setFill(index, color) yesterday :sweat_smile: worked like a charm for my purpose

3 Likes

Thanks!

I hadn’t seen this before. Is index a child shape index or a pixel index?

As far as i understand, its the index of the vertex. If you call getVertexCount() in a certain shape, you are able to use any index up to this count, anything above it will generate an error.

That’s actually how i found this method the first time, i wanted to set a fill with a color and alpha and i saw this method setFill(int, int), which i immediately assumed to be color+alpha. But it is index+color and now i finally found a proper use for it :slight_smile:

3 Likes