Write PNG file with less disk size in Java
Asked Answered
L

3

3

I have a BufferedImage:

BufferedImage bi = new BufferedImage(14400, 14400, BufferedImage.TYPE_INT_ARGB);

I have saved this image to a PNG file using the following code:

public static void saveGridImage(BufferedImage sourceImage, int DPI,
            File output) throws IOException {
        output.delete();

        final String formatName = "png";

        for (Iterator<ImageWriter> iw = ImageIO
                .getImageWritersByFormatName(formatName); iw.hasNext();) {
            ImageWriter writer = iw.next();
            ImageWriteParam writeParam = writer.getDefaultWriteParam();
            ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier
                    .createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
            IIOMetadata metadata = writer.getDefaultImageMetadata(
                    typeSpecifier, writeParam);
            if (metadata.isReadOnly()
                    || !metadata.isStandardMetadataFormatSupported()) {
                continue;
            }

            setDPI(metadata, DPI);

            final ImageOutputStream stream = ImageIO
                    .createImageOutputStream(output);
            try {
                writer.setOutput(stream);
                writer.write(metadata,
                        new IIOImage(sourceImage, null, metadata), writeParam);
            } finally {
                stream.close();
            }
            break;
        }
    }

    public static void setDPI(IIOMetadata metadata, int DPI)
            throws IIOInvalidTreeException {

        double INCH_2_CM = 2.54;

        // for PNG, it's dots per millimeter
        double dotsPerMilli = 1.0 * DPI / 10 / INCH_2_CM;

        IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
        horiz.setAttribute("value", Double.toString(dotsPerMilli));

        IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
        vert.setAttribute("value", Double.toString(dotsPerMilli));

        IIOMetadataNode dim = new IIOMetadataNode("Dimension");
        dim.appendChild(horiz);
        dim.appendChild(vert);

        IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
        root.appendChild(dim);

        metadata.mergeTree("javax_imageio_1.0", root);
    }

When the code executes it creates an PNG file with 400 DPI and Disk Size of 168 MB; this is too much.

Is there any way or parameters I can use to save a smaller PNG?

Before, I had a 1.20 GB TIFF file, and when I converted it to PNG using imagemagick at 400 DPI, the resulting file size was only 700 KB.

So, I think I might be able to save the above file smaller.

Can pngj help me? Because I now have a png file which I can read in pngj library.

Latham answered 24/3, 2013 at 13:11 Comment(2)
What settings did you use in ImageMagick to make a 200 megapixel image into a 700kb file?Neogene
@Arkain i used convert demo400dpi.TIFF -units PixelsPerInch -density 400x400 -quality 100 -strip target.pngLatham
S
2

A 14400x14400 ARGB8 image has a raw (uncompressed) size of 791MB. It will compress more or less according to its nature (has uniform or smooth zones) and according (less important) to the PNG compression parameters.

when i convert it using imagemagic to PNG using 400 DPI , the resulting file size is only 700 KB.

