1 /* Copyright (C) 2005-2011 Fabio Riccardi */
2 
3 package com.lightcrafts.model.ImageEditor;
4 
5 import Jama.Matrix;
6 import com.lightcrafts.jai.utils.Transform;
7 import com.lightcrafts.model.OperationType;
8 import com.lightcrafts.model.SliderConfig;
9 import com.lightcrafts.model.ColorDropperOperation;
10 import com.lightcrafts.utils.ColorScience;
11 import com.lightcrafts.utils.splines;
12 import com.lightcrafts.utils.DCRaw;
13 
14 import com.lightcrafts.mediax.jai.JAI;
15 import com.lightcrafts.mediax.jai.LookupTableJAI;
16 import com.lightcrafts.mediax.jai.PlanarImage;
17 import com.lightcrafts.mediax.jai.RenderedOp;
18 import com.lightcrafts.image.types.RawImageInfo;
19 import com.lightcrafts.image.types.AuxiliaryImageInfo;
20 
21 import java.awt.geom.Point2D;
22 import java.awt.image.renderable.ParameterBlock;
23 import java.awt.image.RenderedImage;
24 import java.text.DecimalFormat;
25 import java.util.Map;
26 import java.util.TreeMap;
27 
28 /**
29  * Created by IntelliJ IDEA.
30  * User: fabio
31  * Date: May 31, 2005
32  * Time: 7:08:49 PM
33  * To change this template use File | Settings | File Templates.
34  */
35 public class WhiteBalanceV2 extends BlendedOperation implements ColorDropperOperation {
36     static final String SOURCE = "Temperature";
37     private final String TINT = "Tint";
38     private float tint = 0;
39     private Point2D p = null;
40 
41     static final OperationType typeV2 = new OperationTypeImpl("White Point V2");
42     static final OperationType typeV3 = new OperationTypeImpl("White Point V3");
43 
44     private float source = 5000;
45     private float REF_T = 5000;
46     private ColorScience.CAMethod caMethod = ColorScience.CAMethod.Bradford;
47 
WhiteBalanceV2(Rendering rendering, OperationType type)48     public WhiteBalanceV2(Rendering rendering, OperationType type) {
49         super(rendering, type);
50         colorInputOnly = true;
51 
52         caMethod = ColorScience.CAMethod.Mixed;
53 
54         AuxiliaryImageInfo auxInfo = rendering.getEngine().getAuxInfo();
55 
56         if (false && auxInfo instanceof RawImageInfo) {
57             final DCRaw dcRaw = ((RawImageInfo)auxInfo).getDCRaw();
58 
59             float[] daylightMultipliers = dcRaw.getDaylightMultipliers();
60             float[] cameraMultipliers = dcRaw.getCameraMultipliers();
61 
62             System.out.println("daylightMultipliers: " + daylightMultipliers[0] + ", " + daylightMultipliers[1] + ", " + daylightMultipliers[2]);
63             System.out.println("cameraMultipliers: " + cameraMultipliers[0] + ", " + cameraMultipliers[1] + ", " + cameraMultipliers[2]);
64 
65             float RGBToXYZMat[][] = ColorScience.RGBToXYZMat;
66 
67             if (false && daylightMultipliers[0] != 0) {
68                 float max = Math.max(daylightMultipliers[0], Math.max(daylightMultipliers[1], daylightMultipliers[2]));
69 
70                 float xyz_dt[] = new float[3];
71                 for (int i = 0; i < 3; i++)
72                     for (int j = 0; j < 3; j++)
73                         xyz_dt[i] += RGBToXYZMat[j][i] * daylightMultipliers[i]/max;
74 
75                 float x = xyz_dt[0]/(xyz_dt[0]+xyz_dt[1]+xyz_dt[2]);
76 
77                 REF_T = ColorScience.findTemperature(daylightMultipliers, 5000, caMethod);
78 
79                 System.out.println("Daylight Temperature a (" + x + ") : " + ColorScience.CCTX(x) + ", " + REF_T);
80 
81                 float[] rgb = new float[3];
82 
83                 for (int i = 0; i < 3; i++)
84                     rgb[i] = 0.18f * daylightMultipliers[i] / max;
85 
86                 float[] wb = ColorScience.neutralTemperature(rgb, 5000, caMethod);
87                 REF_T = wb[0];
88 
89                 System.out.println("Daylight Temperature b (" + x + ") : " + ColorScience.CCTX(x) + ", " + REF_T + ", tint: " + wb[1]);
90 
91                 float[] inverseMultipliers = new float[] {1/daylightMultipliers[0], 1/daylightMultipliers[1], 1/daylightMultipliers[2]};
92                 float invMax = Math.max(inverseMultipliers[0], Math.max(inverseMultipliers[1], inverseMultipliers[2]));
93                 for (int i = 0; i < 3; i++)
94                     rgb[i] = 0.18f * inverseMultipliers[i] / invMax;
95 
96                 int[] rgbi = new int[] {(int) (0xffff * rgb[0]), (int) (0xffff * rgb[1]), (int) (0xffff * rgb[2])};
97 
98                 wb = neutralize(rgbi, caMethod, 5000, 5000);
99                 source = wb[0];
100                 tint = 0; // wb[1];
101 
102                 System.out.println("Daylight Temperature c (" + x + ") : " + ColorScience.CCTX(x) + ", " + source + ", tint: " + wb[1]);
103 
104             }
105 
106             if (cameraMultipliers[0] != 0) {
107                 float max = Math.max(cameraMultipliers[0], Math.max(cameraMultipliers[1], cameraMultipliers[2]));
108 
109                 float xyz_ct[] = new float[3];
110                 for (int i = 0; i < 3; i++)
111                     for (int j = 0; j < 3; j++)
112                         xyz_ct[i] += RGBToXYZMat[j][i] * cameraMultipliers[i]/max;
113 
114                 float x = xyz_ct[0]/(xyz_ct[0]+xyz_ct[1]+xyz_ct[2]);
115 
116                 float[] rgb = new float[3];
117 
118                 for (int i = 0; i < 3; i++)
119                     rgb[i] = 0.18f * cameraMultipliers[i] / max;
120 
121                 float[] wb = ColorScience.neutralTemperature(rgb, REF_T, caMethod);
122                 source = wb[0];
123                 tint = 0.18f * 256 * wb[1];
124 
125                 System.out.println("Camera Temperature a (" + x + ") : " + ColorScience.CCTX(x) + ", " + source + ", tint: " + tint);
126 
127                 float[] inverseMultipliers = new float[] {1/cameraMultipliers[0], 1/cameraMultipliers[1], 1/cameraMultipliers[2]};
128                 float invMax = Math.max(inverseMultipliers[0], Math.max(inverseMultipliers[1], inverseMultipliers[2]));
129                 for (int i = 0; i < 3; i++)
130                     rgb[i] = 0.18f * inverseMultipliers[i] / invMax;
131 
132                 int[] rgbi = new int[] {(int) (0xffff * rgb[0]), (int) (0xffff * rgb[1]), (int) (0xffff * rgb[2])};
133 
134                 wb = neutralize(rgbi, caMethod, 5000, REF_T);
135                 source = wb[0];
136                 tint = 0; // wb[1];
137 
138                 System.out.println("Camera Temperature b (" + x + ") : " + ColorScience.CCTX(x) + ", " + source + ", tint: " + wb[1]);
139             }
140 
141             double dmax = Math.max(daylightMultipliers[0], Math.max(daylightMultipliers[1], daylightMultipliers[2]));
142 
143             for (int c=0; c < 3; c++)
144                 daylightMultipliers[c] /= dmax;
145 
146             /* float[] wb = new float[] {(cameraMultipliers[0] / (cameraMultipliers[1] * daylightMultipliers[0])),
147                                       (cameraMultipliers[1] / (cameraMultipliers[1] * daylightMultipliers[1])),
148                                       (cameraMultipliers[2] / (cameraMultipliers[1] * daylightMultipliers[2]))};
149 
150             System.out.println("wb: " + wb[0] + ", " + wb[1] + ", " + wb[2]);
151 
152             for (int i = 0; i < 3; i++)
153                 wb[i] = 1/wb[i];
154 
155             System.out.println("inverse wb: " + wb[0] + ", " + wb[1] + ", " + wb[2]);
156 
157             float n[] = neutralize(new int[] {(int) (128 * 256 * wb[0]),
158                                               (int) (128 * 256 * wb[1]),
159                                               (int) (128 * 256 * wb[2])}, caMethod, source, REF_T);
160             if (n != null) {
161                 source = n[0];
162                 // tint = Math.min(Math.max(n[1], -20), 20);
163             } */
164         }
165 
166         addSliderKey(SOURCE);
167         addSliderKey(TINT);
168 
169         setSliderConfig(SOURCE, new SliderConfig(1000, 40000, source, 10, true, new DecimalFormat("0")));
170         setSliderConfig(TINT, new SliderConfig(-20, 20, tint, 0.1, false, new DecimalFormat("0.0")));
171     }
172 
neutralDefault()173     public boolean neutralDefault() {
174         return false;
175     }
176 
setSliderValue(String key, double value)177     public void setSliderValue(String key, double value) {
178         value = roundValue(key, value);
179 
180         if (key == SOURCE && source != value) {
181             source = (float) value;
182         } else if (key == TINT && tint != value) {
183             tint = (float) value;
184         } else
185             return;
186 
187         super.setSliderValue(key, value);
188     }
189 
W(float original, float target)190     static float[] W(float original, float target) {
191         float[] originalW = ColorScience.W(original);
192         float[] targetW = ColorScience.W(target);
193         return new float[]{originalW[0] / targetW[0], originalW[1] / targetW[1], originalW[2] / targetW[2]};
194     }
195 
setColor(Point2D p)196     public Map<String, Float> setColor(Point2D p) {
197         this.p = p;
198         settingsChanged();
199         this.p = null;
200 
201         Map<String, Float> result = new TreeMap<String, Float>();
202         result.put(SOURCE, source);
203         result.put(TINT, tint);
204         return result;
205     }
206 
207     static Matrix RGBtoZYX = new Matrix(ColorScience.RGBtoZYX()).transpose();
208     static Matrix XYZtoRGB = RGBtoZYX.inverse();
209 
neutralize(int pixel[], ColorScience.CAMethod caMethod, float source, float REF_T)210     static float[] neutralize(int pixel[], ColorScience.CAMethod caMethod, float source, float REF_T) {
211         double r = pixel[0];
212         double g = pixel[1];
213         double b = pixel[2];
214         double sat = ColorScience.saturation(r, g, b);
215         int minT = (int) source;
216         double wbr = 0, wbg = 0, wbb = 0;
217 
218         for (int t = 1000; t < 40000; t+= 0.001 * t) {
219             Matrix B = new Matrix(ColorScience.chromaticAdaptation(REF_T, t, caMethod));
220             Matrix combo = XYZtoRGB.times(B.times(RGBtoZYX));
221 
222             Matrix color = new Matrix(new double[][]{{pixel[0]}, {pixel[1]}, {pixel[2]}});
223 
224             color = combo.times(color);
225 
226             r = color.get(0, 0);
227             g = color.get(1, 0);
228             b = color.get(2, 0);
229 
230             double tSat = ColorScience.saturation(r, g, b);
231 
232             if (tSat < sat) {
233                 sat = tSat;
234                 minT = t;
235                 wbr = r / 256;
236                 wbg = g / 256;
237                 wbb = b / 256;
238             }
239         }
240 
241         if (wbr != 0 || wbg != 0 || wbb != 0) {
242             System.out.println("wb: " + wbr + ", " + wbg + ", " + wbb + ", sat: " + sat);
243             return new float[] {minT, (float) (- (wbg - (wbr + wbb) / 2))};
244         } else
245             return new float[] {REF_T, 0};
246     }
247 
whiteBalance(RenderedImage image, float source, float REF_T, float tint, float lightness, ColorScience.CAMethod caMethod)248     static public PlanarImage whiteBalance(RenderedImage image, float source,
249                                            float REF_T, float tint, float lightness,
250                                            ColorScience.CAMethod caMethod) {
251         return whiteBalance(image, source, REF_T, tint, lightness, 1, null, caMethod);
252     }
253 
whiteBalanceMatrix(float source, float REF_T, float mult, float cameraRGB[][], ColorScience.CAMethod caMethod)254     static public float[][] whiteBalanceMatrix(float source, float REF_T, float mult, float cameraRGB[][], ColorScience.CAMethod caMethod) {
255         Matrix B = new Matrix(ColorScience.chromaticAdaptation(REF_T, source, caMethod));
256         Matrix combo = XYZtoRGB.times(B.times(RGBtoZYX));
257 
258         Matrix m = combo.times(new Matrix(new double[][]{{1},{1},{1}}));
259 
260         double max = m.get(1, 0); // Math.max(m.get(1, 0), Math.max(m.get(1, 0), m.get(2, 0)));
261         if (max != 1)
262             combo = combo.times(new Matrix(new double[][]{{1/max, 0, 0},{0, 1/max, 0},{0, 0, 1/max}}));
263 
264         if (cameraRGB != null)
265             combo = combo.times(new Matrix(cameraRGB));
266 
267         if (mult != 1)
268             combo = combo.times(mult);
269 
270         return combo.getArrayFloat();
271     }
272 
tintCast(PlanarImage image, float tint, float lightness)273     static public PlanarImage tintCast(PlanarImage image, float tint, float lightness) {
274         if (tint != 0) {
275             double tred = - tint / 4;
276             double tgreen = tint / 2;
277             double tblue = - tint / 4;
278 
279             double polygon[][] = {
280                 {0,     0},
281                 {lightness, 0},
282                 {1,     0}
283             };
284 
285             polygon[1][1] = tred;
286             double redCurve[][] = new double[256][2];
287             splines.bspline(2, polygon, redCurve);
288 
289             polygon[1][1] = tgreen;
290             double greenCurve[][] = new double[256][2];
291             splines.bspline(2, polygon, greenCurve);
292 
293             polygon[1][1] = tblue;
294             double blueCurve[][] = new double[256][2];
295             splines.bspline(2, polygon, blueCurve);
296 
297             short table[][] = new short[3][0x10000];
298 
299             splines.Interpolator interpolator = new splines.Interpolator();
300 
301             for (int i = 0; i < 0x10000; i++)
302                 table[0][i] = (short) (0xffff & (int) Math.min(Math.max(i + 0xff * interpolator.interpolate(i / (double) 0xffff, redCurve), 0), 0xffff));
303 
304             interpolator.reset();
305             for (int i = 0; i < 0x10000; i++)
306                 table[1][i] = (short) (0xffff & (int) Math.min(Math.max(i + 0xff * interpolator.interpolate(i / (double) 0xffff, greenCurve), 0), 0xffff));
307 
308             interpolator.reset();
309             for (int i = 0; i < 0x10000; i++)
310                 table[2][i] = (short) (0xffff & (int) Math.min(Math.max(i + 0xff * interpolator.interpolate(i / (double) 0xffff, blueCurve), 0), 0xffff));
311 
312             LookupTableJAI lookupTable = new LookupTableJAI(table, true);
313 
314             ParameterBlock pb = new ParameterBlock();
315             pb.addSource(image);
316             pb.add(lookupTable);
317             return JAI.create("lookup", pb, null);
318         } else
319             return image;
320     }
321 
whiteBalance(RenderedImage image, float source, float REF_T, float tint, float lightness, float mult, float cameraRGB[][], ColorScience.CAMethod caMethod)322     static public PlanarImage whiteBalance(RenderedImage image, float source, float REF_T,
323                                            float tint, float lightness, float mult, float cameraRGB[][],
324                                            ColorScience.CAMethod caMethod) {
325         float[][] b = whiteBalanceMatrix(source, REF_T, mult, cameraRGB, caMethod);
326 
327         double[][] t = new double[3][4]; // for BC, last column si going to be zero
328         for (int i = 0; i < 3; i++)
329             for (int j = 0; j < 3; j++)
330                 t[i][j] = b[i][j];
331 
332         RenderedOp cargb = JAI.create("BandCombine", image, t, null);
333 
334         if (tint != 0)
335             return tintCast(cargb, tint, lightness);
336         else
337             return cargb;
338     }
339 
340     private class WhiteBalanceTransform extends BlendedTransform {
WhiteBalanceTransform(PlanarImage source)341         WhiteBalanceTransform(PlanarImage source) {
342             super(source);
343         }
344 
345         @Override
setFront()346         public PlanarImage setFront() {
347             float lightness = 0.18f;
348 
349             if (p != null) {
350                 int pixel[] = pointToPixel(p);
351 
352                 if (pixel != null) {
353                     float n[] = neutralize(pixel, caMethod, source, REF_T);
354                     if (n != null) {
355                         lightness = pixel[1]/255.0f;
356 
357                         source = n[0];
358                         tint = Math.min(Math.max(n[1], -20), 20);
359                     }
360                 }
361             }
362 
363             return whiteBalance(back, source, REF_T, tint, lightness, caMethod);
364         }
365     }
366 
367     @Override
updateOp(Transform op)368     protected void updateOp(Transform op) {
369         op.update();
370     }
371 
372     @Override
createBlendedOp(PlanarImage source)373     protected BlendedTransform createBlendedOp(PlanarImage source) {
374         return new WhiteBalanceTransform(source);
375     }
376 
377     @Override
getType()378     public OperationType getType() {
379         return type;
380     }
381 }
382