blendMode and transparency

Hi,

First, thank you to all and everyone involved with Processing. I only recently discovered Processing and I instantly found it to be super inspirational.

I hope someone can help me with some guidance in relation to blendModes and transparency

I’m trying to add gradient fill to a shape. I’m doing this by drawing the shape and then drawing colored lines over the shape.

My problem is that if the background color changes, the gradient overlay shows.
Or, in other words, my implementation only creates desired result if background color is “aligned” to the blendMode.

Here is an example. Any guidance on how to achieve transparency around the shape will be highly appreciated.

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

void draw() {
  translate(width/2, height/2);
  fill(0);
  ellipse(0, 0, 200, 200);
  
  blendMode(ADD);
  for (float i = -100; i <= 100; i++) {
    float inter = map(i, -100, 100, 0, 1);
    color c = lerpColor(color(255), color(0), inter);
    stroke(c);
    line(-100, i, 100, i);
  }
}
2 Likes

Hi,

Welcome to the forum! :wink:

Your post made me think of a solution without using any blendMode or transparency but rather drawing separate lines to display the gradient.

/**
 * Draw a circle filled with a gradient at location [x, y]
 */
void gradientFilledCircle(int x, int y, int diameter, float rotation, color from, color to) {
  int radius = diameter / 2;
  
  push();
  translate(x, y);
  rotate(rotation);
  
  // Draw each line
  for (int lineX = -radius; lineX < radius; lineX++) {
    float ratio = map(lineX, -radius, radius, 0, 1);
    
    // Compute y location of the line point
    float lineY = sin(acos(ratio * 2 - 1)) * radius;
    
    // or this (from https://www.rapidtables.com/math/trigonometry/arccos/sin-of-arccos.html)
    //float lineY = sqrt(1 - pow(ratio * 2 - 1, 2)) * radius;
    
    // The gradient color
    stroke(lerpColor(from, to, ratio));
    
    line(lineX, lineY, lineX, -lineY);
  }
  
  pop();
}

void setup() {
  size(500, 500);
}

