/* * @(#)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); } }