We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
IndexProgramming Questions & HelpPrograms › Any way to do faster processing of blend options
Page Index Toggle Pages: 1
Any way to do faster processing of blend options? (Read 1323 times)
Any way to do faster processing of blend options?
Jun 15th, 2008, 1:35am
 
Hi Discourse,

I'm creating one example in Processing that tries to replicate "light painting" electronically. It's kinda like your usual painting program - clicking and dragging create lines on the screen - but by drawing several lines with some variation in position and stroke, it creates an stylish stroke that resembles "light painting" like the ones you'd do with a camera capturing the movement done by a flashlight with an open shutter.

So yeah, I'm just drawing several lines. However, to properly simulate the color changes, I'd like to use a different blend mode (ADD) on each line. That way, when two lines stack, you have a much brighter color.

It's all working well, but it's also pretty slow. Probably because I'm just a Processing beginner and I may be doing something terribly wrong. I can't tell the framerate I'm getting, but it seems to be something around 15fps on a 640x480 screen. So I'd like to know whether there's something I'm doing wrong that should be avoided or something that may make the execution faster.

In a nutshell, what I'm doing is creating several images (PGraphics instances with JAVA2D mode) and drawing on them. Then I render all images to the window - the first one using a normal blending mode and the others with ADD - "flattening" them on every frame to create the effect I want.

So, is there any other (faster) way of doing what I'm doing?

Anyway, full source follows.

Code:
float currentCursorX;
float currentCursorY;
float prevX;
float prevY;
int numLayers = 4;

float amountPainted = 0;
float currentSpeed;

int quality = 1; // 1 = normal, 2 = half quality

PGraphics[] img = new PGraphics[numLayers];
float[] amplitude = new float[numLayers];
float[] radius = new float[numLayers];
int[] lineStroke = new int[numLayers];

boolean isPainting = false;

void setup() {
   size(640, 480);
   frameRate(30);
   clearWindow();

   amplitude[0] = 100;
   amplitude[1] = 83;
   amplitude[2] = 66;
   amplitude[3] = 43;

   radius[0] = 0;
   radius[1] = -3;
   radius[2] = 6;
   radius[3] = -8;
   
   lineStroke[0] = 3;
   lineStroke[1] = 3;
   lineStroke[2] = 2;
   lineStroke[3] = 1;
}

void draw() {
   
   if (isPainting) {
       // Cursor position attenuation for smoothing
       currentCursorX -= (currentCursorX - (mouseX/quality)) / 3;
       currentCursorY -= (currentCursorY - (mouseY/quality)) / 3;

       drawLines(prevX, prevY, currentCursorX, currentCursorY);

       prevX = currentCursorX;
       prevY = currentCursorY;
       
   }
   
   if (mousePressed) {
       if (!isPainting) startPainting();
   } else {
       if (isPainting) stopPainting();
   }
}

void clearWindow() {
   background(0);
   int i;
   for (i = 0; i < numLayers; i++) {
       img[i] = createGraphics(width/quality, height/quality, JAVA2D);
   }
}

void startPainting() {
   currentSpeed = 0;
   clearWindow();
   isPainting = true;
   prevX = currentCursorX = mouseX/quality;
   prevY = currentCursorY = mouseY/quality;
}

void stopPainting() {
   isPainting = false;
}

void drawLines(float x1, float y1, float x2, float y2) {
   // Draw all lines with variations
   int i;
   float xDist = x2 - x1;
   float yDist = y2 - y1;
   float lineLength = sqrt(xDist * xDist + yDist * yDist);
   float newAmountPainted = amountPainted + lineLength;
   
   float newSpeed = 1 - (lineLength / 80);
   if (newSpeed < 0) newSpeed = 0;
   
   float cs = 0.1 + currentSpeed * 1.4;
   float ns = 0.1 + newSpeed * 1.5;

   // Erase buffer
   background(0);

   // Draw the lines
   for (i = 0; i < numLayers; i++) {
       drawLine(img[i], x1, y1, amountPainted/amplitude[i], radius[i] * cs, x2, y2, newAmountPainted/amplitude[i], radius[i] * ns, lineStroke[i]);
   }

   // Render layers
   image(img[0], 0, 0, width, height);
   for (i = 1; i < numLayers; i++) {
       blend(img[i], 0, 0, width/quality, height/quality, 0, 0, width, height, ADD);
       // if (i == 3) filter(BLUR, 1);
   }
       
   amountPainted = newAmountPainted;
   currentSpeed = newSpeed;
}

