
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:
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
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
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 * ((2 * $buffer) + $width);
$imgHeight = $numRows * ((2 * $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 * ((2 * $buffer) + $width)) + $buffer;
$targetY = ($y * ((2 * $buffer) + $height)) + $buffer;
//imagecopy(dest, src, destX, destY, srcX, srcY, srcWidth, srcHeight);
imagecopy($mosaic, $img, $targetX, $targetY, 0, 0, $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]);
}
?>
Here, create_mosaic was run in workingDirectory using the following command:$ ./create_mosaic 320 240 video 75 50
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 * ((2 * $buffer) + $width);
$imgHeight = $numRows * ((2 * $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 * ((2 * $buffer) + $width)) + $buffer;
$cutY = ($y * ((2 * $buffer) + $height)) + $buffer;
imagecopy($img, $mosaic, 0, 0, $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]);
}
?>
Here, split_mosaic was run in workingDirectory using the following command:$ ./split_mosaic 320 240 video 75 50
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
Here, jpg_to_video was run in workingDirectory using the following command:$ ./jpg_to_video video 320 240 15
Here is the end result of the video I was working with during this guide:

Download Finished Video (AVI, 5.4MB)
Back to Matthew Beckler's home page
This page was last updated April 15, 2007