In the Java 2D™ API an image is typically a rectangular two-dimensional array of pixels, where each pixel represents the color at that position of the image and where the dimensions represent the horizontal extent (width) and vertical extent (height) of the image as it is displayed.
There are bitmap and vector images. Bitmap images (also known as raster images) are made up of pixels in a grid. Vector images are made up of many individual, scalable objects such as points, lines, curves or polygons. These objects are defined by mathematical equations rather than pixels.
The most important image class for representing such images is the
Applications can directly create a
In either case, the application can then draw on to image by using Java 2D API graphics calls. So, images are not limited to displaying photographic type images. Different objects such as line art, text, and other graphics and even other images can be drawn onto an image (as shown on the following images).
The Java 2D API enables you to apply image filtering operations to
There are bitmap and vector images. Bitmap images (also known as raster images) are made up of pixels in a grid. Vector images are made up of many individual, scalable objects such as points, lines, curves or polygons. These objects are defined by mathematical equations rather than pixels.
The most important image class for representing such images is the
java.awt.image.BufferedImage
class. The Java 2D API stores the contents of such images in memory so that they can be directly accessed.Applications can directly create a
BufferedImage
object or obtain an image from an external image format such as PNG or GIF.In either case, the application can then draw on to image by using Java 2D API graphics calls. So, images are not limited to displaying photographic type images. Different objects such as line art, text, and other graphics and even other images can be drawn onto an image (as shown on the following images).
BufferedImage
and includes several built-in filters. For example, the ConvolveOp
filter can be used to blur or sharpen images.The resulting image can then be drawn to a screen, sent to a printer, or saved in a graphics format such as PNG, GIF etc. To learn more about images see the Working with Images lesson.With the Abstract Window Toolkit (AWT) alone, the only way to display an image is to use the
java.awt.Image
class. However, this class does not allow you to access the image data directly. In fact, the only methods that directly returned information about the image in java.awt.Image
were getHeight()
and getWidth()
, but even then, there were limitations: If the system had not yet loaded the image data, the values would be erroneous, and you would have to use an instance of java.awt.ImageObserver
to be notified when the data became available. If you wanted to manipulate the image data in other ways, you were forced to use the inconvenient producer-consumer model to inspect or manipulate the data as it was decoded from its source.Buffered Images
The
java.awt.image.BufferedImage
class, introduced as part of the Java 2D API with the Java Development Kit (JDK) 1.2, affords the programmer much more freedom to directly manipulate the pixels inside an image. Compared to the producer-consumer model, this class uses an immediate-mode imaging model from which you can inspect and modify pixel data stored directly in memory. You can also access image data in a variety of formats and use several types of filtering operations to manipulate the data.A
BufferedImage
object -- specifically the image inside of it -- has two parts: a ColorModel
object and a Raster
object that represents the image data. See Figure 1.The
ColorModel
object provides an interpretation of the image's pixel data within a color space. A color space is essentially a collection of all the colors that can be shown on a particular device. Computer monitors, for example, often define their color space using the red-green-blue (RGB) color space. A printer, on the other hand, may use a cyan-magenta-yellow-black (CMYK, using the letter K for "black" rather than B for "blue") color space. Images may use one of several subclasses of ColorModel
in the Java 2D API libraries:- A
ComponentColorModel
, in which a pixel is represented by several discrete values, typically bytes, each representing one component of color, such as the red component of an RGB representation - A
DirectColorModel
, in which all components of a color are packed together in separate bits of the same single pixel value - An
IndexColorModel
, in which each pixel is a single value representing an index into a palette of colors
The
Raster
object, on the other hand, stores the actual pixel data for an image in a rectangular array addressed by x-axis and y-axis (x and y) coordinates. It also provides a mechanism for creating subimages from its image data buffer. The Raster
itself is composed of two parts:- A data buffer, which contains the raw image data
- A sample model, which describes how the data is organized in the buffer
A
Raster
also provides methods for accessing specific pixels within the image.Using a
BufferedImage
ObjectTo create a
BufferedImage
object, simply call one of its constructors with the width, height, and an image-type constant.BufferedImage image = new BufferedImage(400, 400, BufferedImage.TYPE_INT_RGB); |
For the image-type parameter, use one of the
BufferedImage
constants shown in Table 1, which specifies how the image data is stored for each of its pixels.Table 1. BufferedImage Color Models |
TYPE_3BYTE_BGR | Blue, green, and red values stored, 1 byte each | |||
TYPE_4BYTE_ABGR | Alpha, blue, green, and red values stored, 1 byte each | |||
TYPE_4BYTE_ABGR_PRE | Alpha and premultiplied blue, green, and red values stored, 1 byte each | |||
TYPE_BYTE_BINARY | 1 bit per pixel, 8 pixels to a byte | |||
TYPE_BYTE_INDEXED | 8-bit pixel value that references a color index table | |||
TYPE_BYTE_GRAY | 8-bit gray value for each pixel | |||
TYPE_USHORT_555_RGB | 5-bit red, green, and blue values packed into 16 bits | |||
TYPE_USHORT_565_RGB | 5-bit red and blue values, 6-bit green values packed into 16 bits | |||
TYPE_USHORT_GRAY | 16-bit gray values for each pixel | |||
TYPE_INT_RGB | 8-bit red, green, and blue values stored in a 32-bit integer | |||
TYPE_INT_BGR | 8-bit blue, green, and red pixel values stored in a 32-bit integer | |||
TYPE_INT_ARGB | 8-bit alpha, red, green, and blue values stored in a 32-bit integer | |||
TYPE_INT_ARGB_PRE | 8-bit alpha and premultiplied red, green, and blue values stored in a 32-bit integer | |||
To draw into a
BufferedImage
, call the createGraphics()
method to obtain the Graphics2D
object that renders into the BufferedImage
, then just call the appropriate rendering methods on theGraphics2D
object. Note that you can use all of the Java 2D API rendering features, including those discussed in the first article, when you're rendering to a BufferedImage.
BufferedImage image = new BufferedImage(400, 400, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = (Graphics2D)image.createGraphics(); g2.setFont(new Font("Serif", Font.PLAIN, 36)); g2.drawString("Hello BufferedImage", 50, 50); |
Historically, you can create a
BufferedImage
from a jpeg
file using the com.sun.image.codec.jpeg.JPEGImageDecoder
class.String filename = "myGraphic.jpg"; InputStream in = ClipImage.class.getResourceAsStream(filename); JPEGImageDecoder decoder = JPEGDecoder.createJPEGDecoder(in); final BufferedImage bufferedImage = decoder.decodeAsBufferedImage(); in.close(); |
However, if you're looking for a simpler route, you can use the Image I/O libraries in
javax.imageio
(JSR 15). The javax.imageio.ImageIO
class provides a set of static convenience methods that perform most simple Image I/O operations. For example, to read an image that is in a standard format (gif
, png
, or jpeg
), do the following:File f = new File("c:\images\myimage.gif"); BufferedImage bufferedImage = ImageIO.read(f); |
To write it back out, use the
write()
method of javax.imageio.ImageIO
. With this method, you can convert one image type to another. In this case, we converted a gif
to a png
.File f = new File("c:\images\myimage.png"); ImageIO.write(bufferedImage, "png", f); |
A
BufferedImage
can be rendered using the drawImage()
method of any Graphics
or Graphics2D
objects. For example, you can render a BufferedImage
into a Component
using the Graphics
object passed into its paint()
method.public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.drawImage(bufferedImage, 0, 0, null); } |
You may have noticed that there is an unnecessary line in the previous code snippet. Isn't there already a
drawImage()
method in the base Graphics
class? Yes, the drawImage()
method invoked above also exists on the base Graphics
class, so it is really unnecessary to cast the incoming Graphics
object to a Graphics2D
. Because the object that is passed in is really a Graphics2D
object, the proper Java 2D method will be called.The easiest way to access specific pixel data of an image is to use the
getRGB()
and setRGB()
methods of the BufferedImage
class for the given x and y coordinates:int rgb = 3096; int oldRGB = image.getRGB(250, 180); image.setRGB(250, 180, rgb); |
The
setRGB()
and getRGB()
methods accept and return a 32-bit color value in the same format and color space as a non-premultiplied INT_RGB
image.int rgb = image.getRGB(x, y); int alpha = ((rgb >> 24) & 0xff); int red = ((rgb >> 16) & 0xff); int green = ((rgb >> 8) & 0xff); int blue = ((rgb ) & 0xff); // Manipulate the r, g, b, and a values. rgb = (a << 24) | (r << 16) | (g << 8) | b; image.setRGB(x, y, rgb); |
You can also directly manipulate the image data of the
Raster
using its various accessor methods, but you must be familiar with the operation of the ColorModel
that it is associated with since you are manipulating the pixel data directly. The lowest-level and potentially most efficient way to access the image data would be to use the methods on the DataBuffer
of the Raster
. However, that requires knowledge of both the ColorModel
and SampleModel
in use.Filtering a
BufferedImage
ObjectOften, a graphics programmer may wish to perform more complex operations on
BufferedImage
objects than individually manipulating pixel values. The Java 2D API defines several filtering operations for BufferedImage
objects that manipulate large amounts of the image at the same time. Each of these image-processing operations is represented by a class that implements the BufferedImageOp
interface. The image manipulation itself is performed in this class's filter()
method.The Java 2D API supports the following implementations of the
BufferedImageOp
interface:- Affine transformation
- Amplitude scaling
- Modification of the look-up table
- Linear combination of bands
- Color conversion
- Convolution
Filtering a
BufferedImage
object using one of the image operation classes is easy. First, construct an instance of one of the BufferedImageOp
classes: AffineTransformOp
, BandCombineOp
,ColorConvertOp
, ConvolveOp
,LookupOp,
or RescaleOp
. Then, call the image operation's filter()
method, passing in the BufferedImage
object that you want to filter and the BufferedImage
where you want to store the results.The following applet, Code Example 1, based on an example in the Java 2D API documentation, illustrates the use of four image-filtering operations:
- Convolution using a 3x3 blurring filter
- Convolution using a 3x3 sharpen filter
- A look-up operation
- A rescale operation
Code Example 1
import java.awt.*; import java.io.*; import java.awt.event.*; import java.awt.image.*; import java.awt.geom.*; import java.awt.font.*; import javax.swing.*; import javax.imageio.*; public class ImageOps extends JApplet { private BufferedImage bi[]; public static final float[] BLUR3x3 = { 0.1f, 0.1f, 0.1f, 0.1f, 0.2f, 0.1f, 0.1f, 0.1f, 0.1f }; public static final float[] SHARPEN3x3 = { 0.f, -1.f, 0.f, -1.f, 5.f, -1.f, 0.f, -1.f, 0.f}; public void init() { setBackground(Color.white); // Load two images that we can use as examples for the // image operations. bi = new BufferedImage[4]; String s[] = { "bld.jpg", "bld.jpg", "boat.gif", "boat.gif"}; for ( int i = 0; i < bi.length; i++ ) { File f = new File("C:/" + s[i]); try { // Read in a BufferedImage from a file. BufferedImage bufferedImage = ImageIO.read(f); // Convert the image to an RGB style normalized image. bi[i] = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_INT_RGB); bi[i].getGraphics().drawImage(bufferedImage, 0, 0, this); } catch (IOException e) { System.err.println("Error reading file: " + f); System.exit(1); } } } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); int w = getSize().width; int h = getSize().height; // Set the color to black. g2.setColor(Color.black); // Create a low-pass filter and a sharpen filter. float[][] data = {BLUR3x3, SHARPEN3x3}; String theDesc[] = { "Convolve LowPass", "Convolve Sharpen", "LookupOp", "RescaleOp"}; // Cycle through each of the four BufferedImage objects. for ( int i = 0; i < bi.length; i++ ) { int iw = bi[i].getWidth(this); int ih = bi[i].getHeight(this); int x = 0, y = 0; // Create a scaled transformation for the image. AffineTransform at = new AffineTransform(); at.scale((w-14)/2.0/iw, (h-34)/2.0/ih); BufferedImageOp biop = null; BufferedImage bimg = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB); switch ( i ) { // IMAGE 1 and 2: Create a convolution // kernel that consists of either the low-pass filter // or the sharpen filter. Set the x and y of the image // so that it appears in the correct quadrant and has // enough room for the descriptive text above. case 0 : case 1 : x = i==0?5:w/2+3; y = 15; Kernel kernel = new Kernel(3, 3, data[i]); ConvolveOp cop = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); // Apply the convolution operation, placing the // result in bimg. cop.filter(bi[i], bimg); // Create the appropriate AffineTransformation that // will be used while drawing IMAGES 1 and 2 biop = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); break; case 2 : x = 5; y = h/2+15; // IMAGE 3: // Create the parameters needed for a LookupOp, which // process the color channels of an image using a // look-up table. This will create a reverse brightness // of the image, similar to a photographic negative. byte chlut[] = new byte[256]; for ( int j=0;j<200 ;j++ ) chlut[j]=(byte)(256-j); ByteLookupTable blut=new ByteLookupTable(0,chlut); LookupOp lop = new LookupOp(blut, null); lop.filter(bi[i], bimg); // Create the appropriate AffineTransformation, which // will be used while drawing the IMAGE 3. biop = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); break; case 3 : x = w/2+3; y = h/2+15; // IMAGE 4: // Perform a rescaling operation, multiplying each // pixel by a scaling factor (1.1), then adding an // offset (20.0). Note that this has nothing to do // with a geometric scaling of an image. RescaleOp rop = new RescaleOp(1.1f,20.0f, null); rop.filter(bi[i],bimg); biop = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); } // Draw the image with the appropriate AffineTransform // operation, as well as the text above it. g2.drawImage(bimg,biop,x,y); TextLayout tl = new TextLayout(theDesc[i], g2.getFont(),g2.getFontRenderContext()); tl.draw(g2, (float) x, (float) y-4); } } public static void main(String s[]) { JFrame f = new JFrame("ImageOps"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); JApplet applet = new ImageOps(); f.getContentPane().add("Center", applet); applet.init(); f.pack(); f.setSize(new Dimension(550,550)); f.setVisible(true); } } |
Note that both the blurring and sharpen filter operations are performed by using convolution. Convolution is the process of weighting or averaging the value of each pixel in an image with the values of neighboring pixels. Most spatial-filtering algorithms, including the 3x3 sharpening algorithm shown in Code Example 1, are based on convolution operations.
low-pass filter = blurring Làm mờ ảnh
sharpen filter = làm sắc nét ảnh
LookupOp trong đã chỉnh sửa ảnh bằng cách đảo màu sắc của mỗi pixel ảnh nguồn
low-pass filter = blurring Làm mờ ảnh
sharpen filter = làm sắc nét ảnh
LookupOp trong đã chỉnh sửa ảnh bằng cách đảo màu sắc của mỗi pixel ảnh nguồn
Double Buffering
When a graphic is complex or is used repeatedly, you can reduce the time it takes to display it by first rendering the image to an offscreen buffer image and then copying the buffer image to the screen. This technique, called double buffering, is often used for animations. A
BufferedImage
can easily be used as an offscreen buffer image. To create a BufferedImage
whose color space, depth, and pixel layout exactly match the window into which you're drawing, call the Component createImage()
or the GraphicsConfiguration.createCompatibleImage()
method. If you need control over the offscreen image's type or transparency, you can construct a BufferedImage
object directly.When you're ready to copy the
BufferedImage
to the screen, call the drawImage()
method on your visible component's Graphics
object and pass in the BufferedImage
.public void paint(Graphics g) { g.drawImage(offscreenBuffer, 0, 0, null); } |
Volatile Images
java.awt.image.VolatileImage
class helps to correct that by allowing you to create a hardware-accelerated offscreen image and to manage the contents of that image. For example, in many operating systems, a VolatileImage
object can be stored in VRAM and can benefit from hardware acceleration.Note that the memory where the image contents actually reside can be lost or invalidated. Hence, the drawing surface needs to be restored or recreated, and the contents of that surface need to be rerendered.
an interface for allowing the user to detect these problems and fix them when they occur.
VolatileImage
provides an interface for allowing the user to detect these problems and fix them when they occur.
Code Example 2 shows how to use a
VolatileImage
object.Code Example 2
VolatileImage vImg = GraphicsConfiguration. createCompatibleVolatileImage(w, h); public void paint(Graphics gScreen) { do { int returnCode = vImg.validate(getGraphicsConfiguration()); if (returnCode == VolatileImage.IMAGE_RESTORED) { // Contents need to be restored. reRender(); } else if (returnCode==VolatileImage.IMAGE_INCOMPATIBLE) { vImg = GraphicsConfiguration. createCompatibleVolatileImage(w, h); reRender(); } gScreen.drawImage(vImg, 0, 0, this); } while (vImg.contentsLost()); } public void reRender() { Graphics2D g2 = vImg.createGraphics(); // Miscellaneous rendering commands to restore // the image g2.dispose(); } |
If you would like more information about using the
Creating a Custom ButtonVolatileImage
class, check out Chet Haase's blog for an in-depth question-and-answer session.At this point, we can apply what we've learned to create a user interface (UI) button that blurs itself when it is not enabled. The following code demonstrates how to override the
BasicButtonUI
class to do just that. Note that you can also override the component's paintComponent()
method by subclassing a custom JButton
class to do the same thing. However, this example also shows how to use the pluggable look and feel of Java Foundation Classes/Swing (JFC/Swing), which is useful if you would like to create more advanced effects, such as displaying semitransparent menus and pop-ups.First, create a class that overrides the
BasicButtonUI
class in javax.swing.plaf.basic
, which we will call CustomButtonUI
. The source code for this class appears in Code Example 3. Note that it reuses the blurring convolution filter from Code Example 1. We want to override two methods: createUI()
, which tells Swing to use our custom button UI, and the paint()
method, which Swing calls upon to actually render our button.Code Example 3
public class CustomButtonUI extends BasicButtonUI { public static final float[] BLUR3x3 = { 0.1f, 0.1f, 0.1f, 0.1f, 0.2f, 0.1f, 0.1f, 0.1f, 0.1f }; public static ComponentUI createUI(JComponent c) { return new CustomButtonUI(); } public void paint(Graphics g, JComponent comp) { Graphics2D panelG2 = (Graphics2D)g; // Create a buffered image to hold the rendering // of the component that is passed in. BufferedImage image = new BufferedImage( comp.getWidth(), comp.getHeight(), BufferedImage.TYPE_INT_ARGB); // Draw the component onto the buffered image. Graphics2D g2 = image.createGraphics(); g2.setColor(g.getColor()); super.paint(g2, comp); // Draw the resulting buffered image onto the current // Graphics context with the same blurring convolution // kernel as in Code Example 1. if (!comp.isEnabled()) { Kernel kernel = new Kernel(3, 3, BLUR3x3); ConvolveOp cop = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); Image newImage = cop.filter(image, null); panelG2.drawImage(newImage, 0, 0, null); } else { panelG2.drawImage(image, 0, 0, null); } } } |
Next, use the static
UIManager.put()
method in your source code to indicate which class should be used for the button's UI.public class BlurredButton { public static void main(String[] args) { UIManager.put("ButtonUI", "CustomButtonUI"); JFrame frame = new JFrame("Button test"); frame.getContentPane().setBackground(Color.black); JButton button = new JButton("Test Enabled"); frame.add(button, BorderLayout.NORTH); JButton button2 = new JButton("Test Disabled"); button2.setEnabled(false); frame.add(button2, BorderLayout.SOUTH); frame.pack(); frame.setSize(200, 100); frame.setVisible(true); } } |
Có thể sửa một chút lớp BlurredButton.java như sau:
Figure 3 shows the result. Button đã bị làm mờ - blurring.
package java2dtest; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class BlurredButton { public static void main(String[] args) { UIManager.put("ButtonUI", "java2dtest.CustomButtonUI"); JFrame frame = new JFrame("Button test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setBackground(Color.black); final JButton button = new JButton("Test Enabled"); frame.add(button, BorderLayout.NORTH); final JButton button2 = new JButton("Test Disabled"); button2.setEnabled(false); frame.add(button2, BorderLayout.SOUTH); button2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { button2.setEnabled(!button2.isEnabled()); button.setEnabled(!button2.isEnabled()); } }); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { button.setEnabled(!button.isEnabled()); button2.setEnabled(!button.isEnabled()); } }); frame.pack(); frame.setSize(200, 100); frame.setVisible(true); } }
Figure 3 shows the result. Button đã bị làm mờ - blurring.
Summary
This article helped you learn a little about how the Java 2D API works with images using the
BufferedImage
class. You learned how the BufferedImage
class stores images and how to manipulate images at both the pixel level and using filter operations. You also learned how to use the VolatileImage
class to take advantage of hardware acceleration. Finally, we discussed how to use these classes in a custom Swing component. In the next article in this series, we will discuss how the Java 2D APIs manipulate and render text.Convolution Từ điển Anh-Việt dịch là sự quấn lại/cuộn lại, is the process of weighting or averaging the value of each pixel in an image with the values of neighboring pixels. Most spatial-filtering algorithms, including the 3x3 sharpening algorithm shown in Code Example 1, are based on convolution operations.
ConvolveOp Implements: BufferedImageOp, RasterOp. Uses a Kernel to perform a convolution on the source image. A convolution is a spatial operation where the pixels surrounding the input pixel are multiplied by a kernel value to generate the value of the output pixel. The Kernel mathematically defines the relationship between the pixels in the immediate neighborhood of the input pixel and the output pixel.
AffineTransformOp Implements: BufferedImageOp, RasterOp. A class that defines an affine transform to perform a linear mapping from 2D coordinates in a source Image or Raster to 2D coordinates in the destination image or Raster. This class can perform either bilinear or nearest neighbor affine transform operations.LookupOp Implements of BufferedImageOp, RasterOp. Performs a lookup operation from the source to the destination. For Rasters, the lookup operates on sample values. For BufferedImages, the lookup operates on color and alpha components. The filter method that is later invoked on an object of the LookupOp class to modify the image uses a color value from a pixel as an ordinal index into a lookup table. It replaces the color value in the pixel with the value stored in the lookup table at that index. Thus, you can modify the color values in the pixels using just about any substitution algorithm that you can devise.
RescaleOp Implements BufferedImageOp, RasterOp. Performs a pixel-by-pixel rescaling of the data in the source image by multiplying each pixel value by a scale factor and then adding an offset.
RasterOp Defines single-input/single-output operations performed on Raster objects. Implemented by AffineTransformOp, BandCombineOp, ColorConvertOp, ConvolveOp, LookupOp, and RescaleOp
BufferedImageOp Describes single-input/single-output operations performed on BufferedImage objects. Implemented by AffineTransformOp, ColorConvertOp, ConvolveOp, LookupOp, and RescaleOp.
volatile Bay hơi, hay fix lỗi mất dữ liệu ảnh.
<< Signed left shift (dịch trái số học)
>> Signed right shift (dịch phải số học)
>>> Unsigned right shift (dịch phải logic)
discrete Rời rạc
blurring Làm mờ nhạt
interpolation nội suy
Thanks for sharing this helpful article, I believe that converting from jpg file to png file is also a very important and necessary issue for everyone. If you don't know which tool to use online conversion and free jpg to png, I recommend using JPG4PNG.COM
ReplyDelete