Emad Omar
Emad Omar

Reputation: 768

Add JPEG encoder/decoder support to installed OpenJDK

I'm trying to scale a JPEG image and save it back using the following code, however, the resulting storage/test/thumbnail.jpg file is just an empty file. I checked the same code with PNG format and it works perfectly. However, for my use case I need a JPG image and not PNG.

import java.awt.geom.AffineTransform
import java.awt.image.AffineTransformOp
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO

fun main() {
    val file = File("storage/test/original.jpg")
    val image = ImageIO.read(file.inputStream())
    val thumbnail = thumbnailOf(image)
    val thumbnailFile = File("storage/test/thumbnail.jpg")
    thumbnailFile.parentFile.mkdirs()
    thumbnailFile.outputStream().use { output ->
        ImageIO.write(thumbnail, "JPEG", output)
    }
}

fun thumbnailOf(image: BufferedImage): BufferedImage {
    val width = image.width
    val height = image.height
    val ratio = width / height.toDouble()

    if (ratio == 0.0) {
        error("Cannot determine ratio for ($width*$height)")
    }

    val newWidth = 600
    val newHeight = (newWidth / ratio).toInt()
    val scaledImage = BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
    val at = AffineTransform()
    at.scale(newWidth / width.toDouble(), newHeight / height.toDouble())
    val ato = AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC)

    return ato.filter(image, scaledImage)
}

The best thing I was able to find so far is this answer https://stackoverflow.com/a/16026254/4112200 , which states that OpenJDK doesn't have a JPEG encoder. So, if that's the case, how can I add the JPEG encoder (or decoder) to my currently installed OpenJDK?

My currently installed OpenJDK

openjdk version "12" 2019-03-19
OpenJDK Runtime Environment (build 12+33)
OpenJDK 64-Bit Server VM (build 12+33, mixed mode, sharing)

Upvotes: 4

Views: 2062

Answers (1)

Emad Omar
Emad Omar

Reputation: 768

Alright, here's how I have solved this.

For my case, the issue didn't have anything to do with JPEG support. JPEG is already supported, however, there were some bugs in my code in addition I used a different approach to scale the image.

  1. The first bug is in this line:
val scaledImage = BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB)

ImageIO.write cannot find an ImageWriter that matches with BufferedImage.TYPE_INT_ARGB and JPG / JPEG. So, I changed it to BufferedImage.TYPE_INT_RGB.

  1. Fixing this causes an exception to be thrown at ato.filter(image, scaledImage)
Exception in thread "main" java.awt.image.ImagingOpException: Unable to transform src image

I don't know what's the reason for that exception and how to fix it, but using a Graphics2D object to scale and render the image worked for me.

  1. Even though a valid image file is generated, but it only contained black background. And that's because the supplied values of scale() were inconsistent.

The following is the final code where this works successfully, and how points 2 and 3 were fixed.

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

fun main() {
    val file = File("storage/test/original.jpg")
    val image = ImageIO.read(file.inputStream())
    val thumbnail = thumbnailOf(image)
    val thumbnailFile = File("storage/test/thumbnail.jpg")
    thumbnailFile.parentFile.mkdirs()
    thumbnailFile.outputStream().use { output ->
        ImageIO.write(thumbnail, "JPEG", output)
    }
}

fun thumbnailOf(image: BufferedImage): BufferedImage {
    val width = image.width
    val height = image.height
    val ratio = width / height.toDouble()

    if (ratio == 0.0) {
        error("Cannot determine ratio for ($width*$height)")
    }

    val newWidth = 600
    val newHeight = (newWidth / ratio).toInt()
    val scale = newWidth / width.toDouble()

    val scaledImage = BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB)

    val graph = scaledImage.graphics as Graphics2D
    // graph.scale((newWidth / width).toDouble(), (newHeight / height).toDouble())
    graph.scale(scale, scale)

    // everything drawn with graph from now on will get scaled.
    graph.drawImage(image, 0, 0, null)
    graph.dispose()

    return scaledImage
}

Upvotes: 2

Related Questions