How to create a black and white image of arbitrary size

Viewed 469

I want to create an arbitrary large image consisting solely of black and white pixel. I am currently using BufferedImage. This, however, hits a hard limit at 65500 Pixel.

Exception in thread "main" javax.imageio.IIOException: Maximum supported image dimension is 65500 pixels
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageWriter.writeImage(Native Method)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageWriter.writeOnThread(JPEGImageWriter.java:1007)
    at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageWriter.write(JPEGImageWriter.java:371)
    at java.desktop/javax.imageio.ImageWriter.write(ImageWriter.java:613)
    at java.desktop/javax.imageio.ImageIO.doWrite(ImageIO.java:1628)
    at java.desktop/javax.imageio.ImageIO.write(ImageIO.java:1554)

How can I create an image of arbitrary size?

Bonus points: As the image is only black and white an efficient data format would be great.

2 Answers

You only hit a limitation of a specific image writer. When I tried with PNG, which is more suitable for truly black & white images anyway, larger images could be written and read back.

I used the following code for testing:

public static void main(String[] args) throws IOException {
    int[][] sizes = {{4000, 4000 }, {40_000, 40_000 }, {70_000, 4000 }, {700_000, 400 }};
    for(int[] size: sizes) {
        int width = size[0], height = size[1];
        System.out.println("trying " + width + " x " + height);
        BufferedImage bi=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_BINARY);
        Graphics2D gfx = bi.createGraphics();
        gfx.setColor(Color.WHITE);
        gfx.fillRect(0, 0, width, height);
        gfx.setColor(Color.BLACK);
        gfx.drawOval(20, 20, width - 40 , height - 40);
        int off = Math.min(width, height) >>> 3;
        gfx.drawOval(off, off, width - off - off, height - off - off);
        gfx.dispose();

        test(bi, "PNG");
        test(bi, "GIF");
        test(bi, "JPEG");
    }
}
static void test(BufferedImage bi, String format) throws IOException {
    Path f = Files.createTempFile("test", "-out."+format);
    try {
        try { ImageIO.write(bi, format, f.toFile()); }
        catch(Throwable t) {
            System.err.println(format+": "+t);
            return;
        }
        BufferedImage read;
        try { read = ImageIO.read(f.toFile()); }
        catch(Throwable t) {
            System.err.println(format+": written, re-reading: "+t);
            return;
        }
        if(bi.getWidth() != read.getWidth() || bi.getHeight() != read.getHeight()) {
            System.err.println(format+": size changed to "
                              +read.getWidth() + " x " + read.getHeight());
            return;
        }
        if(format.equalsIgnoreCase("jpeg")) {
            // turn back to black & white, rounding away JPEG artifacts
            BufferedImage bw = new BufferedImage(
                read.getWidth(), read.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
            Graphics2D gfx = bw.createGraphics();
            gfx.drawImage(read, 0, 0, null);
            gfx.dispose();
            read = bw;
        }
        for(int x = 0, w = bi.getWidth(); x < w; x++) {
            for(int y = 0, h = bi.getHeight(); y < h; y++) {
                if(bi.getRGB(x, y) != read.getRGB(x, y)) {
                    System.err.println(format+": content changed");
                    return;
                }
            }
        }
        System.out.println(format + ": sucessfully written and restored");
    }
    finally {
        Files.deleteIfExists(f);
    }
}

Result:

trying 4000 x 4000
PNG: sucessfully written and restored
GIF: sucessfully written and restored
JPEG: sucessfully written and restored
trying 40000 x 40000
PNG: sucessfully written and restored
GIF: sucessfully written and restored
JPEG: written, re-reading: java.lang.IllegalArgumentException: Invalid scanline stride
trying 70000 x 4000
PNG: sucessfully written and restored
GIF: size changed to 4464 x 4000
JPEG: javax.imageio.IIOException: Maximum supported image dimension is 65500 pixels
trying 700000 x 400
PNG: sucessfully written and restored
GIF: size changed to 44640 x 400
JPEG: javax.imageio.IIOException: Maximum supported image dimension is 65500 pixels

I could not create BufferedImage object whose width × height exceeds the int value range, thought. This limitations seems to be hardcoded at several places of the implementation.

pbm might be the right image format for you, and it doesn't even require using Java's image libraries to work with.

static void writeRandomBlackWhiteImage(
        OutputStream out, long width, long height) throws IOException {
    String header = String.format("P4\n%d\n%d\n", width, height);
    out.write(header.getBytes(StandardCharsets.US_ASCII));

    // bytes = (width * height + 7) / 8, throwing ArithmeticException on overflow
    long bytes = Math.addExact(Math.multiplyExact(width, height), 7L) / 8L;
    while (bytes > 0) {
        byte[] data = new byte[(int) Math.min(0x4000, bytes)];
        ThreadLocalRandom.current().nextBytes(data);
        out.write(data);
        bytes -= data.size;
    }
}

public static void main(String[] args) throws IOException {
    try (OutputStream out = new FileOutputStream("out.pbm")) {
        writeRandomBlackWhiteImage(out, 65536, 65536);
    }
}

Note that (unlike java.awt.image.BufferedImage) this can easily produce images with dimensions greater than Integer.MAX_VALUE. (Although working with those images may be challenging.)

Related