void drawLine(PGraphics __img, float x1, float y1, float amp1, float rad1, float x2, float y2, float amp2, float rad2, int __strokeWeight) {
   float ox1 = x1 + sin(amp1) * rad1;
   float oy1 = y1 + cos(amp1) * rad1;
   float ox2 = x2 + sin(amp2) * rad2;
   float oy2 = y2 + cos(amp2) * rad2;
   __img.beginDraw();
   __img.stroke(#886666);
   __img.smooth();
   __img.strokeWeight(__strokeWeight);
//  __img.strokeCap(ROUND);
   __img.line(ox1, oy1, ox2, oy2);
   __img.endDraw();
}


Some notes, please read:

* I *could* use P3D instead of JAVA2D. However, it's not much faster either, and it's kind of bad since I wouldn't be able to use different stroke weights or use antialiased lines apparently. Correct me if I'm wrong.
* I *could* use a few more modes of attenuation to the cursor position and line drawing position for more smoother curves, but right now this is enough.
* I'm using a lot of floats, while I could a lot of them as ints instead, and there's some more calculations than needed. I don't think it's too massive and wouldn't have that big of an impact though.
* I know I could be using additional classes for line drawing and such, but again, this is just a prototype and having those arrays is enough for now.
Re: Any way to do faster processing of blend optio
Reply #1 - Jun 15th, 2008, 4:58am
 
Nice to see your post, 'cause we're working in a project with similar premises - also trying to acomplish the light painting effect in Processing, though in a quite different context.

Our project is of a audiovisual interactive installation in which the visitors will interact with a video projection by illuminating parts of a dark screen with a flashlight. The system then interprets a video image of the visitor's intervention and uses the area illuminated to display the video in a light painting style, in real-time. We've posted a video of the first test of this system in vimeo.

We managed to speed up a little the trail creation by avoiding the use of blending modes. However, we still use them to blend the image of the trail with the video image that is finally displayed - and so we face the same problems as you.

But the programming shortcut we've used to create the trail is perhaps useful for your project - although I don't know if you'll be able to apply it, since the results and the inputs you're dealing with are quite different from ours.

Anyhow, our method consists of creating an array in which each element corresponds to a pixel of the image displayed and then using the input video pixel's brightness values to perform algebric operations on each element of this array. The array is then used to create the final image display, making the values of each element of the array the color value of its corresponding pixel in the image. The trail is created by maintaining the values of the array to the subsequent draw cycles.

I give you this part of the code in which we're still working on. We'll soon make available the whole source code in our blog, if it's of your interest. Our blog's content is still all in portuguese, but we'll upload the english translation in a few days.

There it goes:
Quote:
void storePixel()
 {
   for (int i = 0; i < numPixels; i++) { // For each pixel/array element:
     pixelBrightness = brightness(video.pixels[i]);
     if (pixelBrightness > threshold) { // If pixel's brighter than threshold value, than:
       setColor[i]= (setColor[i]+1)*bright; // Multiply the corresponding array element's value by the brightness ratio;
       if (setColor[i]>255){
         setColor[i]=255;
       } // If the brightness value overpassed the maximum of 255, make it 255;
       pixels[i]=color(setColor[i], setColor[i], setColor[i]); // Sets the displaying pixel's brightness to its corresponding array element's value;
     }
     else if (setColor[i]>5) { // If pixel's darker than threshold value, yet its corresponding array element is higher than a minimum value, than:
       setColor[i]/=dark; // Divide the corresponding array element by the darkness factor;
       pixels[i]=color(setColor[i], setColor[i], setColor[i]); // Sets the displaying pixel's brightness to its corresponding array element's value.
     }
   }
 }


Good luck!
Re: Any way to do faster processing of blend optio
Reply #2 - Jun 16th, 2008, 2:25pm
 
Hi. maybe you could try doing the additive blend stuff directly in opengl using the jogl library.
i dont quite grasp everything on how it works but here's a little tutorial by robert hodgin wich i have used a lot with good results Smiley

http://www.flight404.com/blog/?p=71
Re: Any way to do faster processing of blend optio
Reply #3 - Jun 16th, 2008, 4:20pm
 
@André: thanks. Nice project. I'll give it a read on the blog. And I'm not sure this is obvious, but I'm from São Paulo so content in Portuguese is no problem for me.

@pelintra: cool! This seems to nail it. I hadn't seen that before on Robert's blog. Wonderful. Thanks! I'll try adapting my code to that and test the speed.
Re: Any way to do faster processing of blend optio
Reply #4 - Jun 16th, 2008, 5:38pm
 
hey zeh.

it should have been quite obvious, actually, if i had just clicked to see your profile. anyhow, thanks for your visit to our blog - really liked your portfolio too.

até logo!
Re: Any way to do faster processing of blend optio
Reply #5 - Jul 9th, 2008, 6:38pm
 
Ok, just as a conclusion....

The final solution was indeed NOT using images as buffers for blending later, but instead draw them directly to the screen using an additive blending mode, using Hodgin's code as pointed by pelintra (above).

Basically, drawing to images then blitting them to screen is insanely slow when compared to directly writing everything using opengl.

Anyway, the final result is not so fine - because the lines are repeatedly drawn on top of each other, there are stronger colors where line caps meet (the whole image buffer thing was used to avoid that, since it'd be a per-image blend, not per-line). Still, it's extremely fast, so as an experiment, I think I can live with that.

In the future I'll be using an array of points to constantly redraw all points with shapes (instead of using strokes) to fix the cap overlap issue. I just hope it'll continue to be fast enough.

Anyway, full code is below.

Code:
import processing.opengl.*;
import javax.media.opengl.*;

float currentCursorX;
float currentCursorY;
float prevX;
float prevY;
int numLayers = 4;

PGraphicsOpenGL pgl;
GL gl;

float amountPainted = 0;
float currentSpeed;

int quality = 1; // 1 = normal, 2 = half quality

float[] amplitude = new float[numLayers];
float[] radius = new float[numLayers];
int[] lineStroke = new int[numLayers];

boolean isPainting = false;

void setup() {
size(640, 480, OPENGL);
frameRate(60);
clearWindow();

amplitude[0] = 100;
amplitude[1] = 83;
amplitude[2] = 66;
amplitude[3] = 43;

radius[0] = 0;
radius[1] = -3;
radius[2] = 6;
radius[3] = -8;

lineStroke[0] = 3;
lineStroke[1] = 3;
lineStroke[2] = 2;
lineStroke[3] = 1;
}

void draw() {

if (isPainting) {
// Cursor position attenuation for smoothing
currentCursorX -= (currentCursorX - (mouseX/quality)) / 2;
currentCursorY -= (currentCursorY - (mouseY/quality)) / 2;


// currentCursorX = mouseX/quality;
// currentCursorY = mouseY/quality;

drawLines(prevX, prevY, currentCursorX, currentCursorY);

prevX = currentCursorX;
prevY = currentCursorY;

}

if (mousePressed) {
if (!isPainting) startPainting();
} else {
if (isPainting) stopPainting();
}
}

void clearWindow() {
background(0);
}

void startPainting() {
currentSpeed = 0;
clearWindow();
isPainting = true;
prevX = currentCursorX = mouseX/quality;
prevY = currentCursorY = mouseY/quality;
}

void stopPainting() {
isPainting = false;
}

void drawLines(float x1, float y1, float x2, float y2) {
// Draw all lines with variations
int i;
float xDist = x2 - x1;
float yDist = y2 - y1;
float lineLength = sqrt(xDist * xDist + yDist * yDist);
float newAmountPainted = amountPainted + lineLength;

float newSpeed = 1 - (lineLength / 80);
if (newSpeed < 0) newSpeed = 0;

float cs = 0.1 + currentSpeed * 1.4;
float ns = 0.1 + newSpeed * 1.5;

pgl = (PGraphicsOpenGL) g;
gl = pgl.beginGL();
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE);
pgl.endGL();

// Erase buffer
//background(0);

// Draw the lines
for (i = 0; i < numLayers; i++) {
drawLine(x1, y1, amountPainted/amplitude[i], radius[i] * cs, x2, y2, newAmountPainted/amplitude[i], radius[i] * ns, lineStroke[i] * ((cs+ns)/2));
}

amountPainted = newAmountPainted;
currentSpeed = newSpeed;
}

void drawLine(float x1, float y1, float amp1, float rad1, float x2, float y2, float amp2, float rad2, float __strokeWeight) {
float ox1 = x1 + sin(amp1) * rad1;
float oy1 = y1 + cos(amp1) * rad1;
float ox2 = x2 + sin(amp2) * rad2;
float oy2 = y2 + cos(amp2) * rad2;
stroke(#885544);
smooth();
strokeWeight(__strokeWeight);
// strokeCap(ROUND);
line(ox1, oy1, ox2, oy2);
}
Page Index Toggle Pages: 1