ImageWriteParam set compression quality not working - java

I am trying to create an app that combines several JPG into one big JPG without any compression. But in my test sample, when I combine 8 images (800KB), the output is only 280KB.
I used this as reference in writing the code. But no matter what value I inserted to param.setCompressionQuality(...), the output is always 280KB and doesn't change.
String name = dir.getName();
System.out.println("Combining images... #" + name);
BufferedImage result = combine(dir);
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(1.0F); // set highest quality
try {
File outputFile = new File(outputFolder + "\\" + name + ".JPG");
ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile);
writer.setOutput(ios);
writer.write(result);
} catch (IOException e) {
e.printStackTrace();
}
dir is a File type variable indicating my source folder location.
combine() is a method that returns BufferedImage.

The reason why it doesn't work, is because you need to pass the ImageWriteParam (param in your code) to the write method to have an effect.
The getDefaultWriteParam() method will only create it for you, it will not stay "attached" to the writer. See the ImageWriter.write API doc for further information.
The code will then look like this:
BufferedImage result = ...
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
...
writer.setOutput(...);
writer.write(null, new IIOImage(result, null, null), param); // nulls are metadata and thumbnails, don't worry :-)

Related

Meta data is missing after creating BufferedImage

I am writing meta data inside PNG image using below code:
public synchronized static byte[] writeMetaDataInPNGImage(BufferedImage buffImg,
String key, String value)
{
byte[][] image = null;
try
{
ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier
.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
// adding metadata
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
textEntry.setAttribute("keyword", key);
textEntry.setAttribute("value", value);
IIOMetadataNode text = new IIOMetadataNode("tEXt");
text.appendChild(textEntry);
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0");
root.appendChild(text);
int width = buffImg.getWidth();
int height = buffImg.getHeight();
metadata.mergeTree("javax_imageio_png_1.0", root);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageOutputStream stream = ImageIO.createImageOutputStream(baos);
writer.setOutput(stream);
writer.write(metadata, new IIOImage(buffImg, null, metadata), writeParam);
stream.close();
return baos.toByteArray();
// return ImageIO.read(new ByteArrayInputStream(baos.toByteArray()));
}
catch (Exception e)
{
System.out.println("Exception while writing \t " + e.getMessage() + " :: "
+ e.getStackTrace());
e.printStackTrace();
}
return null;
}
After writing meta data I am returning byte array with meta data and image data.
byte[] pngjdata = writeMetaDataInPNGImage(img.getAsBufferedImage(),"key", "hello");
If I save the image with pngjdata[] I am able to see the meta data inside image, but if I create BufferedImage from this byte array and save it I am not seeing written meta data.
InputStream in1 = new ByteArrayInputStream(pngjdata);
BufferedImage brImage = ImageIO.read(in1);
Why is brImage not having the meta data written by me?
Why is brImage not having the meta data written by me?
A BufferedImage does not contain the meta data you are looking for. The BufferedImage instance just represents pixel values, color model, sample model etc., or in other words the data necessary to display the image. There's also no API to set/get meta data (but it could be you are confused by the getProperty(name) and related methods that is inherited from the legacy AWT Image API).
Meta data in the ImageIO API is represented as instances of IIOMetadata and it's various DOM-like forms you can obtain via the getAsTree(..) method (like "javax_imageio_png_1.0" for PNG, or the "standard" "javax_imageio_1.0" format).
To keep both pixel data and meta data organized together in your application, you can use the IIOImage class.
You can read both pixel data and meta data together, using the ImageReader.readAll(index, param) method. You can write both at the same time, using ImageWriter.write(null, iioImage, param), like you already do above (just note that the first param to the write method is the stream meta data, which is different from the image meta data, for PNG just pass null).

How to reduce size of a multipart file in java

