diff --git a/Stop_Motion_Animation.pde b/Stop_Motion_Animation.pde index 23250c5..ef47478 100644 --- a/Stop_Motion_Animation.pde +++ b/Stop_Motion_Animation.pde @@ -52,43 +52,49 @@ mode is active, we are just showing a single pre-recorded frame. import processing.video.*; import controlP5.*; import java.io.File; - -Capture cam; -ControlP5 cp5; -Textlabel folderNameLabel; -Textlabel numFramesLabel; -Slider frameSlider; - -String groupName = ""; -String lastFileName = ""; -String filePath = ""; -int sceneNumber = 1; -int numberOfFrames = 0; -PImage loadedFrame; -PImage liveFrame; -boolean weAreLive = true; -boolean weAreInReplay = false; -int currentFrame = 0; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + + +String _groupName = ""; +String _lastFileName = ""; +String _filePath = ""; +String _CWD = ""; +int _numberOfFrames = 0; +PImage _loadedFrame; +PImage _liveFrame; +boolean _weAreLive = true; +boolean _weAreInReplay = false; +int _currentFrame = 0; // GUI Constants -int BORDER_WIDTH = 10; int BUTTON_WIDTH = 150; int BUTTON_HEIGHT = 25; int STANDARD_SPACING = 10; int WEBCAM_LEFT = 2*STANDARD_SPACING+BUTTON_WIDTH; int WEBCAM_UPPER = 3*STANDARD_SPACING+2*BUTTON_HEIGHT; + +// Note that changing these doesn't actually change the webcam resolution selected, you will have +// to do that in the code below, in the setupCamera() function... int WEBCAM_WIDTH = 640; int WEBCAM_HEIGHT = 480; int WINDOW_WIDTH = WEBCAM_WIDTH + 3*STANDARD_SPACING + BUTTON_WIDTH; int WINDOW_HEIGHT = WEBCAM_HEIGHT + 5*STANDARD_SPACING + 3 * BUTTON_HEIGHT; +// GUI Access Variables +Capture cam; +ControlP5 cp5; +Textlabel folderNameLabel; +Textlabel numFramesLabel; +Slider frameSlider; + void setup () { setupUserInterface (); - setupDefaultGroupName (); - setupCamera (); + startNewMovie (); } void setupUserInterface () { @@ -110,10 +116,20 @@ void setupUserInterface () { .setSize(BUTTON_WIDTH,BUTTON_HEIGHT) ; - cp5.addButton ("Load previous photos") + cp5.addButton ("Add to previous movie") .setPosition (3*STANDARD_SPACING+2*BUTTON_WIDTH, STANDARD_SPACING) .setSize(BUTTON_WIDTH,BUTTON_HEIGHT) ; + + cp5.addButton ("Import and clean frames") + .setPosition (WINDOW_WIDTH-BUTTON_WIDTH-2*STANDARD_SPACING-BUTTON_WIDTH/3, STANDARD_SPACING) + .setSize(BUTTON_WIDTH,BUTTON_HEIGHT) + ; + + cp5.addButton ("Help") + .setPosition (WINDOW_WIDTH-BUTTON_WIDTH/3-STANDARD_SPACING, STANDARD_SPACING) + .setSize(BUTTON_WIDTH/3,BUTTON_HEIGHT) + ; cp5.addTextlabel ("Number of Frames") .setText ("NUMBER OF FRAMES: ") @@ -166,10 +182,11 @@ void setupUserInterface () { .setSliderMode (Slider.FIX) .setDecimalPrecision (0) ; + } - void setupDefaultGroupName () { + void setupDefaultGroupName () { // We want a group name that is unlikely to conflict with any previously-created group // names, so just use the date and time. int d = day(); @@ -185,8 +202,9 @@ void setupUserInterface () { date[3] = nf(h,2); date[4] = nf(min,2); date[5] = nf(sec,2); - groupName = join(date, "-"); - folderNameLabel.setText(groupName); + _groupName = join(date, "-"); + folderNameLabel.setText(_groupName); + _CWD = sketchPath(""); } void setupCamera () { @@ -198,7 +216,6 @@ void setupCamera () { if (cameras.length == 0) { println ("There are no cameras connected."); - //exit(); } else { println ("Available cameras:"); for (int i = 0; i < cameras.length; i++) { @@ -234,24 +251,15 @@ public void controlEvent(ControlEvent theEvent) { } else if (eventName == " >>> Play >>>") { playFrames(); } else if (eventName == "Start a new movie") { - setupDefaultGroupName(); - setupCamera(); - numberOfFrames = 0; - weAreLive = true; - weAreInReplay = false; - currentFrame = 0; - numFramesLabel.setText (nfc(numberOfFrames)); - } else if (eventName == "Jump to Live View") { - weAreLive = true; - weAreInReplay = false; - currentFrame = numberOfFrames+1; - numFramesLabel.setText (nfc(numberOfFrames)); - frameSlider.setRange (1, numberOfFrames); - frameSlider.setValue (numberOfFrames); + startNewMovie(); } else if (eventName == "Delete Last Photo") { deleteFrame(); - } else if (eventName == "Click here to load previous photos") { + } else if (eventName == "Add to previous movie") { loadPrevious(); + } else if (eventName == "Import and clean frames") { + cleanFolder(); + }else if (eventName == "Help") { + open (_CWD+"data/help/Help.html"); } } } @@ -259,29 +267,28 @@ public void controlEvent(ControlEvent theEvent) { public void takePhoto () { - weAreInReplay = false; - weAreLive = true; + _weAreInReplay = false; + _weAreLive = true; int badCounter = 0; int MAX_BAD_FRAMES_BEFORE_GIVING_UP = 100; boolean success = false; while (badCounter < MAX_BAD_FRAMES_BEFORE_GIVING_UP && success == false) { - if (cam != null && cam.available() == true) { - cam.read(); - loadedFrame = cam.get(); - numberOfFrames++; - numFramesLabel.setText (nfc(numberOfFrames)); - frameSlider.setRange (1, numberOfFrames); - frameSlider.setValue (numberOfFrames); + if (_liveFrame != null) { + _loadedFrame = _liveFrame; + _numberOfFrames++; + numFramesLabel.setText (nfc(_numberOfFrames)); + frameSlider.setRange (1, _numberOfFrames); + frameSlider.setValue (_numberOfFrames); // Create the filename: String[] parts = new String[3]; parts[0] = "Image Files"; - parts[1] = groupName.replaceAll("[^a-zA-Z0-9\\-_]", ""); - parts[2] = createFilename (groupName, numberOfFrames); + parts[1] = _groupName.replaceAll("[^a-zA-Z0-9\\-_]", ""); + parts[2] = createFilename (_groupName, _numberOfFrames); String filename = join (parts,File.separator); - loadedFrame.save (filename); - lastFileName = filename; + _loadedFrame.save (filename); + _lastFileName = filename; print ("Wrote frame to "); println (filename); success = true; @@ -299,34 +306,45 @@ public void takePhoto () private String createFilename (String group, int frame) { // Strip the group name of anything but letters, numbers, dashes, and underscores - String sanitizedGroupName = group.replaceAll("[^a-zA-Z0-9\\-_]", ""); - String filename = sanitizedGroupName + "_Frame" + nf(frame,4) + ".tif"; + String sanitized_groupName = group.replaceAll("[^a-zA-Z0-9\\-_]", ""); + String filename = sanitized_groupName + "_Frame" + nf(frame,4) + ".tif"; return filename; } +private void startNewMovie () +{ + setupDefaultGroupName(); + setupCamera(); + _numberOfFrames = 0; + _weAreLive = true; + _weAreInReplay = false; + _currentFrame = 0; + numFramesLabel.setText (nfc(_numberOfFrames)); +} + private void playFrames () { println ("Playing frames"); int fps = 15; - weAreLive = false; - weAreInReplay = true; + _weAreLive = false; + _weAreInReplay = true; frameRate (fps); - currentFrame = 1; + _currentFrame = 1; } private void deleteFrame () { - if (numberOfFrames > 0) { - File f = new File (lastFileName); + if (_numberOfFrames > 0) { + File f = new File (_lastFileName); if (f.exists()) { f.delete(); } - numberOfFrames--; - currentFrame = numberOfFrames; - numFramesLabel.setText (nfc(numberOfFrames)); - frameSlider.setRange (1, numberOfFrames); - frameSlider.setValue (numberOfFrames); + _numberOfFrames--; + _currentFrame = _numberOfFrames; + numFramesLabel.setText (nfc(_numberOfFrames)); + frameSlider.setRange (1, _numberOfFrames); + frameSlider.setValue (_numberOfFrames); } } @@ -339,18 +357,18 @@ private void loadPrevious () void loadPreviousFromFile (File selection) { if (selection != null) { - numberOfFrames = 0; + _numberOfFrames = 0; String pathToFile = selection.getPath(); - filePath = pathToFile.substring(0,pathToFile.lastIndexOf(File.separator)); + _filePath = pathToFile.substring(0,pathToFile.lastIndexOf(File.separator)); String filename = selection.toPath().getFileName().toString(); // We have to figure out the sequence: String[] parts = filename.split ("[_.]"); - groupName = parts[0]; + _groupName = parts[0]; int selectedFrameNumber = Integer.parseInt(parts[1].substring(5)); String extension = parts[2]; - folderNameLabel.setText (groupName); + folderNameLabel.setText (_groupName); // Now actually load up the old photos: int loadingPhotoNumber = 1; @@ -360,10 +378,10 @@ void loadPreviousFromFile (File selection) while (lastLoadWasSuccessful) { lastLoadWasSuccessful = LoadFrame (loadingPhotoNumber); if (lastLoadWasSuccessful == true) { - numberOfFrames++; - numFramesLabel.setText (nfc(numberOfFrames)); - frameSlider.setRange (1, numberOfFrames); - frameSlider.setValue (numberOfFrames); + _numberOfFrames++; + numFramesLabel.setText (nfc(_numberOfFrames)); + frameSlider.setRange (1, _numberOfFrames); + frameSlider.setValue (_numberOfFrames); } else { failCount++; } @@ -383,8 +401,8 @@ boolean LoadFrame (int frameNumber) boolean lastLoadWasSuccessful = false; String[] parts = new String[3]; parts[0] = "Image Files"; - parts[1] = groupName.replaceAll("[^a-zA-Z0-9\\-_]", ""); - parts[2] = createFilename (groupName, frameNumber); + parts[1] = _groupName.replaceAll("[^a-zA-Z0-9\\-_]", ""); + parts[2] = createFilename (_groupName, frameNumber); String filename = join (parts,File.separator); println (filename); @@ -393,7 +411,7 @@ boolean LoadFrame (int frameNumber) if (newFrame == null) { lastLoadWasSuccessful = false; } else { - loadedFrame = newFrame; + _loadedFrame = newFrame; lastLoadWasSuccessful = true; } @@ -401,6 +419,70 @@ boolean LoadFrame (int frameNumber) } +void cleanFolder () { + // Find all of the files in the folder that end in .tif, renumber them, and reload. + selectFolder("Choose the folder containing the images to rename", "cleanSelectedFolder"); +} + + +void cleanSelectedFolder (File selectedFolder) { + if (selectedFolder != null) { + File[] fList = selectedFolder.listFiles(); + ArrayList tifFiles = new ArrayList(); + for (File file : fList) { + if (file.isFile() && match(file.getName(), "^(.*)\\.tif$") != null) { + tifFiles.add (file); + } + } + Collections.sort(tifFiles); // Make sure it's alphabetical + + int frameCounter = 1; + ArrayList newTempFiles = new ArrayList(); + for (File file: tifFiles) { + try{ + String filename = selectedFolder.getCanonicalPath() + File.separator + "RENAMING_TEMP_FILE_Frame" + nf(frameCounter,4) + ".tif"; + PImage newFrame = loadImage (filename); + } catch (IOException e) { + println ("getCanonicalPath failed! I have no idea what that means. Giving up."); + return; + } + frameCounter++; + } + frameCounter = 1; + String[] parts = new String[3]; + parts[0] = _CWD; + parts[1] = "Image Files"; + parts[2] = _groupName.replaceAll("[^a-zA-Z0-9\\-_]", ""); + File directory = new File (join (parts,File.separator)); + File finalFile = null; + try { + Files.createDirectory (directory.toPath()); + } catch (Exception e) { + } + for (File file: newTempFiles) { + try { + String filename = directory.getCanonicalPath() + File.separator + createFilename (_groupName, frameCounter); + finalFile = new File (filename); + boolean status = file.renameTo (finalFile); + if (status) { + String msg = "Renamed " + file.getName() + " to " + finalFile; + println (msg); + } else { + String msg = "Rename " + file.getName() + " to " + finalFile + " FAILED... stopping"; + println (msg); + return; + } + } catch (Exception e) { + } + frameCounter++; + } + if (finalFile != null) { + loadPreviousFromFile (finalFile); + } + } +} + + void delay(int delay) { int time = millis(); @@ -417,43 +499,43 @@ void draw() { line (STANDARD_SPACING,2*STANDARD_SPACING+BUTTON_HEIGHT, WINDOW_WIDTH-STANDARD_SPACING,2*STANDARD_SPACING+BUTTON_HEIGHT); - if (weAreLive) { + if (_weAreLive) { boolean cameraHasGoodData = (cam != null && cam.available() == true); if (cameraHasGoodData) { - if (keyPressed && keyCode == CONTROL && loadedFrame != null) { - image (loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); + if (keyPressed && keyCode == CONTROL && _loadedFrame != null) { + image (_loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); } else { cam.read(); - liveFrame = cam.get(); - image (liveFrame, WEBCAM_LEFT, WEBCAM_UPPER); - if (keyPressed && keyCode == SHIFT && loadedFrame != null) { - blend (loadedFrame, 0, 0, WEBCAM_WIDTH, WEBCAM_HEIGHT, WEBCAM_LEFT, WEBCAM_UPPER, WEBCAM_WIDTH, WEBCAM_HEIGHT, LIGHTEST); + _liveFrame = cam.get(); + image (_liveFrame, WEBCAM_LEFT, WEBCAM_UPPER); + if (keyPressed && keyCode == SHIFT && _loadedFrame != null) { + blend (_loadedFrame, 0, 0, WEBCAM_WIDTH, WEBCAM_HEIGHT, WEBCAM_LEFT, WEBCAM_UPPER, WEBCAM_WIDTH, WEBCAM_HEIGHT, LIGHTEST); } } - } else if (liveFrame != null) { - image (liveFrame, WEBCAM_LEFT, WEBCAM_UPPER); - if (keyPressed && keyCode == SHIFT && loadedFrame != null) { - blend (loadedFrame, 0, 0, WEBCAM_WIDTH, WEBCAM_HEIGHT, WEBCAM_LEFT, WEBCAM_UPPER, WEBCAM_WIDTH, WEBCAM_HEIGHT, LIGHTEST); + } else if (_liveFrame != null) { + image (_liveFrame, WEBCAM_LEFT, WEBCAM_UPPER); + if (keyPressed && keyCode == SHIFT && _loadedFrame != null) { + blend (_loadedFrame, 0, 0, WEBCAM_WIDTH, WEBCAM_HEIGHT, WEBCAM_LEFT, WEBCAM_UPPER, WEBCAM_WIDTH, WEBCAM_HEIGHT, LIGHTEST); } } - } else if (weAreInReplay) { + } else if (_weAreInReplay) { // Show the current frame instead: - boolean success = LoadFrame (currentFrame); + boolean success = LoadFrame (_currentFrame); if (success == true) { - image (loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); - currentFrame++; - frameSlider.setValue (currentFrame); + image (_loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); + _currentFrame++; + frameSlider.setValue (_currentFrame); } - if (currentFrame > numberOfFrames || success == false) { - weAreLive = true; - weAreInReplay = false; - currentFrame = numberOfFrames+1; - frameSlider.setValue (currentFrame); + if (_currentFrame > _numberOfFrames || success == false) { + _weAreLive = true; + _weAreInReplay = false; + _currentFrame = _numberOfFrames+1; + frameSlider.setValue (_currentFrame); frameRate (30); } } else { - if (currentFrame <= numberOfFrames && loadedFrame != null) { - image (loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); + if (_currentFrame <= _numberOfFrames && _loadedFrame != null) { + image (_loadedFrame, WEBCAM_LEFT, WEBCAM_UPPER); } else { // Just do nothing, I guess... } diff --git a/data/help/Help.html b/data/help/Help.html new file mode 100644 index 0000000..4422b5f --- /dev/null +++ b/data/help/Help.html @@ -0,0 +1,62 @@ + + + +Stop Motion Animation Help + + + +

