/*
 * ReduceRGB
 *
 * Copyright (c) 2003, 2004 Marco Schmidt.
 * All rights reserved.
 */

package net.sourceforge.jiu.color.reduction;

import net.sourceforge.jiu.data.IntegerImage;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.MemoryRGB48Image;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.data.RGB48Image;
import net.sourceforge.jiu.ops.ImageToImageOperation;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.WrongParameterException;

/**
 * Reduces the color depth of RGB truecolor images.
 * This class uses a simple approach, it just drops some of the 
 * lowest bits and scales the value back to eight or sixteen bits per sample.
 * <h3>Supported image classes</h3>
 * This class works with {@link net.sourceforge.jiu.data.RGB24Image}
 * and {@link net.sourceforge.jiu.data.RGB48Image}.
 * <h3>Usage example</h3>
 * Reduce a 24 or 48 bits per pixel RGB image to 15 bits per pixel:
 * <pre>
 * PixelImage inputImage = ...; // initialize
 * ReduceRGB reduce = new ReduceRGB();
 * reduce.setBitsPerSample(5);
 * reduce.setInputImage(inputImage);
 * reduce.process();
 * PixelImage reducedImage = reduce.getOutputImage();
 * </pre>
 * @author Marco Schmidt
 * @since 0.12.0
 * @see net.sourceforge.jiu.color.reduction.ReduceShadesOfGray
 */
public class ReduceRGB extends ImageToImageOperation
{
	/**
	 * Number of significant bits per channel in the destination RGB image.
	 */
	private Integer destBits;

	/*private byte[] createByteLut(int inDepth)
	{
		int outDepth = destBits.intValue();
		lut = new int[1 << inDepth];
		final int SHIFT = inDepth - outDepth;
		final int MAX_IN_VALUE = (1 << inDepth) - 1;
		final int MAX_OUT_VALUE = (1 << outDepth) - 1;
		for (int i = 0; i < lut.length; i++)
		{
			int value = i >> SHIFT;
			lut[i] = (value * MAX_IN_VALUE) / MAX_OUT_VALUE;
		}
	}*/


	public void process() throws MissingParameterException, WrongParameterException
	{
		if (destBits == null)
		{
			throw new MissingParameterException(
				"The number of destination bits has not been specified.");
		}
		int bits = destBits.intValue();
		ensureInputImageIsAvailable();
		ensureImagesHaveSameResolution();
		PixelImage in = getInputImage();
		boolean rgb24 = in instanceof RGB24Image;
		boolean rgb48 = in instanceof RGB48Image;
		if (!(rgb24 || rgb48))
		{
			throw new WrongParameterException(
				"Input image must be either RGB24Image or RGB48Image.");
		}
		if (rgb24 && bits >= 8)
		{
			throw new WrongParameterException(
				"Number of output bits per sample must be 7 or lower for RGB24Image.");
		}
		PixelImage out = getOutputImage();
		int inDepth = 0;
		if (rgb24)
		{
			inDepth = 8;
		}
		if (rgb48)
		{
			inDepth = 16;
		}
		int maxOutputValue = 1;
		int maxShiftedValue = (1 << bits) - 1;
		if (bits <= 8)
		{
			if (out == null)
			{
				out = new MemoryRGB24Image(in.getWidth(), in.getHeight());
			}
			else
			{
				if (!(out instanceof RGB24Image))
				{
					throw new WrongParameterException(
						"Output image must be of type RGB24Image.");
				}
			}
			maxOutputValue = 255;
		}
		else
		if (bits <= 16)
		{
			if (out == null)
			{
				out = new MemoryRGB48Image(in.getWidth(), in.getHeight());
			}
			else
			{
				if (!(out instanceof RGB48Image))
				{
					throw new WrongParameterException(
						"Output image must be of type RGB48Image.");
				}
			}
			maxOutputValue = 65535;
		}
		else
		{
			throw new WrongParameterException("Can only process up to 16 bits per sample.");
		}
		final int SHIFT = inDepth - bits;
		IntegerImage ii = (IntegerImage)in;
		IntegerImage oo = (IntegerImage)out;
		for (int y = 0; y < in.getHeight(); y++)
		{
			for (int x = 0; x < in.getWidth(); x++)
			{
				for (int c = 0; c < 3; c++)
				{
					int inputSample = ii.getSample(c, x, y);
					int outputSample = ((inputSample >> SHIFT) * maxOutputValue) / maxShiftedValue;
					oo.putSample(c, x, y, outputSample);
				}
			}
			setProgress(y, in.getHeight());
		}
	}

	/**
	 * Specifies the number of bits per sample in the output image.
	 * @param bits number of bits in output image, from 1 to 15
	 * @throws IllegalArgumentException if bits is smaller than 1 or larger than 15
	 */
	public void setBitsPerSample(int bits)
	{
		if (bits < 1)
		{
			throw new IllegalArgumentException("Number of bits must be 1 or larger.");
		}
		if (bits > 15)
		{
			throw new IllegalArgumentException("Number of bits must be 15 or smaller.");
		}
		destBits = new Integer(bits);
	}
}
