/*
* @(#)CIEXYChromaticityDiagramImageProducer.java
*
* Copyright (c) 2010 The authors and contributors of JHotDraw.
*
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.color;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.awt.Color;
import java.awt.Point;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.util.Arrays;
/**
* Produces a CIE xy Chromaticity Diagram.
* <p>
* The diagram shows a projection of the CIE XYZ cube on a xy plane.
* The projection is based on the following equations:
* <p>
* x = X / (X + Y + Z), y = Y / (X + Y + Z), z = 1 - x - y.
* </p>
* The equations can be rewritten as:
* <p>
* X = (x*(Y+Z)/(1-x), Y = (y*(X+Z)/(1-y).
*
*
* @author Werner Randelshofer
* @version $Id$
*/
public class CIEXYChromaticityDiagramImageProducer extends MemoryImageSource {
private static final float eps = 0f;// 0.000001f;
private static final float ceps = 0f;
protected int[] pixels;
protected int w, h;
protected ColorSpace colorSpace;
protected int radialIndex = 1;
protected int angularIndex = 0;
protected int verticalIndex = 2;
protected boolean isPixelsValid = false;
protected float verticalValue = 1f;
protected boolean isLookupValid = false;
public enum OutsideGamutHandling {
CLAMP,
LEAVE_OUTSIDE
};
/** By default, clamps non-displayable RGB values. */
private OutsideGamutHandling outsideGamutHandling = OutsideGamutHandling.LEAVE_OUTSIDE;
public CIEXYChromaticityDiagramImageProducer(int w, int h) {
super(w, h, null, 0, w);
this.colorSpace = ICC_ColorSpace.getInstance(ICC_ColorSpace.CS_CIEXYZ);
pixels = new int[w * h];
this.w = w;
this.h = h;
setAnimated(true);
newPixels(pixels, ColorModel.getRGBdefault(), 0, w);
}
public boolean needsGeneration() {
return !isPixelsValid;
}
public void regenerateDiagram() {
if (!isPixelsValid) {
generateImage();
}
}
public void generateImage() {
float wf = 0.8f / (float) w;
float hf = 0.9f / (float) h;
// float wf = 1f / (float) w;
// float hf = 1f / (float) h;
// Clear pixels
Arrays.fill(pixels, 0);
float[] rgb = new float[3];
for (int iY=0;iY<=100;iY++) {
float Y = (100-iY)/100f;
float[] XYZ = new float[3];
for (int ix = 0; ix < w; ix++) {
float x = ix * wf;
for (int iy = 0; iy < h; iy++) {
if (pixels[ix + iy * w] != 0) {
continue;
}
float y = 0.9f - iy * hf;
float z = 1f - x - y;
if (y == 0) {
XYZ[0] = XYZ[1] = XYZ[2] = 0;
} else {
XYZ[1] = Y; // Y=Y
XYZ[0] = x * XYZ[1] / y; // X=x*Y/y
XYZ[2] = z * XYZ[1] / y; // Z = (1-x-y)*Y/y
}
int alpha = XYZ[0] >= ceps && XYZ[1] >= ceps && XYZ[2] >= ceps
&& XYZ[0] <= 1 - ceps && XYZ[1] <= 1 - ceps && XYZ[2] <= 1 - ceps ? 255 : 0;
if (alpha == 255) {
//rgb = colorSpace.toRGB(XYZ);
//toRGB(XYZ,rgb);
toRGB(XYZ,rgb);
alpha = (rgb[0] >= eps && rgb[1] >= eps && rgb[2] >= eps
&& rgb[0] <= 1 - eps && rgb[1] <= 1 - eps && rgb[2] <= 1 - eps) //
? 255 : 0;
if (alpha == 255) {
// rgb = colorSpace.toRGB(XYZ);
// pixels[ix + iy * w] = (alpha << 24) | ((int) (rgb[0] * 255f) << 16) | ((int) (rgb[1] * 255f) << 8) | (int) (rgb[2] * 255f);
pixels[ix + iy * w] = (alpha << 24) | ((0xff&(int) (rgb[0] * 255f)) << 16) | ((0xff&(int) (rgb[1] * 255f)) << 8) | (0xff&(int) (rgb[2] * 255f));
}
}
}
}
}
}
@Nullable public Point getColorLocation(Color c) {
float[] components = ColorUtil.fromColor(colorSpace, c);
return getColorLocation(components);
}
@Nullable public Point getColorLocation(float[] components) {
return null;
}
@Nullable public float[] getColorAt(int x, int y) {
return null;
}
public int getWidth() {
return w;
}
public int getHeight() {
return h;
}
public void toRGB(float[] ciexyz, float[] rgb) {
double X = ciexyz[0];
double Y = ciexyz[1];
double Z = ciexyz[2];
// sRGB conversion
// Convert to sRGB as described in
// http://www.w3.org/Graphics/Color/sRGB.html
/*
double Rs = 3.2410 * X + -1.5374 * Y + -0.4986 * Z;
double Gs = -0.9692 * X + 1.8760 * Y + 0.0416 * Z;
double Bs = 0.0556 * X + -0.2040 * Y + 1.0570 * Z;
/* /
// Convert to sRGB as described in
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
double Rs = 3.2404542 * X + -1.5371385 * Y + -0.4985314 * Z;
double Gs = -0.9692660 * X + 1.8760108 * Y + 0.0415560 * Z;
double Bs = 0.0556434 * X + -0.2040259 * Y + 1.0572252 * Z;
/* /
// proPhoto RGB conversion http://www.colour.org/tc8-05/Docs/colorspace/PICS2000_RIMM-ROMM.pdf
double Rs = 1.3460 * X + -0.2556 * Y + -0.0511 * Z;
double Gs = -0.5446 * X + 1.5082 * Y + 0.0205 * Z;
double Bs = 0.0 * X + 0.0 * Y + 1.2123 * Z;
* /
// One to one 'conversion'
double Rs = 1 * X + 0 * Y + 0 * Z;
double Gs = 0 * X + 1 * Y + 0 * Z;
double Bs = 0 * X + 0 * Y + 1 * Z;
*/
// Convert to Wide Gamut RGB as described in
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
double Rs = 1.4628067 * X + -0.1840623 * Y + -0.2743606 * Z;
double Gs = -0.5217933 * X + 1.4472381 * Y + 0.0677227 * Z;
double Bs = 0.0349342 * X + -0.0968930 * Y + 1.2884099 * Z;
if (Rs <= 0.00304) {
Rs = 12.92 * Rs;
} else {
Rs = 1.055 * Math.pow(Rs, 1 / 2.4) - 0.055;
}
if (Gs <= 0.00304) {
Gs = 12.92 * Gs;
} else {
Gs = 1.055 * Math.pow(Gs, 1 / 2.4) - 0.055;
}
if (Bs <= 0.00304) {
Bs = 12.92 * Bs;
} else {
Bs = 1.055 * Math.pow(Bs, 1 / 2.4) - 0.055;
}
switch (outsideGamutHandling) {
case CLAMP:
Rs = Math.min(1, Math.max(0, Rs));
Gs = Math.min(1, Math.max(0, Gs));
Bs = Math.min(1, Math.max(0, Bs));
break;
}
rgb[0] = (float) Rs;
rgb[1] = (float) Gs;
rgb[2] = (float) Bs;
//return new float[]{(float) Rs, (float) Gs, (float) Bs};
// return sRGB.fromCIEXYZ(ciexyz);
}
}