Introduction
GPaint is a program to create Google-like logos. It is a command line program which takes as input a font name, a font size, the text, a "color cycling scheme", and a file name. The font of the Google logo is Catull, but Catull is a commercial font, so chances are you don't have it on your computer. Don't worry though, Book Antiqua looks a lot like Catull and, don't forget, you can use any font you like. When GPaint is invoked like this:
C:\>gpaint "book antiqua" 80 "Google" "2159d6;e73c21;efba00;2159d6;31b639" logo
You will get this .bmp file as a result (converted to jpeg):

OK, I admit, it is not exactly like the Google logo, but it's nice, isn't it?
GPaint will change the color for each character in the string. For example, suppose the color cycling scheme is "ff0000,0000ff", then GPaint will paint the first character in red, the second in blue, the third again in red etc.
GPaint Internals
GPaint uses the following classes: FastBitmap
, Layer
, Layers
, LayeredImage
and GPaint
.
FastBitmap
is a class which contains a Bitmap
. It is called FastBitmap
because it provides methods to access the bits of the Bitmap
member without using the very slow GetPixel()
and SetPixel()
methods of the Bitmap
class. The code has to be compiled with the /unsafe option because the GetPixel()
and SetPixel()
methods of FastBitmap
use pointers.
LayeredImage
represents an image which consists of 1 or more layers. Programs like Photoshop and GIMP use a stack of layers to create attractive images. GPaint contains a small "Image Composition Engine" like the ones found in those programs - a very small one.
Layers
represents the layer stack. It has methods to add and copy a layer.
Layer
represents, well, a layer.
Let's see how GPaint uses these classes to generate a logo. Here's some code which you can find in the Main()
method of the GPaint
class:
...
LayeredImage image = new LayeredImage(totalwidth + 4,
tm.tmHeight + 4 + 4);
First, we create a new LayeredImage
. Totalwidth
is the width of the string and tm.tmHeight
the height of the string in the given font. The width and height is increased by 4 because of the drop shadow. We add another 4 pixels to the height to make sure the drop shadow is completely visible, but this value is arbitrary. Now that we have an image, we can continue to add some layers.
Layer bg = image.Layers.Add();
bg.Clear(Color.Black);
bg.DrawText(0, 0, s, font, new SolidBrush(Color.White));
The layer bg
contains the text in white on a black background. We will use this layer as a mask.
Layer copybg = image.Layers.Copy(bg);
copybg.Blur(4, 4);
copybg
is a copy of the bg
layer. We blur it 4 pixels horizontally and 4 vertically. This layer will be used as a bump map, and an inverted copy of this layer will serve as the drop shadow.
Layer white = image.Layers.Add();
white.Clear(Color.White);
The layer white
is simply the background.
Layer shadow = image.Layers.Copy(copybg);
shadow.Invert();
shadow.Opacity = 0.5;
shadow.OffsetX += 4;
shadow.OffsetY += 4;
Layer shadow
contains the drop shadow. We copy the bump map, invert its colors, set the opacity to 0.5 to make it less dark, and move it a bit.
Int32 offsetx = 0;
Int32 colorindex = 0;
Layer final = image.Layers.Add();
for (Int32 i = 0; i < s.Length; i++) {
Color c = colors[colorindex];
colorindex++;
if (colorindex >= colors.Length)
colorindex = 0;
SolidBrush brush = new SolidBrush(c);
final.FillRectangle(offsetx, 0, widths[i], tm.tmHeight, brush);
offsetx += widths[i];
}
final
is the layer we will bump map. colors
is the color cycling scheme and widths
contains the width of each character in the string. A rectangle is painted "above" each character in the right color.
final.BumpMap(copybg, 135, 45, 3, false);
Now that we have a "flag-like" bitmap, we can bump map it with azimuth 135, elevation 45 and depth 3.
final.Mask = (FastBitmap)bg.Bitmap.Clone();
As said in the beginning, bg
will be used as a mask. As a result, only the bump mapped text will be visible on this layer and the white background and drop shadow will show through.
FastBitmap result = image.Flatten();
result.Save(args[4] + ".bmp", ImageFormat.Bmp);
And finally, ladies and gentlemen, we flatten the image and write it to disk.
Some Thoughts
- Maybe a better name would be GString?
- I should organize the code a bit better. Putting filters in the layer class is not a good idea, but it's not my intention to write a complete image manipulation library.
- Because I
DllImport()
GetCharWidth()
to get the width of a character, I don't think GPaint runs on Mono.
- If someone can try this program with font Catull, please tell me where I can see a picture.
Exercise
Write a program which adds drop shadows to pictures automatically.