Printshop with Processing – Preparing algorithmic content for offset print

Created by Andreas Gysin and used at a resonate.io workshop in Belgrade. First documented at ertdfgcvb.com.

Last month at Resonate festival in Belgrade Andreas Gysin ran a workshop titled “Printshop With Processing” aimed at graphic designers who want to integrate Processing in their print-production workflow. The participants discussed and experimented with different ways on how to output algorithmically created graphical ornaments or representations of data to multiple page documents, ready for offset print with process (CMYK) and spot colors. Using this tutorial and examples, previously edited vector graphics, typography blocks or entire layouts in PDF documents can be imported, manipulated and used as templates instead of adding them later-on ‘by hand’.

Andreas created few examples that demonstrate the use of a slightly modified processing pgraphicspdf class which permits, among a few other things, to set colors in cmyk space. The actual Processing PDF library permits to create PDF documents with the familiar Processing graphics API. The library includes the iText Open Source Library for Java but does not expose all of it’s functionalities. The purpose of the custom PDF class is to give access to some useful functions (by breaking the seamless integration in Processing). The library is now just a visible Processing class which allows eventual implementation of further functions.

→ download from github
view on github

CMYK

A few examples that demonstrate the use of a slightly modified Processing PGraphicsPDF class which permits, among a few other things, to set colors in CMYK space. This sketch generates a two sided PDF document in CMYK color mode with some extra spot colors. A font from the font library will be used for text output.

cmyk

// This skecth generates a two sided PDF document 
// in CMYK color mode with some extra spot colors.
// A font from the font library will be used for text output.

final PVector MARGIN = new PVector(mm(20), mm(30));
final PVector GRID   = new PVector(mm(30), mm(30));
final PVector GUTTER = new PVector(mm(7), mm(7));

