Pages

.

Procedural Paint in Java: Perlin noise


A few days ago, I showed how to implement java.awt.Paint in a way that lets you vary the paint appearance according to the x-y position of a point onscreen -- in other words, treating Paint as a procedural texture. It turns out to be pretty straightforward. Implementing the Paint interface means providing an implementation for Paint's one required method, createContext():
   public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints)
Most of the formal parameters are hints and can be ignored. Note that the createContext()method returns a java.awt.PaintContext object. PaintContext is an interface, so you have to implement it as well, and this (it turns out) is where the real action occurs. The methods of the PaintContext interface include:

public void dispose() {};
public ColorModel getColorModel();
public Raster getRaster(int x,
int y,
int w,
int h);

The dispose() method releases any resources that were allocated by the class. In many cases, you'll allocate nothing and thus your dispose method can be empty. The getColorModel() method can, in most cases, be a one-liner that simply returns ColorModel.getRGBdefault(). Where things get interesting is in getRaster(). That's where you have the opportunity to set pixel values for all the pixels in the raster based on their x-y values.

One of the most widely used procedural textures is Ken Perlin's famous noise algorithm. It might be an exaggeration (but not by much) to say that the majority of the CGI world's most interesting textures start from, or at least in some way use, Perlin noise. One could say it's the texture that launched a thousand Oscars. (In 1997, Perlin won an Academy Award for Technical Achievement from the Academy of Motion Picture Arts and Sciences for his noise algorithm; that's how foundationally important it is in cinematic CGI.)

It turns out to be pretty easy to implement Perlin noise in custom Paint; see the 100 lines of code shown below. Note that in order to use this code, you need the class ImprovedNoise.java, which is a nifty reference implementation of Perlin noise provided by Ken Perlin here.

(Scroll code sideways to see lines that don't wrap.)
/* PerlinPaint
* Kas Thomas
* 1 February 2010
* Public domain.
* http://asserttrue.blogspot.com/
*
* Demonstration of a custom java.awt.Paint implementation.
* This Paint uses a two-dimensional Perlin noise texture,
* based on Perlin's improved reference implmentation
* (see ImprovedNoise.java, http://mrl.nyu.edu/~perlin/noise/).
* Thanks to David Jones (Code Monk) for the idea.
*/


import java.awt.Color;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

class PerlinPaint implements Paint {

static final AffineTransform defaultXForm =
AffineTransform.getScaleInstance(0.15, 0.15);

// Colors a and b stored in component form.
private float[] colorA;
private float[] colorB;
private AffineTransform transform;

public PerlinPaint(Color a, Color b) {
colorA = a.getComponents(null);
colorB = b.getComponents(null);
transform = defaultXForm;
}

public PerlinPaint(Color a, Color b, AffineTransform transformArg) {
colorA = a.getComponents(null);
colorB = b.getComponents(null);
transform = transformArg;
}

public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform transform,
RenderingHints hints) {
return new Context(cm, transform);
}

public int getTransparency() {
return java.awt.Transparency.OPAQUE;
}

class Context implements PaintContext {

public Context(ColorModel cm_, AffineTransform transform_) { }

public void dispose() {}

public ColorModel getColorModel() {
return ColorModel.getRGBdefault();
}

// getRaster makes heavy use of the enclosing NoisePaint instance
public Raster getRaster(int xOffset, int yOffset, int w, int h) {

WritableRaster raster =
getColorModel().createCompatibleWritableRaster( w, h );

float [] color = new float[4];

for ( int y = 0; y < h; y++ ) {
for ( int x = 0; x < w; x++ ) {

// treat each x-y as a point in Perlin space
float [] p = { x + xOffset, y + yOffset };

transform.transform(p, 0, p, 0, 1);

float t = (float)ImprovedNoise.noise( p[0], p[1], 2.718);

// ImprovedNoise.noise returns a float in the range [-1..1],
// whereas we want a float in the range [0..1], so:
t = (1 + t)/2;

for ( int c = 0; c < 4; c++ ) {
color[ c ] = lerp( t, colorA[ c ] ,colorB[ c ] );
// We assume the default RGB model, 8 bits per band.
color[ c ] *= 0xff;
}
raster.setPixel( x,y, color );
}
}
return raster;
}

float lerp( float t, float a, float b ) {
return a + t * ( b - a );
}
}
}


