Friday, July 20, 2012

More fun with pixels

This week I was in Basel at the Canoo office again and that means every evening I had some time to play around with fun JavaFX stuff. Since I've started looking at the WritableImage and PixelWriter stuff I could not stop so today I will again share some pixels and code with you.


BiLinear gradient:
Yes, again...but this time in JavaFX. So you might now that stuff already from former blogposts and for those of you that do not know what I'm talking about, here is a little image that explains it...




To create the image above I've used the following code...

public class BiLinearGradient {
  private final Color COLOR_00; // upper left corner
  private final Color COLOR_10; // upper right corner
  private final Color COLOR_01; // lower left corner
  private final Color COLOR_11; // lower right corner

  public BiLinearGradient(Color c00, Color c10, Color c01, Color c11) {
    COLOR_00 = c00;
    COLOR_10 = c10;
    COLOR_01 = c01;
    COLOR_11 = c11;
  }

  private Color interpolateColor(Color c1, Color c2, double frac) {
    double red   = c1.getRed() + (c2.getRed() - c1.getRed()) * frac;
    double green = c1.getGreen() + (c2.getGreen() - c1.getGreen()) * frac;
    double blue  = c1.getBlue() + (c2.getBlue() - c1.getBlue()) * frac;
    double alpha = c1.getOpacity() + (c2.getOpacity() - c1.getOpacity()) * frac;
    red   = red < 0 ? 0 : (red > 1 ? 1 : red);
    green = green < 0 ? 0 : (green > 1 ? 1 : green);
    blue  = blue < 0 ? 0 : (blue > 1 ? 1 : blue);
    alpha = alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha);
    return Color.color(red, green, blue, alpha);
  }


  private Color biLinearInterpolateColor(Color c00, Color c10, Color c01, 
                                         Color c11, double fracX, double fracY) {
    Color interpolatedColX1 = interpolateColor(c00, c10, fracX);
    Color interpolatedColX2 = interpolateColor(c01, c11, fracX);
    return interpolateColor(interpolatedColX1, interpolatedColX2, fracY);
  }


  public Image getImage(double w, double h) {
    int    width     = (int) w  <= 0 ? 100 : (int) w;
    int    height    = (int) h <= 0 ? 100 : (int) h;
    double fractionX = 0;
    double fractionY = 0;
    WritableImage raster      = new WritableImage(width, height);
    PixelWriter   pixelWriter = raster.getPixelWriter();
    double fractionStepX      = 1.0 / (w - 1);
    double fractionStepY      = 1.0 / (h - 1);


    for (int y = 0; y < h ; y++) {
      for (int x = 0 ; x < w ; x++) {
        pixelWriter.setColor(x, y, 
                             biLinearInterpolateColor(COLOR_00, COLOR_10, 
                                                      COLOR_01, COLOR_11, 
                                                      fractionX, fractionY));
        fractionX += fractionStepX;
        fractionX = fractionX > 1 ? 1 : fractionX;
      }
      fractionY += fractionStepY;
      fractionY = fractionY > 1 ? 1 : fractionY;
      fractionX = 0;
    }
    return raster;
  }
}


The important thing in the code above is the interpolateColor() method because this method is able to calculate a color between two given colors by a given fraction between 0.0 - 1.0.

Pixelate:
When I was scanning the web I've found a script written in PHP that pixelates a given image...I know...PHP...there are better implementations I'm sure but I found it and converted the code to JavaFX and now I could pixelate a given image too...oh look whose that...


Original and pixelated version (12 px blocksize)
In this example I do not use the PixelWriter but the PixelReader to get the color of each pixel of the image. To create the such an pixelated image you just need one method like the following...

