I recently revisited an old piece of code I wrote as a answer on this forum to allow an overlay onto another program. However this code didn’t use the graphics commands that processing provides. I fixed this problem and wrote easy to use code that allows using standard processing commands.
Main.pde
Code
OverlayHandler overlayHandler;
void setup() {
//creates the handler
overlayHandler=OverlayHandler.create(this);
}
void draw() {
//Wipes the screen
overlayHandler.wipe();
stroke(0);
strokeWeight(3);
fill(255);
ellipse(300, 300, 20, 20);
noFill();
stroke(255,0,0);
ellipse(mouseX,mouseY,50,50);
}
void mousePressed(){
println("Mouse was Pressed at",mouseX,mouseY);
}
void keyPressed(){
if(key==ESC) exit();
}
OverlayHandler.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import processing.awt.*;
import java.lang.reflect.*;
import processing.core.*;
public class OverlayHandler {
public Canvas canv=new ResultOutput();
public JFrame drawingFrame=new JFrame("OverlayWindow");
public BufferedImage graphics_internal;
public PGraphicsJava2D overridenPGraphics;
public PApplet sketch;
public final Color transparent=new Color(0, 0, 0, 0);
public Point mousePos=MouseInfo.getPointerInfo().getLocation();
//Creates an OverlayHandler
public OverlayHandler() {
}
public static OverlayHandler create(PApplet pa) {
OverlayHandler ret=new OverlayHandler();
ret.setupOverlay(pa);
return ret;
}
//Sets up the Overlay. This should be called in setup before any painting takes place.
public void setupOverlay(PApplet pa) {
sketch=pa;
//creates the BufferedImage
graphics_internal=new BufferedImage(pa.displayWidth, pa.displayHeight, BufferedImage.TYPE_INT_ARGB);
//creates an empty window
drawingFrame.setUndecorated(true);
drawingFrame.setBackground(transparent);
//Adds the canvas (it will later only show a BufferedImage)
drawingFrame.add(canv);
canv.setBackground(transparent);
drawingFrame.setSize(pa.displayWidth, pa.displayHeight);
drawingFrame.setAlwaysOnTop(true);
//Puts the window on the screen
drawingFrame.setVisible(true);
//The window can't be moved
drawingFrame.getRootPane().putClientProperty("apply.awt.draggableWindowBackground", false);
drawingFrame.pack();
//hides the Processing window
pa.getSurface().setVisible(false);
//Creates a PGraphicsJava2D object that used the image given here as its output
overridenPGraphics=new PGraphicsJava2D() {
@Override public Graphics2D checkImage() {
return (Graphics2D) graphics_internal.getGraphics();
}
};
//Sets up the PGraphics object
overridenPGraphics.setParent(pa);
overridenPGraphics.setSize(pa.displayWidth, pa.displayHeight);
overridenPGraphics.image=graphics_internal;
overridenPGraphics.g2=overridenPGraphics.checkImage();
//All graphics commands from processing now run through this object
pa.g=overridenPGraphics;
pa.registerMethod("draw", overlayInstance);
pa.registerMethod("pre", overlayInstance);
pa.registerMethod("mouseEvent", overlayInstance);
addListeners();
}
//One needs to be careful when shapes block the cursor as these may take away the ability to click on the window below.
//Having this on makes the mouse only be able to interact with the window below.
public boolean alwaysAllow=false;
public void alwaysAllowClicking(boolean b) {
alwaysAllow=b;
}
//Clears the screen
public void wipe() {
wipe(0, 0, sketch.displayWidth, sketch.displayHeight);
}
//Prevents the sketch from continuing while the canvas is painting.
public volatile boolean paintLock=false;
//Clears a part of the screen
public void wipe(int x1, int y1, int x2, int y2) {
//Wipes the BufferedImage
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
overridenPGraphics.g2.setComposite(composite);
overridenPGraphics.g2.setColor(transparent);
overridenPGraphics.g2.fillRect(x1, y1, x2, y2);
//Allows things to be properly drawn again
overridenPGraphics.g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
}
//creates interactions with the sketch.
public OverlayUpkeep overlayInstance=new OverlayUpkeep();
public class OverlayUpkeep {
//Updates the mouse positions
public void pre() {
sketch.pmouseX=mousePos.x;
sketch.pmouseY=mousePos.y;
mousePos=MouseInfo.getPointerInfo().getLocation();
sketch.mouseX=mousePos.x;
sketch.mouseY=mousePos.y;
}
public void mouseEvent(processing.event.MouseEvent e) {
pre();
}
//Updates the canvas after each draw
public void draw() {
paintLock=true;
//refresh the canvas
SwingUtilities.updateComponentTreeUI(drawingFrame);
//Stopps the sketch until the canvas is done painting
while(paintLock);
}
}
//Canvas used for the window
public class ResultOutput extends Canvas {
public void paint(Graphics g) {
//The use of BufferStrategy was ditched because it wasn't compatible with transparent pixels.
//This is the reason the already existing SmoothCanvas wasn't used.
g.drawImage(graphics_internal, 0, 0, sketch.displayWidth, sketch.displayHeight, null);
//This allows you to always click
paintLock=false;
if (alwaysAllow) g.clearRect(mousePos.x, mousePos.y, 1, 1);
}
}
//Uses already existing code to add functionality like registering key and mouse presses to the canvas
public void addListeners() {
PSurfaceAWT currentSurface=(PSurfaceAWT)sketch.getSurface();
try {
Field canvasField=PSurfaceAWT.class.getDeclaredField("canvas");
Method addListenersMethod=PSurfaceAWT.class.getDeclaredMethod("addListeners");
addListenersMethod.setAccessible(true);
canvasField.setAccessible(true);
Object currCanvas=canvasField.get(currentSurface);
canvasField.set(currentSurface, canv);
addListenersMethod.invoke(currentSurface);
canvasField.set(currentSurface, currCanvas);
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
}
Code
This creates an PGraphicsJava2D object to replace the standard PGraphics object used by the sketch.
I couldn’t use the standard SmoothCanvas because the way it paints the graphics onto the screen (found in PGraphicsAWT.SmoothCanvas.render()) isn’t compatible with transparent pixels (using BufferStrategy) has this problem.
One can observe some kind of “flickering” when this runs. I suspect that this is because using SwingUtilities.updateComponentTreeUI(drawingFrame);
may not be the best way for merely refreshing a image. If anyone has a suggestion on how to improve this feel free to post it here.
Edit: I think I fixed by replacing the canvas with a JPanel here’s the updated code:
Code
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import processing.awt.*;
import processing.core.*;
public class OverlayHandler {
public JPanel canv=new ResultOutput();
public JFrame drawingFrame=new JFrame("OverlayWindow");
public BufferedImage graphics_internal;
public PGraphicsJava2D overridenPGraphics;
public PApplet sketch;
public final Color transparent=new Color(0, 0, 0, 0);
public AlphaComposite clear_composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
public Point mousePos=MouseInfo.getPointerInfo().getLocation();
//Creates an OverlayHandler
public OverlayHandler() {
}
public static OverlayHandler create(PApplet pa) {
OverlayHandler ret=new OverlayHandler();
ret.setupOverlay(pa);
return ret;
}
//Sets up the Overlay. This should be called in setup before any painting takes place.
public void setupOverlay(PApplet pa) {
//hides the Processing window
pa.getSurface().setVisible(false);
sketch=pa;
//creates the BufferedImage
graphics_internal=new BufferedImage(pa.displayWidth, pa.displayHeight, BufferedImage.TYPE_INT_ARGB);
//creates an empty window
drawingFrame.setUndecorated(true);
drawingFrame.setBackground(transparent);
//Adds the canvas (it will later only show a BufferedImage)
canv.setFocusable(true);
canv.setFocusTraversalKeysEnabled(false);
canv.setBackground(transparent);
drawingFrame.setLayout(null);
drawingFrame.setSize(pa.displayWidth, pa.displayHeight);
canv.setPreferredSize(new Dimension(pa.displayWidth, pa.displayHeight));
//canv.setSize(pa.displayWidth, pa.displayHeight);
drawingFrame.setLayout(null);
drawingFrame.setContentPane(canv);
drawingFrame.setAlwaysOnTop(true);
//Puts the window on the screen
drawingFrame.setVisible(true);
//The window can't be moved
drawingFrame.getRootPane().putClientProperty("apply.awt.draggableWindowBackground", false);
drawingFrame.pack();
//Creates a PGraphicsJava2D object that used the image given here as its output
overridenPGraphics=new PGraphicsJava2D() {
@Override public Graphics2D checkImage() {
return (Graphics2D) graphics_internal.getGraphics();
}
};
//Sets up the PGraphics object
overridenPGraphics.setParent(pa);
overridenPGraphics.setSize(pa.displayWidth, pa.displayHeight);
overridenPGraphics.image=graphics_internal;
overridenPGraphics.g2=overridenPGraphics.checkImage();
//All graphics commands from processing now run through this object
pa.g=overridenPGraphics;
pa.registerMethod("draw", overlayInstance);
pa.registerMethod("pre", overlayInstance);
pa.registerMethod("mouseEvent", overlayInstance);
addListeners();
}
//One needs to be careful when shapes block the cursor as these may take away the ability to click on the window below.
//Having this on makes the mouse only be able to interact with the window below.
public boolean alwaysAllow=false;
public void alwaysAllowClicking(boolean b) {
alwaysAllow=b;
}
//Clears the screen
public void wipe() {
wipe(0, 0, sketch.displayWidth, sketch.displayHeight);
}
//Prevents the sketch from continuing while the canvas is painting.
public volatile boolean paintLock=false;
//Clears a part of the screen
public void wipe(int x, int y, int dx, int dy) {
wipe(x, y, dx, dy, overridenPGraphics.g2);
}
//Clears a part of the screen
public void wipe(int x, int y, int dx, int dy, Graphics2D g2) {
//Wipes the BufferedImage
g2.setComposite(clear_composite);
g2.setColor(transparent);
g2.fillRect(x, y, dx, dy);
//Allows things to be properly drawn again
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
}
//creates interactions with the sketch.
public OverlayUpkeep overlayInstance=new OverlayUpkeep();
public class OverlayUpkeep {
//Updates the mouse positions
public void pre() {
sketch.pmouseX=mousePos.x;
sketch.pmouseY=mousePos.y;
mousePos=MouseInfo.getPointerInfo().getLocation();
sketch.mouseX=mousePos.x;
sketch.mouseY=mousePos.y;
}
public void mouseEvent(processing.event.MouseEvent e) {
pre();
}
//Updates the canvas after each draw
public void draw() {
paintLock=true;
//refresh the canvas
canv.repaint();
//Stopps the sketch until the canvas is done painting
while (paintLock);
}
}
//Canvas used for the window
public class ResultOutput extends JPanel {
public void paintComponent(Graphics g) {
//Wipes the screen before putting the current image there
Graphics2D g2=(Graphics2D)g;
wipe(0, 0, getWidth(), getHeight(), g2);
//The use of BufferStrategy was ditched because it wasn't compatible with transparent pixels.
//This is the reason the already existing SmoothCanvas wasn't used.
g.drawImage(graphics_internal, 0, 0, sketch.displayWidth, sketch.displayHeight, null);
paintLock=false;
//This allows you to always click onto the window below
if (alwaysAllow) {
//Wipes the pixel under the cursor
mousePos=MouseInfo.getPointerInfo().getLocation();
wipe(mousePos.x, mousePos.y, 1, 1, g2);
}
}
}
//Uses already existing listeners from the canvas of the standard processing windows and puts them onto the new panel
public void addListeners() {
Canvas scanv=(Canvas)((PSurfaceAWT)sketch.getSurface()).getNative();
for (MouseListener ml : scanv.getMouseListeners()) canv.addMouseListener(ml);
for (MouseMotionListener ml : scanv.getMouseMotionListeners()) canv.addMouseMotionListener(ml);
for (MouseWheelListener ml : scanv.getMouseWheelListeners()) canv.addMouseWheelListener(ml);
for (KeyListener kl : scanv.getKeyListeners()) canv.addKeyListener(kl);
for (FocusListener kl : scanv.getFocusListeners()) canv.addFocusListener(kl);
}
}