Cartoon animation program
A cartoon can be thought of as a series of drawings. To simulate movement, the drawings can be slightly different from each other.
Remember drawing simple cartoons using a pad of paper? Simply flipping through the pages made the drawings come to life.
This was tedious work: a computer can help.
Just after first IBM PC came out in Aug 81, I wrote a cartoon animation program in C++ and assembly code. The concept was very simple: just use the mouse (I had to write my own mouse driver for a RS-232 serial mouse and had to hijack the COM1 port for my DOS program) to let the user draw some lines on the screen. Then the user could save those lines as a cartoon frame, and then draw another frame. The program could then calculate multiple frames in between the user created frames, creating smooth animation.
That early 80's version of cartoon still runs on XP (although it writes directly to the video memory so it requires full screen mode.
It doesn't run on Vista (I think you can download a DOS compatible window to make it work.)
A zip file of the 80's version is available here. Unzip the contents to a folder (check out the date stamps!), then run cartoon.exe and hit Shift-A.
It includes some stored samples, like flying birds, alphabet, basketball. Because this was written before Windows, it won't work with your mouse. You can see the main menu bar at the top. The only thing you can do with this program is run the stored samples by hitting Shift-A. Q will Quit. Try typing other chars to invoke various commands.
If you get it to run on Vista, please let me know the details.
About 10 years ago, I wrote another version of Cartoon in Foxpro and it was modified and published as a Solution Sample.
Start Visual Foxpro, Task Pane, Solution Samples. In the "Search for sample" text, box, type in Animation. "Display line animation in a form"
You can run the form, or click on the button on the right of the task pane that opens the form in the Form Designer, so you can see the source code.
You can also open the form in the Class Browser, then export the code into a single file using the "View Class Code" button.
Foxpro excerpt of the inbetween algorithm (notice how cursors (in-memory data tables) are used)
SELECT (lcTable)
DO WHILE !EOF("shadow")
mr = recno()
mr2 = recno("shadow")
FOR nb = 0 TO nBetween
THISFORMSET.frmAnimation.cls
GO mr
IF mr2 < RECCOUNT("shadow")
GO mr2 IN shadow
ENDIF
nFrames1 = &lcTable..frameno
nFrames2 = shadow.frameno
SCAN WHILE &lcTable..frameno = nFrames1
nx1 = &lcTable..x1 + nb * (shadow.x1 - &lcTable..x1) / nBetween
ny1 = &lcTable..y1 + nb * (shadow.y1 - &lcTable..y1) / nBetween
nx2 = &lcTable..x2 + nb * (shadow.x2 - &lcTable..x2) / nBetween
ny2 = &lcTable..y2 + nb * (shadow.y2 - &lcTable..y2) / nBetween
THISFORMSET.frmAnimation.line(nx1,ny1,nx2,ny2)
IF !EOF("shadow")
SKIP IN shadow
IF shadow.frameno # nFrames2
SKIP -1 IN shadow
ENDIF
ENDIF
ENDSCAN
SELECT shadow
IF !EOF()
SKIP
LOCATE REST FOR shadow.Frameno # nFrames2
ENDIF
SELECT (lcTable)
wait wind "" time .05
ENDFOR
ENDDO
USE IN shadow
SCAN REST
THISFORMSET.frmAnimation.line(x1,y1,x2,y2)
ENDSCAN
THISFORMSET.frmAnimation.frameno = nFrames1 + 1
Below is a more up to date example using WPF. The equivalent animation code is in tmr_tick().
Try running it, hitting the Demo button.
Start Visual Studio 2008.
Choose File->New Project->Visual Basic->WPF Application.
(This also works with Temporary projects)
Open Window1.xaml.vb. Paste in the code below, then hit F5 to run.
I had my wife and kids playing with it for quite a while!
Try adding a frame or MouseWheel while a cartoon is playing.
Try right click and then draw: it changes the way you draw.
Experiment with using a touchpad and a mouse for drawing.
Try using variable frame rates. Add a feature to save/restore the current cartoon.
Try drawing your name, or the letters of the alphabet.
My original had features like copy/paste from frames, color fill
See also:
Why was the original IBM PC 4.77 Megahertz?
<Cartoon Code>
Class Window1
Private WithEvents btnNewFrame As Button
Private WithEvents btnErase As Button
Private WithEvents btnPlay As Button
Private WithEvents btnDemo As Button
Private WithEvents btnReset As Button
Private txtStatus As TextBlock
Private _AnimControl As AnimControl
Sub Load() Handles MyBase.Loaded
Me.Width = 800
Me.Height = 600
Dim xaml = _
<DockPanel
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" LastChildFill="True">
<StackPanel Background="Transparent" Orientation="Vertical" DockPanel.Dock="Top">
<TextBlock>Draw to create lines for a cartoon frame. Add a new frame, hit play</TextBlock>
<TextBlock>Rebirth of Calvin's cartoon program circa 1982
<Hyperlink>https://blogs.msdn.com/calvin_hsia</Hyperlink>
</TextBlock>
</StackPanel>
<Border DockPanel.Dock="Bottom" Height="25">
<StackPanel Orientation="Horizontal">
<Button Name="btnNewFrame" ToolTip="Add current drawing to cartoon, so you can create a new one">_New Frame</Button>
<Button Name="btnErase">_Erase</Button>
<Button Name="btnPlay" ToolTip="Animate the current frames or stop animation">_Play</Button>
<Button Name="btnDemo">_Demo</Button>
<Button Name="btnReset" ToolTip="Erase all frames">_Reset</Button>
<TextBox Name="txtBetween" Text="{Binding Path=txtBetween.text}"></TextBox>
<TextBlock Name="txtStatus"></TextBlock>
</StackPanel>
</Border>
<UserControl Name="MyCtrl"/>
</DockPanel>
Dim dPanel = CType(System.Windows.Markup.XamlReader.Load(xaml.CreateReader), DockPanel)
Dim MyCtrl = CType(dPanel.FindName("MyCtrl"), UserControl)
_AnimControl = New AnimControl(Me)
MyCtrl.Content = _AnimControl
btnPlay = CType(dPanel.FindName("btnPlay"), Button)
btnNewFrame = CType(dPanel.FindName("btnNewFrame"), Button)
btnPlay = CType(dPanel.FindName("btnPlay"), Button)
btnDemo = CType(dPanel.FindName("btnDemo"), Button)
btnErase = CType(dPanel.FindName("btnErase"), Button)
btnReset = CType(dPanel.FindName("btnReset"), Button)
txtStatus = CType(dPanel.FindName("txtStatus"), TextBlock)
Me.Content = dPanel
End Sub
Sub btnNewFrame_Click() Handles btnNewFrame.Click
_AnimControl.NewFrame()
RefreshStatus()
End Sub
Sub btnPlay_Click() Handles btnPlay.Click
btnNewFrame_Click() ' save any currently drawn changes first
_AnimControl.Play()
End Sub
Sub btnDemo_Click() Handles btnDemo.Click
_AnimControl.Demo()
End Sub
Sub btnErase_Click() Handles btnErase.Click
_AnimControl.EraseBtn()
End Sub
Sub btnReset_Click() Handles btnReset.Click
_AnimControl.Reset()
End Sub
Friend Sub RefreshStatus()
Me.txtStatus.Text = String.Format("Frame count = {0} CurLineCnt = {1} CurFrame= {2} Between = {3}", _
_AnimControl._UserFrameList.Count, _AnimControl._CurLineList.Count, _
_AnimControl._ndxUserFrame, _AnimControl._nBetween)
End Sub
End Class
Public Class AnimControl
Inherits FrameworkElement
Private WithEvents _timer As New System.Windows.Threading.DispatcherTimer
Private _Window1 As Window1
Friend _nBetween As Integer = 10 ' # of frames being calc'd between user frames
Friend _ndxUserFrame As Integer ' index into user created frames.
Private _ndxBetween As Integer ' from 0 to nBetween
Friend _nBetweenDyn As Integer = 0 ' # to add to _nBetween for next animation: adjustable by mousewheel
Private _ptCurrent As Point?
Private _ptOld As Point?
Private _fPenDown As Boolean
Private _oPen = New Pen(Brushes.Black, 2)
Private _PenModeDrag As Boolean = True ' Click to create line segs, or continuous drag to create multiple segs
' lines to draw for current image: could be while composing, or playing. Could be real frame or calc'd frame
Friend _CurLineList As New List(Of cFrameLine)
'Frames stored by user
Friend _UserFrameList As New List(Of cCartoonFrame)
Sub New(ByVal w As Window1)
_Window1 = w
End Sub
Sub Reset()
Me._timer.IsEnabled = False 'stop playback, if any
Me._UserFrameList.Clear() ' erase all user data
Me._nBetweenDyn = 0
EraseBtn()
End Sub
Sub EraseBtn() ' erase current frame
_CurLineList.Clear()
Me._ptOld = Nothing
Me._fPenDown = False
Me.InvalidateVisual()
End Sub
Sub Demo()
Reset()
Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(10, 300)))
Me._CurLineList.Add(New cFrameLine(New Point(10, 300), New Point(300, 300)))
Me._CurLineList.Add(New cFrameLine(New Point(300, 300), New Point(300, 10)))
Me._CurLineList.Add(New cFrameLine(New Point(300, 10), New Point(10, 10)))
Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 30))
Me._CurLineList.Clear() ' reset for next frame
Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(300, 10)))
Me._CurLineList.Add(New cFrameLine(New Point(300, 10), New Point(300, 300)))
Me._CurLineList.Add(New cFrameLine(New Point(300, 300), New Point(10, 300)))
Me._CurLineList.Add(New cFrameLine(New Point(10, 300), New Point(10, 10)))
Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 50))
Me._CurLineList.Clear() ' reset for next frame
Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(300, 10)))
Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 10))
Play()
End Sub
Sub NewFrame()
If _CurLineList.Count > 0 Then
Dim curFrame = New cCartoonFrame(_CurLineList, 10)
_UserFrameList.Add(curFrame)
EraseBtn()
End If
End Sub
Friend Sub Play()
If _UserFrameList.Count < 2 Then
MsgBox("Need at least 2 frames to animate")
Return
End If
If _timer.IsEnabled Then ' if we're already playing, stop
_timer.IsEnabled = False
Else
_timer.Interval = New TimeSpan(0, 0, 0, 0, 50) ' days,hrs,mins,secs,msecs
_timer.IsEnabled = True
End If
Me._fPenDown = False
Me._ndxUserFrame = 0
Me._ndxBetween = 1 ' 1st is drawn now, next by timer tick
Me._CurLineList.Clear()
Me._CurLineList.AddRange(Me._UserFrameList(0)._Lines) 'get the 1st frame
Me.InvalidateVisual() ' show it
End Sub
Sub tmr_tick() Handles _timer.Tick ' let's do the animating
If _ndxUserFrame = Me._UserFrameList.Count - 1 Then ' we've reached the end: let's restart
Me._ndxUserFrame = 0
Me._ndxBetween = 0
End If
Me._CurLineList.Clear()
Dim frmLeft = Me._UserFrameList(Me._ndxUserFrame) ' the frame on the left
Dim frmRight = Me._UserFrameList(Me._ndxUserFrame + 1) ' the frame on the right
_nBetween = Math.Max(0, frmLeft._nBetween + Me._nBetweenDyn) ' recorded value plus mousewheel adjustment
Dim nLinesToDraw = Math.Max(frmLeft._Lines.Count, frmRight._Lines.Count) - 1
For ndx = 0 To nLinesToDraw ' calc the lines to draw
Dim lineLeft = frmLeft._Lines(Math.Min(ndx, frmLeft._Lines.Count - 1))
Dim lineRight = frmRight._Lines(Math.Min(ndx, frmRight._Lines.Count - 1))
Dim pt0 As New Point With _
{.X = lineLeft.pt0.X + Me._ndxBetween * (lineRight.pt0.X - lineLeft.pt0.X) / (_nBetween + 1), _
.Y = lineLeft.pt0.Y + Me._ndxBetween * (lineRight.pt0.Y - lineLeft.pt0.Y) / (_nBetween + 1)}
Dim pt1 As New Point With _
{.X = lineLeft.pt1.X + Me._ndxBetween * (lineRight.pt1.X - lineLeft.pt1.X) / (_nBetween + 1), _
.Y = lineLeft.pt1.Y + Me._ndxBetween * (lineRight.pt1.Y - lineLeft.pt1.Y) / (_nBetween + 1)}
Dim newLine = New cFrameLine(pt0, pt1)
Me._CurLineList.Add(newLine)
Next
If Me._ndxBetween > Me._nBetween Then ' we've reached the right
Me._ndxUserFrame += 1 ' advance to next user frame
Me._ndxBetween = 0 ' we don't want to redraw frmRight when it becomes frmLeft
End If
Me._ndxBetween += 1 ' advance to next frame
Me.InvalidateVisual()
End Sub
Protected Overrides Sub OnRender(ByVal drawingContext As System.Windows.Media.DrawingContext)
drawingContext.DrawRectangle(Brushes.AliceBlue, New Pen(Brushes.Purple, 1), New Rect(0, 0, Me.RenderSize.Width, Me.RenderSize.Height))
For Each fr In Me._CurLineList ' draw the lines in the current frame
drawingContext.DrawLine(_oPen, fr.pt0, fr.pt1)
Next
If Me._fPenDown Then
If Me._ptOld.HasValue Then
drawingContext.DrawLine(_oPen, Me._ptOld, Me._ptCurrent)
Else
drawingContext.DrawLine(_oPen, Me._ptOld, Me._ptCurrent)
End If
End If
_Window1.RefreshStatus()
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)
If e.RightButton = MouseButtonState.Pressed Then
Me._PenModeDrag = Not Me._PenModeDrag ' toggle modes on right click
Else
If Me._PenModeDrag Then
Me._ptOld = e.GetPosition(Me)
Else
If e.RightButton = MouseButtonState.Pressed Then
Me._fPenDown = False
Me._ptOld = Nothing
Else
Me._fPenDown = True
Me._ptCurrent = e.GetPosition(Me) ' get cur pos rel to self
If Not Me._ptOld.HasValue Then
Me._ptOld = Me._ptCurrent ' same
End If
Me.InvalidateVisual()
End If
End If
End If
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Input.MouseEventArgs)
If Me._PenModeDrag Then
If e.LeftButton = MouseButtonState.Pressed Then
If Me._ptOld.HasValue Then
Me._ptCurrent = e.GetPosition(Me)
Dim newFrameLine = New cFrameLine(Me._ptOld, Me._ptCurrent)
Me._CurLineList.Add(newFrameLine)
Me._ptOld = Me._ptCurrent
Me.InvalidateVisual()
End If
End If
Else
If Me._fPenDown Then
If Me._ptOld.HasValue Then
Me._ptCurrent = e.GetPosition(Me)
End If
Me.InvalidateVisual()
End If
End If
End Sub
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Input.MouseButtonEventArgs)
If Me._fPenDown Then
Me._ptCurrent = e.GetPosition(Me) ' get cur pos rel to self
Dim newFrameLine = New cFrameLine(Me._ptOld, Me._ptCurrent)
Me._CurLineList.Add(newFrameLine)
Me._ptOld = Me._ptCurrent
Me._fPenDown = False
Me.InvalidateVisual()
End If
End Sub
Protected Overrides Sub OnMouseWheel(ByVal e As System.Windows.Input.MouseWheelEventArgs)
If Me._timer.IsEnabled Then ' only when playing
Me._nBetweenDyn += If(e.Delta > 0, 2, -2)
End If
End Sub
<DebuggerDisplay("{ToString()}")> _
Public Class cCartoonFrame ' User created cartoon frame
Public ReadOnly _Lines As New List(Of cFrameLine) ' a frame is a list of lines
' # of frames to gen between real user frames
Public ReadOnly _nBetween As Integer
Sub New(ByVal lst As List(Of cFrameLine), ByVal nBetween As Integer)
_Lines.AddRange(lst)
_nBetween = nBetween
End Sub
Public Overrides Function ToString() As String
Return String.Format("LineCount = {0}", _Lines.Count)
End Function
End Class
'A line to be animated. Could belong to a real or gen'd user frame, or while user is actively drawing
<DebuggerDisplay("{ToString()}")> _
Public Class cFrameLine ' User created cartoon line. It's just 2 points.
Friend ReadOnly pt0 As Point
Friend ReadOnly pt1 As Point
Sub New(ByVal pt0 As Point, ByVal pt1 As Point)
Me.pt0 = pt0
Me.pt1 = pt1
End Sub
Public Overrides Function ToString() As String
Return String.Format("Line = ({0}) - ({1})", pt0.ToString, pt1.ToString)
End Function
End Class
End Class
</Cartoon Code>
Comments
Anonymous
January 29, 2009
PingBack from http://blog.a-foton.ru/index.php/2009/01/29/cartoon-animation-program/Anonymous
January 29, 2009
Lovin' the concept that 39-lines of VFP code become 271-lines of code using WPF.Anonymous
January 30, 2009
I just installed VS 2008 along with SP1, and when I followed your directions, I ended up with 46 errors. Obviously there are some things assumed that I'm missing, any hints?Anonymous
January 30, 2009
Are you sure you had VB->WPF application? Did you paste into the Window1.Xaml.VB file? what are the errors? Did you remove the double spacing? (to remove, see http://blogs.msdn.com/calvin_hsia/archive/2005/08/07/448889.aspx)Anonymous
January 31, 2009
Well, color me emarassed. Removing the double spacing does indeed makes a world of difference. Thanks to VFP's rr to r replacement, that was quite trivial. Amazing to compare the VFP version and see how it's become more complicated to accomplish the same effects. Thanks for reminding me of the Solution Sample. I'm just going to have to play in VS a bit more, but my daytime VFP job keeps me very busy! Thanks again for a proper hint!Anonymous
February 05, 2009
Calvin - can you remember what model of mouse it was that you handled in the drawing prg, and was it a bus mouse or serial mouse ?Anonymous
February 15, 2009
Well................am i suppose to cut and paste somehwere? where should i paste?Anonymous
February 15, 2009
Mimi: the post above says to start Visual Studio, etc. to paste. Be sure to remove any double spacingAnonymous
February 15, 2009
Alan: yes: the mouse I used was a 1200 baud serial optical mouse from VisiOnAnonymous
March 30, 2009
When I wrote my cartoon animation program almost 30 years ago (see Cartoon animation program ) I neededAnonymous
April 02, 2009
Calvin, are you aware of the Phun program at http://www.phunland.com/wiki/Home . . . make sure you watch the video on the home page. You can get lost in this program and kids of all ages love it.Anonymous
May 28, 2009
In the last post, Area fill algorithm: crayons and coloring book , I showed a program that emulates aAnonymous
June 09, 2009
PingBack from http://cellulitecreamsite.info/story.php?id=9028Anonymous
June 09, 2009
PingBack from http://quickdietsite.info/story.php?id=9419Anonymous
June 18, 2009
PingBack from http://cutebirdbaths.info/story.php?id=1076Anonymous
May 31, 2010
Hi, May I know if you have a code in Visual Foxpro 6 to show only a portion of my screen into a video projector. Thank you Rolly