Help with Pioneer Library System's Stop Motion Animator

+

+ Welcome to Pioneer Library's System's Stop Motion Animation software. It's designed to be easy to use + and hard to break. +

+

Quick Start

+
    +
  1. Click "Start a New Movie" at the top of the screen
  2. +
  3. Write down the date stamp in the upper left corner
  4. +
  5. Click "Take Photo" or press the spacebar to take a photo
  6. +
  7. Tweak your scene, then take another photo
  8. +
  9. Keep repeating until you've got enough frames to be worth watching (15 per second)
  10. +
  11. Press ">>>PLAY>>>" to see your animation
  12. +
+ That's it! Your frames are automatically saved, so when you are done and want to start a new movie, just + click "Start a New Movie" again and repeat the process. Your frames were stored in a folder called "Image Files" + and then the date stamp you wrote down in step 2. + +

What do all the buttons do?

+ This section provides a more detailed overview of exactly what each button does, including a little bit + of technical detail. +

Start a New Movie

+ Calculates a new date stamp (Year-Month-Day-Hour-Minute-Second), resets your number of frames, and starts + recording all new frames into a folder named with the new date stamp. It also re-initializes the camera, so + if your camera wasn't ready when you first launched the program, clicking this will scan for cameras again. +

Add to Previous Movie

