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:

My toys over the years

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>