void draw() {
  background(#16a085);
  
  translate(width/2, height/2);
  gradientFilledCircle(0, 0, 200, HALF_PI, color(#d35400), color(#2980b9));
  
  noLoop();
}

test

It now works with any background :+1:

The rotation works but when animated, there’s glitches because of the lines and float precision but it can be solved by drawing it on a separate PGraphics buffer and rotate it.

Anyway is this what you wanted to do?

2 Likes

Thanks @josephh,

Your reply is useful and I have bookmarked it for later reference.

However, I think I could have been more clear in my original post.

I used an ellipse for simplicity while what I’m actually looking to achieve should support arbitrery shapes.

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

void draw() {
  int s = 100;
  translate(width/2, height/2);
  fill(0);
  push();
  translate(0, -s);
  fill(0);
  beginShape();
  vertex(-s * 0.10, s * 0.10);
  quadraticVertex(0, 0, s * 0.10, s * 0.1);
  vertex(s * 0.10, s * 0.10);
  
  vertex(s * 0.8, s * 0.9);
  quadraticVertex(s * 0.9, s, s * 0.8, s * 1.1);
  vertex(s * 0.8, s * 1.1);
  
  vertex(s * 0.1, s * 1.9);
  quadraticVertex(0, s * 2, -s * 0.1, s * 1.9);
  vertex(-s * 0.1, s * 1.9);
  
  vertex(-s * 0.8, s * 1.1);
  quadraticVertex(-s * 0.9, s, -s * 0.8, s * 0.9);
  vertex(-s * 0.8, s * 0.9);
  endShape(CLOSE);
  pop();
  
  blendMode(ADD);
  for (float i = -s; i <= s; i++) {
    float inter = map(i, -s, s, 0, 1);
    color c = lerpColor(color(255), color(0), inter);
    stroke(c);
    line(-s, i, s, i);
  }
}

Gradient Fill

1 Like

Hi @jensschroder ,

It’s kinda tough to give you advice if you’re new to Processing. What you’re asking how to do is already beyond the introductory tool set, and paradoxically becomes easier if you use some more advanced tools. You may want to consider skipping the Processing way of stacking lines. In stead, or in addition, use the underlying renderer tools directly. The default renderer is based on Java AWT, which provides some of the gradient functionality you’re looking for.

screencap

import processing.awt.PGraphicsJava2D;
import java.awt.LinearGradientPaint;
import java.awt.geom.Point2D;
import java.awt.Color;
import java.awt.geom.Path2D;

float s = 200;

PGraphicsJava2D graphics;

Point2D start = new Point2D.Float(0.0, 0.0);
Point2D end = new Point2D.Float(400.0, 400.0);
float[] colorStops = new float[] { 0.0, 0.5, 1.0 };
Color[] colors = new Color[] {
  new Color(0xffff0000, true),
  new Color(0x7f7f007f, true),
  new Color(0xff0000ff, true) };
LinearGradientPaint gradient = new LinearGradientPaint(
  start, end, colorStops, colors);

Path2D.Float path = new Path2D.Float();

void settings() {
  size(400, 400, JAVA2D);
}

void setup() {
  graphics = (PGraphicsJava2D)getGraphics();

  path.moveTo(-s * 0.10, s * 0.10);
  path.quadTo(0.0, 0.0, s * 0.10, s * 0.1);
  path.lineTo(s * 0.10, s * 0.10);

  path.lineTo(s * 0.8, s * 0.9);
  path.quadTo(s * 0.9, s, s * 0.8, s * 1.1);
  path.lineTo(s * 0.8, s * 1.1);

  path.lineTo(s * 0.1, s * 1.9);
  path.quadTo(0, s * 2, -s * 0.1, s * 1.9);
  path.lineTo(-s * 0.1, s * 1.9);

  path.lineTo(-s * 0.8, s * 1.1);
  path.quadTo(-s * 0.9, s, -s * 0.8, s * 0.9);
  path.lineTo(-s * 0.8, s * 0.9);

  path.closePath();
}

void draw() {
  blendMode(BLEND);
  background(0xff202020);
  translate(width * 0.5, height * 0.5);
  translate(0.0, -s);
  graphics.g2.setPaint(gradient);
  graphics.g2.fill(path);
}

There are other renderers that come with Processing - based on JavaFX and OpenGL - as well as newer ones that you can find through libraries, such as Skia. Your overall strategy will depend on what renderer you use and how complex you need the gradient to be.

There are also other posts on gradients on this forum if you use the search tool in the top right corner.

Best,
Jeremy

4 Likes

Thanks a lot @behreajj !

I will read up up on alternative renderers.

Hi again!

Thanks @behreajj for the insight on how to do this with Java AWT! :wink:

Just for fun, here’s a method that support arbitrary shapes by using a mask (of black pixels) that we then fill with the gradient using PGraphics :

/**
 * Draw a rhombus shape mask of size s
 */
void rhombusMask(PGraphics mask, int s) {
  // It's important to fill it with black
  mask.beginDraw();
  
  mask.fill(0);
  mask.noStroke();
  
  mask.translate(mask.width / 2, mask.height / 2 - s);
  
  mask.beginShape();
  mask.vertex(-s * 0.10, s * 0.10);
  mask.quadraticVertex(0, 0, s * 0.10, s * 0.1);
  mask.vertex(s * 0.10, s * 0.10);
  
  mask.vertex(s * 0.8, s * 0.9);
  mask.quadraticVertex(s * 0.9, s, s * 0.8, s * 1.1);
  mask.vertex(s * 0.8, s * 1.1);
  
  mask.vertex(s * 0.1, s * 1.9);
  mask.quadraticVertex(0, s * 2, -s * 0.1, s * 1.9);
  mask.vertex(-s * 0.1, s * 1.9);
  
  mask.vertex(-s * 0.8, s * 1.1);
  mask.quadraticVertex(-s * 0.9, s, -s * 0.8, s * 0.9);
  mask.vertex(-s * 0.8, s * 0.9);
  mask.endShape(CLOSE);
  
  mask.endDraw();
}

/**
 * Take a PGraphics with some black mask and return a new image filled 
 * with a vertical gradient
 */
PGraphics createGradientFromMask(PGraphics mask, color from, color to) {
  mask.loadPixels();
  
  // We are going to compute the bounds of the mask
  // And store the mask pixels in an array
  ArrayList<PVector> maskPixels = new ArrayList<PVector>();
  int minY = mask.height;
  int maxY = 0;
  
  for (int x = 0; x < mask.width; x++) {
    for (int y = 0; y < mask.height; y++) {
      int loc = x + y * mask.width;
      
      // If it's a black pixel
      if (mask.pixels[loc] == color(0)) {
        // Add it to the array
        maskPixels.add(new PVector(x, y));
        
        // Update min and max y
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
      }
    }
  }
  
  // Create a new graphics object
  PGraphics gradient = createGraphics(mask.width, mask.height);
  
  gradient.beginDraw();
  gradient.loadPixels();
  
  // For every pixel, draw it with the gradient
  for (PVector pixel : maskPixels) {
    int loc = (int) pixel.x + (int) pixel.y * gradient.width;
    gradient.pixels[loc] = lerpColor(from, to, map(pixel.y, minY, maxY, 0, 1));
  }
  
  gradient.updatePixels();
  
  return gradient;
}

PGraphics mask, gradient;
int shapeSize = 150;

void setup() {
  size(500, 500);
  
  // Create the mask and add a shape
  mask = createGraphics(shapeSize * 2, shapeSize * 2);
  rhombusMask(mask, shapeSize);
  
  // Create the gradient from the mask
  gradient = createGradientFromMask(mask, color(#d35400), color(#2980b9));
}

void draw() {
  background(#16a085);
  
  // Display the final gradient centered
  imageMode(CENTER);
  image(gradient, width / 2, height / 2);
  
  noLoop();
}

gradient gradient

2 Likes

Just saw that we have a competitor here!

1 Like