1 /* RgbProfileConverter.java -- RGB Profile conversion class
2    Copyright (C) 2004 Free Software Foundation
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package gnu.java.awt.color;
40 
41 import java.awt.color.ICC_Profile;
42 import java.awt.color.ICC_ProfileRGB;
43 import java.awt.color.ProfileDataException;
44 
45 /**
46  * RgbProfileConverter - converts RGB profiles (ICC_ProfileRGB)
47  *
48  * This type of profile contains a matrix and three
49  * tone reproduction curves (TRCs).
50  *
51  * Device RGB --> CIE XYZ is done through first multiplying with
52  * a matrix, then each component is looked-up against it's TRC.
53  *
54  * The opposite transform is done using the inverse of the matrix,
55  * and TRC:s.
56  *
57  * @author Sven de Marothy
58  */
59 public class RgbProfileConverter implements ColorSpaceConverter
60 {
61   private float[][] matrix;
62   private float[][] inv_matrix;
63   private ToneReproductionCurve rTRC;
64   private ToneReproductionCurve gTRC;
65   private ToneReproductionCurve bTRC;
66   private ColorLookUpTable toPCS;
67   private ColorLookUpTable fromPCS;
68 
69   /**
70    * CIE 1931 D50 white point (in Lab coordinates)
71    */
72   private static float[] D50 = { 0.96422f, 1.00f, 0.82521f };
73 
74   /**
75    * Constructs an RgbProfileConverter from a given ICC_ProfileRGB
76    */
RgbProfileConverter(ICC_ProfileRGB profile)77   public RgbProfileConverter(ICC_ProfileRGB profile)
78   {
79     toPCS = fromPCS = null;
80     matrix = profile.getMatrix();
81 
82     // get TRCs
83     try
84       {
85         rTRC = new ToneReproductionCurve(profile.getGamma(ICC_ProfileRGB.REDCOMPONENT));
86       }
87     catch (ProfileDataException e)
88       {
89         rTRC = new ToneReproductionCurve(profile.getTRC(ICC_ProfileRGB.REDCOMPONENT));
90       }
91     try
92       {
93         gTRC = new ToneReproductionCurve(profile.getGamma(ICC_ProfileRGB.GREENCOMPONENT));
94       }
95     catch (ProfileDataException e)
96       {
97         gTRC = new ToneReproductionCurve(profile.getTRC(ICC_ProfileRGB.GREENCOMPONENT));
98       }
99     try
100       {
101         bTRC = new ToneReproductionCurve(profile.getGamma(ICC_ProfileRGB.BLUECOMPONENT));
102       }
103     catch (ProfileDataException e)
104       {
105         bTRC = new ToneReproductionCurve(profile.getTRC(ICC_ProfileRGB.BLUECOMPONENT));
106       }
107 
108     // If a CLUT is available, it should be used, and the TRCs ignored.
109     // Note: A valid profile may only have CLUTs in one direction, and
110     // TRC:s without useful info, making reverse-transforms impossible.
111     // In this case the TRC will be used for the reverse-transform with
112     // unpredictable results. This is in line with the Java specification,
113     try
114       {
115         toPCS = new ColorLookUpTable(profile, ICC_Profile.icSigAToB0Tag);
116       }
117     catch (Exception e)
118       {
119         toPCS = null;
120       }
121 
122     try
123       {
124         fromPCS = new ColorLookUpTable(profile, ICC_Profile.icSigBToA0Tag);
125       }
126     catch (Exception e)
127       {
128         fromPCS = null;
129       }
130 
131     // Calculate the inverse matrix if no reverse CLUT is available
132     if(fromPCS == null)
133         inv_matrix = invertMatrix(matrix);
134     else
135       {
136         // otherwise just set it to an identity matrix
137         inv_matrix = new float[3][3];
138         inv_matrix[0][0] = inv_matrix[1][1] = inv_matrix[2][2] = 1.0f;
139       }
140   }
141 
toCIEXYZ(float[] in)142   public float[] toCIEXYZ(float[] in)
143   {
144     // CLUT takes precedence
145     if (toPCS != null)
146       return toPCS.lookup(in);
147 
148     float[] temp = new float[3];
149     float[] out = new float[3];
150 
151     // device space --> linear gamma
152     temp[0] = rTRC.lookup(in[0]);
153     temp[1] = gTRC.lookup(in[1]);
154     temp[2] = bTRC.lookup(in[2]);
155 
156     // matrix multiplication
157     out[0] = matrix[0][0] * temp[0] + matrix[0][1] * temp[1]
158              + matrix[0][2] * temp[2];
159     out[1] = matrix[1][0] * temp[0] + matrix[1][1] * temp[1]
160              + matrix[1][2] * temp[2];
161     out[2] = matrix[2][0] * temp[0] + matrix[2][1] * temp[1]
162              + matrix[2][2] * temp[2];
163 
164     return out;
165   }
166 
toRGB(float[] in)167   public float[] toRGB(float[] in)
168   {
169     return SrgbConverter.XYZtoRGB(toCIEXYZ(in));
170   }
171 
fromCIEXYZ(float[] in)172   public float[] fromCIEXYZ(float[] in)
173   {
174     if (fromPCS != null)
175       return fromPCS.lookup(in);
176 
177     float[] temp = new float[3];
178     float[] out = new float[3];
179 
180     // matrix multiplication
181     temp[0] = inv_matrix[0][0] * in[0] + inv_matrix[0][1] * in[1]
182               + inv_matrix[0][2] * in[2];
183     temp[1] = inv_matrix[1][0] * in[0] + inv_matrix[1][1] * in[1]
184               + inv_matrix[1][2] * in[2];
185     temp[2] = inv_matrix[2][0] * in[0] + inv_matrix[2][1] * in[1]
186               + inv_matrix[2][2] * in[2];
187 
188     // device space --> linear gamma
189     out[0] = rTRC.reverseLookup(temp[0]);
190     out[1] = gTRC.reverseLookup(temp[1]);
191     out[2] = bTRC.reverseLookup(temp[2]);
192 
193     // FIXME: Sun appears to clip the return values to [0,1]
194     // I don't believe that is a Good Thing,
195     // (some colorspaces may allow values outside that range.)
196     // So we return the actual values here.
197     return out;
198   }
199 
fromRGB(float[] in)200   public float[] fromRGB(float[] in)
201   {
202     return fromCIEXYZ(SrgbConverter.RGBtoXYZ(in));
203   }
204 
205   /**
206    * Inverts a 3x3 matrix, returns the inverse,
207    * throws an IllegalArgumentException if the matrix is not
208    * invertible (this shouldn't happen for a valid profile)
209    */
invertMatrix(float[][] matrix)210   private float[][] invertMatrix(float[][] matrix)
211   {
212     float[][] out = new float[3][3];
213     double determinant = matrix[0][0] * (matrix[1][1] * matrix[2][2]
214                          - matrix[2][1] * matrix[1][2])
215                          - matrix[0][1] * (matrix[1][0] * matrix[2][2]
216                          - matrix[2][0] * matrix[1][2])
217                          + matrix[0][2] * (matrix[1][0] * matrix[2][1]
218                          - matrix[2][0] * matrix[1][1]);
219 
220     if (determinant == 0.0)
221       throw new IllegalArgumentException("Can't invert conversion matrix.");
222     float invdet = (float) (1.0 / determinant);
223 
224     out[0][0] = invdet * (matrix[1][1] * matrix[2][2]
225                 - matrix[1][2] * matrix[2][1]);
226     out[0][1] = invdet * (matrix[0][2] * matrix[2][1]
227                 - matrix[0][1] * matrix[2][2]);
228     out[0][2] = invdet * (matrix[0][1] * matrix[1][2]
229                 - matrix[0][2] * matrix[1][1]);
230     out[1][0] = invdet * (matrix[1][2] * matrix[2][0]
231                 - matrix[1][0] * matrix[2][2]);
232     out[1][1] = invdet * (matrix[0][0] * matrix[2][2]
233                 - matrix[0][2] * matrix[2][0]);
234     out[1][2] = invdet * (matrix[0][2] * matrix[1][0]
235                 - matrix[0][0] * matrix[1][2]);
236     out[2][0] = invdet * (matrix[1][0] * matrix[2][1]
237                 - matrix[1][1] * matrix[2][0]);
238     out[2][1] = invdet * (matrix[0][1] * matrix[2][0]
239                 - matrix[0][0] * matrix[2][1]);
240     out[2][2] = invdet * (matrix[0][0] * matrix[1][1]
241                 - matrix[0][1] * matrix[1][0]);
242     return out;
243   }
244 }
245