I have Java Spring MVC application in which there is an option to upload an image and save to the server. i have the following method:
#RequestMapping(value = "/uploaddocimagecontentsubmit", method = RequestMethod.POST)
public String createUpdateFileImageContentSubmit(#RequestParam("file") MultipartFile file, ModelMap model)
{
//methods to handle file upload
}
I am now trying to reduce the size of the image refering the following:
increasing-resolution-and-reducing-size-of-an-image-in-java and decrease-image-resolution-in-java
The problem I am facing is that in the above examples, we are dealing with java.io.File Objects which are saved to a specified location. I dont want to save the image. Is there any way that I can use something similar to compress my Multipart Image file and continue with the upload.
Why don't you resize it on the client before upload? That will save some bandwidth
BlueImp JQuery Upload can do this
It was my first time taking a deep dive into the ImageIO package. I came across the MemoryCacheImageOutputStream, which allows you to write an image output stream to an output stream, i.e. ByteArrayOutputStream. From there, The data can be retrieved using toByteArray() and toString(), after compression. I used toByteArray, as I am storing images to postgresql and it stores the images as a byte array. I know this is late, but I hope it helps someone.
private byte[] compressImage(MultipartFile mpFile) {
float quality = 0.3f;
String imageName = mpFile.getOriginalFilename();
String imageExtension = imageName.substring(imageName.lastIndexOf(".") + 1);
// Returns an Iterator containing all currently registered ImageWriters that claim to be able to encode the named format.
// You don't have to register one yourself; some are provided.
ImageWriter imageWriter = ImageIO.getImageWritersByFormatName(imageExtension).next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Check the api value that suites your needs.
// A compression quality setting of 0.0 is most generically interpreted as "high compression is important,"
// while a setting of 1.0 is most generically interpreted as "high image quality is important."
imageWriteParam.setCompressionQuality(quality);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// MemoryCacheImageOutputStream: An implementation of ImageOutputStream that writes its output to a regular
// OutputStream, i.e. the ByteArrayOutputStream.
ImageOutputStream imageOutputStream = new MemoryCacheImageOutputStream(baos);
// Sets the destination to the given ImageOutputStream or other Object.
imageWriter.setOutput(imageOutputStream);
BufferedImage originalImage = null;
try (InputStream inputStream = mpFile.getInputStream()) {
originalImage = ImageIO.read(inputStream);
} catch (IOException e) {
String info = String.format("compressImage - bufferedImage (file %s)- IOException - message: %s ", imageName, e.getMessage());
logger.error(info);
return baos.toByteArray();
}
IIOImage image = new IIOImage(originalImage, null, null);
try {
imageWriter.write(null, image, imageWriteParam);
} catch (IOException e) {
String info = String.format("compressImage - imageWriter (file %s)- IOException - message: %s ", imageName, e.getMessage());
logger.error(info);
} finally {
imageWriter.dispose();
}
return baos.toByteArray();
}

Compression of PNG using JAI (Java Advanced Imaging)

I am using javax.imageio API and JAI for compressing different types of images. It works fine for JPEG using JPEGImageWriter and GIF using GIFImageWriter. But it is not supporting for PNG compression using PNGImageWriter which throws an exception like compression type is not set or "No valid compression", etc. So I used this below ImageWriter for PNG. It works but image quality is too bad.
Can anyone suggest how to use PNGImageWriter for PNG compression and which JAR contains it?
File input = new File("test.png");
InputStream is = new FileInputStream(input);
BufferedImage image = ImageIO.read(is);
File compressedImageFile = new File(input.getName());
OutputStream os =new FileOutputStream(compressedImageFile);
Iterator<ImageWriter>writers =
ImageIO.getImageWritersByFormatName("jpg"); // here "png" does not work
ImageWriter writer = (ImageWriter) writers.next();
ImageOutputStream ios = ImageIO.createImageOutputStream(os);
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.5f);
writer.write(null, new IIOImage(image, null, null), param);
It seems the default com.sun.imageio.plugins.png.PNGImageWriter that is bundled with the JRE does not support setting compression. This is kind of surprising, as the format obviously supports compression. However, the PNGImageWriter always writes compressed.
You can see from the source code that it uses:
Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
Which will give you good, but slow compression. It might be good enough for you, but for some cases it might be better to use faster compression and larger files.
To fix your code, so that it would work with any format name, change the lines:
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.5f);
to:
ImageWriteParam param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()) {
// NOTE: Any method named [set|get]Compression.* throws UnsupportedOperationException if false
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.5f);
}
It will still write compressed PNGs.
If you need more control over the PNG compression, like setting compression or filter used, you need to find an ImageWriter that supports it. As you mention JAI, I think the CLibPNGImageWriter that is part of jai-imageio.jar or jai-imageio-tools.jar supports setting compression. You just need to look through the ImageWriters iterator, to see if you have it installed:
Iterator<ImageWriter>writers = ImageIO.getImageWritersByFormatName("png");
ImageWriter writer = null;
while (writers.hasNext()) {
ImageWriter candidate = writers.next();
if (candidate.getClass().getSimpleName().equals("CLibPNGImageWriter")) {
writer = candidate; // This is the one we want
break;
}
else if (writer == null) {
writer = candidate; // Any writer is better than no writer ;-)
}
}
With the correct ImageWriter, your code should work as expected.
Please check that this code may work for you.
ImageWriteParam writeParam = writer.getDefaultWriteParam();
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("PackBits");
writeParam.setCompressionQuality(0.5f);
Thanks.

Using ImageIO to write JPEG 2000 with layers (i.e. decomposition levels)