+ Opens a window asking you to select a frame from a pre-existing movie. You can choose any frame you want, but + the movie must be in the standard "Image Files/YYYY-MM-DD-HH-MM-SS/YYYY-MM-DD-HH-MM-SS_Frame####.tif" format and + location. If it is anywhere else you must use the "Import and Clean Frames" button instead. Adding to a previous + movie will reset your date stamp to that movie's original stamp, and update the number of frames to reflect the + frames you have already taken. New frames will simply be added to the end of that movie, just like you never + closed it. +

Import and Clean Frames

+ The basic idea of this button is to let you choose any folder full of images anywhere on your computer and + create a stop-motion animation from them. What is actually does is scan through the folder you select and + locate all of the TIFF files (*.tif). It lists them alphabetically, then moves and renames them + into a folder following the standard folder structure. This has a couple of uses: first, you can use it to + move frames from somewhere else on your computer into the place where this program expects them to be. Second, + you can use it to re-number frames of a movie where you have manually deleted (or added) frames from the + middle. +

Help

+ Opens this help file in your default web browser. +

Take Photo

+ Stores the currently-displayed webcam image into a TIFF-formatted image file in a folder named with the + date stamp displayed in the upper left corner of the window. We use TIFF so that playback is smooth, but keep + in mind that these are pretty large files (around 1MB) so you need to make sure that the actual executable file + for this program is located someplace with enough storage for all the movies. +

Delete Last Photo

+ Deletes the most recently-taken photo. To delete photos from the middle of the movie you have to manually remove the + files and then import those frames using the "Import and Clean Frames" button. +

Play

+ Loads up each frame and displays it for 1/15 of a second. Frames are loaded from their files on the disk, so we + use the TIFF format so the load is fast. Playback is always 15 frames per second. + + \ No newline at end of file