Image processing

Image processing

To process images such as photos, we need to be able to load photo files into an image object, read and set pixels values of the image, and save the image back into a photo file.

We use the class java.awt.image.BufferedImage to store image data. You can create such an object by saying:

  import java.awt.image.BufferedImage

  val img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)

Or you can load an image from a file using:

  import javax.imageio.ImageIO

  val photo1 = ImageIO.read(new File("photo.jpg"))

The result of this call is a again a BufferedImage object. You can find the width and height of this image using the following methods:

  printf("Photo size is %d x %d\n, photo1.getWidth, photo1.getHeight)

You can save an image object into a file using:

  ImageIO.write(photo2, "jpg", new File("test.jpg"))
or, if you prefer PNG format:
  ImageIO.write(photo2, "png", new File("test.png"))

The pixels of a java.awt.image.BufferedImage object can be read and changed using the getRGB and setRGB methods, see below.

Example script

The following small script reads a file photo.jpg, get its dimensions, creates a new empty image of the same size, copy the pixels from the old image img to the new image out (mirroring horizontally), draws a red line diagonally across the photo, and finally saves the new image in a file test.jpg (image.scala):

import java.io.File
import javax.imageio.ImageIO
import java.awt.image.BufferedImage

def phototest(img: BufferedImage): BufferedImage = {
  // obtain width and height of image
  val w = img.getWidth
  val h = img.getHeight

  // create new image of the same size
  val out = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)

  // copy pixels (mirror horizontally)
  for (x <- 0 until w)
    for (y <- 0 until h)
      out.setRGB(x, y, img.getRGB(w - x - 1, y) & 0xffffff)
  // draw red diagonal line
  for (x <- 0 until (h min w))
    out.setRGB(x, x, 0xff0000)

def test() {
  // read original image, and obtain width and height
  val photo1 = ImageIO.read(new File("photo.jpg"))
  val photo2 = phototest(photo1) 

  // save image to file "test.jpg"
  ImageIO.write(photo2, "jpg", new File("test.jpg"))



The method getRGB(x: Int, y: Int): Int returns the color of the pixel at position (x, y), the method setRGB(x: Int, y: Int, color: Int) sets the color of this pixel.

Colors are represented as Int objects. The three components red, green, and blue are "packed" together into one integer. Each component has 8 bits, and therefore can have a value between 0 and 255. The packed color is a 32-bit integer, whose bits look like this:

  tttt tttt rrrr rrrr gggg gggg bbbb bbbb
The top 8 bits are either zero, or represent the "transparency" of the pixel. We will not use these transparency bits. The next 8 bits represent red, the next 8 bits represent green, and the last 8 bits represent blue. This is why this representation is called "RGB".

Given red, green, and blue components with values in the range 0 to 255, we can pack them together like this:

  val color = (red * 65536) + (green * 256) + blue
Given a packed integer color, we can extract the three components as follows:
  val red = (color & 0xff0000) / 65536
  val green = (color & 0xff00) / 256
  val blue = (color & 0xff)
(The & operator here is the bitwise-and operator. It makes sure that only the bits we are interested in are used.)