Create simple 2D image preview from panoramic HDR

228 views Asked by At

Is there some really simple and basic code for making preview for HDR images (like getting 2D BufferedImage output or something)?

I am using this HDR image.

I tried this (it uses TwelveMonkeys), but no success at all (it simply stuck/frozen at ImageReader reader = readers.next();)

I edited it a bit to suit my needs like this, testing where it got broken/stuck/frozen...and it always happen after TEST 1, that is TEST 2 is never reached, tho no IllegalArgumentException is thrown - if I remove the if() section, then TEST 3 is never reached (I am using NetBeansIDE v12.4, Win7 x64):

public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
    BufferedImage bi = null;

    // Create input stream
    // I WROTE DOWN THE STRING FOR THIS EXAMPLE, normally it is taken from the hdrFile
    // HDR image size is 23.7MB if it matters at all?
    ImageInputStream input = ImageIO.createImageInputStream(new File("Z:/HDR/spiaggia_di_mondello_4k.hdr"));

    try {
        // Get the reader
        Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
        System.err.println("=====>>> TEST 1");

        if (!readers.hasNext()) {
            throw new IllegalArgumentException("No reader for: " + hdrFile);
        }
        System.err.println("=====>>> TEST 2");

        ImageReader reader = readers.next();
        System.err.println("=====>>> TEST 3");

        try {
            reader.setInput(input);

            // Disable default tone mapping
            HDRImageReadParam param = (HDRImageReadParam) reader.getDefaultReadParam();
            param.setToneMapper(new NullToneMapper());

            // Read the image, using settings from param
            bi = reader.read(0, param);
        } finally {
            // Dispose reader in finally block to avoid memory leaks
            reader.dispose();
        }
    } finally {
        // Close stream in finally block to avoid resource leaks
        input.close();
    }

    // Get float data
    float[] rgb = ((DataBufferFloat) bi.getRaster().getDataBuffer()).getData();

    // Convert the image to something easily displayable
    BufferedImage converted = new ColorConvertOp(null).filter(bi, new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_RGB));

    return converted;
}
3

There are 3 answers

3
qraqatit On BEST ANSWER

Well, if you don't mind occasional extreme halucinogenic oversaturation of some colors here and there (I was unable solving the issue - if anyone knows how to, please, feel free to update my code), you can try this (it is using JavaHDR) + I also added a bit of brightness and contrast to it as all HDR I tested looked too dark for the preview, so if you do not like that you can remove that part from the code:

public int rgbToInteger(int r, int g, int b) {
    int rgb = r;
    rgb = (rgb << 8) + g;
    rgb = (rgb << 8) + b;
    return rgb;
}

public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
    HDRImage hdr = HDREncoder.readHDR(hdrFile, true);
    int width = hdr.getWidth();
    int height = hdr.getHeight();
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            int r = (int) (hdr.getPixelValue(x, y, 0) * 255);
            int g = (int) (hdr.getPixelValue(x, y, 1) * 255);
            int b = (int) (hdr.getPixelValue(x, y, 2) * 255);
            bi.setRGB(x, y, rgbToInteger(r, g, b));
        }
    }

    //***** YOU CAN REMOVE THIS SMALL SECTION IF YOU FEEL THE IMAGE IS TOO BRIGHT FOR YOU
    float brightness = 2f;
    float contrast = 20f;
    RescaleOp rescaleOp = new RescaleOp(brightness, contrast, null);
    rescaleOp.filter(bi, bi);
    //***** 

    return bi;
}
0
fafa On

After a long discussion with @HaraldK and his code addition, I am posting the final correct code for this problem, that is in fact mix of @qraqatit code updated a bit with the @HaraldK addition that corrects wrong color tone mapping, here it is:

public int rgbToInteger(int r, int g, int b) {
    int rgb = r;
    rgb = (rgb << 8) + g;
    rgb = (rgb << 8) + b;
    return rgb;
}

public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
    HDRImage hdr = HDREncoder.readHDR(hdrFile, true);
    int width = hdr.getWidth();
    int height = hdr.getHeight();
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    float colorToneCorrection = 0.75f;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            float r = hdr.getPixelValue(x, y, 0);
            int red = (int) ((r / (colorToneCorrection + r)) * 255);
            float g = hdr.getPixelValue(x, y, 1);
            int green = (int) ((g / (colorToneCorrection + g)) * 255);
            float b = hdr.getPixelValue(x, y, 2);
            int blue = (int) (int) ((b / (colorToneCorrection + b)) * 255);
            bi.setRGB(x, y, rgbToInteger(red, green, blue));
        }
    }

    //MAKE THE RESULTING IMAGE A BIT BRIGHTER
    float brightness = 1.35f;
    float contrast = 0f;
    RescaleOp rescaleOp = new RescaleOp(brightness, contrast, null);
    rescaleOp.filter(bi, bi);

    return bi;
}
15
Harald K On

I can compile and run the code you posted (changing the path obviously) without problems on my two macOS machines, testing on all the LTS Java versions (8, 11 and 17). In addition, I run code similar to this as part of the CI/CD pipeline of my project that tests on Windows and Linux as well. I think there is something wrong with the setup in your IDE or Java on your computer. I am not able to reproduce the "freeze"-situation you describe...

Here is the output of running the program (I also printed the resulting BufferedImage for verification):

=====>>> TEST 1
=====>>> TEST 2
=====>>> TEST 3
image = BufferedImage@5a42bbf4: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 1024 height = 512 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0

Running with the code as-is (with the NullToneMapper and no post-processing), the image looks like this, due to unnormalized values:

enter image description here

Running with the default/built-in tone mapper, or simply reading the image with ImageIO.read(hdrFile) as suggested in the comments, the image will look like this:

enter image description here

Finally, playing a bit with the code using a custom global tone mapper; param.setToneMapper(new DefaultToneMapper(0.75f)), I get a result like this:

enter image description here