In many cases, the images we are going to use need some adjustments. These can span from rotating the images to changing their size. In this topic, a few but of much interest transformations will be examined.
Image resizing
There are many methods for resizing an image. Here we will rely on the powerful drawImage() method of the java.awt.Graphics2D library. It is used for drawing an image at a certain position of the target image. The function can be called with various parameters. The form of the function that we are going to use is the following:
drawImage(image: Image, x: Int, y: Int, width: Int, height: Int, observer: ImageObserver)
Here image is the image we are going to draw on the target image, x and y denote the coordinates of the top-left starting position of the target image, width and height denote the width and height of the drawn image on the target image respectively. The observer option is used to notify the application of any updates to an asynchronously loaded picture; usually set to null.
A characteristic feature of this function, which we will use for resizing, is that the drawn image is scaled to fit into the provided width and height parameters. Following is an example function that returns a resized image of the input image, with the provided width and height dimensions. The dispose() function is used to free the resources attached to theGraphics2D instance.
fun resizeImage(image: BufferedImage, width: Int, height: Int): BufferedImage {
// Create an image with the new dimensions
val resized = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
// Get a Graphics2D instance
val graphics = resized.createGraphics()
// Draw the scaled image
graphics.drawImage(image, 0, 0, width, height, null)
// Dispose of the Graphics2D instance and release any system resources it is using
graphics.dispose()
return resized
}
Note that the aspect ratio of the target image doesn't have to be the same as of the original image. In case the dimensions of the target image are smaller than the scaled source image, the scaled image is cropped.
Following is an example of resizing an original image to a smaller one .
Another method requires the use of the java.awt.Graphics2D class scale() method:
scale(sx: Double, sy: Double)
Here sx and sy are the scaling factors in the horizontal and the vertical direction respectively.
We will also need a simpler form of the drawImage() method (without the dimension parameters), that is:
drawImage(image: Image, x: Int, y: Int, observer: ImageObserver)
The scale() method defines the factor per dimension, which will be used by the drawImage() to copy the image over another one, like in the following example:
fun resizeImage(image: BufferedImage, factor: Double): BufferedImage {
// Dimensions of the new image
// If the target is smaller than these dimensions,
// then the image will be cropped
val w = (factor * image.width).toInt()
val h = (factor * image.height).toInt()
// Create an image with the new dimensions
val resized = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
// Get a Graphics2D instance
val graphics = resized.createGraphics()
// Set the scaling factor
graphics.scale(factor, factor)
// Draw the scaled image
graphics.drawImage(image, 0, 0, null)
// Dispose of the Graphics2D instance and release any system resources it is using
graphics.dispose()
return resized
}Image rotation
Rotating an image is similar to the latter method we have used for resizing; the difference is in using the java.awt.Graphics2D class translate() and rotate() methods instead of scale(). The simplest form of the rotate() method is the following:
rotate(theta: Double)
Here theta is the rotation angle in radians. A positive value denotes clockwise rotation around the top-left corner. The form of the translate() method that we are going to use is the following:
translate(x: Int, y: Int)
Here (x, y) is the new origin of the Graphics context coordinate system. Actually, it means moving x pixels in the horizontal direction and of y pixels in the vertical direction.
An example code for rotating an image counterclockwise by 90 degrees (-90) is the following:
fun rotateLeftImage(image: BufferedImage): BufferedImage {
// Get the dimensions of the image
val w = image.width
val h = image.height
// Create an image with inverted dimensions
val rotatedLeft = BufferedImage(h, w, BufferedImage.TYPE_INT_RGB)
// Get a Graphics2D instance
val graphics = rotatedLeft.createGraphics()
// Move down by w
graphics.translate(0, w)
// Rotate 90 degrees counterclockwise
// Degrees are changed to radians
graphics.rotate(-90.0 * PI / 180.0)
// Draw the rotated image
graphics.drawImage(image, 0, 0, null)
// Dispose of the Graphics2D instance and release any system resources it is using
graphics.dispose()
return rotatedLeft
}
Below is an example of an image rotated left by 90 degrees.
An image can be rotated by any degree. However, mind the target image dimensions so that no parts of the image are cropped.
Image mirroring
An image can be mirrored across one of its horizontal or vertical sides (axes of symmetry). This is a simple process that consists of correctly copying the pixels of the original image to their symmetrical position across the axis of symmetry. Following is a function that deploys just that. It returns a BufferedImage instance of the mirrored original image across the vertical side.
fun mirrorYAxis(image: BufferedImage): BufferedImage {
// Create a BufferedImage instance with the same parameters as the image
val mirrored = BufferedImage(image.width, image.height, image.type)
for (x in 0 until image.width) {
for (y in 0 until image.height) {
val color = Color(image.getRGB(x, y))
// Copy each pixel to its mirrored position across the image's vertical side
mirrored.setRGB(image.width - x - 1, y , color.rgb)
}
}
return mirrored
}
Following is an example of using the above function.
Image mirroring can be accomplished with the use of another form of the drawImage() function, which is the following:
drawImage(img: Image,
dx1: Int, dy1: Int, dx2: Int, dy2: Int,
sx1: Int, sy1: Int, sx2: Int, sy2: Int,
observer: ImageObserver)
(dx1, dy1) is the position of the top-left corner on the target image where the corner position (sx1, sy1) of the source image is going to be drawn. (dx2, dy2) is the position of the bottom-right corner of the target image where the corner position (sx2, sy2) of the source image is going to be drawn. This provides us many options of how to transform the original image.
The following code has the same result as the previous code snippet.
fun mirrorYAxis(image: BufferedImage): BufferedImage {
// Get the image dimensions
val w = image.width
val h = image.height
// Create a BufferedImage instance with the same parameters as the image
val resized= BufferedImage(w, h, image.type)
// Get a Graphics2D instance
val graphics = resized.createGraphics()
// The top-right corner of the source image goes to the top-left corner of the target image,
// the bottom-left corner of the source image goes to the bottom-right corner of the target image
graphics.drawImage(image, 0, 0, w - 1, h - 1, w - 1, 0, 0, h - 1, null)
// Dispose of the Graphics2D instance and release any system resources it is using
graphics.dispose()
return resized
}
By changing the parameters of the drawImage() function, we can get the mirror of the original image across the horizontal side. For example:
// Bottom-left corner of the source image to the top-left corner of the target image,
// top-right corner of the source image to the bottom-right corner of the target image
graphics.drawImage(image, 0, 0, w - 1, h - 1, 0, h - 1, w - 1, 0, null)Conclusion
We have looked into some fundamental image transformations which can be used in most cases. These transformations are by no means limited to the methods presented here, but what you've learned is the basis for learning more about this subject.