[Home] [Puzzles & Projects] [Delphi Techniques] [Math topics] [Library] [Utilities]
Drawing an image of phases of the moon is a good exercise in the use of the arc and floodfill functions of a TCanvas control.
We'll use a TPaintbox control to provide the canvas. TCanvas's OnPaint exit will contain the actual drawing code. The ellipse function requires opposite corners be specified. If the differences between the x corner coordinates and y corner coordinates are the same, a circle is produced - a good starting point for a full moon. The fun part is eliminating portions of the circle to represent phases of the moon.
A Little Lunar Background
Moon phases exist because we usually see only a portion of the side of the moon illuminated by the sun. The moon rotates around the earth once a month (moonth?). The plane of rotation is almost the same as the plane of the earth's rotation around the sun (5 degrees difference). During the 2 weeks that the moon is inside of our solar orbit, between us and the sun, we see less than half of the side of the moon that is facing us, a crescent moon. When it's outside of our orbit, we see between half and all of the moon it's called a gibbous moon. A moon is said to be waxing during the period when the visible portion is increasing and waning when the visible portion is decreasing. They combine to produce the repeating "moon adjective" sequence: new , waxing crescent, first quarter, waxing gibbous, full, waning gibbous, last quarter, waning crescent, and finally back to new describing a full cycle of phases of the moon.
Looking down from the North star , the moon rotates around the earth counter-clockwise, the same direction that we rotate around the sun. This causes the illumination from the left during the waning phase, as it move around "in front" of us and from the right as it waxes on around to the full moon position.
Now we have enough information to produce our moon animation. We'll pass 2 parameters, moonphase, a number between 0 and 1 representing the visible fraction of the moon's face, and waxing, a Boolean flag indicating whether illumination is increasing or decreasing.
Imagine a circular disk suspended at eye level with the face vertical to the ground (I used a floor fan.) Now walk around it and notice the apparent disk assumes an elliptical shape, collapsing to a vertical line and expanding again to an ellipse and circle as you move around it. Expand the disk to a ball and uses a bright light for the disk and you'll see the same effect, except that the ball blocks your view of the back half of the disk, so you'll see half an ellipse moving across the face of the ball. TCanvas has an Arc procedure that will draw half ellipses and that's what we'll use.
ARC and Floodfill
Arc takes 8 parameters representing 4 points. Two opposite corners defining an ellipse and 2 points representing endpoints of 2 lines from the center of the ellipse. The arc is drawn counterclockwise around the perimeter of the ellipse from the point where the first line intersects it to the point of intersection with the second line. Our starting and ending points will always be the top and bottom center of the moon image. The tricky bit is determining which half of the ellipse to draw.
Floodfill will fill in the dark part of the moon. To use floodfill, we must specify a point inside of the area to be filled. Mode fsborder will then fill outward in all directions until a specified color (the black border for us) is reached. Near the edges can be a little troublesome. For example, the moon may be a pixel smaller than the width specified. So the point RightX-1, CenterY might be on the border, not inside. The code has some tests to avoid these anomalies.
Here's a table I used to help sort out which side to draw and which side to fill with black.
Notice that there are two cases, phase 0.0 and phase 1.0, where waxing/waning doesn't matter. In all other cases we'll darken the left side if waxing and right side if waning. We'll also have to draw the ellipse arc from bottom to top for the right side crescents by putting the bottom coordinate first in the call to Arc.
Also notice that the radius of the ellipse defining the arc varies from the radius of the moon, Rad, to 0 and back to Rad as phase moves from the 0 to 1. I experimentally determined the function as Delta=Rad*abs (Moonphase- 0.5), where abs is Delphi's absolute value function.
I added an image of the real moon just to see how it would work. You'll see a few pieces of code to handle it. In FormActivate, we create and load a bitmap if the image file exists. The image file was resized beforehand to closely match the TPaintBox size. The border image of the image is cleaned up at load time by setting to black all pixels that are more than Rad pixels from the center. I also discovered that TBitmap uses the Windows palette to realize colors. As a result black is displayed as the darkest color defined in the current palette. No real harm except that is the moon is set to true black with Floodfill, it becomes darker than the surrounding sky. I found 2 solutions. Setting the bitmap's PixelFormat property to pf24bit after the image is loaded will force a palette that recognizes true black. In the second fix, the one currently implemented, the paint routine uses the value of pixel[1,1] as black for border drawing and filling. Variable myBlack contains true black if image is not being displayed or the current palette representation if it is.
With that background, I think the code will make some sense. Have fun.
Copyright © 2000-2018, Gary Darby All rights reserved.