package psychWithJava;
/*
* CLUT.java
*
* Provides methods to perform (inverse) lookup operations.
* Extends the 8 bit version to 16 bits for bits++
*
*/
import static java.lang.Math.*;
import java.io.InputStream;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* Provides methods to perform (inverse) color lookup operations.
*
* @author Huseyin Boyaci
*/
public class CLUT {
private int DIM;
private double maxLum;
private double[] pix2Lum;
private int[] lum2Pix;
private boolean monotonicIncrease = true;
private boolean monotonicDecrease = true;
/**
* Constructs a CLUT object for an 8 bit video display system.
* A file with name "filename" contains the 28=256
* luminance values, one for each pixel from 255 to 0 in descending order.
* The file
* may contain the absolute luminance values, but by default CLUT stores and
* works with relative values between 0.0 and 255.0.
*
* @param filename
* the name of the file containing luminance values for all pixels
* from 255 to 0 in descending order.
*
* @throws IllegalArgumentException
* if the file has different number of elements than 256
*/
public CLUT(String filename){
this(filename,8);
}
/**
* Constructs a CLUT object for an 8 bit video display system.
* A double array "table" contains the 28=256
* luminance values, one for each pixel from 255 to 0 in descending order.
* The table may contain the absolute luminance values, but by default CLUT
* stores and
* works with relative values between 0.0 and 255.0.
*
* @param table
* the double array containing luminance values for all pixels
* from 255 to 0 in descending order.
*
* @throws IllegalArgumentException
* if the file has different number of elements than 256
*/
public CLUT(double[] table){
this(table,8);
}
/**
* Constructs a CLUT object for a system with an arbitrary number of bits.
* File with name "filename" contains the 2bits lines of
* luminance values for each pixel from 2bits-1 to 0
* in descending order. The file
* may contain the absolute luminance values, but by default CLUT stores and
* works with relative values between 0.0 and 2bits-1.
*
* @param filename
* the name of the file containing luminance values for all pixels
* from 2bits-1 to 0 in descending order.
*
* @param bits
* number of bits of the system.
*/
public CLUT(String filename, int bits) throws IllegalArgumentException{
InputStream stream = CLUT.class.getResourceAsStream(filename);
Scanner in = new Scanner(stream);
DIM = (int)pow(2,bits);
pix2Lum = new double[DIM];
lum2Pix = new int[DIM];
maxLum = 0;
double[] table = new double[DIM];
for (int pix = DIM - 1; pix > -1; pix--) {
try {
table[pix] = in.nextDouble();
} catch (NoSuchElementException e) {
throw new IllegalArgumentException(
"Error in CLUT(String): wrong input file - file too short");
}
}
if (in.hasNext())
throw new IllegalArgumentException(
"Error in CLUT(String): suspicious input file - file too long");
in.close();
setClut(table);
}
/**
* Constructs a CLUT object for a system with an arbitrary number of bits.
* Double array table contains the 2bits
* luminance values for each pixel from 2bits-1 to 0
* in descending order. The table
* may contain the absolute luminance values, but by default CLUT stores and
* works with relative values between 0.0 and 2bits-1.
*
* @param table
* the double array containing luminance values for all pixels
* from 2bits-1 to 0 in descending order.
*
* @param bits
* number of bits of the system.
*/
public CLUT(double[] table, int bits) throws IllegalArgumentException{
DIM = (int)pow(2,bits);
pix2Lum = new double[DIM];
lum2Pix = new int[DIM];
if (table.length != DIM)
throw new IllegalArgumentException(
"Error in CLUT(double[], int): incompatible data");
else
setClut(table);
}
/**
* Sets a new color look-up table (CLUT).
*
* @param table
* new look-up table
*/
public void setClut(double[] table){
if (table.length != DIM)
throw new IllegalArgumentException(
"Error in CLUT8.setClut(double[]): incompatible data");
maxLum = 0;
pix2Lum = table;
double lastLum = pix2Lum[0];
for (double lum : pix2Lum) {
maxLum = max(maxLum, lum);
if (monotonicIncrease && lastLum > lum)
monotonicIncrease = false;
else if(monotonicDecrease && lastLum < lum)
monotonicDecrease = false;
lastLum = lum;
}
if(!monotonicIncrease && !monotonicDecrease){
System.err
.println("Warning in CLUT.setClut(double[]): "
+ "LUT is not monotonically increasing or decreasing, "
+ "speed may degrade");
}
for (int pix = 0; pix < DIM; pix++)
pix2Lum[pix] *= (double)(DIM-1) / maxLum;
if(monotonicIncrease){
int lastPixel = 0;
for (int lum = 0; lum < DIM; lum++) {
double diff = Double.MAX_VALUE;
for (int j = lastPixel; j < DIM; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
lum2Pix[lum] = j;
lastPixel = j;
break;
}
else if (diffTmp <= diff) {
lum2Pix[lum] = j;
lastPixel = j;
diff = diffTmp;
}
else
break;
}
}
}
else if(monotonicDecrease){
int lastPixel = 0;
for (int lum = DIM-1; lum >= 0; lum--) {
double diff = Double.MAX_VALUE;
for (int j = lastPixel; j < DIM; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
System.err.println(lum);
lum2Pix[lum] = j;
lastPixel = j;
break;
}
else if (diffTmp <= diff) {
lum2Pix[lum] = j;
lastPixel = j;
diff = diffTmp;
}
else
break;
}
}
}
else{
for (int lum = 0; lum < DIM; lum++) {
double diff = Double.MAX_VALUE;
for (int j = 0; j < DIM; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
lum2Pix[lum] = j;
break;
}
else if (diffTmp <= diff) {
lum2Pix[lum] = j;
diff = diffTmp;
}
}
}
}
}
/**
* returns the maximum available luminance
*
* @return maximum luminance
*/
public double getMaxLum() {
return maxLum;
}
/**
* Returns the pixel whose luminance is CLOSEST to the required luminance.
* This is a slower but more precise lookup for real (non-whole) values
* between 0 and 2bits-1
*
* @param lum
* required Luminance (real number, i.e. double)
*
* @return pixel whose luminance is the closest to required Luminance
*/
public int lum2Pix(double lum) {
int intLum = (int) round(lum);
int pixel = lum2Pix[intLum];
if (lum == (double) intLum)
return pixel;
else {
if(monotonicIncrease){
int pixelStart = lum2Pix[max(intLum - 1, 0)];
int pixelEnd = lum2Pix[min(intLum + 1, DIM - 1)];
double diff = Double.MAX_VALUE;
for (int j = pixelStart; j <= pixelEnd; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
pixel = j;
break;
}
else if (diffTmp <= diff) {
pixel = j;
diff = diffTmp;
}
else
break;
}
}
else if(monotonicDecrease){
int pixelStart = lum2Pix[min(intLum + 1, DIM-1)];
int pixelEnd = lum2Pix[max(intLum - 1, 0)];
double diff = Double.MAX_VALUE;
for (int j = pixelStart; j <= pixelEnd; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
pixel = j;
break;
}
else if (diffTmp <= diff) {
pixel = j;
diff = diffTmp;
}
else
break;
}
}
else {
double diff = Double.MAX_VALUE;
for (int j = 0; j < DIM; j++) {
double diffTmp = abs(pix2Lum[j] - lum);
if (diffTmp == 0) {
pixel = j;
break;
}
else if (diffTmp <= diff) {
pixel = j;
diff = diffTmp;
}
}
}
return pixel;
}
}
/**
* Returns the pixel whose luminance is CLOSEST to the required luminance.
* This is a faster lookup for whole numbers between 0 and 2bits-1.
*
* @param lum
* required Luminance (whole number, integer)
*
* @return pixel whoe luminance is the closest to required Luminance
*/
public int lum2Pix(int lum) {
return lum2Pix[lum];
}
/**
* Returns the RELATIVE luminance of the given pixel. The relative value is
* between 0 and 2bits-1 (not 0 and 1)
*
* @param pixel
* whose luminance is sought
* @return Luminance8 of the pixel
*/
public double pix2Lum(int pixel) {
return pix2Lum[pixel];
}
}