Figure 1 - Turn the page animation.
This article presents a method for creating a page turn effect that could be useful when displaying bitmaps side by side as if they are pages in a book or photo album. The page turn effect gives a realistic transition between the simulated pages.
The inspiration for writing this article came from a desire to create a reusable photo album component to display pictures. I chose to use C# and GDI+ to enhance my understanding of graphics programming in the .NET environment.
The technical background for creating this effect came from The Page Turn Effect in Flash MX by Sham Bhangal, author of Flash Hacks. Sham's article uses a line of symmetry to control the visible areas of the pages participating in the animation. There are differences between the Flash MX and the GDI+ approaches; however, the concepts of calculating and then using a line of symmetry to control visible page areas are the same.
Throughout this article, it is assumed that pages 3 and 4 are the current pages and that the animation will show the transition to pages 5 and 6. In other words, the animation will show page 4 being turned.
The following figure illustrates the important areas and variables used throughout the animation:
Figure 2 - Areas and important parameters.
The entire page turn animation can be summarized as:
- Draw the current pages 3 and 4 in their entirety. The visible part of page 4 after drawing areas B and C will represent area A.
- Calculate the line of symmetry and the clipping regions for areas B and C.
- Draw area B. This is the visible portion of the page "under" the page being turned (page 6).
- Translate the coordinates to the hot spot, and rotate the coordinates.
- Draw area C. This is the visible portion of the "underside" of the page being turned (page 5).
The current pages
The Page Turn Effect in Flash MX article points out that the current pages do not participate in the animation. This means that for each frame of the animation the current pages (3 and 4) are drawn first without clipping or any transformations. The visible areas B and C will be drawn on top of the current pages.
I borrowed the term "hot spot" from The Page Turn Effect in Flash MX article. The hot spot represents the location of the page fold along the horizontal axis, and it is the point where a line of symmetry will start. During the animation, the hot spot always moves from the page edge to the center of the book. The distance from the page edge to the hot spot (x) is increased for each frame of the animation. The animation will stop when the hot spot reaches the center of the book; when x = PAGE_WIDTH.
Line of symmetry
The line of symmetry represents the page fold. It is used to control the visible portion of the pages participating in the animation. In .NET terms, the line of symmetry controls two things:
- The clipping region bounding areas B and C.
- The translated coordinate origin for area C.
The line of symmetry can be described by the following equations:
- a = 45 + ( (45 *x) / PAGE_WIDTH )
- h = x Tan ( a )
Notice that angle a will increase as the distance x increases. The equation shows that when x = 0 then a = 45 degrees, and when x = PAGE_WIDTH then a = 90 degrees.
When the animation starts, x will be 0. Therefore areas B and C will not be visible. As x increases, the line of symmetry will form the hypotenuse of a triangle. The triangle bounded by the line of symmetry, distance "x", and the calculated height "h" will create a closed path. Figure 2 shows an example of this case.
As x continues to increase, the condition h>=PAGE_HEIGHT will be become true. When this happens, the path under the line of symmetry will be changed from a triangle to a trapezoid. The height of the trapezoid will be bounded by PAGE_HEIGHT. Figure 1 is an example of this case.
The closed path, either triangle or trapezoid, will define the clipping regions for areas B and C. To see this graphics path in action, set the constant
true in the provided source code. This will draw a gold outline around the path.
This is the visible area of the page being turned, page 4, after areas B and C are drawn.
Area B is from the bitmap representing the page "under" the page being turned. In this example, area B is the visible part of page 6. Area B is simply drawn by setting a clipping region for the animation frame to the closed path under the line of symmetry. The bitmap for this page is then drawn directly over the page being turned. It should be obvious that as x increases (and angle a increases) more of this page will become visible.
To see the relationship of area A to area B, set the constant
false. This simply removes area C from the animation.
Area C is from the bitmap representing the underside of the page being turned. In this example, area C is the visible part of page 5. Area C is defined by the line of symmetry, but it is on the opposite side of the page. For example, as page 4 is turned, area B will be the lower right portion of page 6 and area C will be the lower left portion of page 5. Figure 3 shows how the graphics paths for area B and area C are related:
Figure 3 - Relationship between areas B and C.
An off screen bitmap,
pageUndersideImage, is used when drawing area C. The clip region for the new bitmap is defined by the graphics path under the line of symmetry. The image for page 5 is drawn into this new buffer. This newly created image will be drawn adjacent to area B.
Stitching area C to area B
When the off screen image for area C is ready, it can be drawn onto the current animation frame:
- First the coordinate system is translated to the hot spot.
- The coordinate system is then rotated by 180-2a degrees. Refer to Figure 4 to see this relationship.
- The image holding area C is drawn at the coordinates (-x,-PAGE_HEIGHT). Figure 5 illustrates drawing area C on the rotated coordinate system.
Figure 4 - Rotation angles to match lines of symmetry.
Figure 5 - Rotated coordinate system
I found lining up areas B and C to be challenging because the colors of the current page would bleed through along the line of symmetry (in this case a few red pixels from page 4). The workaround that I implemented include setting a graphic object's
PixelOffsetMode.Half while drawing area B or C:
I also found that adding 1 pixel (or subtracting 1 pixel) when translating the coordinates to the hot spot, before drawing area C, also prevents color bleeding:
To use the
PixelOffsetMode workaround, set the boolean
true in the provided source.
Painting the animation
Each frame of the animation is drawn to an off-screen buffer,
CurrentShownBitmap, in the
timer1_Tick event handler. The
OnPaint event handler simply draws the contents of
CurrentShownBitmap to the screen.
Key functional descriptions
Calculation of the graphics paths for area B and area C for each frame of the animation is preformed by the function:
private GraphicsPath GetPageUnderGraphicsPath(int x,
ref double a, int height, int width,
bool isUnderSide, TurnType type)
x represents the hot spot's distance from the page edge (as discussed above). The
width parameters indicate the current page's height and width. The
isUnderside parameter is used to tell the routine if the graphics path being calculated is for area C (the underside of the page being turned). The last parameter
type indicates if the current animation is for a left or right page turn. Essentially,
type are used to get the orientation of the graphics path correct. Parameter
a represents the current angle a as discussed above. The value for
a is returned from this function and later used when rotating the coordinate axis before drawing area C (see figures 4 and 5).
Using the code
Setting up the control
For simplicity, this control contains its own bitmaps. Initialization of the bitmaps occurs in the function
LoadSamples(). This function is called in the constructor of the sample form.
Controlling the animation frame rate and count
The frame rate of the animation is controlled by a timer. The
TickSpeed can be used to control the rate (in millisecond resolution). The number of animation frames is controlled by how far the hot spot is moved during each timer tick. The
MoveXBy is exposed to control the distance
x has moved.
Adjusting the height
Clipping along the top of the underside image (page) is a problem . The
HeightAdjustment has been introduced to allow a little more room at the top of the control to work around clipping issues.
Starting the animation
This component exposes two methods to start the animation.
Points of interest
- I originally tried to solve the non linear equation h = x Tan( 45 + ((45 * (x)) / PAGE_WIDTH) ) for the value of x where h=PAGE_HEIGHT. My brute force attempt at solving this equation is included with the source code for this article. Is there a more elegant solution to this problem?