JavaDon
JavaDon

Reputation: 41

Writing a large JPanel PNG without OutOfMemoryError nor increasing VM heap size

I have a jPanel whose width is 23500 and height 43000. I'm writing a PNG image file, but receive OutOfMemoryError: Java heap space. Is it possible to write such a large image from an equally large jPanel? I've spent about 8 hours online and simply cannot find the answer.

The error is thrown from the bufferedReader declaration. Any help would be greatly appreciated! I'll post the solution when I figure it out.

My code is below:

try {
    BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics g = bufferedImage.createGraphics();
    uMLDiagramPanelJScrollPane.getViewport().getView().print(bufferedImage.createGraphics());
    g.dispose();
    ImageIO.write(bufferedImage, "PNG", new File(System.getProperty("user.home") + "/Desktop/" + "image.png/"));
} catch (IOException e) {
    e.printStackTrace();
}

Upvotes: 2

Views: 149

Answers (1)

JavaDon
JavaDon

Reputation: 41

The large PNG image this answer produces may not be able to be opened due to it's (arbitrarily large) size. However, one can always take the PNG to a printshop (assuming you don't have a poster printer). It's a pure java method. How it works: it recycles a bufferedImage to sequentially write to disk, an ARGB PNG file using graphical translations of the (arbitrarily large) component.

public void putArbitrarilyLargeComponentPNGImageOnWeeComputerDesktop(Component component) throws Exception {
    int width = component.getWidth();
    int componentHeight = component.getHeight();
    int clipHeight = 1000;
    int pixelRowsDrawn = 0;
    BufferedImage bufferedImage;
    Graphics g = null;
    ByteBuffer iHDRTypeAndDataPartsForCRCByteBuffer = ByteBuffer.allocate(17);
    FileOutputStream fos = new FileOutputStream(new File(System.getProperty("user.home") + "/Desktop/arbitrarilyLargePNG.png"), true);
    ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536);
    DeflaterOutputStream dos = new DeflaterOutputStream(compressed, new Deflater(9), true);
    CRC32 crc = new CRC32();
    // Write 8-bytes PNG file signature and 4-byte IHDR data part length.
    fos.write(new byte[] { -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13 });
    iHDRTypeAndDataPartsForCRCByteBuffer.wrap(new byte[17]);
    iHDRTypeAndDataPartsForCRCByteBuffer.put("IHDR".getBytes());
    iHDRTypeAndDataPartsForCRCByteBuffer.put(ByteBuffer.allocate(4).putInt(width).array());
    iHDRTypeAndDataPartsForCRCByteBuffer.put(ByteBuffer.allocate(4).putInt(componentHeight).array());
    //See 4.1.1. at <https://www.w3.org/TR/PNG-Chunks.html> starting with "Bit depth"
    iHDRTypeAndDataPartsForCRCByteBuffer.put(new byte[] { 8, 6, 0, 0, 0 });
    fos.write(iHDRTypeAndDataPartsForCRCByteBuffer.array());
    crc.update(iHDRTypeAndDataPartsForCRCByteBuffer.array());
    fos.write(ByteBuffer.allocate(4).putInt((int) crc.getValue()).array());

    while (pixelRowsDrawn < componentHeight) {
        if (componentHeight - pixelRowsDrawn < clipHeight) clipHeight = componentHeight - pixelRowsDrawn;
        bufferedImage = new BufferedImage(width, clipHeight, BufferedImage.TYPE_INT_ARGB);
        g = bufferedImage.createGraphics();
        g.translate(0, -pixelRowsDrawn);
        component.paint(g);

        compressed.reset();
        int pixel, x = 0, y = 0;
        while (y < clipHeight) {
            x = 0;
            dos.write(0);
            while (x < width) {
                pixel = bufferedImage.getRGB(x, y);
                dos.write(new byte[] { (byte) ((pixel >> 16) & 0xff), (byte) ((pixel >> 8) & 0xff), (byte) (pixel & 0xff), (byte) ((pixel >> 24) & 0xff) });
                x++;
            }
            y++;
        }

        if ((pixelRowsDrawn = pixelRowsDrawn + clipHeight) == componentHeight) dos.finish();
        fos.write(ByteBuffer.allocate(4).putInt(compressed.size()).array());
        fos.write("IDAT".getBytes());
        crc.reset();
        crc.update("IDAT".getBytes());
        crc.update(compressed.toByteArray());
        fos.write(compressed.toByteArray());
        fos.write(ByteBuffer.allocate(4).putInt((int) crc.getValue()).array());
    }

    fos.write(new byte[] { 0, 0, 0, 0 });
    fos.write("IEND".getBytes());
    crc.reset();
    crc.update("IEND".getBytes());
    fos.write(ByteBuffer.allocate(4).putInt((int) crc.getValue()).array());

    g.dispose();
}

Upvotes: 1

Related Questions