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