diff --git a/filmless_calibration/filmless_calibration.pde b/filmless_calibration/filmless_calibration.pde new file mode 100644 index 0000000..b2e1506 --- /dev/null +++ b/filmless_calibration/filmless_calibration.pde @@ -0,0 +1,123 @@ +int DPI = 1440; +//Change this to a DPI supported by your printer. +// +//Make sure to convert the generated "page_#.tif" files to the set DPI +//and print at 100% scale. By default, Processing will generate a file +//@ 72 DPI which may make the scaling math difficult. Use Preview, Photoshop +//or ImageMagick to convert the file to the DPI you set here. + +String PITCH = "long"; // long, short //7.62, 7.605 +String FORMAT = "16mm"; //16mm or super16 +int PERFS = 1; //single (1) or double (2) perf film +float PAGE_W = 8.5; //page width in inches +float PAGE_H = 11.0; //page height in inches +float SAFE_W = .25; //safe area on each side of the page +float SAFE_H = .5; //safe area on top and bottom of page + +//Change these to calibrate for printer stretch. +//Use either the total length markings in the bottom left or top right of the page +//to compare the desired length to the actual printed length OR use the +//10cm rulers on either axis to compare the printed length that should be 100mm. +// +//In the case that the printed ruler measures 101.5mm down, change the variable to: +//MAGIC_H_CORRECTION = 100 / 101.5; or desired length / printed length +// +//Once you have this fraction, you can confirm it with a second print of this file +//or then use it in the filmless_processing.pde sketch. + +float MAGIC_H_CORRECTION = 1.0; +float MAGIC_W_CORRECTION = 1.0; + +/** DON'T CHANGE THESE **/ + +float IN = 25.4; +float LONG_H = 7.62; +float SHORT_H = 7.605; +float STD16_W = 10.26; //0.413" +float STD16_H = 7.49; //0.295" +float SUPER16_W = 12.52; //0.492" +float SUPER16_H = 7.41; //0.292' +float PERF_W = 1.829; +float PERF_H = 1.27; +float DPMM = DPI / IN; + +int SPACING = PITCH.equals("long") ? round(LONG_H * DPMM * MAGIC_H_CORRECTION) : round(SHORT_H * DPMM * MAGIC_H_CORRECTION); +int PAGE_W_PIXELS = ceil((PAGE_W - (SAFE_W * 2)) * DPI * MAGIC_W_CORRECTION); +int PAGE_H_PIXELS = ceil((PAGE_H - (SAFE_H * 2)) * DPI * MAGIC_H_CORRECTION); +int FRAME_W = FORMAT.equals("super16") ? round(SUPER16_W * DPMM * MAGIC_W_CORRECTION) : round(STD16_W * DPMM * MAGIC_W_CORRECTION); +int FRAME_H = FORMAT.equals("super16") ? round(SUPER16_H * DPMM * MAGIC_H_CORRECTION) : round(STD16_H * DPMM * MAGIC_H_CORRECTION); +int LEFT_PAD = round(((16 - STD16_W) / 2) * DPMM * MAGIC_W_CORRECTION); //space to left of frame +int COLUMNS = floor(PAGE_W_PIXELS / (16 * DPMM)); +int ROWS = floor(PAGE_H_PIXELS / SPACING); +int FRAME_LINE = round((SPACING - FRAME_H) / 2); + +PGraphics page; + +void printInfo() { + println("PAGE SIZE: " + PAGE_W_PIXELS + "x" + PAGE_H_PIXELS); + println("FRAME SIZE: " + FRAME_W + "x" + FRAME_H); + println("FRAMES PER STRIP: " + ROWS); + println("STRIPS PER PAGE: " + COLUMNS); + println("FRAMES PER PAGE: " + (ROWS * COLUMNS)); + + println("CALIBRATION W (MM): " + (16 * COLUMNS)); + println("CALIBRATION H (MM): " + (ROWS * (SPACING / DPMM))); + + println("SOUNDTRACK SAMPLE RATE: " + (SPACING * 24)); +} + +void setup () { + printInfo(); + page = createGraphics(PAGE_W_PIXELS, PAGE_H_PIXELS); + page.beginDraw(); + page.background(255); + page.stroke(0); + page.fill(0); + page.textSize(60); + float leftX = 20 * DPMM * MAGIC_W_CORRECTION; + float topY = 20 * DPMM * MAGIC_H_CORRECTION; + + float mmX = DPMM * MAGIC_W_CORRECTION; + float mmY = DPMM * MAGIC_H_CORRECTION; + + float cmX = 10 * mmX; + float cmY = 10 * mmY; + + //Draw 10cm rulers to compare + page.line(0, topY, 0, topY + (cmY * 10)); + page.line(leftX, 0, leftX + (cmX * 10), 0); + + int len; + for (int i = 0; i < 10; i++) { + page.text(i + "", 6 * mmX, topY + (cmY * i) + 20); + page.text(i + "", leftX + (cmX * i) - 10, 6 * mmY); + for (int m = 0; m < 10; m++) { + if (m == 0 || m == 5) { + len = 5; + } else { + len = 3; + } + page.line(0, topY + (cmY * i) + (m * mmY), len * mmX, topY + (cmY * i) + (m * mmY)); + page.line(leftX + (cmX * i) + (m * mmX), 0, leftX + (cmX * i) + (m * mmX), len * mmY); + } + } + + page.line(0, topY + (cmY * 9) + (10 * mmY), 5 * mmX, topY + (cmY * 9) + (10 * mmY)); + page.text(10 + "", 6 * mmX, topY + (cmY * 10) + 20); + + page.line(leftX + (cmX * 9) + (10 * mmX), 0, leftX + (cmX * 9) + (10 * mmX), 5 * mmY); + page.text(10 + "", leftX + (cmX * 10) - 10, 6 * mmY); + + page.noFill(); + page.rect(0, 0, 10 * DPMM, 10 * DPMM); + page.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, 0, 10 * DPMM, 10 * DPMM); + page.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM); + page.rect(0, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM); + + page.text((16 * COLUMNS) + "mm", ((16 * COLUMNS) * DPMM) - (10 * DPMM), 12 * DPMM); + page.text((ROWS * (SPACING / DPMM)) + "mm", 11 * DPMM, ((ROWS * (SPACING / DPMM)) * DPMM) - 100); + + page.endDraw(); + page.save("calibration_" + PAGE_W + "x" + PAGE_H + "_" + COLUMNS + "strips_" + ROWS + "frames_" + PITCH + "_" + DPI + "dpi.tif"); + exit(); +} diff --git a/filmless_processing/filmless_processing.pde b/filmless_processing/filmless_processing.pde new file mode 100644 index 0000000..8af802d --- /dev/null +++ b/filmless_processing/filmless_processing.pde @@ -0,0 +1,306 @@ + +import processing.sound.*; +import soundtrack.optical.*; + +// Export video to image sequence using ffmpeg +// ffmpeg -i video.mov -f image2 -r 24 /tmp/image-%04d.png +// ffmpeg -i video.mov -acodec pcm_s16le -ac 1 audio.wav //-ar 16000 sets rate + +/** + * CHANGE THESE + **/ + +String DESKTOP = System.getProperty("user.home") + "/Desktop"; +String SOURCE = DESKTOP + "/frames/"; //path to directory containing frames +String SOUND = DESKTOP + "/audio/audio.wav"; //leave empty string if silent +String RENDER_PATH = DESKTOP + "/"; //path to directory where pages will be placed + +//types: unilateral, variable area, dual variable area, maurer, variable density +String SOUNDTRACK_TYPE = "unilateral"; +int DPI = 1440; //maximum printer DPI +String PITCH = "long"; // long, short //7.62, 7.605 +String FORMAT = "16mm"; //16mm or super16 +int PERFS = 1; //single (1) or double (2) perf film +float PAGE_W = 8.5; //page width in inches +float PAGE_H = 11.0; //page height in inches +float SAFE_W = .25; //safe area on each side of the page +float SAFE_H = .5; //safe area on top and bottom of page +color BACKGROUND = color(0); //the color that will fill the entire frame where there's no image +boolean NEGATIVE = false; //true to invert image data +boolean SHOW_PERFS = true; //set to true to print perfs for cutting registration +color PERFS_COLOR = color(255); +int SOUND_OFFSET = 25; + +//This is a magic number that is used to scale the vertical (H) or horizontal (W) resolution +//because the printer sometimes lies to you. +float MAGIC_H_CORRECTION = 251.45999 / 253.4; //1.0; +float MAGIC_W_CORRECTION = 1.0; + +/** + * CONSTANTS (DON'T CHANGE PLZ) + **/ +float IN = 25.4; +float LONG_H = 7.62; +float SHORT_H = 7.605; +float STD16_W = 10.26; //0.413" +float STD16_H = 7.49; //0.295" +float SUPER16_W = 12.52; //0.492" +float SUPER16_H = 7.41; //0.292" +float PERF_W = 1.829; +float PERF_H = 1.27; +float DPMM = DPI / IN; + +int SPACING = PITCH.equals("long") ? round(LONG_H * DPMM * MAGIC_H_CORRECTION) : round(SHORT_H * DPMM * MAGIC_H_CORRECTION); +int PAGE_W_PIXELS = ceil((PAGE_W - (SAFE_W * 2)) * DPI * MAGIC_W_CORRECTION); +int PAGE_H_PIXELS = ceil((PAGE_H - (SAFE_H * 2)) * DPI * MAGIC_H_CORRECTION); +int FRAME_W = FORMAT.equals("super16") ? round(SUPER16_W * DPMM * MAGIC_W_CORRECTION) : round(STD16_W * DPMM * MAGIC_W_CORRECTION); +int FRAME_H = FORMAT.equals("super16") ? round(SUPER16_H * DPMM * MAGIC_H_CORRECTION) : round(STD16_H * DPMM * MAGIC_H_CORRECTION); +int LEFT_PAD = round(((16 - STD16_W) / 2) * DPMM * MAGIC_W_CORRECTION); //space to left of frame +int COLUMNS = floor(PAGE_W_PIXELS / (16 * DPMM)); +int ROWS = floor(PAGE_H_PIXELS / SPACING); +int FRAME_LINE = round((SPACING - FRAME_H) / 2); +int PAGES = 0; +int FRAMES = 0; +int SOUND_W = ceil(DPMM * (12.52 - 10.26)); + +SoundtrackOptical soundtrack; +String[] frames; +PImage frameBuffer; +PGraphics frameBlank; +PGraphics pageBuffer; +PGraphics soundBuffer; + +void setup () { + size(640, 480); + //surface.setResizable(true); + println(SOURCE); + frames = listFrames(SOURCE); + if (frames == null) { + println("Frames not found, check SOURCE path"); + exit(); + } + + FRAMES = frames.length; + PAGES = ceil((float) FRAMES / (ROWS * COLUMNS)); + pageBuffer = createGraphics(PAGE_W_PIXELS, PAGE_H_PIXELS); + + if (!SOUND.equals("")) { + soundtrack = new SoundtrackOptical(this, SOUND, DPI, 1.0, SOUNDTRACK_TYPE, PITCH, !NEGATIVE); + } + + printInfo(); + + if (PERFS == 2 && !SOUND.equals("") ) { + println("WARNING: Double perf film and soundtrack will interfere with one another. Are you sure?"); + } + + if (FORMAT.equals("super16") && !SOUND.equals("")) { + println("WARNING: Super16 frame and soundtrack will interfere with one another. Are you sure?"); + } + + if (FORMAT.equals("super16") && PERFS == 2) { + println("WARNING: Super16 frame and double perf film will interfere with one another. Are you sure?"); + } + + text("DISPLAY", 200, 200); + frameBlank = createGraphics(FRAME_W, FRAME_H); + noLoop(); + delay(1000); + thread("renderPages"); +} + +void draw () { + background(0); + fill(255); + text("SOURCE DIR: " + SOURCE, 10, 20); + text("DPI: " + DPI, 10, 40); + text("STRETCH: " + MAGIC_W_CORRECTION + " x " + MAGIC_H_CORRECTION, 10, 60); + text("FRAMES: " + FRAMES, 10, 80); + text("FRAME SIZE: " + FRAME_W + "x" + FRAME_H + " px", 10, 100); + text("PAGES: " + PAGES, 10, 120); + text("PAGE SIZE: " + PAGE_W_PIXELS + "x" + PAGE_H_PIXELS + " px", 10, 140); + text("FRAMES/PAGE: " + (ROWS * COLUMNS), 10, 160); + text("SECONDS/PAGE: " + ((ROWS * COLUMNS) / 24), 10, 180); +} + +void printInfo() { + println("STRETCH: " + MAGIC_W_CORRECTION + " x " + MAGIC_H_CORRECTION); + println("PAGE SIZE: " + PAGE_W_PIXELS + "x" + PAGE_H_PIXELS); + println("FRAME SIZE: " + FRAME_W + "x" + FRAME_H); + println("FRAMES PER STRIP: " + ROWS); + println("STRIPS PER PAGE: " + COLUMNS); + println("FRAMES PER PAGE: " + (ROWS * COLUMNS)); + println("SECONDS PER PAGE: " + ((ROWS * COLUMNS) / 24)); + println("FRAMES: " + FRAMES); + println("PAGES: " + PAGES); + //println("RENDER_PATH: " + RENDER_PATH); + if (!SOUND.equals("")) { + println("SOUNDTRACK SAMPLE RATE: " + (SPACING * 24)); + } +} + +String[] listFrames (String dir) { + ArrayList tmp = new ArrayList(); + String output[]; + File file; + int arraySize; + int o = 0; + if (dir.substring(dir.length() - 1, dir.length()) != "/") { + dir = dir + "/"; + } + file = new File(dir); + if (file.isDirectory()) { + String names[] = file.list(); + names = sort(names); + for (int i = 0; i < names.length; i++) { + if (names[i].toLowerCase().contains(".jpg") || + names[i].toLowerCase().contains(".jpeg") || + names[i].toLowerCase().contains(".tif") || //only works with Processing tiffs + names[i].toLowerCase().contains(".png")) { + tmp.add(dir + names[i]); + } + } + + arraySize = tmp.size(); + + if (!SOUND.equals("")) { + arraySize += SOUND_OFFSET; + } + + output = new String[arraySize]; + + if (!SOUND.equals("")) { + for (int i = 0; i < SOUND_OFFSET; i++) { + output[o] = "_BLANK_"; + o++; + } + } + + for (int i = 0; i < tmp.size(); i++) { + output[o] = tmp.get(i); + o++; + } + sort(output); + return output; + } else { + return null; + } +} + +String leftPad (int val) { + String str = "" + val; + if (str.length() == 1) { + str = "0" + str; + } + return str; +} + +void renderPages() { + //surface.setSize(PAGE_W_PIXELS, PAGE_H_PIXELS / SEGMENTS); + //delay(1000); + int cursor; + int leftX; + int topY; + int perfLeft; + int perfRight; + int perfTop; + int soundTop; + int soundLeft; + boolean hasFrames = false; + + frameBlank.beginDraw(); + frameBlank.background(BACKGROUND); + frameBlank.endDraw(); + + + for (int page = 0; page < PAGES; page++) { + pageBuffer.beginDraw(); + pageBuffer.textSize(60); + pageBuffer.clear(); + pageBuffer.background(255); + pageBuffer.stroke(0); + pageBuffer.noFill(); + //draw calibration marks to be overwritten if needed + pageBuffer.rect(0, 0, 10 * DPMM, 10 * DPMM); + pageBuffer.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, 0, 10 * DPMM, 10 * DPMM); + pageBuffer.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM); + pageBuffer.rect(0, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM); + //pageBuffer.stroke(0); + for (int x = 0; x < COLUMNS; x++) { + //pageBuffer.line(x * (16 * DPMM), 0, x * (16 * DPMM), PAGE_H_PIXELS); + for (int y = 0; y < ROWS; y++) { + cursor = (page * (COLUMNS * ROWS)) + (x * ROWS) + y; + if (cursor >= FRAMES) { + hasFrames = false; + break; + } + hasFrames = true; + println("Frame " + cursor + "/" + FRAMES); + + topY = (y * SPACING) + FRAME_LINE; + leftX = x * (round(16 * DPMM)) + LEFT_PAD; + perfTop = round((y * SPACING) - ((PERF_H / 2) * DPMM)); + + pageBuffer.noStroke(); + pageBuffer.fill(BACKGROUND); + pageBuffer.rect(x * (round(16 * DPMM)), (y * SPACING), round(16*DPMM), SPACING); + + if (SHOW_PERFS){ + perfLeft = round(x * (round(16 * DPMM)) + (.85 * DPMM)); + pageBuffer.fill(PERFS_COLOR); + //rect([1.829, 1.27, 2], d = .5, center = true); + //.85 from side + pageBuffer.rect(perfLeft, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM); + + //last perf + if (y == ROWS - 1) { + perfTop = round(((y + 1) * SPACING) - ((PERF_H / 2) * DPMM)); + pageBuffer.rect(perfLeft, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM); + } + } + + if (SHOW_PERFS && PERFS == 2) { + perfRight = round((x + 1) * (round(16 * DPMM)) - (PERF_W * DPMM) - (.85 * DPMM)); + pageBuffer.rect(perfRight, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM); + + if (y == ROWS - 1) { + perfTop = round(((y + 1) * SPACING) - ((PERF_H / 2) * DPMM)); + pageBuffer.rect(perfRight, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM); + } + } + + if (frames[cursor].equals("_BLANK_")) { + pageBuffer.image(frameBlank, leftX, topY, FRAME_W, FRAME_H); + } else { + frameBuffer = loadImage(frames[cursor]); + if (NEGATIVE) { + frameBuffer.filter(INVERT); + } + frameBuffer.resize(FRAME_W, FRAME_H); + pageBuffer.image(frameBuffer, leftX, topY, FRAME_W, FRAME_H); + } + + if (!SOUND.equals("")) { + soundTop = y * SPACING; + soundLeft = (x * round(16 * DPMM)) + LEFT_PAD + FRAME_W + round(0.3368 * DPMM); + try { + soundBuffer = soundtrack.buffer(cursor); + if (soundBuffer != null) { + pageBuffer.image(soundBuffer, soundLeft, soundTop, round((12.52 - 10.26 - 0.3368) * DPMM), SPACING); + } + } catch (Error e) { + // + } + } + if (hasFrames) { + text((x + 1) + "", (x * 16 * DPMM) + (8 * DPMM), ROWS * SPACING + 20); + } + } + } + pageBuffer.endDraw(); + pageBuffer.save(RENDER_PATH + "page_" + page + ".tif"); + println("Saved page_" + page + ".tif"); + } + printInfo(); + exit(); +}