void setup() {
  // Create an A4 (210 x 297 mm) document.
  // For precise document size creation it’s possible to use float values for witdh and height
  // Watch out for PDF instances which are cast as PGraphics: println(((PGraphics) pdf).width));
  // Please note that the pdf library needs an absolute path  
  PDF pdf = new PDF(this, mm(210), mm(297), sketchPath(System.currentTimeMillis() + ".pdf"));
  // prints a list of all available (system) fonts
  // println(pdf.listFonts());
  // eventually substitute with a font present in your library:
  pdf.textFont(createFont("AkzidenzGroteskBE-Bold", 11));

  // Page 1, Section 1.
  pdf.pushMatrix();
  section(0, "1.", "C, M, Y, K", pdf);
  pdf.noStroke();

  pdf.fillCMYK(1, 0, 0, 0);
  pdf.rect(0, 0, GRID.x, GRID.y);

  pdf.translate(GRID.x + GUTTER.x, 0);
  pdf.fillCMYK(0, 1, 0, 0);
  pdf.rect(0, 0, GRID.x, GRID.y);

  pdf.translate(GRID.x + GUTTER.x, 0);
  pdf.fillCMYK(0, 0, 1, 0);
  pdf.rect(0, 0, GRID.x, GRID.y);

  pdf.translate(GRID.x + GUTTER.x, 0);
  pdf.fillCMYK(0, 0, 0, 1);
  pdf.rect(0, 0, GRID.x, GRID.x);

  pdf.popMatrix();

  // Page 1, Section 2.
  // For black colors it's possible to use K only
  pdf.pushMatrix();
  section(2, "2.", "K\nat 5—20%", pdf);
  for (int i=0; i<4; i++) {
    pdf.fillK(i * 0.05 + 0.05);
    pdf.rect((GRID.x + GUTTER.x) * i, 0, GRID.x, GRID.y);
  }
  pdf.popMatrix();

  // Page 1, Section 3.
  // A few spot colors, in this case Pantone Process C
  // The CMYK quantities have only preview purposes
  pdf.pushMatrix();
  section(4, "3.", "A few Pantone \nProcess C \nspot colors", pdf);
  for (int i=0; i<8; i++) {
    pdf.fillK(0.05);

    if (i == 0) pdf.fillSpot("PANTONE 193 C", 1, 0.16, 1.0, 0.77, 0.05);
    else if (i == 1) pdf.fillSpot("PANTONE 209 C", 1, 0.38, 0.92, 0.58, 0.37);
    else if (i == 2) pdf.fillSpot("PANTONE 7548 C", 1, 0, 0.23, 1.0, 0);
    else if (i == 3) pdf.fillSpot("PANTONE CoolGray 1 C", 1, 0.13, 0.11, 0.12, 0);
    //skip 4
    else if (i == 5) pdf.fillSpot("PANTONE 5753 C", 0.8, 0.59, 0.43, 0.89, 0.28); // the second argument is the amount 
    else if (i == 6) pdf.fillSpot("PANTONE 653 C", 1, 0.87, 0.64, 0.18, 0.3);
    else if (i == 7) pdf.fillSpot("PANTONE 655 C", 1, 1, 0.9, 0.37, 0.37);

    if (i != 4) {
      float rx = (GRID.x + GUTTER.x) * (i % 4);
      float ry = (GRID.x + GUTTER.x) * (i / 4);
      pdf.rect(rx, ry, GRID.x, GRID.y);
    }
  }
  pdf.popMatrix();

  // Let's add a second page
  pdf.nextPage();

  // It is possible to enable or disable overprint.  
  // To see the result of this page you may need to actually print the document 
  // or open it with a program that previews overpint.
  // Page 2, Section 4.
  // Overprint disabled  
  pdf.pushMatrix();   
  section(0, "4.", "Left:\nwithout\noverprint\n\nRight:\nwith\noverprint\n\nPrint out \nto see \nthe difference", pdf);

  float d = GRID.x / 4;
  for (int i=0; i<2; i++) {

    if (i == 0) pdf.overPrint(false);
    else pdf.overPrint(true);  

    pdf.pushMatrix();
    pdf.translate((GRID.x + GUTTER.x) * 2 * i, 0);    
    pdf.fillCMYK(1, 0, 0, 0);
    pdf.rect(0, 0, GRID.x, GRID.y);
    pdf.fillCMYK(0, 1, 0, 0);
    pdf.rect(d, d, GRID.x, GRID.y);

    pdf.translate(0, (GRID.y + GUTTER.y)*2);
    pdf.fillCMYK(0, 1, 0, 0);
    pdf.rect(d, d, GRID.x, GRID.y);
    pdf.fillCMYK(1, 0, 0, 0);
    pdf.rect(0, 0, GRID.x, GRID.y);

    pdf.translate(0, (GRID.y + GUTTER.y)*2);
    pdf.fillCMYK(1, 0, 0, 0);
    pdf.rect(0, 0, GRID.x, GRID.y);
    pdf.fillCMYK(0, 1, 0, 0);
    pdf.rect(d, d, GRID.x, GRID.y);
    pdf.fillCMYK(0, 0, 1, 0);
    pdf.rect(d*2, d*2, GRID.x, GRID.y);
    pdf.fillCMYK(0, 0, 0, 1);
    pdf.rect(d*3, d*3, GRID.x, GRID.y);

    pdf.popMatrix();
  }
  pdf.popMatrix();

  // Save and close
  pdf.dispose();
  exit();
}

// Converts mm to PostScript points
// This function should be called “pt” but I like how it looks when used:
// mm(29) will be converted to 29mm
public float mm(float pt) {
  return pt * 2.83464567f;
}

// A small helper function to convert CMYK to Processing RGB, for preview purposes
// Formula from http://www.easyrgb.com/index.php?X=MATH
// Convert CMYK > CMY > RGB
// c,m,y,k ranges from 0.0 to 1.0
int CMYKtoRGB(float c, float m, float y, float k) {
  float C = c * (1-k) + k;
  float M = m * (1-k) + k;
  float Y = y * (1-k) + k;
  float r = (1-C) * 255;
  float g = (1-M) * 255;
  float b = (1-Y) * 255;
  return color(r, g, b);
}

void section(int offs, String nr, String title, PGraphics g) {
  // Watch out: CMYK and Spot colors are not pushed in the style stack by pushStyle
  // Stroke weights, RGB colors, textModes, etc. do. 
  g.pushStyle(); 
  float offsY = (GRID.y + GUTTER.y) * offs;
  float w = GRID.x * 5 + GUTTER.x * 4;

  ((PDF) g).strokeK(1.0); 
  ((PDF) g).fillK(1.0);

  g.translate(MARGIN.x, MARGIN.y + offsY);
  g.strokeWeight(0.3);
  g.line(0, -GUTTER.y / 2, w, -GUTTER.y / 2);
  g.textAlign(RIGHT, TOP);
  g.text(nr, -GUTTER.x, 0);
  g.textAlign(LEFT, TOP);
  g.text(title, 0, 0); 
  g.translate(GRID.x + GUTTER.x, 0);  // final offset, ready to draw
  g.popStyle();
}

