Move between different PGraphics

Hello, I’ve developed a system for live visuals that involves rendering graphics in Processing, and I’m trying to add functionality to fade between two different displays: one that is currently being displayed on a projection, and a seocnd that is sort of a staging ground where I can preview visuals before comitting them to the room.
The method I’ve been trying to use is based on the MultipleWindows example included in Processing (Examples->Demos->Tests->MultipleWindows) but I’ve been struggling to make sense of it such that I can use it for my own purposes.

This is the closest approximation of what I want to be able to do that I’ve managed to make:

Child child1;
Child child2;
PGraphics canvas1;
PGraphics canvas2;
float tint1;
float tint2;
void setup() {
  size(400, 400);
  canvas1 = createGraphics(400, 400);
  canvas2 = createGraphics(400, 400);
  child1 = new Child("Child1",canvas1);
  child2 = new Child("Child2",canvas2);
}

void draw() {
   //draw to two different PGraphics objects:
  canvas1.beginDraw();
  canvas1.noStroke();
  canvas1.background(0);
  canvas1.fill(255);
  canvas1.ellipse(canvas1.width/2, canvas1.height/2, 60, 60);
  canvas1.endDraw();

  canvas2.beginDraw();
  canvas2.noStroke();
  canvas2.background(255);
  canvas2.fill(0);
  canvas2.ellipse(canvas1.width/2, canvas1.height/2, 60, 60);
  canvas2.endDraw();


  //fade between the output of the two PGraphics objects:
  tint1 = map(mouseX, 0, width, 0, 255);
  tint2 = map(mouseX, 0, width, 255, 0);
  tint(255, tint1);
  image(canvas1, 0, 0);
  tint(255, tint2);
  image(canvas2, 0, 0);
}

//display the PGraphics in separate windows:
class Child extends PApplet {
  private PGraphics canvas;
  private String name;
  
  public Child(String name, PGraphics canvas) {
    super();
    this.canvas = canvas;
    this.name = name;
    PApplet.runSketch(new String[]{this.name}, this);
  }
  
  public void settings(){
    size(400,400);
  }
  
  public void setup(){
    surface.setTitle(this.name);
  }
  
  public void draw(){
    image(this.canvas, 0, 0);
  }
  
  
}

In this example, I’m drawing to two different PGraphics instances simultaneously, and fading between them in a third window. This works okay but is obviously very clunky and poorly organised.


What I would like to be able to do is something like the following, where I can dynamically switch between which PGraphics context I am drawing specific assets to, but I think don’t quite understand how exactly PGraphics works and is intended to be used:

Child child1;
Child child2;

PGraphics canvas1;
PGraphics canvas2;

float tint1;
float tint2;

Tester tester;

void setup() {
  size(400, 400);
  canvas1 = createGraphics(400, 400);
  canvas2 = createGraphics(400, 400);
  child1 = new Child("Child1",canvas1);
  child2 = new Child("Child2",canvas2);
  tester = new Tester();
}

void draw() {
  tint1 = map(mouseX, 0, width, 0, 255);
  tint2 = map(mouseX, 0, width, 255, 0);

  canvas1.beginDraw();
  canvas1.background(0);
  tester.display(canvas1);
  canvas1.endDraw();
  

  canvas2.beginDraw();
  canvas2.noStroke();
  canvas2.background(255);
  canvas2.fill(0);
  canvas2.ellipse(canvas1.width/2, canvas1.height/2, 60, 60);
  canvas2.endDraw();

  tint(255, tint1);
  image(canvas1, 0, 0);
  tint(255, tint2);
  image(canvas2, 0, 0);
}

class Child extends PApplet {
  private PGraphics canvas;
  private String name;
  
  public Child(String name, PGraphics canvas) {
    super();
    this.canvas = canvas;
    this.name = name;
    PApplet.runSketch(new String[]{this.name}, this);
  }
  
  public void settings(){
    size(400,400);
  }
  
  public void setup(){
    surface.setTitle(this.name);
  }
  
  public void draw(){
    image(this.canvas, 0, 0);
  }
  
  
}

