// Slit Camera - Movie procesed to a timeline image. // Copyright Martin Dixon 2012/13 // http:// wwww.slitcam.com // Use slit_can_imgs_v0_03.pde (or more recent version)to input directory of images instead of video. // // Program Map: // declarations // setup() called first // draw() called every time screen refreshed // movieEvent() called every movie frame read. import processing.video.*; // Change the behaviour of the program here: //+++start // if you want to change the display area (width, height pixels on PC screen) then change the first line (size) of 'setup' below. String moviefilename = "street.mov"; // input .mov movie file in sketch data folder String outfilename = "street.tiff"; // output image file (normally TIFF or PNG) created in sketch folder boolean horizontal_slit = true; // use horizontal slit (otherwise vertical) // choose one of: float fixedslit = 0.5; // value between 0 & 1. Use column (or row) of frame pixels this fraction across // // the image (0.5 = central slit). int startpoint = 0; // Start from this frame. int maxframes = 8000; // Stop after this many frames processed (or end of movie or end of scan). // // Equals max output image width. Ususally set to a largish value say 8000. boolean scanR = false; // start with left (or top) column (or row). Overrides fixedslit. boolean scanL = false; // start with right (or bottom). Overrides fixedslit. boolean scanRateOne = false; // The scan "slit" will move along 1 pixel for each frame processed. // // Ignored unless scanR or scanL true. Overrides scanRate. int scanFrames = 3364; // How many frames (images) the slit will take to move the slit all // // the way across the frame. E.g. 100 or 2000, usually you would set this to the // // actual number of frames in the clip - this can be found by running this program first // // in an ordinary fixed slit mode with maxframes set to a large value. // // This will involve interpolation between adjacent pixels. // // Ignored unless scanR or scanL true and scanRateOn false. int closeafter = 15000; // Close window after this many miliseconds. Default 15000. 0 for never (user) close. // // More experimental parameters: boolean difference = false; // show change from last slit pixel by pixel. Just a (bad?) idea. int average = 1; // Average adjacent pixels. 1 = no average, 2, 4 etc will use numeric r/g/b average of // // this number of pixels perpendicular to the slit. // // Intended to remove some noise - not sure if effective. float speed = 1; // Does not appear to have any affect! // // Speed of movie - might want to reduce to reduce jittery video rendition // // (should not affect output) if PC is very slow. //+++end // you probably dont want to change anything except possibly (window) "size" in "setup" below here. // define global variables int frame, lastframe, lastframecount, outcol; int SlitPixels; int slitOffset = 0; int scanWidth = 0; int scanInc = 0; long lastTime = 0; // Variable to hold onto Video (potentially you could change this program to use a live "Capture" object); Movie video; PImage outimg; int videodisplaywidth = 10; int videodisplayheight = 10; String infotext = ""; String infotext2 = ""; boolean startscan = false; boolean ending = false; boolean interpolatedScan = false; PImage currslit; int[] prevslit, tempslit; int startpointn = 0; int dsplWidth = width-350; int dsplHeight = height / 2; void setup() { size(1300, 1024); // you might want to change the screen window width, height. // size(800, 500); // e.g. smaller window. // a little validation if (average < 2) average=1; if (speed<=0) speed=1; // Initialize columns and rows video = new Movie(this, moviefilename); video.speed(speed); // this seems to have no affect for 1st play video.play(); outcol = -1; frame = -1; lastframe = -1; lastframecount=0; dsplWidth = width-350; dsplHeight = height / 2; textFont(createFont("Verdana", 24)); // Note much initialisation occurs in movieEvent as 1st frame needs too be read before frame size is known. } void draw() { // show stuff on screen String s = ""; background(200); // Show image so far and current video frame if (outimg!=null) image(outimg, 0, 0, dsplWidth, dsplHeight); image(video, 0, dsplHeight, dsplWidth, dsplHeight); // draw a line to indicate slit on video display if (startscan && !ending) { stroke (30, 256, 0, 180); int offsetDone; int hh, ww; if (horizontal_slit) { hh = dsplHeight; ww = dsplWidth; } else { hh = dsplWidth; ww = dsplHeight; } if (scanR) offsetDone = hh * slitOffset / scanWidth; else if (scanL) offsetDone = hh * slitOffset / scanWidth; else offsetDone = int(hh * fixedslit); if (horizontal_slit) line (0, hh + offsetDone, dsplWidth, hh + offsetDone); else line (offsetDone, ww, offsetDone, ww + ww); } // ending, re-size and output if (startscan) { if (ending) { if (closeafter > 0) if ( millis () - lastTime > closeafter ) exit(); } else if (lastframecount<20) { // is nothing changing? if (lastframe==frame)lastframecount++; else lastframecount=0; } else { // nothing changed output and end if (outimg!=null) { outimg = outimg.get(0, 0, frame+1, outimg.height); //possibly crop image outimg.save(outfilename); infotext2 = "\n\n" + "Output image file (" + outimg.width + " x " + outimg.height +"px) has been written to sketch directory:\n" + outfilename; } else s += "\n\nError: no image generated"; ending = true; lastTime = millis(); } lastframe = frame; } // display text information if (frame>0) s = infotext + "\n\n" + "Frame: " + (frame + 1) + s; else s = infotext + "\n\n" + "Wait for frame " + startpoint + " : " + startpointn + s; textSize(12); fill(10); text(s + infotext2, width-330, height-330, 320, 320); } // Called every time a new frame is available to read void movieEvent(Movie m) { int r = 0; int g = 0; int b = 0; if ( frame + 1 >= maxframes) return; m.read(); if (startpointn < startpoint) { startpointn++; return; } frame++; video.loadPixels(); if (frame == 0) { // initialisation start videodisplaywidth = video.width; videodisplayheight = video.height; if (video.height > height/2) { videodisplaywidth = videodisplaywidth; videodisplayheight = video.height; } infotext = "Input movie file (" + video.width + " x " + video.height + "px):\n" + moviefilename + "\nSlit: "; if (horizontal_slit) { infotext += "Horizontal " ; scanWidth = video.height; SlitPixels = video.width; if (scanR) infotext += "Scan top to bottom "; else if (scanL) infotext += "Scan bottom to top "; else infotext += "Fixed row " + fixedslit * 100 + "% down from top"; } else { scanWidth = video.width; SlitPixels = video.height; if (scanR) infotext += "Scan left to right "; else if (scanL) infotext += "Scan right to left "; else infotext += "Fixed column " + fixedslit * 100 + "% across from left"; } if (scanR || scanL) { if (scanRateOne) infotext += " moving 1 pixel per frame."; else infotext += " moving " + float(scanWidth-1) / scanFrames + " pixels per frame."; } currslit = createImage(1, SlitPixels, RGB); if (scanR) { slitOffset = - 1; scanInc = 1; } else if (scanL) { slitOffset = scanWidth + 1 - average; scanInc = -1; } else slitOffset = int(scanWidth * fixedslit); if (difference) infotext += "Difference "; if (startpoint>0) infotext += "Startframe: " + startpoint + " "; if (average>1) infotext += " Average " + average + "pixels "; if (scanR || scanL) { if (scanRateOne) { if (horizontal_slit) maxframes = video.height; else maxframes = video.width; } else maxframes = scanFrames; maxframes = maxframes + 1 - average; } outimg = createImage(maxframes, SlitPixels, RGB); startscan = true; if ((scanR || scanL) && !scanRateOne) interpolatedScan=true; } // initialisation end // Begin loop for rows int k = slitOffset; int kc = 0; float fractionAcross = 0; if (scanRateOne) k += scanInc; else if (interpolatedScan) { if (scanR) fractionAcross = (scanWidth-1) * float(frame) / scanFrames; else fractionAcross = (scanWidth-1) * float(scanFrames - frame) / scanFrames; k = floor(fractionAcross); kc = ceil(fractionAcross); if (kc > scanWidth-1) { k=scanWidth-1; kc=k; } } slitOffset = k; for (int j = 0; j < SlitPixels; j++) { // Looking up the appropriate color in the pixel array color c = 0; if (interpolatedScan) { if (k == kc) { if (horizontal_slit) c = video.pixels[SlitPixels * k + j ]; // row else c = video.pixels[k + j * scanWidth]; // column } else { if (horizontal_slit) c = lerpColor(video.pixels[SlitPixels * k + j ], video.pixels[SlitPixels * kc + j ], fractionAcross - k); else c = lerpColor(video.pixels[k + j * scanWidth], video.pixels[kc + j * scanWidth], fractionAcross - k); } } else if (average == 1) { if (horizontal_slit) c = video.pixels[SlitPixels * k + j ]; // row else c = video.pixels[k + j * scanWidth]; // column } else { //average the colours r=g=b=0; for (int n = 0 ; n < average; n++ ) { //if (horizontal_slit) c = video.pixels[SlitPixels * (k + n) + j ]; // row //else c = video.pixels[k + n + j * scanWidth]; // column r += (c >> 16) & 0xFF; g += (c >> 8) & 0xFF; b += c & 0xFF; } c = color(r / average, g / average, b / average); } currslit.pixels[j] = c; } currslit.updatePixels(); // End loop for rows if (difference) { // just show difference from last slit column. if (prevslit==null) { prevslit = new int[SlitPixels]; tempslit = new int[SlitPixels]; arraycopy(currslit.pixels, prevslit); } else { arraycopy(currslit.pixels, tempslit); // Begin loop for rows color c; color d; for (int j = 0; j < SlitPixels; j++) { c = currslit.pixels[j]; d = prevslit[j]; r = 127 + (((c >> 16) & 0xFF) - ((d >> 16) & 0xFF)) / 2; g = 127 + (((c >> 8) & 0xFF) - ((d >> 8) & 0xFF)) / 2; b = 127 + ((c & 0xFF) - (d & 0xFF) ) / 2; currslit.pixels[j] = color(r, g, b); } currslit.updatePixels(); arraycopy(tempslit, prevslit); } } // add column to the output image outimg.copy(currslit, 0, 0, 1, SlitPixels, frame, 0, 1, SlitPixels); } // Copyright Martin Dixon 2012/13 // wwww.slitcam.com