Is it possible to construct PDFs by merging many PGraphics?

I’m writing a program with undo/redo functionality. It works fine using image() method to the screen renderer but I lose vector data if I use that technique in PDF.
Is there a way to merge PGraphics, preserving the vector data so I can write a PDF/SVG?

What do you mean by merging PGraphics? Can you give an example of what you’re trying to do?

simply layering one on top of the other

Then sure, that’s possible.

image(graphicsOne, 0, 0);
image(graphicsTwo, 0, 0);

Can you please post a simplified example that shows what you’re trying to do? See this guide for more info:

Yes, and that may be fine for low resolution screen display, but image() rasterizes the PGraphic.

Here’s the code for that sketch.

import processing.pdf.*;

void setup() {
  beginRecord(PDF, "export.pdf");
  size(500, 500);
}
void draw() {
  ellipse(100,50,100,100);
  PGraphics graphicOne = createGraphics(500,500);
  graphicOne.beginDraw();
  graphicOne.ellipse(50,50,100,100);
  graphicOne.endDraw();
  
  image(graphicOne,0,0);
}

void keyPressed(){
  if(key == 'p'){
    endRecord();
    exit();
  }
}

Have you tried using the noSmooth() function?

No, but I did just now. It removes the anti-aliasing, but it’s still a raster graphic.
noSmooth

By the way, I’m working on a painting tool with undo/redo. PGraphics are stored in an ArrayList so an undo operation just reconstructs the whole image by looping through the array and imaging them all to the window. Maybe there’s a better way.

You could store your shapes in a data structure that allows you to draw them on command instead of storing the pixels directly. Something like a Shape class, and then an ArrayList of Shape instances.

I’ve tried something like that but I must confess my ignorance. I don’t know how to even make a line or ellipse store into and redraw from a PShape container.

No I don’t mean PShape, I mean a custom Shape class. Something like this:

interface Shape{
  void drawShape();
}

class Circle implements Shape{
  float x;
  float y;
  float radius;

  void drawShape(){
    ellipse(x, y, radius, radius);
  }
}

ArrayList<Shape> myShapes = new ArrayList<Shape>();
myShapes.add(new Circle(x, y, radius));

for(Shape shape: myShapes){
  shape.drawShape();
}

This is just an example, but hopefully it gets the idea across. This approach would allow you to draw the shapes directly instead of storing a bunch of PGraphics buffers in memory.

Shameless self-promotion: here is a guide on creating classes in Processing:

1 Like

Okay!! Thank you! It’ll take me some time to wrap my head around this, as a drawing tool, but there is hope!

1 Like

Alright, I’m not sure what I’m missing here.I’m getting a Constructor does not exist error. Here’s the .pde I’m working with.

ArrayList<Shape> myShapes = new ArrayList<Shape>();

interface Shape {
  void drawShape();
}

class Circle implements Shape{
  float x;
  float y;
  float radius;

  void drawShape(){
    ellipse(x, y, radius, radius);
  }
}

void setup() {
  size(500, 500);
}
void draw() {
}

void mousePressed() {
  myShapes.add(new Circle(mouseX, mouseY, 50));
}


void keyPressed() {
  if (key == 'd') {
    for (Shape shape : myShapes) {
      shape.drawShape();
    }
  }
}

I think I figured it out! This has the PDF export too.

import processing.pdf.*;

import java.util.*; // lets us use Deque - Double ended queue

Deque<Shape> undoQ = new LinkedList<Shape>();
Deque<Shape> redoQ = new LinkedList<Shape>();
PGraphicsPDF pdf;

void setup() {
  size(500, 500);
  pdf = (PGraphicsPDF) createGraphics(width, height, PDF, "export.pdf");
}
void draw() {
}
interface Shape {
  void drawShape();
  void drawShapePDF();
}
class Circle implements Shape {
  float x;
  float y;
  float radius;
  Circle(float xpos, float ypos, float r) {
    x = xpos;
    y = ypos;
    radius = r;
  }
  void drawShape() {
    ellipse(x, y, radius, radius);
  }
  void drawShapePDF() {
    pdf.ellipse(x, y, radius, radius);
  }
}

void mousePressed() {
  undoQ.add(new Circle(mouseX, mouseY, 50));
  undoQ.peekLast().drawShape();
}

void keyPressed() {
  if (key == 'z') {
    if (undoQ.size()>0) {
      redoQ.add(undoQ.removeLast());
      g.background(205);
      for (Shape i : undoQ) {
        i.drawShape();
      }
    }
  } else if (key == 'y') {
    if (redoQ.size()>0) {
      undoQ.add(redoQ.removeLast());
      for (Shape i : undoQ) {
        i.drawShape();
      }
    }
  } else if (key == 'p') {
    pdf.beginDraw();
    for (Shape i : undoQ) {
      i.drawShapePDF();
    }
    pdf.endDraw();
    pdf.dispose();
  }
}

1 Like

Awesome! Did this fix your problem of shapes being pixelated?

Yes! it looks like I didn’t paste the Shape class. Here’s that.

interface Shape {
  void drawShape();
}
class Circle implements Shape {
  float x;
  float y;
  float radius;
  String group;
  boolean visibility;
  Circle(float xpos, float ypos, float r, String grp, boolean v) {
    x = xpos;
    y = ypos;
    radius = r;
    group = grp;
    visibility = v;
  }
  void drawShape() {
    if(saving){
      pdf.ellipse(x, y, radius, radius);
    }else{
      ellipse(x, y, radius, radius);
    }
  }
}
class Square implements Shape {
  float x;
  float y;
  float xx;
  float yy;
  String group;
  boolean visibility;
  Square(float xpos, float ypos, float xxpos, float yypos, String grp, boolean v) {
    x = xpos;
    y = ypos;
    xx = xxpos;
    yy = yypos;

    group = grp;
    visibility = v;
  }
  void drawShape() {
    if(saving){
      pdf.rect(x, y, xx, yy);
    }else{
      rect(x, y, xx, yy);
    }
  }
}

class Line implements Shape {
  float x;
  float y;
  float px;
  float py;
  String group;
  boolean visibility;
  Line(float xpos, float ypos, float pxpos, float pypos, String grp, boolean v) {
    x = xpos;
    y = ypos;
    px = pxpos;
    py = pypos;
    group = grp;
    visibility = v;
  }
  void drawShape() {
    if(saving){
      pdf.line(px, py, x, y);
    }else{
      line(px, py, x, y);
    }
  }
}
1 Like