class Tester{
  Tester(){
  }
  
  void display(PGraphics pg){
    pg.fill(255);
    pg.ellipse(mouseX,mouseY,70,70);
  }
}
1 Like

It’s not exactly clear to me what you are trying to do, but see if the following demo is somewhere close. The source code creates a PGraphics array which you can cycle through in the main default window. If you want one of those images to be shown in the Graphics window (PApplet) then you hit the send button and it will be displayed in the other window. The array is slightly oversized to prevent overrunning the bounds.

PGraphics [] pg = new PGraphics[9];
int count = 0;
int approved;

GraphicWindow wnd;

void setup() {
  size(700, 700);
  surface.setTitle("Default Window");
  wnd = new GraphicWindow();

  for (int i = 1; i < 8; i++) {
    pg[i] = createGraphics(600, 600);
    pg[i].beginDraw();
    pg[i].background(0, random(255), 0);
    pg[i].stroke(0);
    pg[i].fill(random(255), random(255), random(255));
    pg[i].circle(300, 300, 350);
    pg[i].endDraw();
  }
}

void draw() {
  background(209);
  text(str(count), 10, 25);
  // next
  fill(255, 255, 255);
  rect(50, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Next", 70, 30);
  // send
  fill(255, 255, 0);
  rect(300, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Send", 320, 30);

  if (count > 0)
    image(pg[count], 40, 40);
}

void mousePressed() {
  if ((mouseX >= 50) && (mouseX <= 150) && (mouseY >= 10) && (mouseY <= 34)) {
    count++;
    if (count > 7) {
      count = 0;
    }
  }

  if ((mouseX >= 300) && (mouseX <= 400) && (mouseY >= 10) && (mouseY <= 34)) {
    approved = count;
  }
}

class GraphicWindow extends PApplet {

  public GraphicWindow() {
    PApplet.runSketch(new String[] {this.getClass().getSimpleName()}, this);
  }

  void settings() { // Necessary for PApplet
    size(700, 700);
  }

  void setup() {
    surface.setResizable(true);
  }

  void draw() {
    if (approved > 0)
      image(pg[approved], 40, 40);
  }

  void mousePressed() {
    println("mousePressed in graphic window");
  }
}

1 Like

Hi, thanks, this is helpful and is close to what I’m looking for. I actually think that the aspect that you’ve demonstrated is the part that I have figured out, which is getting a PGraphics instance to display on second window. The part I’ve been struggling with is drawing to a PGraphics instance that is being displayed in two places at once. I get a huge drop in framerate on the window of the child PApplet. I modified the example you provided to demonstrate:

PGraphics [] pg = new PGraphics[9];
int count = 0;
int approved;

GraphicWindow wnd;

void setup() {
  size(700, 700);
  surface.setTitle("Default Window");
  wnd = new GraphicWindow();

  for (int i = 1; i < 8; i++) {
    pg[i] = createGraphics(600, 600);
  }
}

void draw() {
  for (int i = 1; i < 8; i++) {
    pg[i].beginDraw();
    pg[i].background(0, 0, 0);
    pg[i].stroke(0);
    pg[i].circle(mouseX, mouseY, 350);
    pg[i].endDraw();
  }
  background(209);
  text(str(count), 10, 25);
  // next
  fill(255, 255, 255);
  rect(50, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Next", 70, 30);
  // send
  fill(255, 255, 0);
  rect(300, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Send", 320, 30);

  if (count > 0)
    image(pg[count], 40, 40);
}

void mousePressed() {
  if ((mouseX >= 50) && (mouseX <= 150) && (mouseY >= 10) && (mouseY <= 34)) {
    count++;
    if (count > 7) {
      count = 0;
    }
  }

  if ((mouseX >= 300) && (mouseX <= 400) && (mouseY >= 10) && (mouseY <= 34)) {
    approved = count;
  }
}

class GraphicWindow extends PApplet {

  public GraphicWindow() {
    PApplet.runSketch(new String[] {this.getClass().getSimpleName()}, this);
  }

  void settings() { // Necessary for PApplet
    size(700, 700);
  }

  void setup() {
    surface.setResizable(true);
  }

  void draw() {
    if (approved > 0)
      image(pg[approved], 40, 40);
  }

  void mousePressed() {
    println("mousePressed in graphic window");
  }
}

However, it seems to work fine if the PGraphics is drawn in multiple places within the same PApplet, so maybe I’ll ditch the multi-window idea and just go with something like this:

PGraphics [] pg = new PGraphics[9];
color [] colors = new color[9];
int count = 0;
int approved;

void setup() {
  size(1200, 700);
  surface.setTitle("Default Window");

  for (int i = 1; i < 8; i++) {
    pg[i] = createGraphics(500, 500);
    colors[i] = color(random(255),random(255),random(255));
  }
}

void draw() {
  for (int i = 1; i < 8; i++) {
    pg[i].beginDraw();
    pg[i].background(0, 0, 0);
    pg[i].stroke(0);
    pg[i].fill(colors[i]);
    pg[i].circle(mouseX, mouseY, 350);
    pg[i].endDraw();
  }
  background(209);
  text(str(count), 10, 25);
  // next
  fill(255, 255, 255);
  rect(50, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Next", 70, 30);
  // send
  fill(255, 255, 0);
  rect(300, 10, 100, 24);
  fill(0);
  textSize(22);
  text("Send", 320, 30);
  text("Preview Window", 200,580);
  text("Output Window",700,580);

  if (count > 0){
    image(pg[count], 0, 40);
  }
  if (approved > 0){
      image(pg[approved], 500, 40);
  }
}

void mousePressed() {
  if ((mouseX >= 50) && (mouseX <= 150) && (mouseY >= 10) && (mouseY <= 34)) {
    count++;
    if (count > 7) {
      count = 0;
    }
  }

  if ((mouseX >= 300) && (mouseX <= 400) && (mouseY >= 10) && (mouseY <= 34)) {
    approved = count;
  }
}

If it helps any further, this is an example of 3 windows where one fades between the other two (outside Processing IDE).

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;

import processing.core.*;

public class MultiSurface extends PApplet {
	SecondApplet second;
	FadeApplet third;
	PGraphics windowA, windowB, windowFade;
	float fadeAmount = 0;
	boolean fadingIn = true;

	static final int windowW = 500, windowH = 500;
	static int screenWidth, screenHeight;

	public static void main(String[] args) {
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice gd = ge.getDefaultScreenDevice();
		Rectangle bounds = gd.getDefaultConfiguration().getBounds();
		screenWidth = bounds.width;
		screenHeight = bounds.height;
		PApplet.main(MultiSurface.class);
	}

	public void settings() {
		size(windowW, windowH);
	}

	public void setup() {
		windowA = createGraphics(width, height);
		windowB = createGraphics(width, height);
		windowFade = createGraphics(width, height);

		second = new SecondApplet();
		PApplet.runSketch(new String[] { "Window B" }, second);

		third = new FadeApplet();
		PApplet.runSketch(new String[] { "Window Fade" }, third);

		surface.setLocation(screenWidth / 3, (screenHeight - windowW) / 2);
		surface.setTitle("Window A");
	}

	public void draw() {
		windowA.beginDraw();
		windowA.background(255, 0, 0);
		windowA.fill(255);
		windowA.ellipse(mouseX, mouseY, 100, 100);
		windowA.endDraw();

		image(windowA, 0, 0);
		third.updateGraphics(windowA, windowB);
	}

	public class SecondApplet extends PApplet {
		public void settings() {
			size(windowW, windowH);
		}

		@Override
		public void setup() {
			surface.setLocation(screenWidth / 3 + windowW, (screenHeight - windowH) / 2);
			surface.setTitle("Window B");
		}

		public void draw() {
			// Update pg2
			windowB.beginDraw();
			windowB.background(0, 0, 255);
			windowB.fill(255);
			windowB.rect(100, 100, 100, 100);
			windowB.endDraw();

			image(windowB, 0, 0);
		}
	}

	public class FadeApplet extends PApplet {
		public void settings() {
			size(windowW, windowH);
		}

		@Override
		public void setup() {
			surface.setLocation(screenWidth / 3 + windowW * 2, (screenHeight - windowH) / 2);
			surface.setTitle("Window Fade");
		}

		public void draw() {
			image(windowFade, 0, 0);
		}

		public void updateGraphics(PGraphics pg1, PGraphics pg2) {
			windowFade.beginDraw();
			windowFade.background(0);
			windowFade.tint(255, 255 * (1 - fadeAmount));
			windowFade.image(pg1, 0, 0);
			windowFade.tint(255, 255 * fadeAmount);
			windowFade.image(pg2, 0, 0);
			windowFade.endDraw();

			if (fadingIn) {
				fadeAmount += 0.01;
				if (fadeAmount >= 1) {
					fadingIn = false;
				}
			} else {
				fadeAmount -= 0.01;
				if (fadeAmount <= 0) {
					fadingIn = true;
				}
			}
		}
	}
}
1 Like

I was wrong! I thought the drop in frame rate had to do with the fact that I was referencing the same PGraphics instance from two different PApplets, but it turns out I have the same problem even if I reference the same PGraphics instance twice within a single PApplet?
Here’s something very similar to what @micycle posted, but which draws all three PGraphics canvases in one PApplet rather than in three separate ones. @micycle 's seems to work fine with no drop in frame rate on any of the PGraphics, but this very simple thing that I’ve created does not work:

import processing.core.*;
public class Main extends PApplet{

    PGraphics graphics1, graphics2,graphics3;
    GraphicsDeckDisplay graphicsDeck1, graphicsDeck2, graphicsDeck3;
    MouseMover mm;
    public void settings(){
        size(1200,400);
    }

    public void setup(){
        graphics1 = createGraphics(400,400);
        graphics2 = createGraphics(400,400);
        graphics3 = createGraphics(400,400);
        graphicsDeck1 = new GraphicsDeckDisplay(this,400,400,0,0,graphics1);
        graphicsDeck2 = new GraphicsDeckDisplay(this,400,400,400,0,graphics2);
        graphicsDeck3 = new GraphicsDeckDisplay(this,400,400,800,0,graphics3);
        mm = new MouseMover(this);
    }


    public void draw() {
        graphics1.beginDraw();
        graphics1.background(0);
        mm.display(graphics1);
        graphics1.endDraw();

        graphics2.beginDraw();
        graphics2.background(255);
        graphics2.fill(0);
        graphics2.ellipse(200,200,50,50);
        graphics2.endDraw();

        float tintSin = sin(frameCount*0.01f);

        graphics3.beginDraw();
        graphics3.background(0);
        graphics3.tint(255,map(tintSin,-1,1,0,255));
        graphics3.image(graphics1,0,0);
        graphics3.tint(255,map(tintSin,-1,1,255,0));
        graphics3.image(graphics2,0,0);
        graphics3.endDraw();


        graphicsDeck1.display();
        graphicsDeck2.display();
        graphicsDeck3.display();

    }


    public static void main(String[] args){
        String[] processingArgs = {"Main"};
        Main main = new Main();
        PApplet.runSketch(processingArgs,main);
    }

    //displays the content of a PGraphics
    public class GraphicsDeckDisplay{
        private PApplet processing;
        private PGraphics graphics;
        private int width;
        private int height;
        private int x;
        private int y;

        public GraphicsDeckDisplay(PApplet processing, int width, int height, int x, int y, PGraphics graphics){
            this.processing = processing;
            this.width = width;
            this.height = height;
            this.x = x;
            this.y = y;
            this.graphics = graphics;
        }

        public void display(){
            this.processing.image(this.graphics,this.x,this.y,this.width,this.height);
        }

        public PGraphics getGraphics(){
            return this.graphics;
        }

    }

    public class MouseMover {
        PApplet processing;
        public MouseMover(PApplet processing){
            this.processing = processing;

        }

        public void display(PGraphics destination){
            destination.fill(255);
            destination.ellipse(this.processing.mouseX,this.processing.mouseY,60,60);
        }
    }

}

If you move the mouse around on the first “screen,” you won’t see any change because that image isn’t updating for some reason. But, you’ll see movement on the third screen, where the same PGraphics is drawn again. Can a PGraphics only be displayed by one PImage at a time?

Found a solution here: image() causes PImage reference to break when using PGraphics · Issue #5760 · processing/processing · GitHub
A user there said:

it looks like with the default Java2D renderer caching is not correctly cleared when calling endDraw() on an offscreen graphics, but only when drawing it to a second offscreen graphics (not the primary)…Adding an updatePixels() line fixes this from what I can tell - eg.

So I tried that, and it fixed it. This is the example I just posted, but with calls to updatePixels added:

import processing.core.*;
public class Main extends PApplet{

    PGraphics graphics1, graphics2,graphics3;
    GraphicsDeckDisplay graphicsDeck1, graphicsDeck2, graphicsDeck3;
    MouseMover mm;
    public void settings(){
        size(1200,400);
    }

    public void setup(){
        graphics1 = createGraphics(400,400);
        graphics2 = createGraphics(400,400);
        graphics3 = createGraphics(400,400);
        graphicsDeck1 = new GraphicsDeckDisplay(this,400,400,0,0,graphics1);
        graphicsDeck2 = new GraphicsDeckDisplay(this,400,400,400,0,graphics2);
        graphicsDeck3 = new GraphicsDeckDisplay(this,400,400,800,0,graphics3);
        mm = new MouseMover(this);
    }


    public void draw() {
        graphics1.beginDraw();
        graphics1.background(0);
        mm.display(graphics1);
        graphics1.endDraw();

        graphics2.beginDraw();
        graphics2.background(255);
        graphics2.fill(0);
        graphics2.ellipse(200,200,50,50);
        graphics2.endDraw();

        float tintSin = sin(frameCount*0.01f);

        graphics3.beginDraw();
        graphics3.background(0);
        graphics3.tint(255,map(tintSin,-1,1,0,255));
        graphics3.image(graphics1,0,0);
        graphics3.tint(255,map(tintSin,-1,1,255,0));
        graphics3.image(graphics2,0,0);
        graphics3.endDraw();

        //********************************************
        //These lines fixed the problem:
        graphics1.updatePixels();
        graphics2.updatePixels();
        //********************************************


        graphicsDeck1.display();
        graphicsDeck2.display();
        graphicsDeck3.display();

    }


    public static void main(String[] args){
        String[] processingArgs = {"Main"};
        Main main = new Main();
        PApplet.runSketch(processingArgs,main);
    }

    //displays the content of a PGraphics
    public class GraphicsDeckDisplay{
        private PApplet processing;
        private PGraphics graphics;
        private int width;
        private int height;
        private int x;
        private int y;

        public GraphicsDeckDisplay(PApplet processing, int width, int height, int x, int y, PGraphics graphics){
            this.processing = processing;
            this.width = width;
            this.height = height;
            this.x = x;
            this.y = y;
            this.graphics = graphics;
        }

        public void display(){
            this.processing.image(this.graphics,this.x,this.y,this.width,this.height);
        }

        public PGraphics getGraphics(){
            return this.graphics;
        }

    }

    public class MouseMover {
        PApplet processing;
        public MouseMover(PApplet processing){
            this.processing = processing;

        }

        public void display(PGraphics destination){
            destination.fill(255);
            destination.ellipse(this.processing.mouseX,this.processing.mouseY,60,60);
        }
    }

}


Now to test this with a multi-window setup and see if that works!

2 Likes

Updating here for posterity’s sake. I did get everything set up in a multi-window setup by making calls to PGraphics.updatePixels(), but it seems like this is a pretty intensive function, so after a minute or two I start get pretty intense screen tearing:
output2

I don’t really know enough about how PGraphic works to do much troubleshooting, but I’m guessing this has something to do with writing and reading to the buffer at the same time. I might just accept that I won’t have perfectly realtime previews for the moment. Maybe I’ll update one of the preview windows only while I’m holding a key or something so that I can peek at the offscreen buffer before committing it to the main window.

1 Like

This may be of interest:

:)