Preview

Preview is an example which shows how to preview the graphics in rgb color space (monitor) before creating the cmyk output (pdf). This sketch creates some imagery and renders it into a PGraphics preview which can be displayed on screen. Some parameters can be modified with the mouse position. At each click the current snapshot is saved into a PDF file.

preview_app_bw

// This sketch creates some imagery and renders it into 
// a PGraphics preview which can be displayed on screen.
// Some parameters can be modified with the mouse position.
// At each click the current snapshot is saved into a PDF file.

// The final result will be rendered into a small postcard-sized document.
// A6 paper sizes (148 x 105 mm):
// (in some cases it will be desirable to add bleed)
final float CARD_W = mm(148);
final float CARD_H = mm(105);
Circles circles;
PGraphics preview;
PVector previewOffset;
// A CMYK color: at each mousePress we randomize this color.
// It will also be converted approximately to Processing RGB (int) for preview purposes.
CMYKColor col;

void setup() {
  size(800, 600, OPENGL);

  preview = createGraphics(round(CARD_W), round(CARD_H), OPENGL);
  circles = new Circles();

  // make sure the offset gets resetted with the next shift() call:
  previewOffset = new PVector(width, height);   
  shift();
}

void draw() {    
  circles.build(10.0 * mouseY / height + 2.7, dist(0, 0, CARD_W, CARD_H), 1.1);    
  circles.explode(PI * mouseX / width);

  render(preview);  
  image(preview, previewOffset.x, previewOffset.y, preview.width/2, preview.height/2);
}

void mousePressed() {
  savePDF();   
  shift();  
  col = new CMYKColor(random(1), random(1), random(1), 0); 
  circles.reSeed();
}

void render(PGraphics g) {
  // The nasty part: 
  // we check if our renderer is a PDF instance
  // so we can use the custom stroke and fill settings.
  // Pay extra attention to the (float) width and height!
  boolean isPDF = g instanceof PDF;
  float ox, oy;
  g.beginDraw();
  if (isPDF) {
    ox = ((PDF) g).width / 2;
    oy = ((PDF) g).height / 2;
    // There are also some advantages:
    // the fine stroke will not be rendered in the small preview window   
    // ((PDF) g).overPrint(true);
    // ((PDF) g).strokeCMYK(1, 0, 0, 0);
    ((PDF) g).strokeCMYK(col);
    circles.drawCircles(g, ox, oy);
  } 
  else {    
    ox = g.width / 2;
    oy = g.height / 2;
    g.stroke(CMYKtoRGB(col));
    g.background(255);
  }

  circles.drawShapes(g, ox, oy);
  g.endDraw();
}

void savePDF() {  
  // For precise document size creation it’s possible to use float values for witdh and height
  // Watch out for PDF instances which are cast as PGraphics: 
  // println(((PGraphics) pdf).width)); 
  // will output 0...
  // Please note that the pdf library needs an absolute path:  
  PDF pdf = new PDF(this, CARD_W, CARD_H, sketchPath("data/" + System.currentTimeMillis() + ".pdf"));
  render(pdf);
  pdf.dispose();
}

// Shift around the preview image, load a new map and randomize the color
void shift() {  
  float m = 20;
  float w = preview.width/2;
  float h = preview.height/2;
  previewOffset.x += w + m;
  if (previewOffset.x + w + m > width) {
    previewOffset.x = m;
    previewOffset.y += h + m;
    if (previewOffset.y + h + m > height) {
      previewOffset.set(m, m, 0);
      background(230, 230, 230);
    }
  }

  // Randomize the CMYK color
  col = new CMYKColor(random(1), random(1), random(1), 0);
  // Load a random map
  circles.loadMap(loadImage("map_" + (int) random(8) + ".png"));
}

// Converts mm to PostScript points
// This function should be called “pt” but I like how it looks when used:
// mm(29) will be converted to 29mm
public float mm(float pt) {
  return pt * 2.83464567f;
}

