A custom Mockup Tool, built with Processing (updated)

Published by Tim on Wednesday January 3, 2024

Last modified on August 25th, 2024 at 16:01

For my students at Elisava, I have created a new version of my mockup-tool. You need two different files for a mockup: Your animation in the .gif-format and a scene, which should be a .jpg file. Both files have to be stored in the data folder of your sketch and added to the settings section in the code, quite at the top. Make sure you install the GifAnimation library before.

Please note: Unfortunately I can’t give any support for this application, but I am very thankful for feedback. If you need help, please post your question on the Discord server.

import gifAnimation.*;

// #####################################
// The trcc mockup generator - 2024 edition
// Controls: 
// 1-4    - edit points
// r      - record video
// g      - show/hide grid
// p      - change position
// o      - skew

// #####################################
// Settings for the project

int WIDTH = 1920;
int HEIGHT = 1080;
String SCENE_IMAGE_FILENAME = "scene.jpg";
String INPUT = "input.gif";
float SCALE_FROM = 1; // Ken Burns Zoom
float SCALE_TO = 1.2;

// #####################################
// Application State

PImage sceneImage;
PGraphics pg;
PGraphics grid;
boolean record = false;
boolean showGrid = true;
boolean showUi = true;
boolean VectorSelectionMode = false;

// Positions of the 4 Vectors
float initialMagX = 300;
float initialMagY = 200;
float v1x = -initialMagX;
float v1y = -initialMagY;
float v2x = initialMagX;
float v2y = -initialMagY;
float v3x = initialMagX;
float v3y = initialMagY;
float v4x = -initialMagX;
float v4y = initialMagY;

int DESIGN = 0;
int SELECTIMAGE = 1;

int view = DESIGN;

PImage[] animation;

void setup() {
  sceneImage = loadImage(SCENE_IMAGE_FILENAME);
  sceneImage.filter(GRAY);
  pg = createGraphics(WIDTH, HEIGHT, P3D);
  frameRate(30);
  
  animation = Gif.getPImages(this, INPUT);
}

void settings() {
  size(WIDTH / 2, HEIGHT / 2, P3D);
  pixelDensity(2);
}

float offsetX = 0;
float offsetY = 0;
float W = 1;
float H = 1;
float scalarValue = 1;

