I’ve updated my Mockup Generator

Published by Tim on Saturday September 16, 2023

Last modified on September 25th, 2023 at 22:50

For my students at Elisava, I have updated my mockup generator. Now its possible to load animations in the .gif format, instead of inporting a whole folder of single frames in png format, which makes the whole process much smoother.

To use it, you need to install two libraries: GifAnimation and VideoExport. 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.

Related

// #####################################
// The trcc mockup generator - 2023 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 = "20.jpg";
String INPUT = "input/input.gif_50x10.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;

import gifAnimation.*;
PImage[] animation;


void setup() {
  sceneImage = loadImage("scenes/" + 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();
  rec(30,10000);
  editMockup();
  // rec(30,10000);
}

// #####################################
// 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;
}

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

import com.hamoid.*;
VideoExport videoExport;

void rec(int rate, int dur) {
  if (frameCount == 1) {
    videoExport = new VideoExport(this, "../"+sketchname+".mp4");
    videoExport.setFrameRate(rate);  
    videoExport.startMovie();
  }

  videoExport.saveFrame();
  
  if (frameCount == dur) {
   exit(); 
  }
  println(frameCount);

}

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

New work for the New York Times

Once again, I had the honor of illustrating an article for the New York Times that I myself am very […]

Lowtech Painting Machine

A few months ago I made a small app that allows you to create image collages with some kind of […]

Marcus Aurelius Meditations

Since the beginning of the Corona crisis, I have been more and more interested in the history of ancient philosophy. […]

Key visual for Slate + Ash’s new software instrument

Together with Lena Weber I created the promo video for Slate + Ash’s brand new software synth called Choreographs. The […]

Generative portraits for IBM

Some of you may know that I have a split relationship with Instagram. Still, even though I often find myself […]

Form follows Music: The Bach-Project

Generative visuals made from the "Prelude in C" by Johann Sebastian Bach.

Llum Negra / La Luz Negra / Black Light

Custom design-software for CCCB Barcelona