// A small helper function to convert CMYK to Processing RGB, for preview purposes
// Formula from http://www.easyrgb.com/index.php?X=MATH
// Convert CMYK > CMY > RGB
// c,m,y,k ranges from 0.0 to 1.0
int CMYKtoRGB(float c, float m, float y, float k) {
  float C = c * (1-k) + k;
  float M = m * (1-k) + k;
  float Y = y * (1-k) + k;
  float r = (1-C) * 255;
  float g = (1-M) * 255;
  float b = (1-Y) * 255;
  return color(r, g, b);
}

int CMYKtoRGB(CMYKColor col) {
  return CMYKtoRGB(col.getCyan(), col.getMagenta(), col.getYellow(), col.getBlack());
}

preview_thenpreview_newpreview_void

import java.util.Random;

// Just a class to render some circles, based on a lookup map.
// Note that in the drawing methods a PGraphics argument is passed, 
// this will let us select the render target, unfortunately not without some painful casting...

class Circles {
  PImage map;
  ArrayList<Circle>circles;   
  float spacing;
  float strokeSpacing = 0.6;
  int seed;

  Circles() {       
    circles = new ArrayList();
  }

  void build(float spacing, float maxRadius, float mapScale) {
    if (map != null) {
      this.spacing = spacing;      
      int num = floor(maxRadius / spacing) + 1;
      num *= 1.5; // a few extra circles    
      circles.clear();
      for (int i=0; i<num; i++) {
        float radius = i * spacing + (spacing + strokeSpacing) / 2; 
        circles.add(new Circle(radius, map, mapScale));
      }
    }
  }

  void loadMap(PImage map) {
    this.map = map;
  }

  void drawShapes(PGraphics g, float x, float y) {
    g.strokeWeight(max(strokeSpacing, spacing - strokeSpacing));
    g.pushMatrix();
    g.translate(x, y);
    for (Circle c : circles) {
      c.drawShape(g);
    }
    g.popMatrix();
  }

  void drawCircles(PGraphics g, float x, float y) {
    g.noFill();
    g.strokeWeight(strokeSpacing / 2);
    for (Circle c : circles) {
      g.ellipse(x, y, c.radius * 2, c.radius * 2);
    }
  }

  void reSeed() {
    seed = millis();
  }

  void explode(float val) {
    Random r = new Random(seed);
    for (Circle c : circles) {
      float a = map(r.nextFloat(), 0, 1, -val, val);
      c.setAng(a);
    }
  }

  // nested class
  class Circle {
    float ang, dang;
    float radius;
    int steps;
    PVector[] vertices;
    int[] colors;

    Circle(float radius, PImage map, float mapScale) {
      this.radius = radius;

      steps = floor(min(400, max(30, PI * radius * 2)));
      vertices = new PVector[steps];
      colors = new int[steps];

      float a = TWO_PI / steps;    
      float ox = map.width/2;
      float oy = map.height/2;
      for (int i=0; i<steps; i++) {
        float x = cos(a * i) * radius;
        float y = sin(a * i) * radius;
        // the PImage.get(x, y) function returns black 
        // for pixels read outside the image area...
        color c = map.get(round(x / mapScale + ox), round(y / mapScale + oy));
        // we could also store the actual color
        // colors[i] = c;
        // and the use it directly... but instead we just build a lookup map
        if (brightness(c) < 10) {
          colors[i] = 0;
        } 
        else {
          colors[i] = 1;
        }
        vertices[i] = new PVector(x, y);
      }
    }

    void setAng(float a) {
      // dang = a;
      ang = a;
    }

    void drawShape(PGraphics g) {
      // for animation purposes:
      // ang = ang + (dang - ang) * 0.1;      
      PVector p1, p2;
      g.strokeCap(ROUND);
      g.noFill();
      g.pushMatrix();
      g.rotate(ang);            
      // we use LINES to avoid blending
      // this will give an unexpected result with strokeCap other than ROUND      
      g.beginShape(LINES); 
      for (int i=0; i<steps; i++) {
        int c = colors[i%steps];        
        if (c == 1) {
          p1 = vertices[i%steps];
          p2 = vertices[(i + 1) % steps];
          g.vertex(p1.x, p1.y);
          g.vertex(p2.x, p2.y);
        }
      }     
      g.endShape();
      g.popMatrix();
    }
  }
}

Template

Template is an example that loads an existing pdf file and uses it as a template to create a series of business cards, each with a slightly different form. the output file is ready for (offset) print. The example can also add some bleed and cropmarks.

// This sketch loads a template PDF and applies it over the generated graphics.
// Some bleed and cropmarks have also been added.

