Is it possible to construct PDFs by merging many PGraphics?


#1

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?


#2

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


#3

simply layering one on top of the other


#4

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:


#5

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();
  }
}


#6

Have you tried using the noSmooth() function?


#7

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


#8

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.


#9

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.


#10

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.


#11

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:


#12

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


#13

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();
    }
  }
}


#14

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();
  }
}


#15

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


#16

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);
    }
  }
}