Title: Make a cake clock in C#
Daylight Saving Time begins tomorrow morning, so I wrote this program to draw a cake clock that uses slices of cakes to mark the 12 hours.
If you select the File meno's Open command, the following code executes.
Bitmap[] Hours = null;
private void mnuFileOpen_Click(object sender, EventArgs e)
{
if (ofdCake.ShowDialog() == DialogResult.OK)
{
Hours = new Bitmap[12];
int i = 0;
foreach (string filename in ofdCake.FileNames)
{
if (i >= 12) break;
Hours[i++] = LoadBitmapUnlocked(filename);
}
DrawClock();
}
}
This code displays a file selection dialog so you can select the cake slice images. If you click Open, the program makes a new array holding 12 bitmaps and loads the files into the array. If you select more than 12 files, it only loads the first 12. Otherwise it does no error handling, so you might want to fix that if you need to give the program to a civilian (i.e. non-programmer). You migt also want to allow the user to remove some pictures and add others without resetting the whole thing, but this works and I only had an hour to write it.
This code uses the LoadBitmapUnlocked function described in the post Load images without locking their files in C# to open the image files.
After it loads the files, the code calls the following DrawClock method to draw the clock.
private void DrawClock()
{
const int wid = 1200;
const int hgt = 900;
int min_wid_hgt = Math.Min(wid, hgt);
float cx = wid / 2f;
float cy = hgt / 2f;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
using (LinearGradientBrush brush = new LinearGradientBrush(
new Point(0, 0),
new Point(0, hgt),
Color.White, Color.LightGreen))
{
ColorBlend blend = new ColorBlend(3);
blend.Colors = new Color[]
{
Color.White,
Color.LightGray,
Color.LightGreen,
};
blend.Positions = new float[]{ 0.0f, 0.66f, 1.0f };
brush.InterpolationColors = blend;
//gr.Clear(Color.White);
gr.FillRectangle(brush, 0, 0, wid, hgt);
float slice_wid = min_wid_hgt / 3.5f;
float radius = (min_wid_hgt - 20) / 2f - slice_wid / 2f;
for (int i = 0; i < 12; i++)
{
if (Hours[i] == null) break;
float degrees = i * 360f / 12f;
gr.RotateTransform(degrees + 180);
float scale = slice_wid / Hours[i].Width;
gr.ScaleTransform(scale, scale, MatrixOrder.Append);
double radians = degrees * Math.PI / 180;
float x = (float)(cx + radius * Math.Cos(radians));
float y = (float)(cy + radius * Math.Sin(radians));
gr.TranslateTransform(x, y, MatrixOrder.Append);
RectangleF rect = new RectangleF(
-Hours[i].Width / 2,
-Hours[i].Height / 2,
Hours[i].Width,
Hours[i].Height);
gr.DrawImage(Hours[i], rect);
gr.ResetTransform();
}
// Draw the clock hands.
float pen_thickness = min_wid_hgt / 50f;
using (Pen pen = new Pen(Color.Blue, pen_thickness))
{
pen.EndCap = LineCap.Round;
pen.StartCap = LineCap.Round;
using (Pen outline_pen = new Pen(brush, pen_thickness * 1.1f))
{
outline_pen.EndCap = LineCap.Round;
outline_pen.StartCap = LineCap.Round;
float long_len = min_wid_hgt * 0.4f;
double long_angle = 2 * Math.PI * 11.0 / 12.0;
float x1 = (float)(cx + long_len * Math.Cos(long_angle));
float y1 = (float)(cy + long_len * Math.Sin(long_angle));
gr.DrawLine(outline_pen, cx, cy, x1, y1);
gr.DrawLine(pen, cx, cy, x1, y1);
float short_len = min_wid_hgt * 0.3f;
double short_angle = 2 * Math.PI * 7.0 / 12.0;
float x2 = (float)(cx + short_len * Math.Cos(short_angle));
float y2 = (float)(cy + short_len * Math.Sin(short_angle));
gr.DrawLine(outline_pen, cx, cy, x2, y2);
gr.DrawLine(pen, cx, cy, x2, y2);
RectangleF rect = new RectangleF(
cx - pen_thickness,
cy - pen_thickness,
2 * pen_thickness,
2 * pen_thickness);
gr.FillEllipse(Brushes.DarkBlue, rect);
}
}
}
}
picClock.Image = bm;
}
This function creates constants to define the size of the clock image and computes some basic geometry. It then creates a bitmap with the desired size and makes a Graphics object associated with the bitmap.
Next, it defines a LinearGradientBrush that shaes top-to-bottom form white to light gray to light green. It sets blend positions so the light gray is 66% of the way through the gradient. After it has initialized the brush, the program uses it to fill the bitmap.
The code then enters a loop to draw the slices. The program will draw each slice centered at the origin. It uses geometric tansformations to scale, rotate, and translate the slice into position.
First, it calls the Graphics object's RotateTransform method to rotate the slice appropriately. (Note that this method requires the rotation angle in degrees.)
Next, the code adds a scale transformation to give the slice image the desired width. Notice the last parameter to ScaleTransform, which appends the scale operation so it comes after the rotation. For some unknowable reason, Microsoft made the default put any new transformations before any old ones so you get some weird results if you forget to add Append.
The progam then uses some simple math to figure out where this slice should be drawn. (Note that the Math library's Sin and Cos functions require their arguments to be in radians.) It then adds a translate transformation to put the slice there.
Finally, the program draws the slice centered at the origin at its normal size. The transformations rotate, scale, and translate the image into position.
The code resets the Graphics object's transformations before starting the next trip through the for loop.
Having drawn the slices, the program then draws the clock's hands. It starts by creating a thick blue pen and gives it round start and end caps.
The code creates another pen that is slightly thicker than the first, also with round start and end caps. This pen's color is defined by the same brush used to clear the bitmap's backrgound, so it will not show if it is drawn over parts of the image that are not covered by a cake slice.
Now the program uses some more calculations to figure out where the hands should be drawn. It draws the hands, first with the outline pen and then with the blue pen.
The code finishes by drawing a dark blue dot in the center.
The program's final piece of interesting code executes when you select the File menu's Save As command.
private void mnuFileSaveAs_Click(object sender, EventArgs e)
{
if (sfdCake.ShowDialog() == DialogResult.OK)
{
SaveImage(picClock.Image, sfdCake.FileName);
}
}
This code displays a save file dialog. If you select an output file and click Save, the code calls the SaveImage method to save the image into a file with an appropriate file type. For example, if the file you select has a .png extension, then the SaveImage saves the file with the PNG format. See the post Save images with an appropriate format depending on the file name's extension in C# for information about how that method works.
Download the example to experiment with it and to see additional details.
|