Simple algebra to create effects along a gradient. I thought of a nice new feature for the Delaunay Android app, the app ‘polygonises’ pictures by overlaying triangles onto a photo, I guessed it’d look good if the opacity could be controlled in a similar way to a gradient tool in a drawing application. Having the user tap once to mark the start of the gradient and again to mark the end is easy, but the gradient could be at any angle across the screen, to calculate the opacity for a triangle/point we need to use some basic algebra – I say basic; I did struggle a little with this, and there may be better ways of doing it.

In the processing code below there are 5 points, and a line representing a linear gradient tool created by the user. The code calculates the opacity for each point based on its relation to the gradient vector.

PImage backgroundImage;

Point gStart = new Point(0, 0);
Point gEnd = new Point(160, 300);

Point aPoint = new Point(185, 80);
Point bPoint = new Point(185, 130);
Point cPoint = new Point(185, 180);
Point dPoint = new Point(185, 230);
Point ePoint = new Point(185, 280);

int r = 20;
int c = 255;

void setup() {
     size(320, 320);
     backgroundImage = loadImage("sally.jpg");  
     ellipseMode(RADIUS);
}

void draw() {
     background(backgroundImage);

     //Draw the gradient
     stroke(c);
     line(gStart.x, gStart.y, gEnd.x, gEnd.y);

     noStroke();

     //Draw each of the points and calculate the opacity based on the gradient vector
     int aOpacity = gradientOpacity(gStart, gEnd, aPoint);
     fill(c, aOpacity);
     ellipse(aPoint.x, aPoint.y, r, r);

     int bOpacity = gradientOpacity(gStart, gEnd, bPoint);
     fill(c, bOpacity);
     ellipse(bPoint.x, bPoint.y, r, r);

     int cOpacity = gradientOpacity(gStart, gEnd, cPoint);
     fill(c, cOpacity);
     ellipse(cPoint.x, cPoint.y, r, r);

     int dOpacity = gradientOpacity(gStart, gEnd, dPoint);
     fill(c, dOpacity);
     ellipse(dPoint.x, dPoint.y, r, r);

     int eOpacity = gradientOpacity(gStart, gEnd, ePoint);
     fill(c, eOpacity);
     ellipse(ePoint.x, ePoint.y, r, r);
}

int gradientOpacity(Point pa, Point pb, Point p){
     //Given a point in 2D space find the normal intersecting the gradient line
     Point intersect = nearestPointOnLine(pa.x, pa.y, pb.x, pb.y, p.x, p.y, true);

     //To illustrate draw a line from our point to the gradient line
     stroke(255);
     line(p.x, p.y, intersect.x, intersect.y);
     noStroke();

     //Distance from gradient start point to the intersection  
     int intersectDistance = (int) distance(pa.x, pa.y, intersect.x, intersect.y);

     //Length of the gradient line
     int gradDistance = (int) distance(pa.x, pa.y, pb.x, pb.y);

     //We have the gradient length and the intersection point along it, we now need to remap that value to the range 0 - 255
     int remappedOpacity = numberRemap(intersectDistance, 0, gradDistance, 0, 255);
     return remappedOpacity;
}

//Same as Processings own map();
int numberRemap(int actual, int low, int high, int targetLow, int targetHigh){
     return targetLow + (actual - low) * (targetHigh - targetLow) / (high - low);
}

 //Distance between two points
double distance(double ax, double ay, double bx, double by){
     return Math.sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by));
}

//Thank you stackoverflow: http://stackoverflow.com/questions/1459368/snap-point-to-a-line
Point nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py, boolean clampToSegment) {
     double apx = px - ax;
     double apy = py - ay;

     double abx = bx - ax;
     double aby = by - ay;

     double ab2 = abx * abx + aby * aby;
     double ap_ab = apx * abx + apy * aby;
     double t = ap_ab / ab2;

     if (clampToSegment) {
          if (t < 0) {
               t = 0;
          } else if (t > 1) {
               t = 1;
          }
     }
     return new Point((int)(ax + abx * t),(int) (ay + aby * t));
}

//Simple class to mimic Android's Point obj
class Point {
     public int x;
     public int y;

     Point(int x, int y){
          this.x = x;
          this.y = y;
     }
}

Here’s an example of the code running in a prototype Delaunay triangulation app, two gradient controls – controlling the fill and wireframe opacity:

10052591823_1ca49e1940