Setting Jpg Compression Level with Imageio in Java

Setting jpg compression level with ImageIO in Java

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);

Java BufferedImage JPG compression without writing to file

Just pass your ByteArrayOutputStream to ImageIO.createImageOutputStream(...) like this:

// The important part: Create in-memory stream
ByteArrayOutputStream compressed = new ByteArrayOutputStream();

try (ImageOutputStream outputStream = ImageIO.createImageOutputStream(compressed)) {

// NOTE: The rest of the code is just a cleaned up version of your code

// Obtain writer for JPEG format
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("JPEG").next();

// Configure JPEG compression: 70% quality
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);

// Set your in-memory stream as the output
jpgWriter.setOutput(outputStream);

// Write image as JPEG w/configured settings to the in-memory stream
// (the IIOImage is just an aggregator object, allowing you to associate
// thumbnails and metadata to the image, it "does" nothing)
jpgWriter.write(null, new IIOImage(image, null, null), jpgWriteParam);

// Dispose the writer to free resources
jpgWriter.dispose();
}

// Get data for further processing...
byte[] jpegData = compressed.toByteArray();

PS: By default, ImageIO will use disk caching when creating your ImageOutputStream. This may slow down your in-memory stream writing. To disable it, use ImageIO.setCache(false) (disables disk caching globally) or explicitly create an MemoryCacheImageOutputStream (local), like this:

ImageOutputStream outputStream = new MemoryCacheImageOutputStream(compressed);

Can't get JPEG compression to work with ImageIO

You have to use a different write() overload and pass an IIOImage and your custom params to it:

ImageWriter writer  = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam param = writer.getDefaultWriteParam();

param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);

writer.setOutput(new FileImageOutputStream(
new File(String.format("screen.%.1f.jpg", quality))));
writer.write(null, new IIOImage(image, null, null), param);
writer.dispose();

Quality loss using ImageIO.write

Use ImageWriter.

ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Needed see javadoc
param.setCompressionQuality(1.0F); // Highest quality
writer.write(image);

For non-photo images use .png.


As @PiotrekDe commented, the following seems more logical.

writer.write(null, new IIOImage(image, null, null), param)

Java how to set jpg quality

Finally did it with this code ...

try
{

ImageOutputStream ios = ImageIO.createImageOutputStream(var7);
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(0.85f);
writer.setOutput(ios);
writer.write(null, new IIOImage(var6,null,null),iwp);
writer.dispose();

//ImageIO.write(var6, "jpg", var7);
}

BufferedImage reduces Image size

JPEG is lossy compression: it throws away lots of information in order to keep the file small.  (An uncompressed image file could be orders of magnitude larger.)

It's intended to throw away information that you're not likely to see or care about, of course; but it still loses some image data.

And the loss is generational: if you have an image that came from a JPEG file, and then recompress it to a JPEG file, it will usually lose more data, giving a worse-quality result than the first JPEG file — even if the compression settings are exactly the same.  (Trying to approximate an already-compressed image won't work the same as trying to approximate the original source image. And there's no way to recover information which is already lost!)

That's almost certainly what's happening here.  Your code reads a JPEG file and expands it into a BufferedImage (which holds the uncompressed image data), and then compresses it again into a new JPEG file, which loses further quality.  It's probably using a lot higher compression than the first file used, hence the smaller size.

I'd be surprised if you couldn't see any difference between the two JPEG files in an image viewer or editor, when magnified.  (JPEG artefacts are most obvious around sharp edges and boundaries, but if you know what to look for you can sometimes see them elsewhere.  Subtle changes can be easier to see if you can line up both images on the exact same area of screen and flip directly between them.)

You can control how much information is lost when creating a JPEG — but the ImageIO.write() method you're using doesn't provide a way to do that.  See this question for how to do it.  (It's in Java, but you should be able to follow it.)

Obviously, the more information you're prepared to lose, the smaller file you can end up with.  But note that if you choose a high-quality setting, the result could be a lot larger than the first JPEG, even though it will probably still lose slightly more quality.

(That's why, if you're doing any sort of processing on an image, it's best to keep it in lossless formats until the very end, and compress to a lossy format like JPEG only once, to avoid losing quality each time you save and reload.)

As you indicate, another reason could be the loss of non-image data — you're unlikely to notice the loss of metadata such as camera settings, but the file could have had a sizeable thumbnail image too.

Why does reading and writing a JPEG with Java ImageIO reduce the file size?

That code isn't just copying (reading and saving) files. It's decoding the images, and then re-encoding them at the default JPEG compression rate (which, judging by the JPEGImageWriteParam documentation, is 0.75).

If you want to change the compression level, check out this question.

If you're trying to copy the files exactly, don't use ImageIO at all.

java image compression for any image format(jpg, PNG, gif)

The problem with your code is that you used ImageWriter.getDefaultWriteParam(). From the following quote from ImageWriter.java:

public ImageWriteParam getDefaultWriteParam()

Returns a new ImageWriteParam object of the appropriate type for this
file format containing default values, that is, those values that
would be used if no ImageWriteParam object were specified. This is
useful as a starting point for tweaking just a few parameters and
otherwise leaving the default settings alone.

The default implementation constructs and returns a new
ImageWriteParam object that does not allow tiling, progressive
encoding, or compression, and that will be localized for the current
Locale (i.e., what you would get by calling new
ImageWriteParam(getLocale()).

Individual plug-ins may return an instance of ImageWriteParam with
additional optional features enabled, or they may return an instance
of a plug-in specific subclass of ImageWriteParam.

The actual behavior depends on individual implementation of specific ImageWriteParam. I believe the reason JPG image works is that the ImageWriteParam for JPG set the canWriteCompressed protected field for the default ImageWriteParam but for PNG image, for some reason it doesn't do that.

If you look at the com.sun.imageio.plugins.png.PNGImageWriteParam.java, you will find it indeed doesn't set it.

In order to make your code work generally, you can do like this:

  File input = new File("digital_image_processing.jpg");
BufferedImage image = ImageIO.read(input);

File compressedImageFile = new File("compress.jpg");
OutputStream os = new FileOutputStream(compressedImageFile);

Iterator<ImageWriter>writers = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = (ImageWriter) writers.next();

ImageOutputStream ios = ImageIO.createImageOutputStream(os);
writer.setOutput(ios);

ImageWriteParam param = writer.getDefaultWriteParam();
// Check if canWriteCompressed is true
if(param.canWriteCompressed()) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.05f);
}
// End of check
writer.write(null, new IIOImage(image, null, null), param);

Update: I was trying to find out a way for you to fine-tune PNG image compression within the framework of ImageIO. But unfortunately, given the current implementation, it seems next to impossible. Java PNG writer plugin has fixed filtering - adaptive filtering and fixed deflater level - 9 which is the best thing it could do.

Convert PNG to JPEG, Using Compression Quality, Why Inverted colors?

Try and write newBufferedImage and not bufferedImageFile on "Code part 2".

So change this line to be:

writer.write(null, new IIOImage(newBufferedImage, null, null), jpegParams);

If you try and write a PNG image in JPEG format your going to end up with weird results.



Related Topics



Leave a reply



Submit