private Group pixelate(Image image, int blockSize) {
  PixelReader pixelReader = image.getPixelReader();
  int         width       = (int) IMAGE.getWidth();
  int         height      = (int) IMAGE.getHeight();
  Group       newImage    = new Group();
  List<Color> colors      = new ArrayList<Color>();


  for (int y = 0; y < height; y += blockSize) {
    for (int x = 0; x < width; x += blockSize) {
      Color col = pixelReader.getColor(x, y);                
      int newRed   = 0;
      int newGreen = 0;
      int newBlue  = 0;
      colors.clear();

      for (int blockY = y; blockY < y + blockSize; ++blockY) {
        for (int blockX = x; blockX < x + blockSize; ++blockX) {
          if (blockX < 0 || blockX >= width) {
            colors.add(col);
            continue;
          }
          if (blockY < 0 || blockY >= height) {
            colors.add(col);
            continue;
          }
          colors.add(pixelReader.getColor(blockX, blockY));
        }
      }

      for(Color color : colors) {
        newRed   += (int) (color.getRed() * 255) & 0xFF;
        newGreen += (int) (color.getGreen() * 255) & 0xFF;
        newBlue  += (int) (color.getBlue() * 255) & 0xFF;
      }
        
      int noOfColors = colors.size();
      newRed   /= noOfColors;
      newGreen /= noOfColors;
      newBlue  /= noOfColors;


      Rectangle box  = new Rectangle(x + blockSize - 1, y + blockSize -1, 
                                     blockSize, blockSize);
      box.setFill(Color.rgb(newRed, newGreen, newBlue));
      newImage.getChildren().add(box);
    }
  }
  return newImage;
}


During my trip to Heidelberg yesterday I suddenly got the idea to use one of my led controls instead of rectangles to pixelate the image which gaves me the following image...




Like I always say...it's all about having fun... :)

Plasma:
Well the next one was really just for fun and is based on some quite old code I've found on the web (to be honest I was not searching for it but simply found it randomly). Again I've converted it to JavaFX and here you go, a plasma implementation...



To achieve that effect you need the following piece of code...

public class Plasma {
  private static final Random RND = new Random();
  private int gridMX, gridMY;
  private int x, y;
  private double maxC, cScale;
  private double rX2, rY2, gX2, gY2, bX2, bY2;
  private double rXA2, rYA2, gXA2, gYA2, bXA2, bYA2;
  private double rX1, rY1, gX1, gY1, bX1, bY1;
  private double rXA1, rYA1, gXA1, gYA1, bXA1, bYA1;
  private double[][] redLookup;
  private double[][] greenLookup;
  private double[][] blueLookup;


  private PixelWriter    pixelWriter;
  private WritableImage  plasmaImage;
  private AnimationTimer timer;


