1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.model.ImageEditor; 4 5 import static com.lightcrafts.model.ImageEditor.Locale.LOCALE; 6 import com.lightcrafts.model.Preview; 7 import com.lightcrafts.model.Region; 8 import com.lightcrafts.jai.utils.Functions; 9 import com.lightcrafts.jai.JAIContext; 10 11 import com.lightcrafts.mediax.jai.*; 12 13 import java.awt.*; 14 import java.awt.geom.Rectangle2D; 15 import java.awt.geom.GeneralPath; 16 import java.awt.image.Raster; 17 18 public class HistogramPreview extends Preview implements PaintListener { 19 private int bins[][] = null; 20 private double[][] controlPoints = null; 21 private int currentFocusZone = -1; 22 final ImageEditorEngine engine; 23 HistogramPreview(final ImageEditorEngine engine)24 HistogramPreview(final ImageEditorEngine engine) { 25 this.engine = engine; 26 } 27 28 @Override getName()29 public String getName() { 30 return LOCALE.get("Histogram_Name"); 31 } 32 33 @Override setDropper(Point p)34 public void setDropper(Point p) { 35 36 } 37 38 @Override setRegion(Region region)39 public void setRegion(Region region) { 40 41 } 42 43 @Override addNotify()44 public void addNotify() { 45 // This method gets called when this Preview is added. 46 engine.update(null, false); 47 super.addNotify(); 48 } 49 50 @Override removeNotify()51 public void removeNotify() { 52 // This method gets called when this Preview is removed. 53 super.removeNotify(); 54 } 55 setFocusedZone(int index, double[][] controlPoints)56 public void setFocusedZone(int index, double[][] controlPoints) { 57 // System.out.println("currentFocusZone: " + currentFocusZone); 58 59 if (currentFocusZone != index || this.controlPoints != controlPoints) { 60 currentFocusZone = index; 61 this.controlPoints = controlPoints; 62 repaint(); 63 } 64 } 65 binmax()66 private int binmax() { 67 int max = 0; 68 for (int[] bin : bins) { 69 int numBins = bin.length; 70 // Skip the first and last bins (pure black and pure white) from normalization 71 for (int i = 5; i < numBins - 5; i++) { 72 if (bin[i] > max) 73 max = bin[i]; 74 } 75 } 76 return (int) (1.1 * max); 77 } 78 79 @Override setSelected(Boolean selected)80 public void setSelected(Boolean selected) { 81 if (!selected) 82 bins = null; 83 } 84 85 @Override paintComponent(Graphics gr)86 protected synchronized void paintComponent(Graphics gr) { 87 Graphics2D g2d = (Graphics2D) gr; 88 89 if (bins == null) 90 engine.update(null, false); 91 92 Dimension bounds = getSize(); 93 94 final float minx = 0; 95 final float miny = 0; 96 final float width = bounds.width; 97 final float height = bounds.height - 18; 98 99 g2d.setColor(Color.lightGray); 100 g2d.fill(new Rectangle2D.Float(minx, miny, width, height + 18)); 101 102 g2d.setRenderingHint( 103 RenderingHints.KEY_ANTIALIASING, 104 RenderingHints.VALUE_ANTIALIAS_ON 105 ); 106 107 if (bins != null) { 108 final int max = binmax(); 109 110 class scaler { 111 private int yscale(double y) { 112 return (int) (height - (height - 4) * (y / (double) max) + 0.5 + miny); 113 } 114 } 115 116 scaler s = new scaler(); 117 118 for (int c = 0; c < bins.length; c++) { 119 Color color = Color.BLACK; 120 121 if (bins.length > 1) 122 switch (c) { 123 case 0: 124 color = Color.RED; 125 break; 126 case 1: 127 color = Color.GREEN; 128 break; 129 case 2: 130 color = Color.BLUE; 131 break; 132 } 133 134 g2d.setColor(color); 135 136 int numBins = bins[c].length; 137 138 int zeroY = s.yscale(0); 139 float xstep = (width+1) / numBins; 140 141 GeneralPath gp = new GeneralPath(); 142 143 gp.moveTo(minx, zeroY); 144 float lastx = minx; 145 float lasty = zeroY; 146 for (int i = 0; i < numBins; i++) { 147 int y = s.yscale(bins[c][i]); 148 float x = xstep * i + minx; 149 if (lasty != zeroY || y != zeroY) { 150 gp.lineTo(x, y); 151 lastx = x; 152 lasty = y; 153 } else { 154 gp.moveTo(x, y); 155 } 156 } 157 if (lasty != zeroY) 158 gp.lineTo(lastx, zeroY); 159 160 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f)); 161 g2d.fill(gp); 162 g2d.setComposite(AlphaComposite.SrcOver); 163 g2d.draw(gp); 164 } 165 } 166 167 float step = width / 16.0f; 168 for (int i = 0; i < 16; i++) { 169 if (i == currentFocusZone) 170 g2d.setColor(Color.yellow); 171 else { 172 float color = (float) ((Math.pow(2, i * 8.0 / (16 - 1)) - 1) / 255.); 173 float[] srgbColor = Functions.fromLinearToCS(JAIContext.systemColorSpace, new float[] {color, color, color}); 174 175 g2d.setColor(new Color((int) (255 * srgbColor[0]), (int) (255 * srgbColor[1]), (int) (255 * srgbColor[2]))); 176 } 177 g2d.fill(new Rectangle2D.Float(minx + step * i, height + miny, step + 0.5f, 18)); 178 } 179 } 180 181 private static float logTable[] = new float[0x10000]; 182 183 static { 184 for (int i = 0; i < 0x10000; i++) 185 logTable[i] = (float) Math.log1p(i); 186 } 187 computeHistogram(Rectangle visibleRect, PlanarImage image)188 private synchronized void computeHistogram(Rectangle visibleRect, PlanarImage image) { 189 int channels = image.getSampleModel().getNumBands(); 190 191 Histogram hist = new Histogram(256, 256, 512, channels); 192 193 bins = hist.getBins(); 194 195 int[] pixel = null; 196 197 int maxPixels = 256; 198 int incX = visibleRect.width >= 2 * maxPixels ? visibleRect.width / maxPixels : 1; 199 int incY = visibleRect.height >= 2 * maxPixels ? visibleRect.height / maxPixels : 1; 200 201 double log2 = Math.log(2); 202 203 int minTileX = image.XToTileX(visibleRect.x); 204 int maxTileX = image.XToTileX(visibleRect.x + visibleRect.width - 1); 205 int minTileY = image.YToTileY(visibleRect.y); 206 int maxTileY = image.YToTileY(visibleRect.y + visibleRect.height - 1); 207 208 for (int tx = minTileX; tx <= maxTileX; tx++) { 209 for (int ty = minTileY; ty <= maxTileY; ty++) { 210 Raster raster = image.getTile(tx, ty); 211 212 int minX = Math.max(visibleRect.x, raster.getMinX()); 213 int maxX = Math.min(visibleRect.x + visibleRect.width, raster.getMinX() + raster.getWidth()); 214 int minY = Math.max(visibleRect.y, raster.getMinY()); 215 int maxY = Math.min(visibleRect.y + visibleRect.height, raster.getMinY() + raster.getHeight()); 216 217 for (int x = minX; x < maxX; x+=incX) { 218 for (int y = minY; y < maxY; y+=incY) { 219 pixel = raster.getPixel(x, y, pixel); 220 for (int c = 0; c < channels; c++) { 221 int v = (int) (511 * logTable[pixel[c]] / (16 * log2)); 222 if (v > 255) 223 bins[c][v - 256]++; 224 else 225 bins[c][0]++; 226 } 227 } 228 } 229 } 230 } 231 232 bins = hist.getBins(); 233 } 234 235 private class Histogrammer extends Thread { 236 PlanarImage image; 237 PlanarImage nextImage = null; 238 Rectangle visibleRect; 239 Histogrammer(Rectangle visibleRect, PlanarImage image)240 Histogrammer(Rectangle visibleRect, PlanarImage image) { 241 super("Histogram Preview Histogrammer"); 242 this.visibleRect = visibleRect; 243 this.image = image; 244 } 245 nextView(Rectangle visibleRect, PlanarImage image)246 synchronized void nextView(Rectangle visibleRect, PlanarImage image) { 247 this.visibleRect = visibleRect; 248 nextImage = image; 249 } 250 getNextView()251 synchronized private boolean getNextView() { 252 if (nextImage != null) { 253 image = nextImage; 254 nextImage = null; 255 return true; 256 } else 257 return false; 258 } 259 260 @Override run()261 public void run() { 262 do { 263 if (getSize().width > 0 && getSize().height > 0) { 264 computeHistogram(visibleRect, image); 265 repaint(); 266 } 267 } while (getNextView()); 268 } 269 } 270 271 private Histogrammer histogrammer = null; 272 273 @Override paintDone(PlanarImage image, Rectangle visibleRect, boolean synchronous, long time)274 public void paintDone(PlanarImage image, Rectangle visibleRect, boolean synchronous, long time) { 275 Dimension previewDimension = getSize(); 276 277 if (previewDimension.getHeight() > 1 && previewDimension.getWidth() > 1) { 278 if (histogrammer == null || !histogrammer.isAlive()) { 279 histogrammer = new Histogrammer(visibleRect, image); 280 histogrammer.start(); 281 } else 282 histogrammer.nextView(visibleRect, image); 283 } 284 } 285 } 286