Tuesday, April 19, 2011

BiLinearGradient in HTML5 canvas...

This is just a short one for all HTML5 canvas lovers...
Because it was not a big deal and might be usefull for one or the other i created also a javascript version of the bilinear gradient paint that one could use in HTML5 canvas. To make it possible i added one class that represents a color in the way that one could get the rgba values from a given color and a class that represents the bilinear gradient paint itself.
The result looks like this...


click here to see it live...
To see it live you just have to click the link below the image. Here is a small example on how to use the BiLinearGradient in JavaScript:


function init()
{


 

}

You have to place the above script in your html page <head> tag. If you don't know how to do that just take a look at the page sourcecode of the linked page below the image.


Here are the two files you need to create the bilinear color interpolation.


    RGBAColor.js


    BiLinearGradient.js


Enjoy coding...



Sunday, April 17, 2011

Bilinear color interpolation

Last week when i was surfing around the web i suddenly found this image...




Even if i didn't need this kind of gradient i asked myself how to achieve this in Java Swing? Well to be honest this is really easy (if you know how to do it) and so i thought double your fun...share your knowledge with others...so here we go...
In principle there's only one little method which is responsible for the whole magic here. This method is able to fade from one color to another by a given fraction in the range from 0...1.


So start with a simple linear gradient that looks like this:




Because we will work with floating point operations it makes sense to use also floating points to create your color objects.
If we now for example would like to get the color which is located at a fraction = 0.5f we simply have to build the difference between each color's red, green, blue and alpha values, multiply them with the fraction of 0.5f and add the result to the values of the start color...sounds to complicated ? Maybe some code makes it easier to understand...
private java.awt.Color interpolateColor(final Color COLOR1, final Color COLOR2, float fraction)
        {            
            final float INT_TO_FLOAT_CONST = 1f / 255f;
            fraction = Math.min(fraction, 1f);
            fraction = Math.max(fraction, 0f);
            
            final float RED1 = COLOR1.getRed() * INT_TO_FLOAT_CONST;
            final float GREEN1 = COLOR1.getGreen() * INT_TO_FLOAT_CONST;
            final float BLUE1 = COLOR1.getBlue() * INT_TO_FLOAT_CONST;
            final float ALPHA1 = COLOR1.getAlpha() * INT_TO_FLOAT_CONST;

            final float RED2 = COLOR2.getRed() * INT_TO_FLOAT_CONST;
            final float GREEN2 = COLOR2.getGreen() * INT_TO_FLOAT_CONST;
            final float BLUE2 = COLOR2.getBlue() * INT_TO_FLOAT_CONST;
            final float ALPHA2 = COLOR2.getAlpha() * INT_TO_FLOAT_CONST;

            final float DELTA_RED = RED2 - RED1;
            final float DELTA_GREEN = GREEN2 - GREEN1;
            final float DELTA_BLUE = BLUE2 - BLUE1;
            final float DELTA_ALPHA = ALPHA2 - ALPHA1;

            float red = RED1 + (DELTA_RED * fraction);
            float green = GREEN1 + (DELTA_GREEN * fraction);
            float blue = BLUE1 + (DELTA_BLUE * fraction);
            float alpha = ALPHA1 + (DELTA_ALPHA * fraction);

            red = Math.min(red, 1f);
            red = Math.max(red, 0f);
            green = Math.min(green, 1f);
            green = Math.max(green, 0f);
            blue = Math.min(blue, 1f);
            blue = Math.max(blue, 0f);
            alpha = Math.min(alpha, 1f);
            alpha = Math.max(alpha, 0f);

            return new Color(red, green, blue, alpha);        
        }


In the method above i first split each color into it's red, green, blue and alpha values, than i build the difference between the stop- and the startcolor, multiply it with the fraction and create a new color from the resulting red, green, blue and alpha values.


With this method which represents a linear interpolation between two colors it's easy to create a bilinear interpolation too (because it means we simply have a combination of two linear interpolations).


For example the bilinear gradient in the first image looks like this...




As you can see we have defined four colors (one for each corner) and we have also fractions in both directions (x and y). Now you need a method that is able to calculate the color between the four colors given by two fractions (one for the x-axis and one for the y-axis). Let's assume we would like to get the color at fraction_x = 0.25f and fraction_y = 0.75f.