  public Plasma(double width, double height) {
    gridMX = (int) width;
    gridMY = (int) height;


    plasmaImage = new WritableImage(gridMX, gridMY);
    pixelWriter = plasmaImage.getPixelWriter();


    maxC = Math.sqrt(((gridMX - 1.0) * (gridMX - 1.0)) 
                           + ((gridMY - 1.0) * (gridMY - 1.0)));
    cScale = (maxC / 100.0);


    redLookup   = new double[gridMX + 1][gridMY + 1];
    greenLookup = new double[gridMX + 1][gridMY + 1];
    blueLookup  = new double[gridMX + 1][gridMY + 1];


    rX1 = RND.nextInt(gridMX);
    rY1 = RND.nextInt(gridMY);
    gX1 = RND.nextInt(gridMX);
    gY1 = RND.nextInt(gridMY);
    bX1 = RND.nextInt(gridMX);
    bY1 = RND.nextInt(gridMY);


    rX2 = RND.nextInt(gridMX);
    rY2 = RND.nextInt(gridMY);
    gX2 = RND.nextInt(gridMX);
    gY2 = RND.nextInt(gridMY);
    bX2 = RND.nextInt(gridMX);
    bY2 = RND.nextInt(gridMY);


    double xr = gridMX / 20.0;
    double yr = gridMY / 20.0;


    rXA1 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    rYA1 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);
    gXA1 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    gYA1 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);
    bXA1 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    bYA1 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);


    rXA2 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    rYA2 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);
    gXA2 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    gYA2 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);
    bXA2 = RND.nextInt((int) (xr * 10)) / xr - (xr / 2.0);
    bYA2 = RND.nextInt((int) (yr * 10)) / yr - (yr / 2.0);


    for (x = 0; x < gridMX; x++) {
      for (y = 0; y < gridMY; y++) {
        redLookup[x][y]   = (int)(((double) x / (double)gridMX) * 255.0);
        greenLookup[x][y] = (int)(((double) y / (double)gridMY) * 255.0);
        blueLookup[x][y]  = 127;
      }
    }


    timer = new AnimationTimer() {
      @Override public void handle(long now) {
        update();
      }
    };
  }


  public Image getImage() {
    return plasmaImage;
  }


  public void start() {
    timer.start();
  }


  public void stop() {
    timer.stop();
  }


  private double getShade(double a, double b) {
    return (1.0 - (Math.sqrt(a * a + b * b) / maxC)) * cScale;
  }


  private void update() {
    if (((rX1 + rXA1) >= gridMX) || ((rX1 + rXA1) < 0)) {
      rXA1 = -rXA1;
    }
    if (((rY1 + rYA1) >= gridMY) || ((rY1 + rYA1) < 0)) {
      rYA1 = -rYA1;
    }
    if (((gX1 + gXA1) >= gridMX) || ((gX1 + gXA1) < 0)) {
      gXA1 = -gXA1;
    }
    if (((gY1 + gYA1) >= gridMY) || ((gY1 + gYA1) < 0)) {
      gYA1 = -gYA1;
    }
    if (((bX1 + bXA1) >= gridMX) || ((bX1 + bXA1) < 0)) {
      bXA1 = -bXA1;
    }
    if (((bY1 + bYA1) >= gridMY) || ((bY1 + bYA1) < 0)) {
      bYA1 = -bYA1;
    }
    if (((rX2 + rXA2) >= gridMX) || ((rX2 + rXA2) < 0)) {
      rXA2 = -rXA2;
    }
    if (((rY2 + rYA2) >= gridMY) || ((rY2 + rYA2) < 0)) {
      rYA2 = -rYA2;
    }
    if (((gX2 + gXA2) >= gridMX) || ((gX2 + gXA2) < 0)) {
      gXA2 = -gXA2;
    }
    if (((gY2 + gYA2) >= gridMY) || ((gY2 + gYA2) < 0)) {
      gYA2 = -gYA2;
    }
    if (((bX2 + bXA2) >= gridMX) || ((bX2 + bXA2) < 0)) {
      bXA2 = -bXA2;
    }
    if (((bY2 + bYA2) >= gridMY) || ((bY2 + bYA2) < 0)) {
      bYA2 = -bYA2;
    }


    rX1 += rXA1;
    rY1 += rYA1;
    gX1 += gXA1;
    gY1 += gYA1;
    bX1 += bXA1;
    bY1 += bYA1;


    rX2 += rXA2;
    rY2 += rYA2;
    gX2 += gXA2;
    gY2 += gYA2;
    bX2 += bXA2;
    bY2 += bYA2;


    for (x = 0; x < gridMX; x++) {
      for (y = 0; y < gridMY; y++) {
        redLookup[x][y]   += getShade(x - rX1, y - rY1);
        greenLookup[x][y] += getShade(x - gX1, y - gY1);
        blueLookup[x][y]  += getShade(x - bX1, y - bY1);


        redLookup[x][y]   -= getShade(x - rX2, y - rY2);
        greenLookup[x][y] -= getShade(x - gX2, y - gY2);
        blueLookup[x][y]  -= getShade(x - bX2, y - bY2);


        redLookup[x][y]   = redLookup[x][y] > 255 ? redLookup[x][y] = 255 : 
                            (redLookup[x][y] < 0 ? 0 : redLookup[x][y]);
        greenLookup[x][y] = greenLookup[x][y] > 255 ? greenLookup[x][y] = 255 : 
                            (greenLookup[x][y] < 0 ? 0 : greenLookup[x][y]);
        blueLookup[x][y]  = blueLookup[x][y] > 255 ? blueLookup[x][y] = 255 : 
                            (blueLookup[x][y] < 0 ? 0 : blueLookup[x][y]);


        pixelWriter.setColor(x, y, Color.rgb((int) redLookup[x][y], (int
                             greenLookup[x][y], (int) blueLookup[x][y]));
      }
    }
  }
}

That's it for another week of JavaFX fun...keep coding...

2 comments:

  1. Hi Gerrit,

    Which LED control of yours did you use, instead of Rectangle, for the second pixelated example?

    Thanks

    ReplyDelete
    Replies
    1. Hi Roberto,
      You could find additional JavaFX controls in the JFXtras project at jfxtras.org. The code is hosted on github in the jfxtras-labs project. The Led control is part of this project. So feel free to take a look.
      Cheers,
      Gerrit

      Delete