Click here to Skip to main content
Click here to Skip to main content

The Aqualizer Strikes Back!

, 15 Oct 2004
Rate this:
Please Sign up or sign in to vote.
The Aqualizer: a program to create aqua buttons.

Introduction

Ah. Aqua buttons. They're everywhere. Except on your website. Don't be frustrated anymore. You've just found the helping hand you need: The Aqualizer! The Aqualizer, or Aqualize, is a fantastic, excellent and downright amazing command line utility to create “aqua”-buttons like this one:

If you want a button like this, download The Aqualizer and type at the command prompt:

C:\>aqualize "The\nAqualizer" button.bmp

Or maybe you want a red button?

C:\>aqualize -c:FF0000 "The\nAqualizer" button.bmp

A black one?

C:\>aqualize -c:000000 -t:FFFFFF "The\nAqualizer" button.bmp

A small one?

C:\>aqualize -s:small -a:0 "Hi!" button.bmp

I found a GIMP tutorial on the net for a aqua button I liked, and thought it would be cool if people, especially programmers, with no knowledge of GIMP or Photoshop could put a nice aqua button on their website. I wrote all the code I needed to follow the steps in the tutorial. The Aqualizer is based on Shadow, my program to add drop shadows to images (see my site).

Composing The Image

Let's take a look at the code in Main(). We're going to create an image with several layers, so we create a new LayeredImage object and call it image:

    // create image
    LayeredImage image = new LayeredImage(width, height);

The first layer we add is the background layer. We fill the layer with the background color.

    // background
    Layer bkgnd = image.Layers.Add();
    bkgnd.Clear(bkcolor);

If the user wants a drop shadow, we create a new layer, select a circle, fill that circle with black, apply a blur filter, move the layer a bit to the right and to the bottom, and set the opacity of the layer so that the shadow is not too dark. The blur filter in Shadow uses a Gauss curve to calculate the contribution of the color of a pixel to the “current” pixel. The blur filter in The Aqualizer uses a Sigmoid curve. A Sigmoid curve is defined by three parameters: alpha, beta and gamma. Alpha is the start point, beta is the inflexion point, and gamma is the end point. The value at alpha is zero and the value at gamma is 1. The advantage of a Sigmoid curve is that you know that the value at alpha is 0; when using a Gauss curve, you cannot be 100% sure that the value will be 0. If you want to know how a blur filter works, see the article on my site.

    if (dropshadow) {
        // shadow
        Layer shadow = image.Layers.Add();
        image.ActiveLayer = shadow;
        image.BackColor = Color.Black;
        image.Selection.SelectEllipse(
            margin,
            margin,
            buttonwidth,
            buttonwidth,
            SelectionMode.Replace);
        image.Fill();
        Blur.ApplyTo(shadow.Bitmap, blurx, blury);
        shadow.OffsetX = shadowoffsetx;
        shadow.OffsetY = shadowoffsety;
        shadow.Opacity = shadowopacity;
    }

Now, we're ready to paint the button. We begin by adding a layer for the border. We select a circle the size of the button and fill it with a radial gradient. Implementing the radial gradient code was easy: I just needed to calculate the distance from the current pixel to the center point, convert it to a value between 0 and 1, and mix the foreground and background color.

    // border
    Layer borderlayer = image.Layers.Add();
    image.Selection.SelectEllipse(
        margin,
        margin,
        buttonwidth,
        buttonwidth,
        SelectionMode.Replace);
    image.ActiveLayer = borderlayer;
    image.ForeColor = bordercolor;
    image.BackColor = Color.Black;
    image.Fill(
        borderfillx0,
        borderfilly0,
        borderfillx1,
        borderfilly1,
        FillType.Radial);

We fill the inner area of the button with the button color. When the method Fill() is called with no parameters, it will fill the current selection with the background color.

    // button color
    image.Selection.SelectEllipse(
        buttoncolorx,
        buttoncolory,
        buttoncolorwidth,
        buttoncolorwidth,
        SelectionMode.Replace);
    image.BackColor = buttoncolor;
    image.Fill();