final float BLEED  = mm(5);
final float CARD_W = mm(85);
final float CARD_H = mm(55);
final int PAGE_NUM = 50;

void setup() {
  // Please note that the pdf library needs an absolute path:  
  PDF pdf = new PDF(this, CARD_W + BLEED * 2, CARD_H + BLEED * 2, sketchPath("data/"+System.currentTimeMillis()+".pdf"));
  // Template is loaded here (alswo with absolute path).
  // The template also contains the extra bleed of 5 mm
  pdf.loadTemplate(sketchPath("data/bc-roberta.pdf"));

  // Just a worm generator
  WormBucket wb = new WormBucket(CARD_W, CARD_H);

  for (int i=0; i<PAGE_NUM; i++) {  
    wb.generate();
    pdf.pushMatrix();
    pdf.translate(BLEED, BLEED);
    pdf.overPrint(true);
    wb.draw(pdf);    
    pdf.popMatrix();
    pdf.applyTemplate(); 
    cropMarks(pdf, BLEED, BLEED, CARD_W, CARD_H, mm(5), mm(2));
    if (i < PAGE_NUM-1) pdf.nextPage();
  }

  pdf.dispose();
  exit();
}

// Adds crop marks
void cropMarks(PGraphics g, float x, float y, float w, float h, float len, float dist) {
  if (g instanceof PDF) ((PDF) g).strokeK(1);
  else g.stroke(0);
  g.strokeWeight(0.25);   
  g.line(x, y - dist, x, y - dist - len);
  g.line(x + w, y - dist, x + w, y - dist - len);
  g.line(x, y + h + dist, x, y + h + dist + len);
  g.line(x + w, y + h + dist, x + w, y + h + dist + len);    
  g.line(x - dist, y, x - dist - len, y);
  g.line(x - dist, y + h, x - dist - len, y + h);
  g.line(x + w + dist, y, x + w + dist + len, y);
  g.line(x + w + dist, y + h, x + w + dist + len, y + h);
}

// Converts mm to PostScript points
// This function should be called “pt” but I like how it looks when used:
// mm(29) will be converted to 29mm
public float mm(float pt) {
  return pt * 2.83464567f;
}

// A small helper function to convert CMYK to Processing RGB, for preview purposes
// Formula from http://www.easyrgb.com/index.php?X=MATH
// Convert CMYK > CMY > RGB
// c,m,y,k ranges from 0.0 to 1.0
int CMYKtoRGB(float c, float m, float y, float k) {
  float C = c * (1-k) + k;
  float M = m * (1-k) + k;
  float Y = y * (1-k) + k;
  float r = (1-C) * 255;
  float g = (1-M) * 255;
  float b = (1-Y) * 255;
  return color(r, g, b);
}

int CMYKtoRGB(CMYKColor col) {
  return CMYKtoRGB(col.getCyan(), col.getMagenta(), col.getYellow(), col.getBlack());
}

template_spaghetti_big

class Worm {
  final static int RIGHT = 0;
  final static int DOWN  = 1;  
  final static int LEFT  = 2;
  final static int UP    = 3;
  final static int LEFT_TURN  = 4;
  final static int RIGHT_TURN = 5;

  float gridSize = 10;  
  int gridW, gridH;
  int x, y;
  int dir; 
  int curveRadius;
  ArrayList<Worm> children = new ArrayList();
  ArrayList<PVector> vertices = new ArrayList();
  boolean alive = true;
  boolean hasParent;
  float curvyness;

  WormBucket bucket;

  CMYKColor col;

  Worm(WormBucket bucket, int gridW, int gridH, float gridSize, float curvyness, int curveRadius) {
    this.bucket = bucket;
    this.gridW = gridW;
    this.gridH = gridH;
    this.gridSize = gridSize;    
    this.curvyness = curvyness;
    this.curveRadius = curveRadius;
    hasParent = false;
    init();  
    col = randomColor();
    addGridPoint();
  }

  Worm(Worm parent, int offset) {
    hasParent = true;   
    bucket = parent.bucket;
    gridW = parent.gridW;
    gridH = parent.gridH;
    gridSize = parent.gridSize;
    curvyness = parent.curvyness;
    curveRadius = parent.curveRadius;
    dir = parent.dir;
    x = parent.x;
    y = parent.y;
    if (dir == RIGHT) y += offset;
    else if (dir == LEFT) y -= offset;
    else if (dir == UP) x += offset;
    else if (dir == DOWN) x -= offset;
    col = randomColor();
    addGridPoint();
  }