The code should be self-explanatory. There are two constructors; both allow you to pick the primary and secondary colors for the texture, but one includes an AffineTransform, whereas the other doesn't. If you use the constructor with the transform, you can scale (or rotate, etc.) the Perlin noise to suit your needs. To achieve the "cloudy" look, the text at the top of this post uses a scaling factor of .06 in x and .05 in y, per the script below. Note that to run the following script, it helps if you have a copy of ImageMunger, the tiny Java app I wrote about a couple weeks ago. ImageMunger is a very simple command-line application: You pass it two command-line arguments, namely a file path pointing at a JPEG or other image file, and a file path pointing at a JavaScript file. ImageMunger opens the image in a JFrame and executes the script. Meanwhile, it also puts two global variables in scope for your script to use: Image (a reference to the BufferedImage object) and Panel (a reference to the JComponent that paints the image). Be sure you have JDK6.

/* perlinText.js
* Kas Thomas
* 1 February 2010
* Public domain.
*
* Run this file using ImageMunger:
* http://asserttrue.blogspot.com/2010/01/simple-java-class-for-running-scripts.html
*/


g2d = Image.createGraphics();

rh = java.awt.RenderingHints;
hint = new rh( rh.KEY_TEXT_ANTIALIASING,rh.VALUE_TEXT_ANTIALIAS_ON );
g2d.setRenderingHints( hint );
transform = g2d.getTransform().getScaleInstance(.06,.05);
perlinPaint = new Packages.PerlinPaint( java.awt.Color.BLUE,java.awt.Color.WHITE,transform);

g2d.setPaint( perlinPaint );
g2d.setFont( new java.awt.Font("Times New Roman",java.awt.Font.BOLD,130) );
g2d.drawString( "Perlin",50,100);
g2d.drawString( "Noise",50,200);

Panel.updatePanel();
Future projects:
  • Implement Perlin's turbulence and Brownian noise as custom Paints.
  • Implement a bump-map (faux 3D-shaded) version of PerlinPaint.
reade more... Résuméabuiyad

Sobel edge detection using Java Advanced Imaging



The Java Advanced Imaging API supports a number of interesting convolutions straight out of the box, and one of them is Sobel edge detection.

The Sobel edge-detection kernel comes in two varieties, corresponding to horizontal edge detection and vertical edge detection:
 1  2  1
0 0 0
-1 -2 -1

1 0 -1
2 0 -2
1 0 -1
You can combine them, and/or run them serially against an image, to detect all edges in an image. And that's what the following code example (in JavaScript) does. You can run the following script against an image of your choice using the ImageMunger app I wrote about a few days ago. Be sure the Java Advanced Imaging JARs are in your classpath.
/* Sobel.js
* Kas Thomas
* 31 January 2010
* Public domain.
*
* An edge-detection routine using
* Java Advanced Imaging.
*
* Requires Java Advanced Imaging library:
* http://java.sun.com/products/java-media/jai/current.html
*
* Run this file using ImageMunger:
* http://asserttrue.blogspot.com/2010/01/simple-java-class-for-running-scripts.html
*
*/


jai = Packages.javax.media.jai;
sobelH = jai.KernelJAI.GRADIENT_MASK_SOBEL_HORIZONTAL;
sobelV = jai.KernelJAI.GRADIENT_MASK_SOBEL_VERTICAL;

pb = new Packages.java.awt.image.renderable.ParameterBlock( );

// ImageMunger puts "Image" in global scope:
pb.addSource( Image );
pb.add( sobelH );
pb.add( sobelV );

renderedOp = jai.JAI.create( "gradientmagnitude", pb );
var image = renderedOp.getRendering().getAsBufferedImage();
Panel.setImage( invertImage( image ) );

// take BufferedImage as arg; flip all bits in all pixels
function invertImage( image ) {

var w = image.getWidth();
var h = image.getHeight();
var pixels = image.getRGB( 0,0, w,h, null, 0,w );

for ( var i = 0; i < pixels.length; i++ )
pixels[ i ] =~ pixels[ i ]; // flip pixel bits

image.setRGB( 0,0, w,h, pixels, 0, w );
return image;
}