We don't want our button to look flat. To achieve a nice effect, we will darken the top and left of the inner area a bit. To darken the left, we select a sickle shape by selecting the inner area and subtracting a circle from the current selection. The selection is feathered and filled with black. The method Feather() applies a blur filter to the selection mask. The same effect could be achieved by growing the selection and then applying a blur filter. To darken the top, we do the same: we select the inner area and subtract a circle about the same size, feather the selection, and fill it with black. We also add a mask to the shade layer because the feathering of the selection influences pixels outside the button, and we don't want that.

    // shade
    Layer shadelayer = image.Layers.Add();
    image.ActiveLayer = shadelayer;
    image.Selection.SelectEllipse(
        buttoncolorx - 2,
        buttoncolory - 2,
        buttoncolorwidth + 4,
        buttoncolorwidth + 4,
        SelectionMode.Replace);
    image.Selection.SelectEllipse(
        shadeleftx,
        shadelefty,
        shadeleftwidth,
        shadeleftheight,
        SelectionMode.Subtract);
    image.Selection.Feather(featherleft);
    image.BackColor = Color.Black;
    image.Fill();
    shadelayer.Opacity = shadeopacity;
    image.Selection.SelectEllipse(
        buttoncolorx - 2,
        buttoncolory - 2,
        buttoncolorwidth + 4,
        buttoncolorwidth + 4,
        SelectionMode.Replace);
    image.Selection.SelectEllipse(
        shadetopx,
        shadetopy,
        shadetopwidth,
        shadetopheight,
        SelectionMode.Subtract);
    image.Selection.Feather(feathertop);
    image.Fill();

    // shade mask -- set smoothing mode !!!
    FastBitmap mask = new FastBitmap(width, height, 
                            PixelFormat.Format24bppRgb);
    Graphics g = Graphics.FromImage(mask._bitmap);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    SolidBrush blackbrush = new SolidBrush(Color.Black);
    SolidBrush whitebrush = new SolidBrush(Color.White);
    g.FillRectangle(
        blackbrush,
        0,
        0,
        width,
        height);
    g.FillEllipse(
        whitebrush,
        buttoncolorx,
        buttoncolory,
        buttoncolorwidth,
        buttoncolorwidth); 
    whitebrush.Dispose();
    blackbrush.Dispose();
    g.Dispose();
    shadelayer.Mask = mask;

Before we can put the text on the button, we have to create a Font object and preprocess the text a bit. If we encounter a '\n' in the text, we replace it by a Environment.NewLine string. If we encounter a double backslash ("\\"), we replace it with a single backslash.

    // text
    Layer textlayer = image.Layers.Add();
    FontStyle fs = FontStyle.Regular;
    for (Int32 i = 0; i < fontstyle.Length; i++) {
        switch (fontstyle[i]) {
            case 'b':
                fs |= FontStyle.Bold;
                break;
            case 'i':
                fs |= FontStyle.Italic;
                break;
            case 'r':
                fs |= FontStyle.Regular;
                break;
            case 's':
                fs |= FontStyle.Strikeout;
                break;
            case 'u':
                fs |= FontStyle.Underline;
                break;
        }
    }
    Font f = new Font(fontface, fontsize, fs);
    StringFormat format = new StringFormat();
    format.Alignment = StringAlignment.Center;
    format.LineAlignment = StringAlignment.Center;
    format.Trimming = StringTrimming.None;
    SolidBrush brush = new SolidBrush(textcolor);
    StringBuilder sb = new StringBuilder(text.Length);
    for (Int32 i = 0; i < text.Length; i++) {
        if (text[i] == '\\') {
            if (i + 1 < text.Length) {
                if (text[i + 1] == 'n') {
                    sb.Append(Environment.NewLine);
                    i++;
                } else if (text[i + 1] == '\\') {
                    sb.Append('\\');
                    i++;
                } else {
                    sb.Append('\\');
                }
            } else {
                sb.Append('\\');
            }
        } else {
            sb.Append(text[i]);
        }
    }
    textlayer.DrawText(0, 0, width, height, 
              sb.ToString(), f, brush, format);
    brush.Dispose();
    f.Dispose();
    textlayer.Opacity = 1.0 - texttrans;

