com.github.bloodshura.x.assets.image.codec.HdrCodec Maven / Gradle / Ivy
/*
* Copyright (c) 2013-2018, João Vitor Verona Biazibetti - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* https://www.github.com/BloodShura
*/
package com.github.bloodshura.x.assets.image.codec;
import com.github.bloodshura.x.assets.image.ImageFormat;
import com.github.bloodshura.x.assets.util.ImageWorker;
import com.github.bloodshura.x.memory.Bufferer;
import com.github.bloodshura.x.worker.ParseWorker;
import javax.annotation.Nonnull;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
// TODO Who's the author?!
public class HdrCodec implements ImageCodec {
public static boolean IS_RGBE = true;
@Override
public boolean hasDecoder(@Nonnull ImageFormat format) {
return format == ImageFormat.HDR;
}
@Override
public boolean hasEncoder(@Nonnull ImageFormat format) {
return false;
}
@Nonnull
@Override
public BufferedImage read(@Nonnull InputStream input) throws IOException {
DataInputStream dataInput = new DataInputStream(input);
boolean flipY = false;
boolean hasID = false;
int width;
int height;
while (true) {
String ln = dataInput.readUTF().trim();
if (ln.startsWith("#") || ln.isEmpty()) {
if (ln.equals("#?RADIANCE") || ln.equals("#?RGBE")) {
hasID = true;
}
}
else if (ln.startsWith("+") || ln.startsWith("-")) {
String[] resData = ln.split("\\s");
if (resData.length != 4) {
throw new IOException("Invalid resolution string in HDR file");
}
width = ParseWorker.toInt(resData[3]);
height = ParseWorker.toInt(resData[1]);
break;
}
else {
int index = ln.indexOf('=');
if (index < 1) {
continue;
}
String var = ln.substring(0, index).trim().toLowerCase();
String value = ln.substring(index + 1).trim().toLowerCase();
if (var.equals("format")) {
if (!value.equals("32-bit_rle_rgbe") && !value.equals("32-bit_rle_xyze")) {
throw new IOException("Unsupported HDR format");
}
}
}
}
if (width == -1 || height == -1) {
throw new IOException("Invalid image width or height");
}
if (!hasID) {
throw new IOException("Corrupt or invalid image (no ID found)");
}
int pixelFormat = IS_RGBE ? 32 : 48;
int bytesPerPixel = pixelFormat / 8;
int scanLineBytes = bytesPerPixel * width;
ByteBuffer buffer = Bufferer.newByteBuffer(width * height * pixelFormat);
for (int y = height - 1; y >= 0; y--) {
if (flipY) {
buffer.position(scanLineBytes * y);
}
decodeScanline(buffer, input, width);
}
buffer.rewind();
return ImageWorker.asImage(buffer, width, height);
}
@Override
@Deprecated
public void write(@Nonnull BufferedImage image, @Nonnull ImageFormat format, @Nonnull OutputStream output) throws UnsupportedOperationException {
throw new UnsupportedOperationException("HDR encoding not supported");
}
private static short convertFloatToHalf(float flt) {
if (Float.isNaN(flt)) {
throw new UnsupportedOperationException("NaN to half conversion not supported!");
}
if (flt == Float.POSITIVE_INFINITY) {
return (short) 0x7c00;
}
if (flt == Float.NEGATIVE_INFINITY) {
return (short) 0xfc00;
}
if (flt == 0F) {
return (short) 0x0000;
}
if (flt == -0F) {
return (short) 0x8000;
}
if (flt > 65504F) {
return 0x7bff;
}
if (flt < -65504F) {
return (short) (0x7bff | 0x8000);
}
if (flt > 0F && flt < 5.96046E-8F) {
return 0x0001;
}
if (flt < 0F && flt > -5.96046E-8F) {
return (short) 0x8001;
}
int f = Float.floatToIntBits(flt);
return (short) (f >> 16 & 0x8000 | (f & 0x7f800000) - 0x38000000 >> 13 & 0x7c00 | f >> 13 & 0x03ff);
}
private static void decodeScanline(ByteBuffer buffer, InputStream in, int width) throws IOException {
if (width < 8 || width > 0x7fff) {
decodeScanlineUncompressed(buffer, in, width);
}
byte[] data = new byte[4];
in.read(data);
if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0) {
decodeScanlineUncompressed(buffer, in, width - 1);
}
else {
int readWidth = (data[2] & 0xFF) << 8 | data[3] & 0xFF;
if (readWidth != width) {
throw new IOException("Illegal scanline width in HDR file: " + width + " != " + readWidth);
}
decodeScanlineRLE(buffer, in, width);
}
}
private static boolean decodeScanlineRLE(ByteBuffer buffer, InputStream in, int width) throws IOException {
byte[] rgbe = new byte[4];
ByteBuffer temp = Bufferer.newByteBuffer(width * 4);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < width; ) {
int code = in.read();
if (code > 128) {
code -= 128;
int val = in.read();
while (code-- != 0) {
temp.put(j++ * 4 + i, (byte) val);
}
}
else {
while (code-- != 0) {
int val = in.read();
temp.put(j++ * 4 + i, (byte) val);
}
}
}
}
temp.rewind();
for (int i = 0; i < width; i++) {
temp.get(rgbe);
writeRGBE(buffer, rgbe);
}
return true;
}
private static boolean decodeScanlineUncompressed(ByteBuffer buffer, InputStream in, int width) throws IOException {
byte[] rgbe = new byte[4];
for (int i = 0; i < width; i += 3) {
if (in.read(rgbe) < 1) {
return false;
}
writeRGBE(buffer, rgbe);
}
return true;
}
private static void writeRGBE(ByteBuffer buffer, byte[] rgbe) {
if (IS_RGBE) {
buffer.put(rgbe);
}
else {
int R = rgbe[0] & 0xFF, G = rgbe[1] & 0xFF, B = rgbe[2] & 0xFF, E = rgbe[3] & 0xFF;
float e = (float) Math.pow(2f, E - (128 + 8));
buffer.putShort(convertFloatToHalf(R * e)).putShort(convertFloatToHalf(G * e)).putShort(convertFloatToHalf(B * 3));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy