package no.hiof.imagepr.trans;

import no.hiof.imagepr.IntensityImage;

/**
 * Utility class for performing some grey level transformations.
 * 
 * @author Trond G. Ziarkowski, Si Van Ly, Kjetil Olai Tollefsr&oslash;d.
 * @version 1.0
 */
public final class GreyLevel {

	/**
	 * Performes histogram equalization on an image.
	 * 
	 * @param input Original image to perform histogram equalization on.
	 * @return A histogram equalized image.
	 */
	public static IntensityImage histogramEqualization(IntensityImage input) {
		IntensityImage output = new IntensityImage(input);
		int rows = input.getHeight();
		int cols = input.getWidth();
		int n = rows * cols;
		short[][] data = output.getData();
		short[][] newData = new short[rows][cols];
		short[] lookup = new short[256];

		// Find histogram
		int[] hist = new int[256];
		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				short val = data[row][col];
				hist[val] = hist[val] + 1;
			}
		}

		// Calculate new values
		double s = 0;
		double normalized;
		for (int i = 0; i < hist.length; i++) {
			normalized = hist[i] / (double)n;
			s += normalized * 255;
			lookup[i] = (short)s;
		}

		// Set new values
		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				short tmp = data[row][col];
				newData[row][col] = lookup[tmp];
			}
		}

		output.setData(newData);
		return output;
	}

	/**
	 * Performes a log transformation on an image. If one of the new values
	 * is over 255, the value is set to 255.
	 * 
	 * s = c * log (1 + r)
	 * 
	 * @param input Original image for processing.
	 * @param c A constant
	 * @return A log transformed image.
	 */
	public static IntensityImage log(IntensityImage input, double c) {
		IntensityImage output = new IntensityImage(input);
		short r;
		int rows = input.getHeight();
		int cols = input.getWidth();
		short[][] data = output.getData();
		short[][] newData = new short[rows][cols];

		// Create a lookup table for values
		short[] lookup = new short[256];
		for (int i = 0; i < lookup.length; i++) {
			double val = (255 * c) * Math.log(1 + (i / 255.0));
			lookup[i] = (val > 255) ? 255 : (short)val;
		}

		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				r = data[row][col];
				newData[row][col] = lookup[r];
			}
		}

		output.setData(newData);
		return output;
	}

	/**
	 * Performs a negative transformation on (inverts) an image.
	 * 
	 * s = 255 - r
	 * 
	 * @param input Original image for processing.
	 * @return A negative transformed image.
	 */
	public static IntensityImage negative(IntensityImage input) {
		//		long start = System.currentTimeMillis();
		IntensityImage output = new IntensityImage(input);
		short s, r;
		int rows = input.getHeight();
		int cols = input.getWidth();
		short[][] data = output.getData();
		short[][] newData = new short[rows][cols];

		// Currently not using lookup because no speed is gained, memory loss?
		//		short[] lookup = new short[256];
		//		for (int i = 0; i < lookup.length; i++) {
		//			lookup[i] = (short)(255 - i);
		//		}

		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				r = data[row][col];
				s = (short) (255 - r);
				newData[row][col] = s;
				//				newData[row][col] = lookup[r];
			}
		}

		output.setData(newData);

		//		Simple fix...
		//		negative.setColormap(IntensityImage.INVGRAY);

		return output;
	}

	/**
	 * Performes a log transformation on an image. In the case where c = 1 and
	 * gamma = 1, the transformation is the Identity transformation ie. the
	 * transformed image is a copy of the original. If one of the new values
	 * is over 255, the value is set to 255.
	 * 
	 * s = c * (r ^ gamma)
	 * 
	 * @param input Original image for processing.
	 * @param c A constant value
	 * @param gamma
	 * @return A power-law transformed image.
	 */
	public static IntensityImage powerLaw(IntensityImage input, double c, double gamma) {
		IntensityImage output = new IntensityImage(input);
		short r;
		int rows = input.getHeight();
		int cols = input.getWidth();
		short[][] data = output.getData();
		short[][] newData = new short[rows][cols];

		// Create a lookup table for values
		short[] lookup = new short[256];
		for (int i = 0; i < lookup.length; i++) {
			double val = (255 * c) * Math.pow(i / 255.0, gamma);
			lookup[i] = (val > 255) ? 255 : (short)val;
		}

		// Set new values
		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols; col++) {
				r = data[row][col];
				newData[row][col] = lookup[r];
			}
		}

		output.setData(newData);
		return output;
	}
}