  CMYKColor randomColor() {    
    int mode = (int) random(3);
    if (mode == 0) return new CMYKColor(1f, 0f, 0f, 0f);
    else if (mode == 1) return new CMYKColor(0f, 1f, 0f, 0f);
    else if (mode == 2) return new CMYKColor(0f, 0f, 1f, 0f);    
    // else if (mode == 3) return new CMYKColor(1f, 1f, 0f, 0f);
    // else if (mode == 4) return new CMYKColor(0f, 1f, 1f, 0f);
    // else if (mode == 5) return new CMYKColor(1f, 0f, 1f, 0f);
    return new CMYKColor(0, 0, 0, 1f);
  }

  void init() {    
    // which side to start:
    int side = floor(random(1, 4)); // not the RIGHT side

    if (side == UP) {
      x = floor(random(1, gridW-1));
      y = 0;
      dir = DOWN;
    } 
    else if (side == DOWN) {
      x = floor(random(1, gridW-1));
      y = gridH;
      dir = UP;
    } 
    else if (side == LEFT) {
      x = 0;
      y = floor(random(1, gridH-1));
      dir = RIGHT;
    }
    else if (side == RIGHT) {
      x = gridW;
      y = floor(random(1, gridH-1));
      dir = LEFT;
    }
  }

  void addChildren(int num) {
    CMYKColor col2 = randomColor();
    for (int i=0; i<num; i++) {
      Worm w = new Worm(this, children.size() + 1);
      if (i%2==0) w.col = col2;
      else w.col = col;
      children.add(w);
      bucket.addWorm(w);
    }
  }

  void freeChild() {
    if (!children.isEmpty()) {
      Worm w = children.remove(children.size()-1);
      w.hasParent = false;
    }
  }

  int getChildCount() {
    return children.size();
  }

  void addGridPoint() {
    vertices.add(new PVector(x * gridSize, y * gridSize));
  }

  void step() {
    if (!hasParent && alive) {
      if (random(1) <= curvyness) {
        if (random(1) > 0.5) {
          int r = curveRadius;
          turn(LEFT_TURN, r);        
          for (Worm w : children) {          
            w.turn(LEFT_TURN, ++r);
          }
        } 
        else {
          int r = getChildCount() + curveRadius;
          turn(RIGHT_TURN, r); 
          for (Worm w : children) {
            w.turn(RIGHT_TURN, --r);
          }
        }
      }
      else {
        move();
        for (Worm w : children) w.move();
      }
      // At some point a child worm might want to leave the parents path:
      if (random(1) < 0.1) freeChild();

      if (!isInGrid()) {
        alive = false;
        // Free children which might still be inside the grid
        while (!children.isEmpty ()) freeChild();
      }
    }
  }

  boolean isInGrid() {
    return  !(x < 0 || x > gridW || y < 0 || y > gridH);
  }

  void draw(PGraphics g) {
    g.noFill();
    if (g instanceof PDF) {
      ((PDF) g).strokeCMYK(col);
    } 
    else {
      g.stroke(CMYKtoRGB(col));
      g.blendMode(MULTIPLY);
    }
    // g.strokeWeight(1);    
    // g.ellipse(vertices.get(0).x, vertices.get(0).y, gridSize*1.5, gridSize*1.5);            
    g.strokeWeight(gridSize-1);
    g.beginShape();
    for (PVector v : vertices) {
      g.vertex(v.x, v.y);
    }
    g.endShape();

    for (Worm w : children) w.draw(g);
  }

  void move() {
    if (dir == RIGHT) x += 1;
    else if (dir == LEFT) x-= 1;
    else if (dir == DOWN) y += 1;
    else if (dir == UP) y -= 1;
    addGridPoint();
  }