To be able to calculate the color at the intersection of the two lines we first have to calculate the color between the upper left corner and the upper right corner at the fraction of 0.25f by using the linear interpolation method and save it for example as upperColorX.
In the second step we calculate the color between the lower left corner and the lower right corner at the fraction of 0.25f and save it as lowerColorX.
Now we have calculated the start- and stopcolor for the vertical linear gradient between the points P1, P2.




In the third and last step we only have to calculate the color between P1 and P2 at the given fraction_y = 0.75f by using the linear interpolation one more time.




Now you know how to get the color of a point that is defined by two fractions between 0...1 which is placed in a square of four colors.


I have put the bilinear interpolation in another method which looks like this...
private Color bilinearInterpolateColor(final Color COLOR_00, Color COLOR_10, final Color COLOR_01, final Color COLOR_11, final float FRACTION_X, final float FRACTION_Y)
        {
            final Color COLOR_X1 = interpolateColor(COLOR_00, COLOR_10, FRACTION_X);
            final Color COLOR_X2 = interpolateColor(COLOR_01, COLOR_11, FRACTION_X);
            return interpolateColor(COLOR_X1, COLOR_X2, FRACTION_Y);
        }


Because i don't want to add these methods all the time i created a BiLinearGradientPaint.class that takes a java.awt.Shape, the colors of the four corners and the fractions in x- and y-direction.
To create the example gradient that i used above you could use the following code:


import eu.hansolo.gradients.BiLinearGradientPaint;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;

public class BilinearPanel extends javax.swing.JPanel
{
    @Override
    protected void paintComponent(java.awt.Graphics g)
    {
        super.paintComponent(g);
        
        final java.awt.Graphics2D G2 = (Graphics2D) g.create();
        G2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        final Color UPPER_LEFT = new Color(1.0f, 0.0f, 0.0f, 1.0f);
        final Color UPPER_RIGHT = new Color(1.0f, 1.0f, 0.0f, 1.0f);
        final Color LOWER_LEFT = new Color(0.0f, 0.0f, 1.0f, 1.0f);
        final Color LOWER_RIGHT = new Color(0.0f, 1.0f, 1.0f, 1.0f);
        
        final Rectangle RECT = new Rectangle(0, 0, 400, 400);
        
        eu.hansolo.gradients.BiLinearGradientPaint BILINEAR_GRADIENT = new eu.hansolo.gradients.BiLinearGradientPaint(RECT, UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT);
        
        G2.setPaint(BILINEAR_GRADIENT);
        G2.fill(RECT);
        
        G2.dispose();
    }
}


And here is the result one more time...






With bilinear gradients one could create interesting things like the following...






To create things like the image above you simply have to combine some rectangles which are filled with bilinear gradients, where the stop color of one rectangle is the start color of the next rectangle and so on.


I hope you enjoy this things like i do and if you would like to create some nice gradients too, you might want to use one of my gradients. I put the ConicalGradientPaint, the ContourGradientPaint and the BiLinearGradientPaint in an extra library that you could download here:


    binary distribution: Gradients.jar


    source (netbeans): Gradients.zip


So enjoy coding...

Friday, April 1, 2011

SteelSeries JavaScript edition

Here we go again and it's again time for some canvas...
Those of you who followed this blog know that i started to port some of the components of the steelseries java library to html5 canvas. Because i'm not a javascript coder i had no idea how to build a library from these components and so i started playing around, trying this and that...
Finally i found a way that looks ok to me (but i might be wrong) and today i could present you the first version of the javascript version of the steelseries library (it only contains the Radial and RadialBargraph right now).

I tried to implement most of the features of the java version but right now there are still some feature missing.
Here is a list of the features that are supported by the javascript version:

GaugeType:

  • TYPE1
  • TYPE2
  • TYPE3
  • TYPE4


PointerType:

  • TYPE1
  • TYPE2
  • TYPE3
  • TYPE4
  • TYPE5
  • TYPE6
  • TYPE7
  • TYPE8

FrameDesign:

  • BLACK_METAL
  • METAL
  • SHINY_METAL
  • BRASS
  • STEEL
  • CHROME
  • GOLD