void draw() {
  background(0);

  if (record) {
    playSequence();
  }

  pg.beginDraw();
  pg.clear();
  pg.imageMode(CENTER);

  pg.push();
  pg.translate(width/2, height/2);

  // Ken Burns Zoom
  if (record) {
    float kenBurnsScalar = map(recordFrame, 1, animation.length, SCALE_FROM, SCALE_TO);
    pg.scale(kenBurnsScalar);
  }
  // /END Ken Burns Zoom

  pg.push();

  pg.translate(offsetX, offsetY);
  //pg.scale(scalarValue);
  pg.scale(W, H);

  if (record) {
    pg.noTint();
  } else {
    pg.tint(255, 170);
  }
  pg.image(sceneImage, 0, 0);
  pg.pop();

  if (vectorSelector == 1) {
    v1x = -width/2+mouseX;
    v1y = -height/2+mouseY;
  }

  if (vectorSelector == 2) {
    v2x = -width/2+mouseX;
    v2y = -height/2+mouseY;
  }

  if (vectorSelector == 3) {
    v3x = -width/2+mouseX;
    v3y = -height/2+mouseY;
  }

  if (vectorSelector == 4) {
    v4x = -width/2+mouseX;
    v4y = -height/2+mouseY;
  }

  pg.imageMode(CENTER);
  pg.push();
  pg.translate(0, 0);
  pg.textureMode(NORMAL);
  pg.textureWrap(CLAMP);
  pg.noFill();

  if (!record) {
    pg.strokeWeight(3);
    pg.stroke(#00ff00);
  } else {
    pg.noStroke();
  }
  //pg.stroke(#00FF00);

  if (record) {
    
    PImage textureImage = animation[recordFrame];
    
    float tilesX = 8;
    float tilesY = 8;

    PVector d1 = new PVector(v1x, v1y);
    PVector d2 = new PVector(v2x, v2y);
    PVector d3 = new PVector(v3x, v3y);
    PVector d4 = new PVector(v4x, v4y);

    int res = 10;
    float yres = 1.0/(res-1);
    float xres = 1.0/(res-1);

    // Precalc grid positions
    PVector[][] grid = new PVector[res][res];
    for (int y=0; y<res; y++) {
      for (int x=0; x<res; x++) {
        grid[x][y] = new PVector(
          lerp(
          lerp(d1.x, d2.x, x*xres),
          lerp(d4.x, d3.x, x*xres),
          y*yres),
          lerp(
          lerp(d1.y, d4.y, y*yres),
          lerp(d2.y, d3.y, y*yres),
          x*xres)
          );
      }
    }

    for (int y=0; y<res-1; y++) {
      for (int x=0; x<res-1; x++) {
        pg.beginShape();
        pg.texture(textureImage);
        pg.vertex(grid[x][y].x, grid[x][y].y, x*xres, y*yres);
        pg.vertex(grid[x+1][y].x, grid[x+1][y].y, (x+1)*xres, y*yres);
        pg.vertex(grid[x+1][y+1].x, grid[x+1][y+1].y, (x+1)*xres, (y+1)*yres);
        pg.vertex(grid[x][y+1].x, grid[x][y+1].y, x*xres, (y+1)*yres);
        pg.endShape();
      }
    }

    pg.endDraw();
  } else {
    pg.beginShape();
    pg.vertex(v1x, v1y, 0, 0);
    pg.vertex(v2x, v2y, 1, 0);
    pg.vertex(v3x, v3y, 1, 1);
    pg.vertex(v4x, v4y, 0, 1);
    pg.endShape(CLOSE);
  }


  pg.pop();
  pg.pop();
  pg.endDraw();

  image(pg, 0, 0);

  if (!record) {
    image(displayGrid(), 0, 0);
    // Show UI only when vectorSelection is Off!
    if (!VectorSelectionMode) {
      //cp5.draw();
    }
  }
  recordVideo();
  editMockup();

}

// #####################################
// Edit Modes

int EDIT_POSITION = 1;
int EDIT_SKEW = 2;

void editMockup() {
  if (editMode == EDIT_POSITION) {
    offsetX = map(mouseX, 0, width, -1000, 1000);
    offsetY = map(mouseY, 0, height, -1000, 1000);
  } else if (editMode == EDIT_SKEW) {
    W = map(mouseX, 0, width, 0.2, 2);
    
    H = map(mouseY, 0, height, 0.2, 2);
    
    scalarValue = 1;
  }
}

PGraphics displayGrid(){
  PGraphics pg = createGraphics(WIDTH,HEIGHT);
  pg.beginDraw();
  pg.clear();
  pg.rectMode(CENTER);
  pg.fill(#ff00ff);
  pg.noStroke();
  float gridThickness = 1;
  pg.rect(width/2,height/2,gridThickness,height);
  pg.rect(width/4,height/2,gridThickness,height);
  pg.rect(width/4*3,height/2,gridThickness,height);
  pg.rect(width/2,height/2,width,gridThickness);
  pg.endDraw();
  return pg;
}


// #####################################
// Video Recording

final String sketchname = getClass().getName();

int recordFrame = 1;
int recordFrameRate = 5;
float sceneScalar = 0;

void launchRecordVideo() {
  record = !record;
  recordFrame = 1;
}

void playSequence() {
  if (recordFrame < animation.length-1) {
    recordFrame++;
  } else {
    recordFrame = 0;
    sceneScalar += 0.01;
    exit();
    println("finished recording");
  }
}

void recordVideo() {
   saveFrame("out/" + INPUT + nf(recordFrame, 4) + ".jpg");
}

int vectorSelector = 0;
int editMode = 0;

void keyReleased() {
  if (key == '1') {
    vectorSelector = 1;
    VectorSelectionMode = true;
    editMode = 0;
  } else if (key == '2') {
    vectorSelector = 2;
    VectorSelectionMode = true;
    editMode = 0;
  } else if (key == '3') {
    vectorSelector = 3;
    VectorSelectionMode = true;
    editMode = 0;
  } else if (key == '4') {
    vectorSelector = 4;
    VectorSelectionMode = true;
    editMode = 0;
  } else {
    vectorSelector = 0;
    VectorSelectionMode = false;
    editMode = 0;
  }

  if (key == 'r') {
    editMode = 0;
    launchRecordVideo();
  }
  if (key == 'g') {
    editMode = 0;
    showGrid = !showGrid;
  }
  if (key == 'p') {
    editMode = EDIT_POSITION;
  }
   if (key == 'o') {
    editMode = EDIT_SKEW;
  }
}

void mouseReleased() {
  vectorSelector = 0;
  VectorSelectionMode = false;
}

Related

Enjoying the content?

I put a lot of love and effort into developing content on Creative Coding. Since 2018, I have published 209 interviews, case studies, and tutorials, along with over 272 lessons in 17 online courses – and there's more to come! If you'd like to support my work and help keep this platform evolving, please consider supporting me on Patreon. Thank you very much!

Speaking Image

Monthly Newsletter

Stay up to date and get new content circling around Creative Coding and Design within Limits, every 5th of the month, directly to your inbox.

Related

p5studio

A prototype for a browser-based design-application, built with p5.js and vue.js.

Building Tools with p5.js (Playlist)

Click here to login or connect!To view this content, you must be a member of Tim's Patreon at €7 or […]

DEMO 2025 – My Submissions

Limitations have always been playing a major role in my creative work; I was only able to develop my best […]

p5.js Design Tools Directory

Hi! In this post I’ll collect case studies and direct links to tools that people have built with p5.js and […]

The 128kb Framework and its Aesthetic Characteristics

One day in early 2024 I started to experiment with a new idea. I wrote down a set of rules […]

My new writing project “downgrade” is live

Hey folks, I hope you are doing great! You may have already read one or two of my essays that […]

Join the 128kb challenge!

Instagram, Twitter, TikTok… All the main platforms that technically have the required features to connect emerging communies for Creative Coding […]

Preview: When Computers create Collages

2023-12-01 Today I want to share with you a first prototype that will be the basis for a new course […]