Lightsaber Rotoscoping Scripts


Process Overview

Lightsaber rotoscoping is a neat little hobby I picked up sometime near the end of High School. It involves recording a video of your friends pretending to have a lightsaber fight, using some sort of lightsaber prop, and then editing the individual frames to add the lightsaber glow. Wooden dowels (0.5" diameter) were used in these videos, and are recommended for pretend fighting, as they will certainly break before you do, if you accidentally get hit.

The term "Rotoscoping" means editing a video clip frame by frame. This is how they did the lightsaber effects in the original Star Wars triology, and perhaps in the new movies as well (I'm not sure). It's fairly tedious, to draw in the lightsaber blades every frame, but I have created a few scripts to help improve this process. The longest part of the rotoscoping, besides identifying the blades and drawing them in, is the series of image-editor transformations you need to apply. The guide that I follow, that I found a long time ago, is written by Darel Finley, and is hosted at http://www.theforce.net/. You might want to go read that before reading the rest of this page.

The big timesaver with my script is the creating of the Mosaic image file. It is (obviously) a mosaic of all the original JPEG's, with a black border between each image. This allows us to draw in the blades, something you need to do for each frame anyway, in one image file. The real benefit arises when we need to apply the filters and transformations. If we edited each file seperately, we would have to go through the same filters and transformations for each file. With the mosaic file, we only need to filter and transform the image once. This saves a lot of time.

The idea of a mosaic file came from Adobe Premier (a video editing application), which has support for a 'timeline' file, which is basically the same thing as my mosaic file. Since I run Linux exclusively, I needed something more flexible, and less proprietary. This is the reason I created these scripts.

The image in the upper-right corner of this page is a graphical version of the following list and explanation:

  1. video_to_jpg - Convert Video to JPEG's
  2. create_mosaic - Create Mosaic from JPEG's
  3. Manually edit the mosaic file
  4. split_mosaic - Split the edited mosaic back into the finished JPEG's
  5. jpg_to_video - Create a finished video from the finished JPEG's

Video to JPEG

This Bash script takes in the name of a video file that can be played in mplayer (I always had MJPEG files to work with) and writes out each frame as an individual jpeg file. The script will create a new folder with the video file's basename (the name minus extension), put a copy of the original video in the new folder, and rename the original file to "$FILENAME"_original.avi

#!/bin/bash
if [ ! $1 ]
then
  echo "Usage: `basename $0` video.avi"
  exit 1
fi

FILENAME=$1
FOLDER=$(basename $FILENAME .avi)

if [ -d $FOLDER ]
then
  rm -r $FOLDER
fi

mkdir $FOLDER
cp $FILENAME $FOLDER
mv $FILENAME "$FOLDER"_original.avi
cd $FOLDER
mplayer -ao null -vo jpeg:quality=100:smooth=0:optimize=0 $FILENAME
exit 0

Affected File and Directories

Here, I have visually presented which files and directories are created by each script. Here, video_to_jpg was run in workingDirectory using the following command:
$ ./video_to_jpg video.avi

Step One Files and Directories

Create Mosaic from JPEG's

This PHP script creates a single-image mosaic from the multiple jpeg files created in the previous step. It puts a black border between each image so there is no overlap from the Gaussian Blur. It will write a very large (depending on original image size and border width) jpeg file with the filename 'mosaic.jpg'. The command-line arguments to create_mosaic are:
./create_mosaic <imageWidth> <imageHeight> <imageDirectory> <numberOfFiles> <imageBufferSize>
Where <imageWidth> and <imageHeight> are the dimensions of the movie's frames, <imageDirectory> is the directory where the images are located (here, video), <numberOfFiles> is (obviously) the number of image files, <borderWidth> is the width of the border to put around each image. 50 is a good number, but anything will work, just make sure that lightsaber effects do not bleed over into the next frame.

#!/usr/bin/php
<?php

function CreateMosaic($width$height$dir$numberOfFiles$buffer)
{
    
//We use the square root to make the mosaic as square as possible
    
$numCols ceil(sqrt($numberOfFiles));
    
$numRows ceil($numberOfFiles/$numCols);
    
    
//these are the total dimensions of the mosaic
    
$imgWidth $numCols * (($buffer) + $width);
    
$imgHeight $numRows * (($buffer) + $height);
    
    
//Create the mosaic in memory, defaults to all black
    
$mosaic imagecreatetruecolor($imgWidth$imgHeight);
    
    echo 
"Adding Images";
    
$i 1;
    for (
$y 0$y $numRows$y++)
    {
        for (
$x 0$x $numCols$x++)
        {
            
//The mosaic will not be fully filled unless the number of files
            // is a perfect square. Normally, when we run out of images to use
            // (when $i > $numberOfFiles), we stop copying, and just wait until
            // the loops finish.
            
if ($i <= $numberOfFiles)
            {
                
$filename "$dir/" sprintf("%08d"$i) . ".jpg";
                
                
//load the current image from file
                
$img imagecreatefromjpeg($filename);
                
                
//these are the location to paste the loaded image
                
$targetX = ($x * (($buffer) + $width)) + $buffer;
                
$targetY = ($y * (($buffer) + $height)) + $buffer;
                
                
//imagecopy(dest, src, destX, destY, srcX, srcY, srcWidth, srcHeight);
                
imagecopy($mosaic$img$targetX$targetY00$width$height);
                echo 
".";
                
$i++;
            }
        }
    }
    echo 
"done!";

    echo 
"Writing Mosaic File...";
    
imagejpeg($mosaic$dir "/mosaic.jpg"100);
    echo 
"done!";
}

if (
$argc 6)
{
    echo 
"Usage: " $argv[0] . " <imageWidth> <imageHeight> <imageDirectory> <numberOfFiles> <imageBufferSize>";
    echo 
"--<imageWidth> and <imageHeight> are the width and height, respectively, of the individual images.";
    echo 
"--<imageDirectory> is the name of the subdirectory where the individual images are stored.";
    echo 
"  They should be numbered using eight numeric digits, starting with 00000001.jpg.";
    echo 
"--<numberOfFiles> is the number of individual images stored in <imageDirectory>.";
    echo 
"--<imageBufferSize> is the size (in pixels) of the black border in the mosaic image.";
    echo 
"  50 is a good number, but you can adjust as you like. Make sure things such as lightsaber";
    echo 
"  blurs don't extend through the buffer into the next frame.";
    echo 
"For more information, please see http://www.mbeckler.org/lightsabers/lightsabers.html";
}
else
{
    
CreateMosaic($argv[1], $argv[2], $argv[3], $argv[4], $argv[5]);
}
?>

Affected File and Directories

Here, create_mosaic was run in workingDirectory using the following command:
$ ./create_mosaic 320 240 video 75 50

Step Two Files and Directories

Manually Edit the Mosaic File

Go read this page, I'll put more details up eventually.

Split Mosaic Into JPEG's

This PHP script does just the opposite that create_mosaic does, and even takes the same command-line arguments. It splits the mosaic file into individual images, after you've edited the mosaic file in Gimp/Photoshop.

#!/usr/bin/php
<?php

/**
 * Delete a file, or a folder and its contents
 *
 * @author      Aidan Lister <aidan@php.net>
 * @version     1.0.3
 * @link        http://aidanlister.com/repos/v/function.rmdirr.php
 * @param       string   $dirname    Directory to delete
 * @return      bool     Returns TRUE on success, FALSE on failure
 */
function rmdirr($dirname)
{
    
// Sanity check
    
if (!file_exists($dirname))
    {
        return 
false;
    }

    
// Simple delete for a file
    
if (is_file($dirname) || is_link($dirname))
    {
        return 
unlink($dirname);
    }

    
// Loop through the folder
    
$dir dir($dirname);
    while (
false !== $entry $dir->read())
    {
        
// Skip pointers
        
if ($entry == '.' || $entry == '..')
        {
                continue;
        }

        
// Recurse
        
rmdirr($dirname DIRECTORY_SEPARATOR $entry);
    }

    
// Clean up
    
$dir->close();
    return 
rmdir($dirname);
}

function 
SplitMosaic($width$height$dir$numberOfFiles$buffer)
{
    
$numCols ceil(sqrt($numberOfFiles));
    
$numRows ceil($numberOfFiles/$numCols);
    
$imgWidth $numCols * (($buffer) + $width);
    
$imgHeight $numRows * (($buffer) + $height);

    echo 
"Loading Mosaic File...";
    
$mosaic imagecreatefromjpeg($dir "/mosaic.jpg");
    echo 
"done!";

    
$outputDir $dir "/finished_frames/";
    if (
file_exists($outputDir))
    {
        
rmdirr($outputDir);
    }
    
mkdir($outputDir);

    echo 
"Splitting Mosaic";
    
$i 1;
    for (
$y 0$y $numRows$y++)
    {
        for (
$x 0$x $numCols$x++)
        {
            if (
$i <= $numberOfFiles)
            {
                
$img imagecreatetruecolor($width$height);
                
$cutX = ($x * (($buffer) + $width)) + $buffer;
                
$cutY = ($y * (($buffer) + $height)) + $buffer;
                
imagecopy($img$mosaic00$cutX$cutY$width$height);
                
imagejpeg($img$outputDir sprintf("%08d"$i) . ".jpg"100);
                echo 
".";
                
$i++;
            }
        }
    }
    echo 
"done!";
}

if (
$argc 6)
{
    echo 
"Usage: " $argv[0] . " <imageWidth> <imageHeight> <imageDirectory> <numberOfFiles> <imageBufferSize>";
    echo 
"--<imageWidth> and <imageHeight> are the width and height, respectively, of the individual images.";
    echo 
"--<imageDirectory> is the name of the subdirectory where the individual images are stored.";
    echo 
"  They should be numbered using eight numeric digits, starting with 00000001.jpg.";
    echo 
"--<numberOfFiles> is the number of individual images stored in <imageDirectory>.";
    echo 
"--<imageBufferSize> is the size (in pixels) of the black border in the mosaic image.";
    echo 
"  50 is a good number, but you can adjust as you like. Make sure things such as lightsaber";
    echo 
"  blurs don't extend through the buffer into the next frame.";
    echo 
"For more information, please see http://www.mbeckler.org/lightsabers/lightsabers.html";
}
else
{
    
SplitMosaic($argv[1], $argv[2], $argv[3], $argv[4], $argv[5]);
}
?>

Affected File and Directories

Here, split_mosaic was run in workingDirectory using the following command:
$ ./split_mosaic 320 240 video 75 50

Step 3 Files and Directories

JPEG to Video

This Bash script takes the name of your video folder, the dimensions of your images/video, and the desired framerate. As far as settings and codecs are concerned, I'm not sure of the best way to combine multiple files into a video using mencoder, but this seems to work alright. Let me know if you have a better way.

#!/bin/bash
if [ ! $4 ]
then
 echo "Usage: `basename $0` folder width height frame_rate"
 exit 1
fi

FOLDER=$1
FILENAME="$FOLDER"_finished.avi
WIDTH=$2
HEIGHT=$3
FRAMERATE=$4
OUTFOLDER="finished_frames"

rm $FILENAME
cd $FOLDER/$OUTFOLDER

mencoder mf://*.jpg -mf w=$WIDTH:h=$HEIGHT:fps=$FRAMERATE:type=jpg -ovc copy -oac copy -o $FILENAME

cp $FILENAME ../../
cd ../../
mplayer $FILENAME
exit 0

Affected File and Directories

Here, jpg_to_video was run in workingDirectory using the following command:
$ ./jpg_to_video video 320 240 15

Step 4 Files and Directories

Finished Results

Here is the end result of the video I was working with during this guide:

Finished Video
Download Finished Video (AVI, 5.4MB)


Back to Matthew Beckler's home page

This page was last updated April 15, 2007