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