BackgroundColor:

  • DARK_GRAY
  • SATIN_GRAY
  • LIGHT_GRAY        
  • WHITE
  • BLACK
  • BEIGE
  • BROWN
  • RED
  • GREEN
  • BLUE


LcdColor:
  • BEIGE
  • BLUE
  • ORANGE
  • RED
  • YELLOW
  • WHITE
  • GRAY
  • BLACK
  • GREEN
  • BLUE2
  • BLUE_BLACK
  • BLUE_DARKBLUE
  • BLUE_GRAY
  • STANDARD
  • BLUE_BLUE
  • REDDARKRED


LedColor:
  • RED_LED
  • GREEN_LED
  • BLUE_LED
  • ORANGE_LED
  • YELLOW_LED
  • CYAN_LED
  • MAGENTA_LED


Color:
  • RED
  • GREEN
  • BLUE
  • ORANGE
  • YELLOW
  • CYAN        
  • MAGENTA
  • WHITE
  • GRAY
  • BLACK
  • RAITH
  • GREEN_LCD
  • JUG_GREEN


The Radial component also supports sections and areas. So there's no track right now (but it will come).

PREPARATION:
To make use of the library you have to add two files to your html page:

  • tween.js
  • steelseries.js

So a typical html5 page header will look like this...

e.g.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Test</title>
    <script type='text/javascript' src='js/tween.js'></script>
    <script type='text/javascript' src='js/steelseries.js'></script>
...

USAGE:
To use the library you need to add a <canvas> tag for each gauge to the <body> of the page.

e.g.
...
<body onload='init()'>
    <canvas id='canvas1' width='200' height='200'>
        No canvas in your browser...sorry...
    </canvas>
    <canvas id='canvas2' width='200' height='200'></canvas>
<canvas id='canvas3' width='200' height='200'></canvas>
</body>
...
Now we only have to add the init() method in the <head> of the page, where we create the canvas components. !!! OLD VERSION !!!
function init()
{
  // Define some sections
  var sections = Array(steelseries.Section(0, 25, 'rgba(0, 0, 220, 0.3)'),
                       steelseries.Section(25, 50, 'rgba(0, 220, 0, 0.3)'), 
                       steelseries.Section(50, 75, 'rgba(220, 220, 0, 0.3)'));
  
  // Define one area
  var areas = Array(steelseries.Section(75, 100, 'rgba(220, 0, 0, 0.3)'));
            
  // Create one radial gauge
  var radial1 = new steelseries.Radial(
                    'canvas1',                              // the canvas element
                    steelseries.GaugeType.TYPE4,            // type of gauge
                    200,                                    // size of gauge
                    0,                                      // minimum value
                    100,                                    // maximum value
                    50,                                     // threshold
                    sections,                               // sections
                    areas,                                  // areas
                    'Test',                                 // title string
                    'Unit',                                 // unit string
                    steelseries.FrameDesign.METAL,          // frame design
                    steelseries.BackgroundColor.DARK_GRAY,  // background color
                    steelseries.PointerType.TYPE8,          // pointer type
                    steelseries.ColorDef.RED,               // pointer color
                    steelseries.LcdColor.STANDARD,          // lcd color
                    true,                                   // lcd visible
                    steelseries.LedColor.RED_LED,           // led color
                    true,                                   // led visible
                    false);                                 // play audio
  
  // Create a second radial gauge
  var radial2 = new steelseries.Radial(
                    'canvas2', 
                    steelseries.GaugeType.TYPE2, 
                    200, 
                    0, 
                    50, 
                    40, 
                    Array(steelseries.Section(0, 40, 'rgba(0, 255, 0, 0.3)')),
                    Array(steelseries.Section(40, 50, 'rgba(255, 0, 0, 0.5)')), 
                    'Test', 
                    'Unit', 
                    steelseries.FrameDesign.CHROME, 
                    steelseries.BackgroundColor.LIGHT_GRAY, 
                    steelseries.PointerType.TYPE2, 
                    steelseries.ColorDef.BLUE, 
                    steelseries.LcdColor.BLUE2, 
                    true, 
                    steelseries.LedColor.BLUE_LED, 
                    true, 
                    false);

  // Create a radial bargraph gauge
  var radial3 = new steelseries.RadialBargraph(
                    'canvas3',                               // the canvas element
                    steelseries.GaugeType.TYPE3,             // type of gauge
                    200,                                     // size of gauge
                    0,                                       // minimum value
                    100,                                     // maximum value
                    50,                                      // threshold
                    null,                                    // sections (not supported)
                    "Title",                                 // title string
                    "Unit",                                  // unit string
                    steelseries.FrameDesign.BLACK_METAL,     // frame design
                    steelseries.BackgroundColor.LIGHT_GRAY,  // background color 
                    steelseries.ColorDef.YELLOW,             // color of the bargraph led's
                    steelseries.LcdColor.YELLOW,             // lcd color
                    true,                                    // lcd visible
                    steelseries.LedColor.YELLOW_LED,         // led color
                    true);                                   // led visible

  // Let's set some values...
  radial1.setValueAnimated(70);
  radial2.setValueAnimated(41);
  radial3.setValueAnimated(70);
}