If you run the Sobel operation by itself, you get a "negative" image, like so:



If you want the inverted version of this image (see example further above), you need to invert the individual pixels of the image. The super-fast way to do it is with a lookup table, but you can also do the pixel inversion manually, in a loop, which is what I've done (for illustrative purposes). In JavaScript the pixel-inversion loop adds about one second of processing time for a 600 x 400 image. The Sobel filter by itself takes around a half a second.

Sobel tends to be very sensitive to noise (it will feature-enhance specks and JPEG artifacts), so it often helps to smooth an image, first, with a blurring operation, prior to applying Sobel.

Future projects:
  • Write a "tunable" version of Sobel that can detect soft or hard edges, according to a tuning parameter.
  • Write a version of Sobel that's tunable by color (viz., detecting just blue edges, or just black edges, or just medium-grey edges).
reade more... Résuméabuiyad

How to implement custom Paint in 50 lines of Java



Java offers many ways to customize strokes and fills, including the use of gradient fills and image fills (see this excellent tutorial by Marty Hall), but we tend to forget that "procedural textures" are easily implemented as custom Paint.

The text shown above was painted using a custom Paint class, SinePaint.java, consisting of around 50 lines of code (not counting imports), as shown below. (Scroll the code sideways to see lines that didn't wrap.) The SinePaint class procedurally generates a sine-wave fill pattern in the red color channel, running in the 'y' direction (vertical sine wave).

/* SinePaint
* Kas Thomas
* 30 January 2010
* Public domain.
* http://asserttrue.blogspot.com/
*
* A quick example of how to implement java.awt.Paint
*/


import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

class SinePaint implements Paint {

public SinePaint() {}

public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints) {
return new Context(cm, xform);
}

public int getTransparency() {
return java.awt.Transparency.OPAQUE;
}

class Context implements PaintContext {

public Context(ColorModel cm_, AffineTransform xform_) { }

public void dispose() {}

public ColorModel getColorModel() {
return ColorModel.getRGBdefault();
}

public Raster getRaster(int xOffset, int yOffset, int w, int h) {

WritableRaster raster =
getColorModel().createCompatibleWritableRaster(w, h);
float [] color = new float[4];

// Row major traversal.
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
color[3] = 255;
color[2] = color[1] = 0;
// Write a sine-wave pattern to the Red channel
color[ 0 ] =
(1 + (float) Math.sin( 6.28f*((double) j)/h )) * .5f * 255;;

raster.setPixel(i, j, color);
} // i
} // j
return raster;
} // getRaster()
} // Context
} // SinePaint

Implementing the Paint interface turns out not to be such a big deal. There's only one required method, createContext():
   public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints)
Most of the formal parameters are hints and can safely be ignored. Note that this method returns a java.awt.PaintContext object. It turns out PaintContext is an interface as well, so you do end up having to implement it, and this is where the real action occurs. The methods of the PaintContext interface include:

public void dispose() {};
public ColorModel getColorModel();
public Raster getRaster(int x,
int y,
int w,
int h);

The dispose() method releases any resources that were allocated by the class. In our case, we allocated nothing and so our dispose method is empty. The getColorModel() method can, in most cases, be a one-liner that simply returns ColorModel.getRGBdefault(). The real action is in getRaster(). That's where you have the opportunity to set the pixel values for all the pixels in the raster based on their x-y values. If you're familiar with shaders and/or procedural textures, you know what this is about. This is your opportunity to shade an area in accordance with a pixel's x-y location onscreen (or rather, within the image).

If you've been using the ImageMunger app I wrote about a few days ago, you can run the following script with it to see SinePaint in operation. (This is the script that produced the colored text shown above.)

/* paintedText.js
* Kas Thomas
* 30 January 2010
* Public domain.
*
* Run this file using ImageMunger:
* http://asserttrue.blogspot.com/2010/01/simple-java-class-for-running-scripts.html
*/


g2d = Image.createGraphics();

rh = java.awt.RenderingHints;
hint = new rh( rh.KEY_TEXT_ANTIALIASING,rh.VALUE_TEXT_ANTIALIAS_ON );
g2d.setRenderingHints( hint );

sinePaint = new Packages.SinePaint( );