And now, ladies and gentlemen, the finishing touch: the highlighting. We select an ellipse and fill the selection with a linear gradient which changes gradually from white to transparent. After that, we flatten the image and we're done. Tadaa! A very nice aqua button!

    // highlight
    Layer highlightlayer = image.Layers.Add();
    image.ActiveLayer = highlightlayer;
    image.ForeColor = Color.White;
    image.BackColor = Color.Transparent;
    image.Selection.SelectEllipse(
        highlightx,
        highlighty,
        highlightwidth,
        highlightheight,
        SelectionMode.Replace);
    image.Fill(
        0,
        highlightfilly0,
        0,
        highlightfilly1,
        FillType.Linear);

    // result
    FastBitmap result = image.Flatten();

Remarks

Remark 1. Maybe you're wondering why you have to specify “small”, “medium” or “large” to set the button size, instead of a width and height. The reason is Pixel Picking. Icon designers will probably know what I mean. When you scale down a vector image, it's possible the image doesn't look too good anymore: you have to manually change the pixels.

Remark 2. .NET code is slow. When the image width was set to 400, it took about 30 seconds to generate the image! To optimize the blur filter, I replaced the calls to GetPixel() with GetPixelInt32(). GetPixelInt32() returns a color as a Int32 instead of a Color structure. Allocating objects inside loops is a bad idea, even in .NET. Floating-point calculations are also slow. But I didn't want to rewrite the code to save an operation here and there, I find the readability of the code more important.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Mike Finnegan

Belgium Belgium
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey26-Feb-12 21:23 
GeneralThanks PinmemberDaniel Miller29-Jul-08 8:11 
GeneralUsage Permission Pinmemberkaiyum25-Apr-08 19:27 
GeneralDotCompile Errors Pinmembertfryar20-Sep-05 12:54 
GeneralRe: DotCompile Errors PinsussAnonymous20-Sep-05 22:34 
QuestionMemory leak? PinmembersPhinX4-Sep-05 18:05 
AnswerRe: Memory leak? PinsussAnonymous4-Sep-05 21:05 
GeneralRe: Memory leak? PinmembersPhinX12-Sep-05 2:52 
Generalbump-mapping PinmemberJerry Evans10-Apr-05 6:27 
GeneralRe: bump-mapping Pinmemberq12345678910-Apr-05 21:48 
GeneralHere PinsussOlleBull18-Nov-04 21:41 
GeneralRe: Here PinmemberAshaman20-Jan-05 6:48 
GeneralRe: Here PinsussOlleBull22-Jan-05 0:04 
GeneralRe: Here Pinmemberj_stagner4-Oct-07 7:26 
GeneralNice - small suggestion (wish) PinsussOlle B11-Nov-04 1:55 
GeneralRe: Nice - small suggestion (wish) PinsussOlle B15-Nov-04 1:10 
GeneralRe: Nice - small suggestion (wish) PinmemberRhy Mednick18-Nov-04 14:39 
GeneralRe: Nice - small suggestion (wish) PinsussOlleBull18-Nov-04 21:43 
GeneralThanks bigtime PinmemberDoubin29-Oct-04 3:21 
Generalbutton shape Pinmemberevoseven27-Oct-04 14:51 
GeneralIt's not .NET that is slow.... PinmemberChristian Graus26-Oct-04 12:17 
GeneralRe: It's not .NET that is slow.... PinsussAnonymous27-Oct-04 21:25 
GeneralRe: It's not .NET that is slow.... Pinmemberq12345678927-Oct-04 21:28 
GeneralRe: It's not .NET that is slow.... PinmemberChristian Graus28-Oct-04 9:14 
GeneralRe: It's not .NET that is slow.... PinmemberJudah Himango28-Oct-04 11:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140814.1 | Last Updated 15 Oct 2004
Article Copyright 2004 by Mike Finnegan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid