/*
    QGL HDR
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "hdr_p.h"

#include <QByteArray>
#include <qmath.h>

#define RGBE_DATA_RED   2
#define RGBE_DATA_GREEN 1
#define RGBE_DATA_BLUE  0
/* number of floats per pixel */
#define RGBE_DATA_SIZE  3


void rgbe2float(float * red, float * green, float * blue, uchar rgbe[4]) {
	float f;
	if (rgbe[3]) {
		f      = static_cast<float>(ldexp(1.0, rgbe[3] - (int)(128 + 8)));
		*red   = rgbe[0] * f;
		*green = rgbe[1] * f;
		*blue  = rgbe[2] * f;
	} else
		*red = *green = *blue = 0.0;
}


/* simple read routine.	will not correctly handle run length encoding */
bool RGBE_ReadPixels(QDataStream * fp, float * data, int numpixels) {
	uchar rgbe[4];
	while (numpixels-- > 0) {
		if (fp->readRawData((char *)rgbe, sizeof(rgbe)) < 1) return false;
		rgbe2float(&data[RGBE_DATA_RED], &data[RGBE_DATA_GREEN], &data[RGBE_DATA_BLUE], rgbe);
		data += RGBE_DATA_SIZE;
	}
	return true;
}


bool RGBE_ReadPixels_RLE(QDataStream * fp, float * data, int scanline_width, int num_scanlines) {
	uchar rgbe[4], *ptr, *ptr_end;
	int i, count;
	uchar buf[2];
	QByteArray scanline_buffer;

	if ((scanline_width < 8) || (scanline_width > 0x7fff)) /* run length encoding is not allowed so read flat*/
		return RGBE_ReadPixels(fp, data, scanline_width * num_scanlines);
	scanline_buffer.resize(4 * scanline_width);
	/* read in each successive scanline */
	while (num_scanlines > 0) {
		if (fp->readRawData((char *)rgbe, sizeof(rgbe)) < 1) {
			return false;
		}
		if ((rgbe[0] != 2) || (rgbe[1] != 2) || (rgbe[2] & 0x80)) {
			/* this file is not run length encoded */
			rgbe2float(&data[RGBE_DATA_RED], &data[RGBE_DATA_GREEN], &data[RGBE_DATA_BLUE], rgbe);
			data += RGBE_DATA_SIZE;
			return RGBE_ReadPixels(fp, data, scanline_width * num_scanlines - 1);
		}
		if ((((int)rgbe[2]) << 8 | rgbe[3]) != scanline_width) {
			return false;
		}
		ptr = (uchar *)scanline_buffer.data();
		/* read each of the four channels for the scanline into the buffer */
		for (i = 0; i < 4; i++) {
			ptr_end = (uchar *)scanline_buffer.data() + ((i + 1) * scanline_width);
			while (ptr < ptr_end) {
				if (fp->readRawData((char *)buf, sizeof(buf[0]) * 2) < 1) {
					return false;
				}
				if (buf[0] > 128) {
					/* a run of the same value */
					count = buf[0] - 128;
					if ((count == 0) || (count > ptr_end - ptr)) {
						return false;
					}
					while (count-- > 0)
						*ptr++ = buf[1];
				} else {
					/* a non-run */
					count = buf[0];
					if ((count == 0) || (count > ptr_end - ptr)) {
						return false;
					}
					*ptr++ = buf[1];
					if (--count > 0) {
						if (fp->readRawData((char *)ptr, sizeof(*ptr) * count) < 1) {
							return false;
						}
						ptr += count;
					}
				}
			}
		}
		/* now convert data from buffer into floats */
		for (i = 0; i < scanline_width; i++) {
			rgbe[0] = scanline_buffer[i];
			rgbe[1] = scanline_buffer[i + scanline_width];
			rgbe[2] = scanline_buffer[i + 2 * scanline_width];
			rgbe[3] = scanline_buffer[i + 3 * scanline_width];
			rgbe2float(&data[RGBE_DATA_RED], &data[RGBE_DATA_GREEN], &data[RGBE_DATA_BLUE], rgbe);
			data += RGBE_DATA_SIZE;
		}
		num_scanlines--;
	}
	return true;
}
