What the Image API is
How to load an image
How to view an image in an ImageView node
How to perform image operations such as reading/writing pixels, creating an image from scratch, and saving the image to the file system
How to take the snapshot of nodes and scenes
What Is the Image API?
Load an image in memory
Display an image as a node in a scene graph
Read pixels from an image
Write pixels to an image
Convert a node in a scene graph to an image and save it to the local file system
An instance of the Image class represents an image in memory. You can construct an image in a JavaFX application by supplying pixels to a WritableImage instance.
An ImageView is a Node. It is used to display an Image in a scene graph. If you want to display an image in an application, you need to load the image in an Image and display the Image in an ImageView.
Images are constructed from pixels. Data for pixels in an image may be stored in different formats. A PixelFormat defines how the data for a pixel for a given format is stored. A WritablePixelFormat represents a destination format to write pixels with full pixel color information.
The PixelReader and PixelWriter interfaces define methods to read from an Image and write data to a WritableImage. Besides an Image, you can read pixels from and write pixels to any surface that contains pixels.
I will cover examples of using these classes in the sections to follow.
Loading an Image
An instance of the Image class is an in-memory representation of an image. The class supports BMP, PNG, JPEG, and GIF image formats. It loads an image from a source, which can be specified as a string URL or an InputStream. It can also scale the original image while loading.
Image(InputStream is)
Image(InputStream is, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth)
Image(String url)
Image(String url, boolean backgroundLoading)
Image(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth)
Image(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading)
In the preceding statement, the specified URL resources/picture/randomness.jpg is not a valid URL. The Image class will treat it as a path expecting it to exist in the CLASSPATH. It treats the resource.picture as a package and the randomness.jpg as a resource in that package.
Make sure to add valid URLs if you want to test the code snippets in this chapter. Either you make sure you use relative URLs like resources/picture/randomness.jpg that are in the CLASSPATH or specify absolute URLs like http://path/to/my/server/resources/picture/randomness.jpg or file://some/absolute/path/resources/picture/randomness.jpg .
Specifying the Image-Loading Properties
requestedWidth
requestedHeight
preserveRatio
smooth
backgroundLoading
The requestedWidth and requestedHeight properties specify the scaled width and height of the image. By default, an image is loaded in its original size.
The preserveRatio property specifies whether to preserve the aspect ratio of the image while scaling. By default, it is false.
The smooth property specifies the quality of the filtering algorithm to be used in scaling. By default, it is false. If it is set to true, a better quality filtering algorithm is used, which slows down the image-loading process a bit.
The backgroundLoading property specifies whether to load the image asynchronously. By default, the property is set to false, and the image is loaded synchronously. The loading process starts when the Image object is created. If this property is set to true, the image is loaded asynchronously in a background thread.
Reading the Loaded Image Properties
width
height
progress
error
exception
The width and height properties are the width and height of the loaded image, respectively. They are zero if the image failed to load.
Viewing an Image
ImageView()
ImageView(Image image)
ImageView(String url)
Displaying an Image in an ImageView Node
Multiple Views of an Image
An Image loads an image in memory from its source. You can have multiple views of the same Image. An ImageView provides one of the views.
Resizing an image in an Image object resizes the image permanently in memory, and all views of the image will use the resized image. Once an Image is resized, its size cannot be altered. You may want to reduce the size of an image in an Image object to save memory.
Resizing an image in an ImageView resizes the image only for this view. You can resize the view of an image in an ImageView even after the image has been displayed.
We have already discussed how to resize an image in an Image object. In this section, we will discuss resizing an image in an ImageView.
fitWidth
fitHeight
preserveRatio
smooth
The fitWidth and fitHeight properties specify the resized width and height of the image, respectively. By default, they are zero, which means that the ImageView will use the width and height of the loaded image in the Image.
The preserveRatio property specifies whether to preserve the aspect ratio of the image while resizing. By default, it is false.
The smooth property specifies the quality of the filtering algorithm to be used in resizing. Its default value is platform dependent. If it is set to true, a better quality filtering algorithm is used.
Displaying the Same Image in Different ImageView in Different Sizes
Viewing an Image in a Viewport
A viewport is a rectangular region to view part of a graphics. It is common to use scrollbars in conjunction with a viewport. As the scrollbars are scrolled, the viewport shows different parts of the graphics.
The ImageView class contains a viewport property, which provides a viewport into the image displayed in the ImageView. The viewport defines a rectangular region in the image. The ImageView shows only the region of the image that falls inside the viewport. The location of the viewport is defined relative to the image, not the ImageView. By default, the viewport of an ImageView is null, and the ImageView shows the whole image.
The Rectangle2D class is immutable. Therefore, you need to create a new viewport every time you want to move the viewport into the image.
Using a Viewport to View Part of an Image
The program declares a few class and instance variables. The VIEWPORT_WIDTH and VIEWPORT_HEIGHT are constants holding the width and height of the viewport. The startX and startY instance variables will hold the x and y coordinates of the mouse when the mouse is pressed or dragged. The ImageView instance variable holds the reference of the ImageView. We need this reference in the mouse-dragged event handler.
It is possible to scale or rotate the ImageView and set a viewport to view the region of the image defined by the viewport.
Understanding Image Operations
JavaFX supports reading pixels from an image, writing pixels to an image, and creating a snapshot of the scene. It supports creating an Image from scratch. If an image is writable, you can also modify the image in memory and save it to the file system. The image API provides access to each pixel in the image. It supports reading and writing one pixel or a chunk of pixels at a time. This section will discuss operations supported by the Image API with simple examples.
Pixel Formats
The Image API in JavaFX gives you access to each pixel in an image. A pixel stores information about its color (red, green, blue) and opacity (alpha). The pixel information can be stored in several formats.
An instance the PixelFormat<T extends Buffer> represents the layout of data for a pixel. You need to know the pixel format when you read the pixels from an image. You need to specify the pixel format when you write pixels to an image. The WritablePixelFormat class inherits from the PixelFormat class, and its instance represents a pixel format that can store full color information. An instance of the WritablePixelFormat class is used when writing pixels to an image.
Both class PixelFormat and its subclass WritablePixelFormat are abstract. The PixelFormat class provides several static methods to obtain instances to PixelFormat and WritablePixelFormat abstract classes. Before we discuss how to get an instance of the PixelFormat, let us discuss types of storage formats available for storing the pixel data.
BYTE_RGB
BYTE_BGRA
BYTE_BGRA_PRE
BYTE_INDEXED
INT_ARGB
INT_ARGB_PRE
In the BYTE_RGB format, the pixels are assumed opaque. The pixels are stored in adjacent bytes as red, green, and blue, in order.
In the BYTE_BGRA format, pixels are stored in adjacent bytes as blue, green, red, and alpha in order. The color values (red, green, and blue) are not pre-multiplied with the alpha value.
The BYTE_BGRA_PRE type format is similar to BYTE_BGRA, except that in BYTE_BGRA_PRE the stored color component values are pre-multiplied by the alpha value.
In the BYTE_INDEXED format, a pixel is as a single byte. A separate lookup list of colors is provided. The single byte value for the pixel is used as an index in the lookup list to get the color value for the pixel.
The INT_ARGB_PRE format is similar to the INT_ARGB format, except that INT_ARGB_PRE stores the color values (red, green, and blue) pre-multiplied with the alpha value.
Pixel format classes are not useful without pixel information. After all, they describe the layout of information in a pixel! We will use these classes when we read and write image pixels in the sections to follow. Their use will be obvious in the examples.
Reading Pixels from an Image
int getArgb(int x, int y)
Color getColor(int x, int y)
Void getPixels(int x, int y, int w, int h, WritablePixelFormat<ByteBuffer> pixelformat, byte[] buffer, int offset, int scanlineStride)
void getPixels(int x, int y, int w, int h, WritablePixelFormat<IntBuffer> pixelformat, int[] buffer, int offset, int scanlineStride)
<T extends Buffer> void getPixels(int x, int y, int w, int h, WritablePixelFormat<T> pixelformat, T buffer, int scanlineStride)
PixelFormat getPixelFormat()
The PixelReader interface contains methods to read one pixel or multiple pixels at a time. Use the getArgb() and getColor() methods to read the pixel at the specified (x, y) coordinate. Use the getPixels() method to read pixels in bulk. Use the getPixelFormat() method to get the PixelFormat that best describes the storage format for the pixels in the source.
The start() method creates an Image. The Image is loaded synchronously.
The logic to read the pixels is in the readPixelsInfo() method. The method receives a fully loaded Image. It uses the getColor() method of the PixelReader to get the pixel at a specified location. It prints the colors for all pixels. At the end, it prints the pixel format, which is BYTE_RGB.
Reading Pixels from an Image
The method reads the pixels from rows in order. The pixels in the first row are read, then the pixels from the second row, and so on. It is important that you understand the meaning of all parameters to the method.
The method reads the pixels of a rectangular region in the source.
The x and y coordinates of the upper-left corner of the rectangular region are specified in the x and y arguments.
The width and height arguments specify the width and height of the rectangular region.
The pixelformat specifies the format of the pixel that should be used to store the read pixels in the specified buffer.
The buffer is a byte array in which the PixelReader will store the read pixels. The length of the array must be big enough to store all read pixels.
The offset specifies the starting index in the buffer array to store the first pixel data. Its value of zero indicates that the data for the first pixel will start at index 0 in the buffer.
The scanlineStride specifies the distance between the start of one row of data in the buffer and the start of the next row of data. Suppose you have two pixels in a row, and you want to read in the BYTE_BGRA format taking 4 bytes for a pixel. One row of data can be stored in 8 bytes. If you specify 8 as the argument value, the data for the next row will start in the buffer just after the data for the previous row data ends. If you specify the argument value 10, the last 2 bytes will be empty for each row of data. The first row pixels will be stored from index 0 to 7. The indexes 8 and 9 will be empty (or not written). Indexes 10 to 17 will store pixel data for the second row leaving indexes 18 and 19 empty. You may want to specify a bigger value for the argument than needed to store one row of pixel data if you want to fill the empty slots with your own values later. Specifying a value less than needed will overwrite part of the data in the previous row.
The x and y coordinates of the upper-left corner of the rectangular region to be read are set to zero. The width and height of the region are set to the width and height of the image. This sets up the arguments to read the entire image.
You want to read the pixel data into the buffer starting at index 0, so you set the offset argument to 0.
You want to read the pixel data in BYTE_BGRA format type, which takes 4 bytes to store data for one pixel. We have set the scanlineStride argument value, which is the length of a row data, to width * 4, so a row data starts at the next index from where the previous row data ended.
You get an instance of the WritablePixelFormat to read the data in the BYTE_BGRA format type. Finally, we call the getPixels() method of the PixelReader to read the pixel data. The buffer will be filled with the pixel data when the getPixels() method returns.
Setting the value for the scanlineStride argument and the length of the buffer array depends on the pixelFormat argument. Other versions of the getPixels() method allow reading pixel data in different formats.
Reading Pixels from an Image in Bulk
Writing Pixels to an Image
You can write pixels to an image or any surface that supports writing pixels. For example, you can write pixels to a WritableImage and a Canvas.
An Image is a read-only pixel surface. You can read pixels from an Image. However, you cannot write pixels to an Image. If you want to write to an image or create an image from scratch, use a WritableImage.
An instance of the PixelWriter interface is used to write pixels to a surface. A PixelWriter is provided by the writable surface. For example, you can use the getPixelWriter() method of the Canvas and WritableImage to obtain a PixelWriter for them.
PixelFormat getPixelFormat()
void setArgb(int x, int y, int argb)
void setColor(int x, int y, Color c)
void setPixels(int x, int y, int w, int h, PixelFormat<ByteBuffer> pixelformat, byte[] buffer, int offset, int scanlineStride)
void setPixels(int x, int y, int w, int h, PixelFormat<IntBuffer> pixelformat, int[] buffer, int offset, int scanlineStride)
<T extends Buffer> void setPixels(int x, int y, int w, int h, PixelFormat<T> pixelformat, T buffer, int scanlineStride)
void setPixels(int dstx, int dsty, int w, int h, PixelReader reader, int srcx, int srcy)
The getPixelFormat() method returns the pixel format in which the pixels can be written to the surface. The setArgb() and setColor() methods allow for writing one pixel at the specified (x, y) location in the destination surface. The setArgb() method accepts the pixel data in an integer in the INT_ARGB format, whereas the setColor() method accepts a Color object. The setPixels() methods allow for bulk pixel writing.
WritableImage(int width, int height)
WritableImage(PixelReader reader, int width, int height)
WritableImage(PixelReader reader, int x, int y, int width, int height)
The third constructor lets you copy a rectangular region from a surface. The (x, y) value is coordinates of the upper-left corner of the rectangular region. The (width, height) value is the dimension of the rectangular region to be read using the reader and the desired dimension of the new image. An ArrayIndexOutOfBoundsException is thrown if the reader reads from a surface that does not have the necessary number of rows and columns to fill the new image.
The WritableImage is a read-write image. Its getPixelWriter() method returns a PixelWriter to write pixels to the image. It inherits the getPixelReader() method that returns a PixelReader to read data from the image.
Writing Pixels to an Image
It is easy to crop an image in JavaFX. Use one of the getPixels() methods of the PixelReader to read the needed area of the image in a buffer and write the buffer to a new image. This gives you a new image that is the cropped version of the original image.
Creating an Image from Scratch
In the previous section, we created new images by copying pixels from another image. We had altered the color and opacity of the original pixels before writing them to the new image. That was easy because we were working on one pixel at a time, and we received a pixel as a Color object. It is also possible to create pixels from scratch and then use them to create a new image. Anyone would admit that creating a new, meaningful image by defining its each pixel in code is not an easy task. However, JavaFX has made the process of doing so easy.
In this section, we will create a new image with a pattern of rectangles placed in a grid-like fashion. Each rectangle will be divided into two parts using the diagonal connecting the upper-left and lower-right corners. The upper triangle is painted in green and the lower in red. A new image will be created and filled with the rectangles.
Create an instance of the WritableImage.
Create buffer (a byte array, an int array, etc.) and populate it with pixel data depending on the pixel format you want to use for the pixel data.
Write the pixels in the buffer to the image.
Pixels are stored in the buffer in the row-first order. The variable i inside the loop computes the position in the buffer where the 3-byte data starts for a pixel. For example, the data for the pixel at (0, 0) starts at index 0; the data for the pixel at (0, 1) starts at index 3; etc. The 3 bytes for a pixel store red, green, and blue values in order of increasing index. Encoded values for the color components are stored in the buffer, so that the expression “byteValue & 0xff” will produce the actual color component value between 0 and 255. If you want a red pixel, you need to set –1 for the red component as “-1 & 0xff” produces 255. For a red color, the green and blue components will be set to zero. A byte array initializes all elements to zero. However, we have explicitly set them to zero in our code. For the lower-half triangle, we set the color to green. The condition “x =<= y/ratio” is used to determine the position of a pixel whether it falls in the upper-half triangle or the lower-half triangle. If the y/ratio is not an integer, the division of the rectangle into two triangles may be a little off at the lower-right corner.
Saving a New Image to a File System
Convert the Image to a BufferedImage using the fromFXImage() method of the SwingFXUtils class.
Pass the BufferedImage to the write() method of the ImageIO class.
A Utility Class to Save an Image to a File
Saving an Image to a File
Taking the Snapshot of a Node and a Scene
JavaFX allows you to take a snapshot of a Node and a Scene as they will appear in the next frame. You get the snapshot in a WritableImage, which means you can perform all pixel-level operations after you take the snapshot. The Node and Scene classes contain a snapshot() method to accomplish this.
Taking the Snapshot of a Node
WritableImage snapshot(SnapshotParameters params, WritableImage image)
void snapshot(Callback<SnapshotResult,Void> callback, SnapshotParameters params, WritableImage image)
A fill color
A transform
A viewport
A camera
A depth buffer
By default, the fill color is white; no transform and viewport are used; a ParallelCamera is used; and the depth buffer is set to false. Note that these attributes are used on the node only while taking its snapshot.
You can specify a WritableImage in the snapshot() method that will hold the snapshot of the node. If this is null, a new WritableImage is created. If the specified WritableImage is smaller than the node, the node will be clipped to fit the image size.
The first version of the snapshot() method returns the snapshot in a WritableImage. The image is either the one that is passed as the parameter or a new one created by the method.
WritableImage getImage()
SnapshotParameters getSnapshotParameters()
Object getSource()
The snapshot() method takes the snapshot of the node using the boundsInParent property of the node. That is, the snapshot contains all effects and transformations applied to the node. If the node is being animated, the snapshot will include the animated state of the node at the time it is taken.
Taking a Snapshot of a Node
Taking the Snapshot of a Scene
WritableImage snapshot(WritableImage image)
void snapshot(Callback<SnapshotResult,Void> callback, WritableImage image)
Compare the snapshot() methods of the Scene class with that of the Node class. The only difference is that the snapshot() method in the Scene class does not contain the SnapshotParameters argument. This means that you cannot customize the scene snapshot. Except this, the method works the same way as it works for the Node class, as discussed in the previous section.
The first version of the snapshot() method is synchronous, whereas the second one is asynchronous. You can specify a WritableImage to the method that will hold the snapshot of the node. If this is null, a new WritableImage is created. If the specified WritableImage is smaller than the scene, the scene will be clipped to fit the image size.
Taking a Snapshot of a Scene
Summary
JavaFX provides the Image API that lets you load and display images and read/write raw image pixels. All classes in the API are in the javafx.scene.image package. The API lets you perform the following operations on images: load an image in memory, display an image as a node in a scene graph, read pixels from an image, write pixels to an image, and convert a node in a scene graph to an image and save it to the local file system.
An instance of the Image class is an in-memory representation of an image. You can also construct an image in a JavaFX application by supplying pixels to a WritableImage instance. The Image class supports BMP, PNG, JPEG, and GIF image formats. It loads an image from a source, which can be specified as a string URL or an InputStream. It can also scale the original image while loading.
An instance of the ImageView class is used to display an image loaded in an Image object. The ImageView class inherits from the Node class, which makes an ImageView suitable to be added to a scene graph.
Images are constructed from pixels. JavaFX supports reading pixels from an image, writing pixels to an image, and creating a snapshot of the scene. It supports creating an image from scratch. If an image is writable, you can also modify the image in memory and save it to the file system. The Image API provides access to each pixel in the image. It supports reading and writing one pixel or a chunk of pixels at a time.
Data for pixels in an image may be stored in different formats. A PixelFormat defines how the data for a pixel for a given format is stored. A WritablePixelFormat represents a destination format to write pixels with full pixel color information.
The PixelReader and PixelWriter interfaces define methods to read data from an Image and write data to a WritableImage. Besides an Image, you can read pixels from and write pixels to any surface that contains pixels.
JavaFX allows you to take a snapshot of a Node and a Scene as they will appear in the next frame. You get the snapshot in a WritableImage, which means you can perform all pixel-level operations after you take the snapshot. The Node and Scene classes contain a snapshot() method to accomplish this.
The next chapter will discuss how to draw on a canvas using the Canvas API.