All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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