Ok, here is our issue:
We are trying to convert a series of black and white .tiff files into jpeg2000 .jpf files, using imageio. We are always getting viewable .jpf files, but they usually do not have the specified number of layers or decomposition levels for zooming.
Here is our code:
//Get the tiff reader
Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByFormatName("tiff");
ImageReader tiffreader = readerIterator.next();
//make an ImageInputStream from our tiff file and have the tiff reader read it
ImageInputStream iis = ImageIO.createImageInputStream(itemFile);
tiffreader.setInput(iis);
//just pass empty params to the tiff reader
ImageReadParam tparam;
tparam = new TIFFImageReadParam();
IIOImage img = tiffreader.readAll(0, tparam);
//set up target file
File f = new File(itemTargetDirectory.getAbsolutePath() + "/" + destFileName);
//we have tried FILTER_97 as well as different ProgressionTypes and compression settings
J2KImageWriteParam param;
param = new J2KImageWriteParam();
param.setProgressionType("layer");
param.setFilter(J2KImageWriteParam.FILTER_53);
//Our problem is that this param is not always respected in the resulting .jpf
param.setNumDecompositionLevels(5);
//get the JPEG 2000 writer
Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByFormatName("JPEG 2000");
J2KImageWriter jp2kwriter = null;
jp2kwriter = (J2KImageWriter) writerIterator.next();
//write the jpf file
ImageOutputStream ios = ImageIO.createImageOutputStream(f);
jp2kwriter.setOutput(ios);
jp2kwriter.write(null, img, param);
It has been an odd experience, as the same code has behaved differently on subsequent runs.
Any insights will be appreciated!
Do all the TIFF files have the same settings (color model)? J2KImageWriter.java shows the decomposition levels getting set (forced) to zero when indexed-color or multi-pixel packed source images are used as input.
Drew was on the right track, and here is the code that ended up sorting things out for us:
public void compressor(String inputFile, String outputFile) throws IOException {
J2KImageWriteParam iwp = new J2KImageWriteParam();
FileInputStream fis = new FileInputStream(new File(inputFile));
BufferedImage image = ImageIO.read(fis);
fis.close();
if (image == null)
{
System.out.println("If no registered ImageReader claims to be able to read the resulting stream");
}
Iterator writers = ImageIO.getImageWritersByFormatName("JPEG2000");
String name = null;
ImageWriter writer = null;
while (name != "com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageWriter") {
writer = (ImageWriter) writers.next();
name = writer.getClass().getName();
System.out.println(name);
}
File f = new File(outputFile);
long s = System.currentTimeMillis();
ImageOutputStream ios = ImageIO.createImageOutputStream(f);
writer.setOutput(ios);
J2KImageWriteParam param = (J2KImageWriteParam) writer.getDefaultWriteParam();
IIOImage ioimage = new IIOImage(image, null, null);
param.setSOP(true);
param.setWriteCodeStreamOnly(true);
param.setProgressionType("layer");
param.setLossless(false);
param.setCompressionMode(J2KImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("JPEG2000");
param.setCompressionQuality(0.1f);
param.setEncodingRate(1.01);
param.setFilter(J2KImageWriteParam.FILTER_97);
writer.write(null, ioimage, param);
System.out.println(System.currentTimeMillis() - s);
writer.dispose();
ios.flush();
ios.close();
image.flush();
}

How do I write a BufferedImage as a PNG with no compression?

I need to write a BufferedImage as a .png with no compression performed. I've looked around, and come up with the following code.
public void save(String outFilePath) throws IOException {
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("png");
ImageWriter writer = iter.next();
File file = new File(outFilePath);
ImageOutputStream ios = ImageIO.createImageOutputStream(file);
writer.setOutput(ios);
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(1.0f);
IIOImage image = new IIOImage(mapImage, null, null);
writer.write(null, image, iwp);
writer.dispose();
//ImageIO.write(mapImage, "png", file);
}
Here's the exception being thrown.
Exception in thread "main" java.lang.UnsupportedOperationException: Compression not supported.
at javax.imageio.ImageWriteParam.setCompressionMode(Unknown Source)
at Map.MapTransformer.save(MapTransformer.java:246)
at Map.MapTransformer.main(MapTransformer.java:263)
PNG images achieve compression by first applying a prediction filter (you can choose among five variants), and then compressing the prediction error with ZLIB. You cannot omit these two steps, what you can do is to specify "NONE" as prediction filter, and compressionLevel=0 for the ZLIB compression, which would roughly correspond to a non-compressed image. The javax.imageio.* package does not allow (I think) to select this parameters, perhaps you can try with this or this

Categories