how to compress a PNG image using Java
Asked Answered
M

5

19

Hi I would like to know if there is any way in Java to reduce the size of an image (use any kind of compression) that was loaded as a BufferedImage and is going to be saved as an PNG.

Maybe some sort of png imagewriteparam? I didnt find anything helpful so im stuck.

heres a sample how the image is loaded and saved

public static BufferedImage load(String imageUrl) {         
    Image image = new ImageIcon(imageUrl).getImage();
    bufferedImage = new BufferedImage(image.getWidth(null),
                                                    image.getHeight(null),
                                                    BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2D = bufferedImage.createGraphics();
    g2D.drawImage(image, 0, 0, null);
    return bufferedImage;
}

public static void storeImageAsPng(BufferedImage image, String imageUrl) throws IOException {
    ImageIO.write(image, "png", new File(imageUrl));
}
Maida answered 27/4, 2010 at 12:55 Comment(3)
Does "any kind of compression" include lossy compression? Because then storing the image as a in JPEG format might save a lot of space.Microhenry
PNG is a lossless compression format (as long as your source image doesn't use more than 8-bits per channel: if you convert, say, a 48-bit RGB picture to PNG, PNG becomes lossy) hence you can't gain much. There are however lots of tools that produces PNG much smaller than usual, and they're particularly useful if you need your PNGs for memory-constrained devices: PNGOUT is such a tool and was programmed by one of the very best programmer ever: advsys.net/ken/utils.htm#pngoutPuton
right, like Joachim asked, no lossy compression, only reduce the size of the image file like used by zip and so and there should be no external tools involved like PNGOUT, so it could be some sort of library that supports and ImageWriteParam for PNG like JPEGImageWriteParam, looking at xmlgraphics-commons 1.3.1 atm. Sorry if im getting confusing in some parts im still a novice, just thoguht the answers could help me out or inspire other people with a similar problem. Thanks for the suggestions so far.Maida
S
5

You may want to try pngtastic. It's a pure java png image optimizer that can shrink may png images down to some degree.

Sarcasm answered 14/1, 2013 at 4:41 Comment(0)
F
5

By calling ImageIO.write you use the default compression quality for pngs. You can choose an explicit compression mode which reduces file size a lot.

String inputPath = "a.png";
String outputPath = "b.png";

BufferedImage image;
IIOMetadata metadata;

try (ImageInputStream in = ImageIO.createImageInputStream(Files.newInputStream(Paths.get(inputPath)))) {
    ImageReader reader = ImageIO.getImageReadersByFormatName("png").next();
    reader.setInput(in, true, false);
    image = reader.read(0);
    metadata = reader.getImageMetadata(0);
    reader.dispose();
}

try (ImageOutputStream out = ImageIO.createImageOutputStream(Files.newOutputStream(Paths.get(outputPath)))) {
    ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
    ImageWriter writer = ImageIO.getImageWriters(type, "png").next();

    ImageWriteParam param = writer.getDefaultWriteParam();
    if (param.canWriteCompressed()) {
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(0.0f);
    }

    writer.setOutput(out);
    writer.write(null, new IIOImage(image, null, metadata), param);
    writer.dispose();
}
Fabianfabianism answered 29/1, 2019 at 13:50 Comment(3)
Have you actually tried this? The default com.sun.imageio.plugins.png.PNGImageWriteParam in Java 7 and 8 throws java.lang.UnsupportedOperationException: Compression not supported.Labium
Yes it works on Java 9. Java 8 and older always use maximum compression. You can put the setCompression… statements in an if (param.isCompressionSupported) or something like that for Java 8 compatibility.Fabianfabianism
It works, but in my experiments I could reduce the file size only by a very small amount, to the 99.86% of the defaults used in the question. Better PNG compressions are available, but it seems that they are not implemented in imageio.Inartistic
C
2

If it's going to be saved as PNG, compression will be done at that stage. PNG has a lossless compression algorithm (basically prediction followed by lempel-ziv compression) with few adjustable parameters (types of "filters") and not much impact in compression amount - in general the default will be optimal.

Corri answered 27/4, 2010 at 19:32 Comment(2)
From what I've read about gimps png-save options, the compression level (a setting from 1 to 9) determines the compression (effort of the encoder), although it is loss-less no matter what you set.Millenarianism
Yes, its just a matter of choosing one of the four 'filters' (actually predictors, see above link) to use, perhaps using distinct filters for different lines. It always is lossless.Corri
M
0

Have a look at the ImageWriterParam class in the same package as the ImageIO class. It mentions compression.

https://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageWriteParam.html

Also look at the example at http://exampledepot.com/egs/javax.imageio/JpegWrite.html and see if it translates well for PNG files.

Millenarianism answered 27/4, 2010 at 13:0 Comment(1)
It will throw an exception if you want to use compression mode explicit for PNGs informing you that this is not supported for this format. So the parameters applied to JpegWriter are not applied to PNG.Gust
S
0

I was facing the same issue but what worked me was always use jpeg as compression format even in the case of png because in case of png file it was not compressing but increasing the size of file even more.

 ImageIO.getImageWriters(type, "jpeg").next() //always jpeg

profileImage formats for me

"data:image/png;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wB...//Z"

"data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wB...//Z"

Complete Code for reference.

    public String compressProfileImage(String profileImage, float compressionQuality) throws IOException {
    if (profileImage == null || profileImage.isEmpty()) return null;
    String[] strings = profileImage.split(",");
    if (strings.length < 2)
        return null;

    byte[] decodedImg = Utils.decodeBase64Tobytes(strings[1]);
    // Read image
    RenderedImage img = ImageIO.read(new ByteArrayInputStream(decodedImg));

    // Create output stream for compressed image data
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    // Write image to output stream with compression
    ImageWriteParam imageWriteParam = createJPEGImageWriteParam(compressionQuality);
    ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
    ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    writer.setOutput(ios);
    writer.write(null, new IIOImage(img, null, null), imageWriteParam);

    // Get the compressed image data
    byte[] compressedImageData = bos.toByteArray();
    return strings[0] + "," + Utils.encodeBase64(compressedImageData);
}

private static ImageWriteParam createJPEGImageWriteParam(float compressionQuality) {
    JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
    jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpegParams.setCompressionQuality(compressionQuality);
    return jpegParams;
}

Working code happy coding!!!

Spirelet answered 20/3, 2024 at 22:33 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.