  void turn(int turn, int radius) {  
    int from = (dir + 2) % 4; // just to simplify the statements

    float r = radius * gridSize;
    float cx, cy;

    if (from == LEFT && turn == RIGHT_TURN) {
      cx = x * gridSize;
      cy = (y + radius) * gridSize;
      arc(cx, cy, r, -HALF_PI, 0);
      x += radius;
      y += radius;
    } 
    else if (from == LEFT && turn == LEFT_TURN) {
      cx = x * gridSize;
      cy = (y - radius) * gridSize;
      arc(cx, cy, r, HALF_PI, 0);
      x += radius;
      y -= radius;
    } 
    else  if (from == RIGHT && turn == RIGHT_TURN) {
      cx = x * gridSize;
      cy = (y - radius) * gridSize;
      arc(cx, cy, r, HALF_PI, PI);
      x -= radius;
      y -= radius;
    } 
    else if (from == RIGHT && turn == LEFT_TURN) {
      cx = x * gridSize;
      cy = (y + radius) * gridSize;
      arc(cx, cy, r, PI + HALF_PI, PI);
      x -= radius;
      y += radius;
    } 

    else if (from == DOWN && turn == RIGHT_TURN) {
      cx = (x + radius) * gridSize;
      cy = y * gridSize;
      arc(cx, cy, r, PI, PI + HALF_PI);   
      x += radius;
      y -= radius;
    } 
    else if (from == DOWN && turn == LEFT_TURN) {
      cx = (x - radius) * gridSize;
      cy = y * gridSize;
      arc(cx, cy, r, 0, -HALF_PI);      
      x -= radius;
      y -= radius;
    } 
    else  if (from == UP && turn == RIGHT_TURN) {
      cx = (x - radius) * gridSize;
      cy = y * gridSize;
      arc(cx, cy, r, 0, HALF_PI);  
      x -= radius;
      y += radius;
    } 
    else if (from == UP && turn == LEFT_TURN) {
      cx = (x + radius) * gridSize;
      cy = y * gridSize;
      arc(cx, cy, r, PI, HALF_PI);
      x += radius;
      y += radius;
    }

    //update dir
    if (turn == LEFT_TURN) dir = (dir + 3) % 4;    
    else if (turn == RIGHT_TURN) dir = (dir + 1) % 4;
  }

  void arc(float cx, float cy, float r, float a1, float a2) {
    int res = 50; // could be proportional to the radius
    float arc = (a2 - a1) / res;
    for (int i = 0; i < res+1; i++) {
      float x = cx + cos(a1 + arc * i) * r;
      float y = cy + sin(a1 + arc * i) * r;
      vertices.add(new PVector(x, y));
    }
  }
}

template_spaghetti

class WormBucket {
  ArrayList<Worm> worms;
  float width, height;
  float gridSize;
  int gridH, gridV;

  WormBucket(float width, float height) {
    this.width = width;
    this.height = height;
    worms = new ArrayList();
  }

  void generate() {    
    worms.clear();    
    gridSize = mm(random(1, 4));
    gridH = (int) (width / gridSize) + 6;
    gridV = (int) (height / gridSize) + 6;    

    int num = (int)random(2, 8);    
    for (int i=0; i<num; i++) {
      float curvyness = random(0.3, 0.9);
      int radius = (int) random(2, 5);
      int childCount = (int) random(2, 7);      
      Worm w = new Worm(this, gridH, gridV, gridSize, curvyness, radius);
      worms.add(w);
      w.addChildren(childCount);
    }

    boolean oneLeft = true;
    while (oneLeft) {
      oneLeft = false;
      for (Worm w : worms) {
        if (w.alive) {
          w.step();
          oneLeft = true;
        }
      }
    }
  }

  void addWorm(Worm w) {
    worms.add(w);
  }

  void draw(PGraphics g) {
    // an offset
    float ox = -3 * gridSize;
    float oy = -3 * gridSize;
    g.pushMatrix(); 
    g.translate(ox, oy);
    //drawGrid(g);
    for (Worm w : worms) w.draw(g);    
    g.popMatrix();
  }

  void drawGrid(PGraphics g ) {
    g.strokeWeight(0.5);
    float w = gridSize * gridH;
    float h = gridSize * gridV;
    for (int i=0; i<gridH+1; i++) {
      float x = i * gridSize;
      g.line(x, 0, x, h);
    }    
    for (int i=0; i<gridV+1; i++) {
      float y = i * gridSize;
      g.line(0, y, w, y);
    }
  }
}

Andreas Gysin

@andreasgysin
http://ertdfgcvb.com/CMYK
http://github.com/ertdfgcvb/CMYK

/++

/+

/++

2 comments on “Printshop with Processing – Preparing algorithmic content for offset print

  1. Hi, I’m trying to run the CMYK pdf file from my processing environment.

    It then displays an “expecting EOF found export” error message in the PDF class. What do I need to do to have it run?