How can I paint better looking strokes?

edited August 2015 in Questions about Code

The following little bit of code draws awful looking strokes. The spacing and flow is just wrong. They're not really usable for a paint program. I'm obviously not doing it right. Where would I start to find information on drawing better strokes? Is it possible to get the strokes you see in normal paint programs, in processing?

PGraphics myGraph;

void setup(){
     size(800,800);
     myGraph = createGraphics(800,800);
     myGraph.beginDraw();
     myGraph.background(255);
     myGraph.strokeWeight(20);
     myGraph.stroke(150,30,30,127);
     myGraph.endDraw();
}
void draw(){
     image(myGraph, 0,0);
}

void mouseDragged(){
     myGraph.beginDraw();
     myGraph.line(pmouseX, pmouseY, mouseX, mouseY);
     myGraph.endDraw();

}

Answers

  • edited August 2015

    I'm thinking I can't use the stroke alpha to control the opacity of the stroke ( as it makes it look horrible) but I have to paint at full strength on a transparent layer and blend it with tint to achieve the opacity I want. Strokes with no alpha look fine. Take the alpha out of the above code and the strokes look fine.

  • edited August 2015 Answer ✓

    Just use a second PGraphics object to buffer your painting operations before rendering them to your main canvas:

    PGraphics canvas;
    PGraphics buffer;
    
    void setup() {
    
        // Set screen size and renderer
        size(800, 800, P2D);
    
        // Create main canvas
        canvas = createGraphics(800, 800, P2D);
        canvas.beginDraw();
        canvas.background(255);
        canvas.endDraw();
    
        // Create stroke buffer
        buffer = createGraphics(800, 800, P2D);
        buffer.beginDraw();
        buffer.smooth(8); // Get smooth lines
        buffer.strokeWeight(20);
        buffer.stroke(150, 30, 30);
        buffer.endDraw();
    
    }
    
    void draw() {
    
        // Render canvas
        image(canvas, 0, 0);
    
        // Render stoke buffer
        tint(255, 127);
        image(buffer, 0, 0);
    
    }
    
    void mouseDragged() {
    
        // Render line in stroke buffer
        buffer.beginDraw();
        buffer.line(pmouseX, pmouseY, mouseX, mouseY);
        buffer.endDraw();
    
    }
    
    void mouseReleased() {
    
        // Render stroke buffer onto canvas
        canvas.beginDraw();
        canvas.tint(255, 127);
        canvas.image(buffer, 0, 0);
        canvas.endDraw();
    
        // Clear stroke buffer
        buffer.beginDraw();
        buffer.clear();
        buffer.endDraw();
    
    }
    

    Btw.: I changed the renderer to P2D because alpha blending is still a bit buggy with the current default renderer.

  • Thanks for that. The one problem there is while its drawing, its at 255 alpha. While searching on the topic I found this thread.

    http://forum.processing.org/one/topic/stroke-opacity-by-temporary-pgraphics.html

    It uses the same technique but uses a trick to keep the screen updated with the opacity. Thanks for the help!

    PGraphics strokePG,canvasPG;
    PImage temp;
    //import codeanticode.tablet.*;
    float opacity = 50;
    //Tablet tablet;
    void setup(){
         size(600,600,JAVA2D);
         strokePG = createGraphics(600,600);
         canvasPG = createGraphics(600,600);
         temp = createImage(600, 600, ARGB);
    //     tablet = new Tablet(this);
         canvasPG.beginDraw();
         canvasPG.background(200,200,255);
         canvasPG.endDraw();
         strokePG.beginDraw();
         strokePG.strokeWeight(20);
         strokePG.stroke(150,80,40);
         strokePG.endDraw();
    }
    void draw(){
         noTint();
         image(canvasPG,0,0);
         if (mousePressed) {
              temp.pixels = strokePG.pixels;
              temp.updatePixels();
              tint(255, 255, 255, opacity);
              image(temp, 0, 0); // using the temp image directly screws things up
          }
    
    
    }
    void mousePressed(){
         strokePG.beginDraw();
         strokePG.clear();
         strokePG.line(mouseX,mouseY,mouseX,mouseY);
         strokePG.endDraw();
    }
    void mouseDragged(){
         strokePG.beginDraw();
         strokePG.line(pmouseX,pmouseY,mouseX,mouseY);
         strokePG.endDraw();
    }
    void mouseReleased(){     
         canvasPG.beginDraw();
         canvasPG.tint(255,opacity);
         canvasPG.image(strokePG,0,0);
         canvasPG.noTint();
         canvasPG.endDraw();
    
  • The reason I rewrote the code is I'm working on a version that incorporates pen pressure into the opacity. The first buffer will be the full on stroke. It will copy to a second buffer with the tint set to pen pressure ( actually an average of the pen pressure and the previous pen pressure) and finally that buffer will copy to the canvas with the tint set to opacity. Thanks again.

  • edited August 2015

    This works pretty good. Between opacity and pen pressure, there's a lot of control and the stroke looks much better. You'd need a tablet to try it.

    PGraphics strokePG1,strokePG,canvasPG;
    PImage temp;
    import codeanticode.tablet.*;
    float opacity = 50;
    float pressure,ppressure;
    Tablet tablet;
    void setup(){
         size(600,600,JAVA2D);
         strokePG = createGraphics(600,600);
         strokePG1 = createGraphics(600,600);
         canvasPG = createGraphics(600,600);
         temp = createImage(600, 600, ARGB);
         tablet = new Tablet(this);
         canvasPG.beginDraw();
         canvasPG.background(200,200,255);
         canvasPG.endDraw();
         strokePG1.beginDraw();
         strokePG1.strokeWeight(20);
         strokePG1.stroke(150,80,40);
         strokePG1.endDraw();
    }
    void draw(){
         noTint();
         image(canvasPG,0,0);
         if (mousePressed) {
              temp.pixels = strokePG.pixels;
              temp.updatePixels();
              tint(255, 255, 255, opacity);
              image(temp, 0, 0); // using the temp image directly screws things up
          } 
    
    
    }
    void mousePressed(){
         strokePG1.beginDraw();
         strokePG1.clear();
         strokePG1.line(mouseX,mouseY,mouseX,mouseY);
         strokePG1.endDraw();
         pressure = 30 * tablet.getPressure();
         strokePG.beginDraw();
         strokePG.tint(255,255,255,pressure);
         strokePG.image(strokePG1,0,0);
         strokePG.endDraw();
    
    }
    void mouseDragged(){
         strokePG1.beginDraw();
         strokePG1.clear();
         strokePG1.line(pmouseX,pmouseY,mouseX,mouseY);
         strokePG1.endDraw();
         ppressure = pressure;
         pressure = 30 *tablet.getPressure();
         pressure = (pressure + ppressure)/2;
         ppressure = pressure;
         strokePG.beginDraw();
         strokePG.tint(255,255,255,pressure);
         strokePG.image(strokePG1,0,0);
         strokePG.endDraw();
    
    
    }
    void mouseReleased(){     
         canvasPG.beginDraw();
         canvasPG.tint(255,opacity);
         canvasPG.image(strokePG,0,0);     
         canvasPG.endDraw(); 
         strokePG.beginDraw();
         strokePG.clear();
         strokePG.endDraw();
    
    }
    
  • edited September 2015

    Hmn... so semi-transparent points/lines do not render correctly onto alpha enabled PGraphics objects, but images do... interesting. Will have a look at PGraphics source.

  • Oh yes there's all sorts of strangeness between pixel alpha and tint alpha. I spent half a day figuring out tint (255,255) didn't mean opaque. It should be opaque, but it isn't. That's why there is noTint(). Thats just wrong. The method is wrong. The method should accommodate 255 whichever way it rounds. 255 should be opaque, but with tint() it isn't. Seems minor but minor eccentricities become exponential.

  • edited September 2015

    Okay, the image() function uses the pixels array to render the PImage's/PGraphic's content, so just call updatePixels() beforehand and everything should work as expected, even without the temp image.

    I didn't have the time to look at tint()'s source, but as stroke()'s transparency works just fine for points and lines, just use that and you can get rid of strokePG1.

    Updated your source code:

    import codeanticode.tablet.*;
    
    PGraphics strokePG, canvasPG;
    float opacity = 50;
    float pressure;
    Tablet tablet;
    
    void setup() {
        size(600, 600, JAVA2D);
        strokePG = createGraphics(600, 600);
        canvasPG = createGraphics(600, 600);
        tablet = new Tablet(this);
        canvasPG.beginDraw();
        canvasPG.background(200, 200, 255);
        canvasPG.endDraw();
        strokePG.beginDraw();
        strokePG.strokeWeight(20);
        strokePG.endDraw();
    }
    
    void draw() {
        noTint();
        image(canvasPG, 0, 0);
        if (mousePressed) {
            strokePG.updatePixels(); // Just call update pixels and you don't need temp
            tint(255, 255, 255, opacity);
            image(strokePG, 0, 0);
        }
    }
    
    float getPressure() {
        return 30.0 * tablet.getPressure();
    }
    
    void mousePressed() {
        pressure = getPressure();
        strokePG.updatePixels();
        strokePG.beginDraw();
        strokePG.stroke(150, 80, 40, pressure);
        strokePG.point(mouseX, mouseY); // Use point for points
        strokePG.endDraw();
    }
    
    void mouseDragged() {
        pressure = (getPressure() + pressure) / 2.0;
        strokePG.updatePixels();
        strokePG.beginDraw();
        strokePG.stroke(150, 80, 40, pressure);
        strokePG.line(pmouseX, pmouseY, mouseX, mouseY);
        strokePG.endDraw();
    }
    
    void mouseReleased() {
        strokePG.updatePixels();
        canvasPG.beginDraw();
        canvasPG.tint(255, opacity);
        canvasPG.image(strokePG, 0, 0);     
        canvasPG.endDraw(); 
        strokePG.beginDraw();
        strokePG.clear();
        strokePG.endDraw();
    }
    

    Unfortunately I can't find my tablet atm., so I just used 127.0 + 100.0 * sin(frameCount * 0.4) for the pressure based stuff. ;)

  • Thanks. I'll give it a try, but previously I was using the the stroke() transparency (i.e., stroke(pencolor, pressure) ) and consistently wound up with errant hues at low pen pressure. With the other method using the pressure to set tint, I get no errant hues. They do mix differently. I appreciate the help and the tip on updatePixels(). That clears up many problems I was having. And gets rid of a ton of spurious beginDraw()/endDraw() calls. I guess they automatically update the pixels. buffer.updatePixels() is a lot cleaner. Thanks!

  • edited September 2015

    I'll try to write a small program demonstrating how color mixes using stroke transparency and tint transparency. I'm talking about the typical way of rendering or shading people do in paint programs. If they're going from a light color to a dark color they'll go over the dark color lightly with the light color and pick up that color with the dropper or grab color tool. They then go over the light and dark color with that etc. etc. and model a smooth transition. Doing that using the stroke() transparency produces many errant hues. Using the tint transparency doesn't give the errant hues. I wonder if its because using tint, you aren't mixing pixel alphas? I don't know what it is but it produces different results.

  • edited September 2015

    The problem is, that endDraw() only updates the area of the PGraphics object that got modified (to save frame time), but apparently this doesn't always work. updatePixels() is slower, as it always updates the whole screen, but it works.

    Glad I could help. :)

Sign In or Register to comment.