Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 4622201
Votes 2
Synopsis javax.imageio cannot compress JPEG images in JDK 1.4
Category java:classes_2d
Reported Against merlin-beta3
Release Fixed 1.4.1(hopper)
State 10-Fix Delivered, bug
Priority: 4-Low
Related Bugs 4415068 , 4695512 , 4792112
Submit Date 11-JAN-2002
Description
There is a bug in the JPEGImageWriter such that it scales the JPEGQTables
incorrectly, making it difficult to compress a JPEG image.  See the attached
workaround for a temporary solution (before this bug is fixed in 1.4.1).
 xxxxx@xxxxx  2002-02-15




FULL PRODUCT VERSION :
java version "1.4.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta3-b84)
Java HotSpot(TM) Client VM (build 1.4.0-beta3-b84, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
There is a bug in the
com.sun.imageio.plugins.jpeg.JPEGImageWriter.write().
In the lines

            switch(param.getCompressionMode()) {
            case ImageWriteParam.MODE_DISABLED:
                throw new IIOException("JPEG compression cannot be disabled");
            case ImageWriteParam.MODE_EXPLICIT:
                float quality = param.getCompressionQuality();
                qTables = new JPEGQTable[2];
>>>>>           qTables[0] =JPEGQTable.K1Div2Luminance.getScaledInstance(quality, true);
                qTables[1] =JPEGQTable.K2Div2Chrominance.getScaledInstance(quality, true);
                break;

there is a call to
JPEGQTable.K1Div2Luminance.getScaledInstance(quality, true);

This method requires a quality value from 1 to 255.
Unfortunately, quality is a value between 0 and 1.
This restriction is enforced by
javax.imageio.ImageWriteParam.setCompressionQuality().

This means you can't compress jpeg images!

The fix is to either modify
com.sun.imageio.plugins.jpeg.JPEGImageWriter.write().
or
JPEGQTable.K1Div2Luminance.getScaledInstance()

to translate the quality to some value between 1 and 255.

Here is a test program if you need to test it out.
Run the program with

    java bug1 <input-jpg-file> <output-jpg-file>
<compression-factor>



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. run the supplied test program with compression values
from 0 to 1 using a jpeg image that's at least 50K in size.
you will not be able to compress it very much.
e.g. compression to 10K is impossible.

EXPECTED VERSUS ACTUAL BEHAVIOR :
using a compression value of .05 should result
in a very small-sized image file.

using a compression value of 1.0 should result
in a very large-sized image file.

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;
import javax.imageio.stream.*;

public class bug1 {
    public static void main(String[] args) {
        // Create a image to save

        // Write generated image to a file
        try {
//
            RenderedImage rendImage = ImageIO.read(new File(args[0]));

            File file = new File(args[1]);
            file.delete();
            ImageOutputStream ios = ImageIO.createImageOutputStream(file);
    
            ImageWriter writer = null;
            Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
            if (iter.hasNext()) {
                writer = (ImageWriter)iter.next();
            }

            writer.setOutput(ios);
            ImageWriteParam iwparam = writer.getDefaultWriteParam();
            iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
            System.out.println(iwparam.getCompressionQuality()) ;
            iwparam.setCompressionQuality(Float.parseFloat(args[2])) ;
            writer.write(null, new IIOImage(rendImage, null, null), iwparam);
            ios.flush();
            writer.dispose();
            ios.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

---------- END SOURCE ----------
(Review ID: 137909) 
======================================================================
Work Around
Rather than creating the ImageWriteParam via ImageWriter.getDefaultWriteParam(), create a new instance of the following class.  This will scale the user-defined
quality factor so that it is correctly interpreted by the JPEGImageWriter.  Note
that this is a temporary workaround for this bug (4622201) only; once this bug
is fixed in 1.4.1, the workaround will cause incorrect behavior and should not
be used.

--------
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;

public class MyJPEGImageWriteParam extends JPEGImageWriteParam {

    public MyJPEGImageWriteParam() {
        super(null);
    }

    public float getCompressionQuality() {
        float quality = compressionQuality;

        if (quality = 0.0f) {
            quality = 0.01f;
        }
        
        if (quality < 0.5f) {
            quality = 0.5f / quality;
        } else {
            quality = 2.0f - (quality * 2.0f);
        }

        return (quality * 2.0f);
    }
}
-------
 xxxxx@xxxxx  2002-02-15
Evaluation
Targetted for hopper.
 xxxxx@xxxxx  2002-01-17

The problem is as described.  There was already a static method in the helper
JPEG class (JPEG.java) called convertToLinearQuality(float quality) which scales
the quality factor so that it can be used properly in getScaledInstance().  This
call should be made before creating the scaled tables in JPEGImageWriter.write()
when we are in the MODE_EXPLICIT compression mode.  Another problem is that we
are using getScaledInstance() on the KxDiv2 luminance/chrominance tables, which
are already scaled by 0.5.  Instead, we should be using getScaledInstance() on
the original Kx tables.

Also note that there was already a regression test
(test/javax/imageio/plugins/jpeg/CompressionBug.java) in place to verify
that JPEG compression was working correctly.  This test started failing (see
bug 4415068), but the test was believed to be showing a false positive, so the
test was changed such that it passed, however incorrectly.  That test will have
to be reverted to its original correct behavior.

There is general confusion over the naming of the method setCompressionQuality()
(it is easy to get confused with words like "high compression", "small
file size", "high quality", etc.) so the API documentation (as well as the
revised regression test) should be clarified accordingly.
 xxxxx@xxxxx  2002-02-15
Comments
  
  Include a link with my name & email   

Submitted On 17-FEB-2002
raulv
If you read the evaluation for bug 4415068 carefully, you 
will see that the test was in fact incorrect, so the 
corrected version should stand.  The evaluation did 
mention that compression was not as high as 
expected, which was probably due to the current bug. 
The earlier regression test did not measure the 
amount of compression, merely whether the 
compressed file was smaller than the original. 


Submitted On 17-FEB-2002
raulv
The "other problem" mentioned above, that 
getScaledInstance is being used on the KxDiv2 tables, 
is a misconception.  The KxDiv2 tables are 
recommended by Pennebaker and Mitchell in the "Pink 
Book" as the tables to use for "visually lossless" 
compression, not the original Kx tables.  Most 
applications use the Kx tables divided by 2 as the 
"standard" tables.  So this is actually correct, not a bug.



PLEASE NOTE: JDK6 is formerly known as Project Mustang