Friday, July 20, 2012

Smoothing a color chart in Java

The goal here is to smooth a colored drawing, to avoid the tile effect.
Before
After
Although they look similar, the path for the two images above is pretty different.
The regular path to draw on a javax.swing.JPanel is to override the method
  public void paintComponent(Graphics gr)
We have defined an array of colors, for the example
  private final static int[][] COLOR_VALUES =
  {
   {255, 255, 255 },
   {208, 244, 250 },
   {161, 233, 246 },
   {115, 222, 241 },
   {68, 211, 237 },
   {21, 200, 232 },
   {21, 207, 223 },
   {20, 214, 214 },
  ...
   {59, 2, 64 },
   {30, 1, 32 },
   {0, 0, 0 }
  };

The tile panel

The method used to the tile (left) approach would look like this:
  private static JPanel colorPane = new JPanel()
    {
      public void paintComponent(Graphics gr)
      {
        ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                          RenderingHints.VALUE_TEXT_ANTIALIAS_ON);      
        ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                          RenderingHints.VALUE_ANTIALIAS_ON);      
        int w = this.getWidth();
        int h = this.getHeight();
        
        paintColors(gr, w, h);

        gr.setColor(Color.black);
        gr.setFont(gr.getFont().deriveFont(40f));
        
        String str = "Some text on top";
        int strWidth  = gr.getFontMetrics(gr.getFont()).stringWidth(str);        
        gr.drawString(str, (w / 2) - (strWidth / 2), (h / 2) + (40 / 2));
      }
      
      private void paintColors(Graphics gr, int w, int h)
      {
        // Draw 7x7
        int wStep = (int)((float)w / 7f);
        int hStep = (int)((float)h / 7f);
        for (int hor=0; hor<7; hor++)
        {
          for (int vert=0; vert<7; vert++)
          {
            Color c = new Color(COLOR_VALUES[(hor * 7) + vert][0],
                                COLOR_VALUES[(hor * 7) + vert][1],
                                COLOR_VALUES[(hor * 7) + vert][2]);
            gr.setColor(c);
            gr.fillRect(hor * wStep, vert * hStep, wStep, hStep);
          }
        }        
      }
    };

The smoothed panel

The other approach is a bit less straightforward.
The way to proceed is to
  • Generate an Image
  • Apply a blur filter on it
  • Display the image
The blur filter could look like this:
  public static BufferedImage blur(BufferedImage bimg, int matrixDim)
  {
    float blurMatrix[] = new float[matrixDim * matrixDim];
    for (int i=0; i< blurMatrix.length; i++)
      blurMatrix[i] = 1f / (float)(matrixDim * matrixDim);

    Kernel kernel = new Kernel(matrixDim, matrixDim, blurMatrix);
    BufferedImageOp blurFilter = new ConvolveOp(kernel, 
                                                ConvolveOp.EDGE_NO_OP,
                                                null);
    bimg = blurFilter.filter(bimg, null);
    return bimg;
  }
And it can be used as follow in the paintComponent method:
      public void paintComponent(Graphics gr)
      {
        ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                          RenderingHints.VALUE_TEXT_ANTIALIAS_ON);      
        ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                          RenderingHints.VALUE_ANTIALIAS_ON);      
        int w = this.getWidth();
        int h = this.getHeight();
        
        // Create the image
        BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);       
        // Create a graphics contents on the buffered image
        Graphics2D g2d = bufferedImage.createGraphics();

        this.paintColors((Graphics)g2d, w, h);
        // Graphics context no longer needed so dispose it
        g2d.dispose();
        // Blur it
        bufferedImage = blur(bufferedImage, w / 7);
        // Display it
        gr.drawImage(bufferedImage, 0, 0, null);

        gr.setColor(Color.black);
        gr.setFont(gr.getFont().deriveFont(40f));
        
        String str = "Some text on top";
        int strWidth  = gr.getFontMetrics(gr.getFont()).stringWidth(str);        
        gr.drawString(str, (w / 2) - (strWidth / 2), (h / 2) + (40 / 2));
      }
And that's it!
It's fully compatible with transparency,
  g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));

But beware, depending on the size of the Kernel matrix, the blur operation can be pretty demanding.

1 comment:

  1. I've been looking for that so long! Thanks for the post!

    ReplyDelete