user1625837 user1625837 - 1 year ago 105
Java Question

Java AWT/ImageIO: Bilinear and Bicubic scaling of a JPEG image result in completely black output

Nearest neighbor scaling works: The entire picture stays intact when I use


Even though it is Scala code, all used libraries are standard Java libraries.


def getBufferedImage(imageFile: BufferedImage = {

def scaleImage(image: BufferedImage, minSize: Double): BufferedImage = {
val before: BufferedImage = image
val w = before.getWidth()
val h = before.getHeight()
val affit = new AffineTransform()
var scale = 1.0
if(h < w) {
if(h > 0) {
scale = minSize / h
} else {
if(w > 0) {
scale = minSize / w
affit.scale(scale, scale)
val affitop = new AffineTransformOp(affit, AffineTransformOp.TYPE_BICUBIC)
affitop.filter(before, null)

def getImageJpegByteArray(image: BufferedImage): Array[Byte] = {
val baos = new
val mcios = new MemoryCacheImageOutputStream(baos)
ImageIO.write(image, "jpeg", mcios)

Calling code snippet:

val img = getBufferedImage(imageFile)
val scaledImg = scaleImage(img, 512)
val result = getImageJpegByteArray(scaledImg)
// result is written to SQLite database

is written to an SQLite database. If I download it from the database and save it as JPEG file, the resulting JPEG is

  • as expected if I use

  • completely black if I use

  • completely black if I use

Consequently, I accuse
of being buggy...

How can I solve this problem?

File magic number of
is always
ff d8 ff
as expected for JPEG.


Java version: Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_71

Operating System: Apple, OS X 10.9.5

Test image:

Answer Source

I was able to reproduce your issue on Java 1.7.0_71 on OS X 10.10.4 (I rewrote your code in Java, I can post the full code if you are interested).

In any case, the problem is not that AffineTransformOp is buggy in itself. In my test program I displayed the image using a minimal Swing JFrame and the scaled image looked all good there. This is likely why most people in the comments did not understand the problem.

Part of the issue is that the BufferedImage returned by AffineTransformOp when you don't provide a destination to the filter method (the second parameter, null in your case), it will create one for you. This image will get type BufferedImage.TYPE_INT_ARGB. Here is the relevant code from AffineTransformOp.createCompatibleDestImage() (lines 456-468, I kept the formatting, to make it easier to spot):

ColorModel cm = src.getColorModel();
if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
    (cm instanceof IndexColorModel ||
     cm.getTransparency() == Transparency.OPAQUE)
    image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
else {
    image = new BufferedImage(cm,
              cm.isAlphaPremultiplied(), null);

Notice the special case for TYPE_NEAREST_NEIGHBOR, which explains why you'll get different behavior when using nearest neighbor algorithm. Normally this is all good, however (as I said, the image displays just fine in a Swing component).

The problem arises when you try to store this image as a JPEG. During the years, there's been a lot of confusion and issues related to the ImageIO JPEG plugin and whether it will allow you to write images with alpha channel (like your TYPE_INT_ARGB image). It does allow that. But, most often ARGB JPEGs will get misinterpreted as CMYK JPEGs (as they are 4 channels, and storing ARGB data in JPEG is very exotic) and will be displayed in all funky colors. In your case though, it seems to be all black...

So, there are two possible solutions:

  • Either write your image in a file format that supports alpha channel, like PNG or TIFF (TIFF requires an extra plugin, so it might not be the best choice). Like this:

    ImageIO.write(image, "PNG", mcios);
  • Or, make sure your BufferedImage is in a pixel format without alpha channel before storing as JPEG. You can do this after the scaling, but the easiest (and fastest) is to just provide the AffineTransformOp with an explicit destination image, like this:

    Rectangle newSize = affitop.getBounds2D(before).getBounds();
    return affitop.filter(before, 
          new BufferedImage(newSize.width,  newSize.height, BufferedImage.TYPE_3BYTE_BGR));

Here is your image, scaled by the program, using JPEG format and the TYPE_3BYTE_BGR:

Scaled image

I'm sure you can rewrite my Java code back to Scala. :-)