g2d.setPaint( sinePaint );
g2d.setFont( new java.awt.Font("Times New Roman",java.awt.Font.BOLD,130) );
g2d.drawString( "Shiny",50,100);
g2d.drawString( "Text",50,200);

Panel.updatePanel();
(Scroll sideways to see lines that didn't wrap.)

Future projects:
  • Make use of the AffineTransform argument to enable scaled and otherwise transformed textures.
  • Implement Perlin noise as a type of Paint.
  • Implement "bump map" 3D effects in Paint.
reade more... Résuméabuiyad

Image rotation in 8 lines using the Java Advanced Imaging API


Last time, I showed that you could rotate an image 180 degrees in what amounts to one line of JavaScript, basically just doing pixels.reverse( ). Rotating an image by an arbitrary amount (something other than 180 degrees) is almost as easy. It requires about 8 lines of code.

JavaScript for doing the rotation with the Java Advanced Imaging API is shown below. JAI makes short work of this and a ton of other graphics transformations. All you have to do is be sure the JAI JARs are in your classpath. Then you can set up a transformation by creating a ParameterBlock with appropriate parameters (in this case, the x- and y-coordinates of the rotation origin, the amount of rotation in radians, and optionally a rendering hint as to what kind of pixel interpolation you'd like; in this case, we don't specify a hint and thus accept the default of INTERP_NEAREST).

/*
* rotate.js
* Kas Thomas
* 29 January 2010
* Public domain.
*
* Requires Java Advanced Imaging library:
* http://java.sun.com/products/java-media/jai/current.html
*
* Run this file using ImageMunger:
* http://asserttrue.blogspot.com/2010/01/simple-java-class-for-running-scripts.html
*/


pb = new Packages.java.awt.image.renderable.ParameterBlock( );
pb.addSource( Image );

pb.add( new java.lang.Float(0) ); // x-origin
pb.add( new java.lang.Float(0) ); // y-origin
pb.add( new java.lang.Float( Math.PI/8) ); // rotation amount

renderedOp = Packages.javax.media.jai.JAI.create( "rotate", pb );
image = renderedOp.getRendering().getAsBufferedImage();

Panel.setImage( image );

Note that JAI expects parameters of type float, which is not what JavaScript provides by default. By default, numbers in JavaScript are doubles. So you have to explicitly create java.lang.Floats as shown.

As in previous posts, I'm using my little ImageMunger app to run the script.

Also, as with previous scripts, performance is quite good (through no fault of my own): rotation occurs at a rate of about 500 pixels per millisecond on a Dell Inspiron laptop with 2.2 GHz Intel Duo processor running (gack!) Windows Vista. Which ain't bad at all. I'll take 500-pixels-per-millisec throughput any day, on any OS.
reade more... Résuméabuiyad

Fast image rotation using JavaScript


Five lines of JavaScript suffice to rotate the image on the left 180 degrees, at a processing speed of one million pixels per second.

Rotating an image 180 degrees turns out to be extremely easy -- and as good an illustration as any that JavaScript doesn't automatically have to mean "slow." The test image shown here (a 600 x 446-pixel JPEG) was rotated 180 degrees using the script below in only 262 milliseconds -- quite literally the blink of an eye. (Note that to use this script, you need the small Java app -- ImageMunger -- that I wrote about a few days ago.)

w = Image.getWidth();
h = Image.getHeight();
pixels = Image.getRGB( 0,0, w,h, null, 0, w );
Image.setRGB( 0,0, w,h, pixels.reverse(), 0, w );
Panel.updatePanel( );
The key to what's going on is the call to pixels.reverse(). When you call getRGB() on a BufferedImage, Java hands you back all the image's pixels in a one-dimensional array. Simply rewriting the array in reverse order has the effect of causing the image to paint lower-right-corner-first (if you know what I mean). It rotates the image 180 degrees.

The reverse() method of the Array object (built into JavaScript) is implemented in bytecode and runs at bytecode speed (as is true of all native methods). It means that the "main loop" (the pixel-reversal code) runs at compiled-code speed.

The real lesson here is that if you want your JavaScript code to run fast, you should take advantage, whenever you can, of native methods of the language. Those methods are nearly always implemented as hand-optimized C or Java -- as I discussed in an earlier post. It's free speed. Don't let it go to waste.
reade more... Résuméabuiyad

HTTPbis

In case you haven't been following developments around things like HTTPbis and the PATCH verb, take a look at this slideshow. HTTP is far from a static specification. It's about to undergo significant clarification and expansion. All I can say is: It's about time.
reade more... Résuméabuiyad

Sharpening an image using java.awt.image.ConvolveOp



The somewhat blurry image on the left was sharpened, to produce the image on the right, using the two dozen or so lines of JavaScript code shown further below (run in conjunction with the ImageMunger Java app; see text for details).


As with contrast adjustment, sharpening an image can be thought of as an exercise in weak signal amplification. Generally it means making the differences between neighboring pixels more noticeable. You can do this by brute-force analysis of pixels, of course, but area operators -- kernel-based convolutions -- are the clean way to go.

A convolution kernel is a 2D matrix of numbers that can be used as coefficients for numerical operations on pixels. Suppose you have a 3x3 kernel that looks like this:
1 2 1
2 0 2
1 2 1
As you loop over all pixels in the image, you would, for any given pixel, multiply the pixel itself by zero; multiply the pixel directly above the given pixel by 2; also multiply by 2 the pixels to the left, right, and below the pixel in question; multiply by one the pixels at 2 o'clock, 4 o'clock, 8 o'clock, and 10 o'clock to the pixel in question; add all these numeric values together; and divide by 9 (the kernel size). The result is the new pixel value for the given pixel. Repeat for each pixel in the image.

In the example just cited, the kernel (1 2 1 etc.) would end up smoothing or blurring the image, because in essence we are replacing a given pixel's value with a weighted average of surrounding pixel values. To sharpen an image, you'd want to use a kernel that takes the differences of pixels. For example:
 0 -1  0
-1 5 -1
0 -1 0
This kernel would achieve a differencing between the center pixel and pixels immediately to the north, south, east, and west. It would cause a fairly harsh, small-radius (high frequency) sharpening-up of image features.

It turns out, Java has good support for kernel-based 2D convolutions of images using java.awt.image.Kernel and java.awt.image.ConvolveOp. It takes only a few lines of JavaScript to run a convolution kernel against a JPEG (or other image) to achieve sharpening of the image; see code listing below. (A few days ago, I posted code for a small Java app -- called ImageMunger -- that opens an image of your choice and runs a script against it. You may want to use that app to run the following code.)

kernel = [ .25, -2, .25,
-2, 10, -2,
.25, -2, .25 ];

function normalizeKernel( ar ) {

for (var i = 0, n = 0; i < ar.length; i++)
n += ar[i];
for (var i = 0; i < ar.length; i++)
ar[i] /= n;

return ar;
}

kernel = normalizeKernel( kernel );

k = new java.awt.image.Kernel( 3,3,kernel );
convolver =
new
java.awt.image.ConvolveOp( k,
java.awt.image.ConvolveOp.EDGE_NO_OP,
null);


target =
new java.awt.image.BufferedImage( Image.getWidth(),
Image.getHeight(),Image.getType() );

g = target.createGraphics( );
g.drawImage( Image, null,0,0 );
g.dispose();
Image = convolver.filter( target, Image );
Panel.updatePanel( );
Recall that the ImageMunger app I talked about here a few days ago exports a couple of global variables to the JavaScript context: namely, Image (a handle to the BufferedImage) and Panel (a reference to the JComponent in which the image is being displayed). With the aid of those globals and appropriate calls to JRE methods, it's very easy to run a convolution. Easy and fast: Expect to process around a thousand pixels per millisecond.

Future projects:
  • Programmatically generate and initialize large kernels. Have a slider-based UI that performs interesting initializations of kernel values.
  • Kernel-based sharpening tends to preferentially add high frequencies to an image, which can be problematic in images that have lots of areas of high-frequency noise. Create a "smart sharpen" algorithm that dynamically tunes the frequency of the sharpening (kernel values) according to the natural "humm" (the natural frequencies) of the area or areas that are being sharpened.
  • As a side benefit of the foregoing, create a sharpening algorithm that won't sharpen JPEG artifacts.
reade more... Résuméabuiyad