Persistence of Vision
by Michael Morrissey <jersey@be.com>

Recently, Ficus Kirkpatrick and I were musing about how to
combine what he was working on (video stuff) with what I was
working on (printing). My suggestion, something along the
lines of finding a 60-page-per-second printer, was put
down soundly. Ficus had a better idea: How about a little
application to generate flipbooks from movie files? I was
intrigued. So, with a little schooling about the new
BMediaFile and BMediaTrack classes from Jeff Bush, a
newsletter article was born:
<ftp://ftp.be.com/pub/samples/interface_kit/flipbook.zip>
Open the source code and follow along!

To use this little app, you'll need at least one movie,
preferably one that shows a lot of motion. Drop the movie
onto the app's window (or open it through the file panel).
Notice the Print Information box. The first item, "Number of
flip pages," tells you how many flip pages will be printed
out. This is controlled by the "Print one frame per..." text
control: printing one frame per 2, say, will print every other
frame of the movie. The next item, "Flip pages per printed
page," lets you know how many flip pages can be tiled onto a
single printed page. This is affected by the paper size you
choose in Page Setup. Finally, "Number of printed pages" does
the arithmetic for you, showing how many pages this little
movie will cost you. The "Generate" button produces and prints
the flipbook.

Even if you don't have a printer, you can still have fun:
just create a Preview printer to see what the pages look
like. Using the Preview printer is also a useful way to
see if your flipbook has enough, or too many, pages, without
wasting lots of time and paper. (To create a Preview
printer, go to the Printers Preferences panel, click
"Add...," choose "Local Printer" and press "Continue," and
select "Preview" from the "Printer Type" menu.)

The sample code uses a few features of the BPrintJob class
that are new to R4.5. PageRect() and PrintableRect() can
now be called on BPrintJobs which haven't been set up yet,
either through the user running PageSetup(), or through a
call to SetSettings(). In this case, these functions return
the printer's default Page rectangle and the printer's
default Printable rectangle. This is how we can suggest
"Number of flip pages per printed page" even though the
user hasn't yet run Page Setup -- we're using the printer's
default Printable rectangle. (I won't go into how this is
implemented on the printer driver side. If you're writing a
printer driver, contact me for details.)

A new BPrintJob function that wasn't used in this sample
code is GetResolution(), which returns the printer's
resolution in the horizontal and vertical directions. Like
PageRect() and PrintableRect(), it returns the printer
driver's default settings if the BPrintJob has not yet been
set up, and the user-selected resolution if it has been set
up.

You'll also notice a new message being caught in
FlipApp::MessageReceived(): B_PRINTER_CHANGED. In R4.5,
this message is broadcast to all running applications when
the user switches the default printer through the Printers
Preferences panel, allowing applications to handle this
change. Applications should, at a minimum, discard the
previous printer's settings message and call ConfigPage()
the next time the user invokes printing, rather than going
directly to ConfigJob().

The application itself is straightforward. After receiving
an entry_ref, either from the Open dialog or through a
drag-n-drop message, we open it through BMediaFile. We then
step through the BMediaTracks, looking for one of type
B_MEDIA_ENCODED_VIDEO. Upon finding such a track, we create
a bitmap of the appropriate size, and tell the BMediaTrack
that when we ask for a frame of video, it should return raw
video. This "asking for a frame" occurs when we call
BMediaTrack::ReadFrame(), where we pass in the
aforementioned bitmap, which is populated with the video
frame. Playing the movie, then, is nothing but a loop of
calls to ReadFrame() followed by DrawBitmap(). (See
FlipView::GenerateFlipbook().)

One way that Flipbook differs from most applications is that
the "document" as such doesn't exist before we print. In
fact, we don't even create a whole page before it is
printed: doing so would needlessly consume memory (a full
page of 32-bit bitmaps is costly!). Instead, we do it
piecemeal: given a video frame, we create a flipbook page,
determine where this flipbook page should be placed on the
printed page, and then call BPrintJob::DrawView().

In DrawView(), the BPrintJob writes the drawing commands
which describe this flipbook page out to the spool file,
along with the BPoint which determines where it is to be
placed on the printed page. We can create the document
this way, piece-by-piece, because until BPrintJob::SpoolPage()
is called, the BPrintJob is working only on one page,
regardless of how many DrawView() commands are issued.

As always, I encourage you to twist the code to suit your
own purposes. The illustrious Miss Melanie Walker informs
me that there are companies that produce heavy, perforated
paper specifically designed for making flipbooks. I haven't
had a chance to find such paper yet, but if you do, you'll
probably need to tweak the Layout code to match the
perforation marks. (Don't be afraid to call SetScale(), in
combination with GetResolution(), to get just the right size
image.) Enjoy!