As you can see the drawback of the current design are the long constructors. I'm still not sure how to make these nicer but i'm working on that...


UPDATE:
Thanx to Andres Almiray who pointed me in the right direction i modified the constructor of the Radial and RadialBargraph component in the way that it now takes a hash as a set of parameters. This means you don't have to put in all the default parameters anymore which makes the initialization of the components look like this...
!!! NEW VERSION !!!


function init()
{
  // Define some sections
  var sections = Array(steelseries.Section(0, 25, 'rgba(0, 0, 220, 0.3)'),
                       steelseries.Section(25, 50, 'rgba(0, 220, 0, 0.3)'), 
                       steelseries.Section(50, 75, 'rgba(220, 220, 0, 0.3)'));
  
  // Define one area
  var areas = Array(steelseries.Section(75, 100, 'rgba(220, 0, 0, 0.3)'));
            
  // Create one radial gauge
  var radial1 = new steelseries.Radial(
                    'canvas1', {
                    section: sections,                      
                    area: areas,                            
                    titleString: 'Test',                    
                    unitString: 'Unit',                     
                    pointerType: steelseries.PointerType.TYPE8
                    });                               

// Create a second radial gauge
  var radial2 = new steelseries.Radial(
                    'canvas2', {
                    gaugeType: steelseries.GaugeType.TYPE2,                     
                    maxValue: 50, 
                    threshold: 40, 
                    section: Array(steelseries.Section(0,40,'rgba(0,255,0,0.3)')),
                    area: Array(steelseries.Section(40,50,'rgba(255,0,0,0.5)')), 
                    titleString: 'Test', 
                    unitString: 'Unit', 
                    frameDesign: steelseries.FrameDesign.CHROME, 
                    backgroundColor: steelseries.BackgroundColor.LIGHT_GRAY, 
                    pointerType: steelseries.PointerType.TYPE2, 
                    pointerColor: steelseries.ColorDef.BLUE, 
                    lcdColor: steelseries.LcdColor.BLUE2,                     
                    ledColor: steelseries.LedColor.BLUE_LED, 
                    });

  // Create a radial bargraph gauge
  var radial3 = new steelseries.RadialBargraph(
                    'canvas3', {
                    gaugeType: steelseries.GaugeType.TYPE3,
                    titleString: "Title",                                
                    unitString: "Unit",          
                    frameDesign: steelseries.FrameDesign.BLACK_METAL,
                    backgroundColor: steelseries.BackgroundColor.LIGHT_GRAY,
                    valueColor: steelseries.ColorDef.YELLOW,
                    lcdColor: steelseries.LcdColor.YELLOW,
                    ledColor: steelseries.LedColor.YELLOW_LED,
                    });                                   

  // Let's set some values...
  radial1.setValueAnimated(70);
  radial2.setValueAnimated(41);
  radial3.setValueAnimated(70);
}


Guess that looks much better than before... :-)




If you put all these things together in one html page you should see something like this...




If you are to lazy to play around you might want to click here to see it in action...


There are two versions of the library available right now, the uncompressed (101 kb) and compressed (61 kb) version. You could download the version clicking on one of the following links:


    uncompressed version:    steelseries.js


    compressed version:       steelseries-min.js


    animation library:           tween.js


Keep in mind that this project is work in progress and things might change in the future...


Enjoy the upcoming weekend and keep coding...