About Me

My photo
I'm a colonist who has declared war on machines and intend to conquer them some day. You'll often find me deep in the trenches fighting off bugs and ugly defects in code. When I'm not tappity-tapping at my WMD (also, known as keyboard), you'll find me chatting with friends, reading comics or playing a PC game.

Sunday, November 15, 2009

LWUIT and WPF Gradients Mash-up #1

Shai Almog's blog is the one-stop-shop for all things LWUIT. I recently read his entry on GradientPainters and it floored me! I thought I could enhance it a bit by adding GradientStops to the LinearGradientPainter.

What is a GradientStop?
If you come from a WPF background, then you know exactly what a GradientStop is. The fillLinearGradient() routine in LWUIT allows us to smoothly transition between two colours. So, we could go from White to Black and end up with this.

However, if we want to smoothly transition between multiple colours then that's where GradientStops can help us out. Let's say we want to vertically transition from White to Red and then from Red to Black. We start with White at the top and transition to red halfway through. Then we go from red at the half position, all the way to black at the bottom. Your mind's eye should have a picture like the one below.

A GradientStop has an offset and colour. The offset is the percentage of the position at which the colour is the strongest. For example, in our multiple colour gradient example above, we would need three GradientStops.
  1. GradientStop with offset 0 and colour White (0xffffff in LWUIT terms).
  2. GradientStop with offset 50 and colour Red (0xff0000).
  3. GradientStop with offset 100 and colour Black (0x000000).
Where's the code?
First, the implementation of the GradientStop class.

public class GradientStop
{
   private int color;
   private byte offset;

   public GradientStop()
   {
       color = 0x000000;   //Default Black at offset 0.
       offset = 0;
   }

   public GradientStop(int colorVal, byte offsetVal)
   {
       color = colorVal;
       offset = offsetVal;
   }

   public int getColor()
   {
       return color;
   }

   public int getOffset()
   {
       return offset;
   }

   public void setColor(int value)
   {
       color = value;
   }

   public void setOffset(byte value)
   {
       offset = value;
   }
}

Now, comes the implementation of the enhanced LinearGradientPainter.

public class LinearGradientPainter implements Painter
{
   private boolean horizontal;
   private byte opacity;
   private Image cache;
   private Vector vGradientStops;

   public LinearGradientPainter(   Vector gradientStops,
                                   byte opacity, boolean horizontal)
   {
       vGradientStops = new Vector();

       if(gradientStops != null)
       {
           //Ensure the gradient stops are ordered by offset in the Vector.
           for(int i = 0; i < gradientStops.size(); ++i)
           {
               GradientStop newGradientStop =
                       (GradientStop) gradientStops.elementAt(i);
               int insertPos = 0;
               for(insertPos = 0; insertPos < vGradientStops.size(); ++insertPos)
               {
                   GradientStop existingGradientStop =
                           (GradientStop) vGradientStops.elementAt(insertPos);
                   if(newGradientStop.getOffset() < existingGradientStop.getOffset())
                   {
                       break;
                   }
               }

               vGradientStops.insertElementAt(newGradientStop, insertPos);
           }
       }

       this.horizontal = horizontal;
       this.opacity = opacity;
   }

   public void paint(Graphics g, Rectangle rect)
   {
       final Dimension d   = rect.getSize();
       final int x         = rect.getX();
       final int y         = rect.getY();
       final int height    = d.getHeight();
       final int width     = d.getWidth();

       //Horizontal painting of gradient.
       if (horizontal)
       {
           if (cache == null || width != cache.getWidth())
           {
               cache = Image.createImage(width, 1);               
               Graphics dc = cache.getGraphics();

               for(int i = 0; i < vGradientStops.size() - 1; ++i)
               {
                   GradientStop thisGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i));
                   GradientStop nextGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i + 1));

                   dc.fillLinearGradient(thisGradientStop.getColor(),
                          nextGradientStop.getColor() ,
                          (int)(width * (thisGradientStop.getOffset() / 100.0)),
                          0,
                          (int)(width * (nextGradientStop.getOffset() / 100.0)),
                          1, horizontal);
               }

               if(opacity < 255)
               {
                   cache = cache.modifyAlpha(opacity);
               }
           }
           for (int iter = 0; iter < height; ++iter)
           {
               g.drawImage(cache, x, y + iter);
           }           
       }
       //Vertical painting of gradient.
       else
       {
           if (cache == null || height != cache.getHeight())
           {
               cache = Image.createImage(1, height);               
               Graphics dc = cache.getGraphics();

               for(int i = 0; i < vGradientStops.size() - 1; ++i)
               {
                   GradientStop thisGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i));
                   GradientStop nextGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i + 1));

                   dc.fillLinearGradient(thisGradientStop.getColor(),
                           nextGradientStop.getColor(),
                           0,
                           (int)(height * (thisGradientStop.getOffset() / 100.0)),
                           1,
                           (int)(height * (nextGradientStop.getOffset() / 100.0)),
                           horizontal);
               }

               if(opacity < 255)
               {
                   cache = cache.modifyAlpha(opacity);
               }
           }
          
           for (int iter = 0; iter < width; ++iter)
           {
               g.drawImage(cache, x + iter, y);
           }           
       }
   }
}

How do I use it?
Well usage is pretty simple. Here's an example for our previous multi-gradient case.

Vector vGradientStops = new Vector();
vGradientStops.addElement(new GradientStop(0xffffff, (byte)0));     
vGradientStops.addElement(new GradientStop(0xff0000, (byte)50));     
vGradientStops.addElement(new GradientStop(0x000000, (byte)100));

myComponent.getStyle().setBgPainter(
             new LinearGradientPainter(vGradientStops, (byte)255, false));    
     

Where do I go from here?
Let your imagination run wild!
What about the RadialGradientPainter?
I haven't forgotten about that. I thought I'd cover just one GradientPainter in this post so as not to bog the reader down. A multi-colour RadialGradientPainter will be covered in a future post.

Have fun!

No comments: