Setting jpg compression level with ImageIO in Java
Asked Answered
D

5

60

I'm using javax.imageio.ImageIO to save a BufferedImage as a jpeg file. In particular, I created the following Java function:

public static void getScreenShot(BufferedImage capture, Path folder, String filename) {
        try {
            ImageIO.write(capture, "jpeg", new File(folder.toString()+"/"+filename+".jpg"));
        } catch (AWTException | IOException ex) {
            Logger.getLogger(ScreenShotMaker.class.getName()).log(Level.SEVERE, null, ex);
        }
}

Likewise any image manipulation software, I wish to change the compression level of the jpeg file. However, I'm searching for this option that seems to be missing in ImageIO.

Can I set the compression level and how?

Dinitrobenzene answered 14/6, 2013 at 12:12 Comment(0)
H
60

You have to use JPEGImageWriteParam and then save the image with ImageWriter.write(). Before to write, set the output via ImageWriter.setOutput.

Set the compression level as follows:

JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);

Where 1f is a float number that stands for 100% quality. Default value is around 70% if I don't remember wrong.

EDIT

Then, you have to do as follows to get an instance of an ImageWriter. There are two ways, a short and a long one (I keep both, just in case).

The short way (suggested by lapo in one comment) is:

final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
  new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level 
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);

or longer way

// use IIORegistry to get the available services
IIORegistry registry = IIORegistry.getDefaultInstance();
// return an iterator for the available ImageWriterSpi for jpeg images
Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class,
                                                 new ServiceRegistry.Filter() {   
        @Override
        public boolean filter(Object provider) {
            if (!(provider instanceof ImageWriterSpi)) return false;

            ImageWriterSpi writerSPI = (ImageWriterSpi) provider;
            String[] formatNames = writerSPI.getFormatNames();
            for (int i = 0; i < formatNames.length; i++) {
                if (formatNames[i].equalsIgnoreCase("JPEG")) {
                    return true;
                }
            }

            return false;
        }
    },
   true);
//...assuming that servies.hasNext() == true, I get the first available service.
ImageWriterSpi writerSpi = services.next();
ImageWriter writer = writerSpi.createWriterInstance();

// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
  new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level 
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);
Habakkuk answered 14/6, 2013 at 12:16 Comment(6)
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();Republicanism
Don't forget to close the FileImageOutputStream or dispose the ImageWriterDelegate
Does anyone know where I can reference what the exact default value is used?Paedo
@Paedo Use jpegParams.getCompressionQuality() without setting it. (You do still need to set jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) first.) The default, for me at least, is 0.75.Saragossa
Here is the documentation stating that the default value is 0.75.Saragossa
You need to close the FileImageOutputStreamBilliton
D
69

A more succinct way is to get the ImageWriter directly from ImageIO:

ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);

ImageOutputStream outputStream = createOutputStream(); // For example implementations see below
jpgWriter.setOutput(outputStream);
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();

The call to ImageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) is needed in order to explicitly set the compression's level (quality).

In ImageWriteParam.setCompressionQuality() 1.0f is maximum quality, minimum compression, while 0.0f is minimum quality, maximum compression.

ImageWriter.setOutput should be passed an ImageOutputStream. While the method accepts Object, according to documentation it's usually not supported:

Use of a general Object other than an ImageOutputStream is intended for writers that interact directly with an output device or imaging protocol. The set of legal classes is advertised by the writer's service provider's getOutputTypes method; most writers will return a single-element array containing only ImageOutputStream.class to indicate that they accept only an ImageOutputStream.

Most cases should be handled by these two classes:

  • FileImageOutputStream - an implementation of ImageOutputStream that writes its output directly to a File or RandomAccessFile.
  • MemoryCacheImageOutputStream - an implementation of ImageOutputStream that writes its output to a regular OutputStream. Usually used with ByteArrayOutputStream (thanks for the tip, @lmiguelmh!).
Dye answered 11/10, 2014 at 22:34 Comment(5)
+1, I wonder why the more complicated one with the manual filtering was 1. posted, 2. accepted and 3. not improved for such a long time...Undertrump
For those of you who don't want to write in disk: ByteArrayOutputStream baos = new ByteArrayOutputStream(); writer.setOutput(new MemoryCacheImageOutputStream(baos)); ... baos.flush(); byte[] returnImage = baos.toByteArray(); baos.close();Spline
It surprises me that I came back to this same question, I can't edit my comment now. If you don't do what I said earlier and use ByteArrayOutputStream directly You will get java.lang.IllegalArgumentException: Illegal output type!Spline
@lmiguelmh: you can't edit your comment after 5 minutes since its creation. Also, I'll update my answer to address the issue with output streams, thanks for bringing it up.Dye
I used jpgWriter.setOutput(ImageIO.createImageOutputStream(new File(path))) as something went wrong with FileImageOutputStream.Googins
H
60

You have to use JPEGImageWriteParam and then save the image with ImageWriter.write(). Before to write, set the output via ImageWriter.setOutput.

Set the compression level as follows:

JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);

Where 1f is a float number that stands for 100% quality. Default value is around 70% if I don't remember wrong.

EDIT

Then, you have to do as follows to get an instance of an ImageWriter. There are two ways, a short and a long one (I keep both, just in case).

The short way (suggested by lapo in one comment) is:

final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
  new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level 
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);

or longer way

// use IIORegistry to get the available services
IIORegistry registry = IIORegistry.getDefaultInstance();
// return an iterator for the available ImageWriterSpi for jpeg images
Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class,
                                                 new ServiceRegistry.Filter() {   
        @Override
        public boolean filter(Object provider) {
            if (!(provider instanceof ImageWriterSpi)) return false;

            ImageWriterSpi writerSPI = (ImageWriterSpi) provider;
            String[] formatNames = writerSPI.getFormatNames();
            for (int i = 0; i < formatNames.length; i++) {
                if (formatNames[i].equalsIgnoreCase("JPEG")) {
                    return true;
                }
            }

            return false;
        }
    },
   true);
//...assuming that servies.hasNext() == true, I get the first available service.
ImageWriterSpi writerSpi = services.next();
ImageWriter writer = writerSpi.createWriterInstance();

// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
  new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level 
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);
Habakkuk answered 14/6, 2013 at 12:16 Comment(6)
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();Republicanism
Don't forget to close the FileImageOutputStream or dispose the ImageWriterDelegate
Does anyone know where I can reference what the exact default value is used?Paedo
@Paedo Use jpegParams.getCompressionQuality() without setting it. (You do still need to set jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) first.) The default, for me at least, is 0.75.Saragossa
Here is the documentation stating that the default value is 0.75.Saragossa
You need to close the FileImageOutputStreamBilliton
E
1

A more general method will be (from Igor's answer) :

static void saveImage(BufferedImage image,File jpegFiletoSave,float quality) throws IOException{
    // save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to 70%

        ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
        ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
        jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        jpgWriteParam.setCompressionQuality(quality);

        jpgWriter.setOutput(ImageIO.createImageOutputStream(jpegFiletoSave));
        IIOImage outputImage = new IIOImage(image, null, null);
        jpgWriter.write(null, outputImage, jpgWriteParam);
        jpgWriter.dispose();

    }
Expertism answered 20/8, 2019 at 9:54 Comment(0)
G
0

Found same method in my ancient library:

/**
 * Work method.
 * Reads the jpeg image in rendImage, compresses the image, and writes it back out to outfile.
 * JPEGQuality ranges between 0.0F and 1.0F, 0-lowest, 1-highest. ios is closed internally
 *
 * @param rendImage   [@link RenderedImage} instance with an Rendered Image
 * @param ios         {@link ImageOutputStream} instance,
 *                    note that it is disposed in this method
 * @param JPEGQuality float value for the JPEG compression quality (0..1(max))
 * @return {@code true} if image was successfully compressed
 *         else {@code false} on any error, e.g. bad (null) parameters
 */
public static boolean compressJpegFile( RenderedImage rendImage, ImageOutputStream ios, float JPEGQuality )
{
    if ( rendImage == null )
        return false;
    if ( ios == null )
        return false;
    if ( ( JPEGQuality <= 0.0F ) || ( JPEGQuality > 1.0F ) )
        return false;
    ImageWriter writer = null;
    try
    {

        // Find a jpeg writer
        Iterator iter = ImageIO.getImageWritersByFormatName( "jpg" );
        if ( iter.hasNext() )
            writer = (ImageWriter) iter.next();

        if ( writer == null )
            throw new IllegalArgumentException( "jpg writer not found by call to ImageIO.getImageWritersByFormatName( \"jpg\" )" );
        writer.setOutput( ios );

        // Set the compression quality
        ImageWriteParam iwparam = new MyImageWriteParam();
        iwparam.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
        iwparam.setCompressionQuality( JPEGQuality );
        //      float res = iwparam.getCompressionQuality();

        // Write the image
        writer.write( null, new IIOImage( rendImage, null, null ), iwparam );

        return true;
    }
    catch ( Exception e )
    {
        return false;
    }
    finally
    {
        if ( writer != null )
            writer.dispose();
        // Cleanup
        try
        {
            ios.flush();
            ios.close();
        }
        catch ( IOException e )
        {
        }
    }
}
Godless answered 5/3, 2020 at 22:10 Comment(0)
F
0

If u require a byte[] output see a modified version of @user_3pij answer below:

    private static byte[] compressImageToByteArray(BufferedImage image, float quality)
        throws IOException {
    // save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to
    // 70%

    ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
    ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
    jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpgWriteParam.setCompressionQuality(quality);

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
    jpgWriter.setOutput(ios);
    IIOImage outputImage = new IIOImage(image, null, null);
    jpgWriter.write(null, outputImage, jpgWriteParam);
    byte[] result = bos.toByteArray();
    jpgWriter.dispose();
    return result;
}
Filmy answered 25/11, 2020 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.