Compression of PNG using JAI (Java Advanced Imaging) - java

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.

Related

Java BufferedImage JPG compression without writing to file

I've seen several examples of making compressed JPG images from Java BufferedImage objects by writing to file, but is it possible to perform JPG compression without writing to file? Perhaps by writing to a ByteArrayOutputStream like this?
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);
ImageOutputStream outputStream = createOutputStream();
jpgWriter.setOutput(outputStream);
IIOImage outputImage = new IIOImage(image, null, null);
// in this example, the JPG is written to file...
// jpgWriter.write(null, outputImage, jpgWriteParam);
// jpgWriter.dispose();
// ...but I want to compress without saving, such as
ByteArrayOutputStream compressed = ???
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);

ImageWriteParam set compression quality not working

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

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

Reduce JPEG image size without scale its width/height by Java

I want to reduce one jpeg image size(3M reduce to 1M) by Java, without scale(no change for image height and width). IN this site, I could not find a solution. Below is what I have tried:
1 Using ImageIO:
BufferedImage image = ImageIO.read(inputFile);
ImageWriter writer = null;
Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
if(iter.hasNext()){
writer = (ImageWriter) iter.next();
}
ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile);
writer.setOutput(ios);
ImageWriteParam iwParam = writer.getDefaultWriteParam();
iwParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwParam.setCompressionQuality(compressionQuality);
writer.write(null, new IIOImage(image, null, null ), iwParam);
For solution 1, I set compressionQuality for jpg but I can not obtain origina image compressQuality and the newImage I get sometimes is bigger than originals.
The compression quality used is not stored with the JPEG image.
If you need to get below a certain threshold you must try several times while lowering the compression quality each time until you reach your limit. Be aware that very low settings give bad images.
I am unfamiliar with the MODE_EXPLICIT flag. It might also be a tunable parameter.
I found the following code example for reducing the quality.
The important part is just to set iwp.setCompressionQuality. Hopes this helps.
BufferedImage bi = null;
bi = ImageIO.read(new File("image.jpg"));
Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter) iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// reduced quality.
iwp.setCompressionQuality(0.1f);
File file = new File("c:/image_low.jpg");
FileImageOutputStream output = null;
output = new FileImageOutputStream(file);
writer.setOutput(output);
IIOImage image = new IIOImage(bi, null, null);
writer.write(null, image, iwp);
writer.dispose();

Convert PNG to bitonal TIFF

-Edit-
FYI.. I am converting b&w documents scanned in as greyscale or color.
1)The first solution worked, it just reversed black & white (black background, white text). It also took nearly 10 minutes.
2)The JAI solution in the 2nd answer didn't work for me. I tried it before posting here.
Has anyone worked with other libraries free or pay that handle image manipulation well?
-Original-
I am trying to convert an PNG to a bitonal TIFF using Java ImageIO. Has anyone had any luck doing this? I have got it to convert from PNG to TIFF. I am not sure if I need to convert the BufferedImage (PNG) that I read in or convert on the TIFF as I write it. I have searched and searched but nothing seems to work? Does anyone have an suggestions where to look?
Here is the code that converts...
public static void test() throws IOException {
String fileName = "4848970_1";
// String fileName = "color";
String inFileType = ".PNG";
String outFileType = ".TIFF";
File fInputFile = new File("I:/HPF/UU/" + fileName + inFileType);
InputStream fis = new BufferedInputStream(new FileInputStream(fInputFile));
ImageReaderSpi spi = new PNGImageReaderSpi();
ImageReader reader = spi.createReaderInstance();
ImageInputStream iis = ImageIO.createImageInputStream(fis);
reader.setInput(iis, true);
BufferedImage bi = reader.read(0);
int[] xi = bi.getSampleModel().getSampleSize();
for (int i : xi) {
System.out.println("bitsize " + i);
}
ImageWriterSpi tiffspi = new TIFFImageWriterSpi();
TIFFImageWriter writer = (TIFFImageWriter) tiffspi.createWriterInstance();
// TIFFImageWriteParam param = (TIFFImageWriteParam) writer.getDefaultWriteParam();
TIFFImageWriteParam param = new TIFFImageWriteParam(Locale.US);
String[] strings = param.getCompressionTypes();
for (String string : strings) {
System.out.println(string);
}
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("LZW");
File fOutputFile = new File("I:\\HPF\\UU\\" + fileName + outFileType);
OutputStream fos = new BufferedOutputStream(new FileOutputStream(fOutputFile));
ImageOutputStream ios = ImageIO.createImageOutputStream(fos);
writer.setOutput(ios);
writer.write(null, new IIOImage(bi, null, null), param);
ios.flush();
writer.dispose();
ios.close();
}
I have tried changing the compression to type "CCITT T.6" as that appears to be what I want, but I get an error " Bits per sample must be 1 for T6 compression! " Any suggestion would be appreciated.
Most likely, you want something like this to convert to 1 bit before you save to TIFF with CCITT compression.
To expound a little bit - be aware that converting from other bit depths to 1 bit is non-trivial. You are doing a data reduction operation and there are dozens of domain specific solutions which vary greatly in output quality (blind threshold, adaptive threshold, dithering, local threshold, global threshold and so on). None of them are particularly good at all image types (adaptive threshold is pretty good for documents, but lousy for photographs, for example).
As plinth said, you have to do the conversion, Java won't do it magically for you...
If the PNG image is already black & white (as it seems, looking at your comment), using a threshold is probably the best solution.
Somebody seems to have the same problem: HELP: how to compress the tiff. A solution is given on the thread (untested!).

Categories