Embossing an Image


The first button in Graphicizer is Emboss, which converts an image into an "embossed" image, making it look as though it were embossed on paper in a three-dimensional way. For example, you can see the results when the sample image (image.gif) that comes with the application is embossed in Figure 3.4.

Figure 3.4. Embossing an image.


To create an embossed image, Graphicizer has to get access to the individual pixels in the image. You can load these pixels into an array using the Java PixelGrabber class, which is what Graphicizer does. You can see the significant methods of the PixelGrabber class in Table 3.5.

Table 3.5. The Significant Methods of the java.awt.image.PixelGrabber Class

Method

Does This

ColorModel getColorModel()

Returns the color model used by the data stored in the array

int getHeight()

Returns the height of the pixel buffer, as measured in pixels

Object getPixels()

Returns the pixel buffer used by the PixelGrabber object

int getWidth()

Returns the width of the pixel buffer, as measured in pixels

boolean grabPixels()

Gets all the pixels in the rectangle of interest and transfers them individually to the pixel buffer

boolean grabPixels(long ms)

Gets all the pixels in the rectangle of interest and transfers them individually to the pixel buffer, subject to the timeout time, ms (in milliseconds)

void setColorModel(ColorModel model)

Sets the color model used by this PixelGrabber object

void setDimensions(int width, int height)

Sets the dimensions of the image to be grabbed

 void setPixels(int srcX, int srcY, int srcW, int srcH, ColorModel model, byte[] pixels, int srcOff, int srcScan) 

Sets the actual pixels in the image

void startGrabbing()

Makes the PixelGrabber object start getting the pixels and transferring them to the pixel buffer


NOTE

You can also access the pixels in an image with the BufferedImage class's getrGB and setRGB methods. So why doesn't the Graphicizer do things that way? It turns out that these methods take a significant amount of time to execute, and when you multiply that by the millions of pixels you can have in an image, you wind up with a problem. It's a heck of a lot faster to load all the pixels from the image into an array using a PixelGrabber object, work on the array in Java code, and then simply fill the image from the array again.


How do you go about embossing the image, now that you have it stored in an array? You can emboss an image by finding the difference between each pixel and its neighbor and then adding that difference to the color gray.

To start each drawing operation, Graphicizer stores the present image in a backup object, bufferedImageBackup, in case the user selects the Undo menu item:

 public void actionPerformed(ActionEvent event) {     .     .     .     if(event.getSource() == button1){         bufferedImageBackup = bufferedImage;     .     .     . } 

If the user selects the File menu's Undo item, Graphicizer can use the backed-up version of the image, bufferedImageBackup, to restore the original image.

After creating a backup buffered image, the code uses a PixelGrabber object, pg, to load the actual pixels from the image into an array.

To create that pixel grabber, you pass the PixelGrabber constructor the image you want to grab and the offset at which to start in the imagein this case, (0, 0). You also pass the width and height of the image, the array to store the image in (named pixels in this example), the offset into the array at which you want to start storing data (that's 0 here), and the "scansize," which is the distance from one row of pixels to the next in the array (that'll be width here). Here's what this looks like in code:

 public void actionPerformed(ActionEvent event) {     .     .     .     if(event.getSource() == button1){         bufferedImageBackup = bufferedImage;         int width = bufferedImage.getWidth();         int height = bufferedImage.getHeight();         int pixels[] = new int[width * height];         PixelGrabber pg = new PixelGrabber(bufferedImage, 0, 0,             width, height, pixels, 0, width);         try {             pg.grabPixels();         }         catch(InterruptedException e){             System.out.println(e.getMessage());         }     .     .     . } 

If this operation is successful, the image has been stored in the int array pixels. That's perfect, because now you can work with each individual pixel as an integer in an array, without having to go to the trouble of fetching it from the image itself, which can take a good deal of time.

The image data is ready to work on, but first, you need to pay some attention to the border of the image. Because the embossing algorithm compares each pixel to its neighbor and draws the resulting difference, that leaves some untouched space at the edges of the image, so the code first fills in a two-pixel border around the image with gray:

 public void actionPerformed(ActionEvent event) {     .     .     .     if(event.getSource() == button1){         .         .         .         for (int x = 0; x <= 1; x++){             for (int y = 0; y < height - 1; y++){                 pixels[x + y * width] = 0x88888888 ;             }         }         for (int x = width - 2; x <= width - 1; x++){             for (int y = 0; y < height - 1; y++){                 pixels[x + y * width] = 0x88888888 ;             }         }         for (int x = 0; x <= width - 1; x++){             for (int y = 0; y <= 1; y++){                 pixels[x + y * width] = 0x88888888 ;             }         }     .     .     . } 

Now you're ready to emboss the image. You can do that by looping over every pixel in the image, first looping over rows (the X direction in the array), then with an inner loop looping over each column (the Y direction in the array):

 public void actionPerformed(ActionEvent event) {     .     .     .         for (int x = 2; x < width - 1; x++){             for (int y = 2; y < height - 1; y++){             .             .             .             }         }     }     .     .     . } 

Inside these loops, you can compare the red, green, and blue component of each pixel to its neighbor, add that difference to a neutral gray, and store the results in the pixel array. That's the operation you perform to emboss the image.

Here's what the actual byte-by-byte manipulation looks likethe color components of each pixel are extracted, compared to their neighbor, and then repacked into the integer for that pixel in the pixels array:

 public void actionPerformed(ActionEvent event) {     .     .     .     if(event.getSource() == button1){         .         .         .         for (int x = 2; x < width - 1; x++){             for (int y = 2; y < height - 1; y++){                 int red = ((pixels[(x + 1) + y * width + 1] & 0xFF)                     - (pixels[x + y * width] & 0xFF)) + 128;                 int green = (((pixels[(x + 1) + y * width + 1]                     & 0xFF00) / 0x100) % 0x100 - ((pixels[x + y * width]                     & 0xFF00) / 0x100) % 0x100) + 128;                 int blue = (((pixels[(x + 1) + y * width + 1]                     & 0xFF0000) / 0x10000)                     % 0x100 - ((pixels[x + y * width] & 0xFF0000) / 0x10000)                     % 0x100) + 128;                 int avg = (red + green + blue) / 3;                 pixels[x + y * width] = (0xff000000 | avg << 16                     | avg << 8 | avg);             }         }     }     .     .     . } 

That stores the new image in the pixels array. How do you get it into the bufferedImage object for display? There's no easy way to do that, because no BufferedImage constructor takes an array of pixels.

You have to get this done by first creating an Image object using the Component class's createImage method, then creating a new BufferedImage object, and finally using the BufferedImage object's createGraphics and drawImage methods to draw the Image object in the BufferedImage object (which is the way you convert from Image objects to BufferedImage objectsthere is no BufferedImage constructor that will do the job for you).

Here's how the code loads the pixels array into the bufferedImage object and then paints it on the screen:

 public void actionPerformed(ActionEvent event) {     .     .     .         image = createImage(new MemoryImageSource(width,             height, pixels, 0 , width));         bufferedImage = new BufferedImage (width, height,             BufferedImage.TYPE_INT_BGR );         bufferedImage.createGraphics().drawImage(image, 0, 0, this );         repaint();     }     .     .     . } 

And that's all you need; now you've embossed the image by getting into the pixels in the image and working with them one by one.



    Java After Hours(c) 10 Projects You'll Never Do at Work
    Java After Hours: 10 Projects Youll Never Do at Work
    ISBN: 0672327473
    EAN: 2147483647
    Year: 2006
    Pages: 128

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net