(I don't understand why you speak of DPI, that has nothing to do, what matters is the size in pixels) Are you saying that you are getting a 14400x14400 ARGB of 700KB? That would represent a compression of 1/1000, hard to believe unless the image is practically flat. You should first understand what is going on here.

Anyway, here's a sample code with PNGJ

/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writeARGB(BufferedImage bi, OutputStream os) {
    if(bi.getType() != BufferedImage.TYPE_INT_ARGB) 
       throw new PngjException("This method expects  BufferedImage.TYPE_INT_ARGB" );
    ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
    PngWriter pngw = new PngWriter(os, imi);
    pngw.setCompLevel(9);// maximum compression, not critical usually
    pngw.setFilterType(FilterType.FILTER_AGGRESSIVE); // see what you prefer here
    DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
    SinglePixelPackedSampleModel samplemodel =  (SinglePixelPackedSampleModel) bi.getSampleModel();
    if(db.getNumBanks()!=1) 
        throw new PngjException("This method expects one bank");
    ImageLine line = new ImageLine(imi);
    for (int row = 0; row < imi.rows; row++) {
        int elem=samplemodel.getOffset(0,row);
        for (int col = 0,j=0; col < imi.cols; col++) {
            int sample = db.getElem(elem++);
            line.scanline[j++] =  (sample & 0xFF0000)>>16; // R
            line.scanline[j++] =  (sample & 0xFF00)>>8; // G
            line.scanline[j++] =  (sample & 0xFF); // B
            line.scanline[j++] =  (((sample & 0xFF000000)>>24)&0xFF); // A
        }
        pngw.writeRow(line, row);
    }
    pngw.end();
}
Springtail answered 24/3, 2013 at 15:11 Comment(8)
DataBufferInt db = (DataBufferInt) bi.getData().getDataBuffer(); line throws OutofMemory Exception.Latham
yes, getData() returns a copy. See new version with getRaster()Springtail
I have succesfully created 5 images using above code. thanks. however the average file disk size is around 104 mb and program takes 10-15 minutes to complete conversion. can i insert here lietner for pngwriter for my program ui ? and do i know in advance how much total time it will take to write target png ?Latham
can i reduce more in size or is it the limit ?Latham
Not much to do, I'm afraid, the image it too big. You can play a little with pngw.setFilterType() pngw.setCompLevel() (speed vs compression) pngw.setDeflaterStrategy() (little efect normally)Springtail
Ok and what about Pnwwriter progress listener as i asked you in my previous comment?Latham
PngWriter writes line by line, it's trivial to write your own progress bar or listenerSpringtail
but how do i know that for each line how much data is written from total data , so i can compare that for one line if it is 4 mb is written out of 800 mb , so i can visualize it.Latham
M
1

I would attempt to fiddle with the settings on the writeParam object you're creating. Currently you're calling getDefaultWriteParam(); which gives you a basic writeParam object. My guess is the default would be NO compression.

After doing that, you can probably set some of the compression modes to reduce the file size.

writeParam.setCompressionMode(int mode);
writeParam.setCompressionQuality(float quality);
writeParam.setCompressionType(String compressionType);

See http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html And specifically http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html#setCompressionMode(int)

Medina answered 24/3, 2013 at 13:24 Comment(7)
i am setting compression mode to MODE_EXPLICIT compressionquality to 1 , what about the index value for PNG how can i set it ? and what do i pass in compression type ?Latham
If you're setting compression quality to 1 then you're saying "high image quality is important" which is NOT what you want if you're trying to compress it.Medina
Try with some value < 1 like .5 or 0.0Medina
and what about compression type ? and index ?Latham
@Latham can't you just find this out using Jazzepi's answer as a knowledge base?Thorp
You should really read the reference I pointed you to, nothing I'm telling you isn't there, and I haven't consumed the API before, so I can't articulate anything new that isn't already present for you to read. setCompressionMode takes one of the public static int fields on the class. Take a look at docs.oracle.com/javase/6/docs/api/javax/imageio/… and setCompressionType needs to be passed "compressionType - one of the Strings returned by getCompressionTypes(), or null to remove any previous setting."Medina
Just to reiteriate I cannot emphasize enough that this information is there in the class Javadoc for ImageWriteParam and you should read it.Medina
P
0

sample code for pngj that works for 2.x versions of the leonbloy's pngj library

      /** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
  public static void writePNGJARGB(BufferedImage bi, /*OutputStream os, */File file) {
      
      System.out.println(".....entering PNGj alternative image file save mode....." );
        if(bi.getType() != BufferedImage.TYPE_INT_ARGB) throw new PngjException("This method expects  BufferedImage.TYPE_INT_ARGB" );
        ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
        PngWriter pngw = new PngWriter(file, imi, false);
                // PngWriter pngw = new PngWriter(file,imginfo,overwrite); //params
         pngw.setCompLevel(7); // tuning compression, not critical usually
         pngw.setFilterType(FilterType.FILTER_PAETH); // tuning, see what you prefer here
                System.out.println("..... PNGj metadata = "+pngw.getMetadata() );
        DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
        if(db.getNumBanks()!=1) {
                    throw new PngjException("This method expects one bank");
                }
        SinglePixelPackedSampleModel samplemodel =  (SinglePixelPackedSampleModel) bi.getSampleModel();
        ImageLineInt line = new ImageLineInt(imi);
        int[] dbbuf = db.getData();
        for (int row = 0; row < imi.rows; row++) {
            int elem=samplemodel.getOffset(0,row);
            for (int col = 0,j=0; col < imi.cols; col++) {
                                int sample = dbbuf[elem++];
                                line.getScanline()[j++] =  (sample & 0xFF0000)>>16; // R
                                line.getScanline()[j++] =  (sample & 0xFF00)>>8; // G
                                line.getScanline()[j++] =  (sample & 0xFF); // B
                                line.getScanline()[j++] =  (((sample & 0xFF000000)>>24)&0xFF); // A
                        }
                      //pngw.writeRow(line, /*imi.rows*/);
                        pngw.writeRow(line);  
                }
                pngw.end();
    }
Philipson answered 17/4, 2022 at 2:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.