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.
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. |