]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/MipMapBufferedImage.java
Temporarily added debug logging to MipMapBufferedImage for debugging
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / MipMapBufferedImage.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.utils;
13
14 import java.awt.Color;
15 import java.awt.Graphics2D;
16 import java.awt.Point;
17 import java.awt.RenderingHints;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Rectangle2D;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import com.kitfox.svg.SVGDiagram;
30
31 /**
32  * Video-ram cache suitable for rasterized PaintableSymbols scalable vector graphics.
33  * <p>
34  * This implementation rasterizes the same symbol from different mip map levels.
35  * 
36  * @see VRamImage
37  * @author Toni Kalajainen
38  */
39 public class MipMapBufferedImage extends BufferedImage {
40
41     private static final Logger LOGGER = LoggerFactory.getLogger(MipMapBufferedImage.class);
42
43     /** Extra margin to the bounds reported by batik */
44     public static final double MARGIN_PERCENT = 3;
45
46     // Was 800 in VRam.. ?
47     public static final double MAX_DIMENSION  = 600;
48     public static final double MIN_DIMENSION  = 4;
49
50     //Shape outline;
51
52     Map<Double, IRaster>       rasters        = new HashMap<Double, IRaster>();
53     double[]                   resolutions;
54     double                     minResolution, maxResolution;
55
56     /**
57      * @param original
58      * @param imageBounds
59      * @param referenceSize
60      * 
61      * FIXME: shouldn't be SVG dependent
62      */
63     public MipMapBufferedImage(SVGDiagram original, Rectangle2D imageBounds, Point referenceSize) {
64         super(original, imageBounds, referenceSize);
65         initializeRasters();
66     }
67
68     private void initializeRasters() {
69         if (LOGGER.isDebugEnabled())
70             LOGGER.debug("initializeRasters({}): diagram={}, referenceSize={}, imageBounds={}, maxres={}, minres={}", this.toString(), source, referenceSize, imageBounds, maxResolution(), minResolution());
71         List<Double> resolutions = new ArrayList<Double>();
72
73         if (referenceSize != null && !imageBounds.isEmpty()) {
74             // Init rasters - they are built on-demand
75             double maxResolution = maxResolution();
76             double minResolution = minResolution();
77             double fitResolution = fitResolution(referenceSize);
78             double resolution = fitResolution;
79             while (true) {
80                 double next = resolution * 2;
81                 if (next > maxResolution)
82                     break;
83                 resolution = next;
84             }
85             while (resolution > minResolution) {
86                 IRaster r = createRaster(resolution);
87                 rasters.put(resolution, r);
88                 resolutions.add(resolution);
89                 resolution /= 2;
90             }
91         } else {
92             // Init rasters - they are built on-demand
93             double maxResolution = maxResolution();
94             double minResolution = minResolution();
95             double resolution = maxResolution;
96             while (resolution > minResolution) {
97                 IRaster r = createRaster(resolution);
98                 rasters.put(resolution, r);
99                 resolutions.add(resolution);
100                 resolution /= 2;
101             }
102         }
103
104         if (LOGGER.isDebugEnabled())
105             LOGGER.debug("initializeRasters({}): resolutions={}", this.toString(), resolutions);
106
107         // arraylist -> array
108         this.resolutions = new double[resolutions.size()];
109         for (int i=0; i<resolutions.size(); i++)
110             this.resolutions[i] = resolutions.get(resolutions.size()-1-i);
111         this.minResolution = this.resolutions[0];
112         this.maxResolution = this.resolutions[this.resolutions.length-1];
113         //System.out.println("RESOLUTIONS: " + Arrays.toString(this.resolutions));
114     }
115
116     protected IRaster createRaster(double resolution) {
117         return new BufferedRaster(resolution);
118     }
119
120     private double fitResolution(Point p)
121     {
122         double wid = imageBounds.getWidth();
123         double hei = imageBounds.getHeight();
124         double rx = p.x / wid;
125         double ry = p.y / hei;
126         return Math.min(rx, ry);
127     }
128
129     private double maxResolution()
130     {
131         double wid = imageBounds.getWidth();
132         double hei = imageBounds.getHeight();
133         return MAX_DIMENSION/Math.sqrt(wid*hei);
134     }
135
136     private double minResolution()
137     {
138         double wid = imageBounds.getWidth();
139         double hei = imageBounds.getHeight();
140         return MIN_DIMENSION/Math.sqrt(wid*hei);
141     }
142
143     protected double requiredResolution(AffineTransform at)
144     {
145         double m00 = at.getScaleX();
146         double m11 = at.getScaleY();
147         double m10 = at.getShearY();
148         double m01 = at.getShearX();
149         // Project unit vector to canvas
150         double sx = Math.sqrt( m00*m00 + m10*m10 );
151         double sy = Math.sqrt( m01*m01 + m11*m11 );
152         return Math.max(sx, sy);
153         //return Math.sqrt(sx*sx+sy*sy);
154     }
155
156     protected double findClosestResolution(double resolution)
157     {
158         int index = Arrays.binarySearch(resolutions, resolution);
159         if (index>=0) return resolutions[index];
160         index  = -(index+1);
161
162         if (index>=resolutions.length) index = resolutions.length-1;
163         if (index<0) index = 0;
164         return resolutions[index];
165     }
166
167     @Override
168     public void paint(Graphics2D g) {
169         // Quality rendering requested, do not render from cache
170         //QualityHints.HIGH_QUALITY_HINTS.setQuality(g);
171         if (g.getRenderingHint(RenderingHints.KEY_RENDERING) == RenderingHints.VALUE_RENDER_QUALITY)
172         {
173             try {
174                 source.render(g);
175             } catch (Exception e) {
176                 // NOTE: Catching Exception instead of SVGException due to an
177                 // NPE when encountering invalid color SVG definitions (e.g.
178                 // rgb(256,-1,0))
179                 e.printStackTrace();
180             }
181             return;
182         }
183
184         double requiredResolution = requiredResolution(g.getTransform());
185         // This scale makes the mipmapped painting use a mipmap that is smaller
186         // than the requested image pixel size in cases where the required
187         // resolution only slightly exceeds the size of an available mipmap.
188         requiredResolution *= 0.95;
189         //System.out.println("required resolution: " + requiredResolution);
190
191         if (requiredResolution > getRasterRenderingThresholdResolution()) {
192             Graphics2D g2d = (Graphics2D) g.create();
193             setupSourceRender(g2d);
194             try {
195                 source.render(g2d);
196             } catch (Exception e) {
197                 // NOTE: Catching Exception instead of SVGException due to an
198                 // NPE when encountering invalid color SVG definitions (e.g.
199                 // rgb(256,-1,0))
200                 e.printStackTrace();
201             } finally {
202                 g2d.dispose();
203             }
204         } else {
205             Object origInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
206             if (origInterpolationHint==null)
207                 origInterpolationHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
208
209             g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
210
211             double closestResolution = findClosestResolution(requiredResolution);
212             //System.out.println("  resolutions: " + Arrays.toString(resolutions));
213             //System.out.println("  closest resolution: " + closestResolution);
214             IRaster raster = rasters.get(closestResolution);
215             try {
216                 raster.paint( g );
217             } finally {
218                 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, origInterpolationHint);
219             }
220         }
221     }
222
223     protected void setupSourceRender(Graphics2D g2d) {
224         g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
225         g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
226         g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
227         g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
228         g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
229         g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
230     }
231
232     protected double getRasterRenderingThresholdResolution() {
233         return maxResolution;
234     }
235
236     @Override
237     public synchronized void releaseRaster() {
238         for (IRaster r : rasters.values())
239             r.release();
240     }
241
242     static interface IRaster extends Comparable<IRaster> {
243         double getResolution();
244         void paint(Graphics2D g);
245         void release();
246     }
247
248     static abstract class Raster implements IRaster {
249         protected final double resolution;
250
251         public Raster(double resolution) {
252             this.resolution = resolution;
253         }
254
255         public double getResolution() {
256             return resolution;
257         }
258
259         public int compareTo(IRaster o) {
260             double r = getResolution();
261             double or = o.getResolution();
262             if (or < r)
263                 return -1;
264             if (or > r)
265                 return 1;
266             return 0;
267         }
268     }
269
270     class BufferedRaster extends Raster {
271         java.awt.image.BufferedImage image;
272         //int widMargin, heiMargin;
273         int wid, hei;
274
275         BufferedRaster(double resolution) {
276             super(resolution);
277             double wid = imageBounds.getWidth();
278             double hei = imageBounds.getHeight();
279             this.wid = (int) (wid * resolution);
280             this.hei = (int) (hei * resolution);
281 //            widMargin = (int) (wid * resolution * (MARGIN_PERCENT/100)) +1;
282 //            heiMargin = (int) (hei * resolution * (MARGIN_PERCENT/100)) +1;
283         }
284
285         synchronized java.awt.image.BufferedImage getOrCreate()
286         {
287             if (image!=null) return image;
288             image = new java.awt.image.BufferedImage(
289                     (wid+0*2+1),
290                     (hei+0*2+1),
291                     java.awt.image.BufferedImage.TYPE_INT_ARGB);
292
293             Graphics2D target = image.createGraphics();
294             target.setBackground(new Color(255,255,255,0));
295             target.clearRect(0, 0, image.getWidth(), image.getHeight());
296
297             target.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
298             target.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
299             target.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
300             target.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
301
302 //            target.translate(widMargin, heiMargin);
303             target.scale(resolution, resolution);
304             target.translate(-imageBounds.getMinX(), -imageBounds.getMinY());
305             try {
306                 source.render(target);
307             } catch (Exception e) {
308                 // TODO Auto-generated catch block
309                 // NOTE: Catching Exception instead of SVGException due to an
310                 // NPE when encountering invalid color SVG definitions (e.g.
311                 // rgb(256,-1,0))
312                 e.printStackTrace();
313             }
314 //            source.paint(
315 //                    new GraphicsContextImpl(target, new Rectangle2D.Double(0,0, image.getWidth(), image.getHeight()), null)
316 //            );
317             target.dispose();
318
319             return image;
320         }
321
322         public void paint(Graphics2D g) {
323             java.awt.image.BufferedImage image = getOrCreate();
324             if (image==null)
325             {
326                 try {
327                     source.render(g);
328                 } catch (Exception e) {
329                     // TODO Auto-generated catch block
330                     // NOTE: Catching Exception instead of SVGException due to an
331                     // NPE when encountering invalid color SVG definitions (e.g.
332                     // rgb(256,-1,0))
333                     e.printStackTrace();
334                 }
335                 return;
336             }
337             AffineTransform af = g.getTransform();
338             Object rh = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
339             try {
340                 /// Bicubic interpolation is very slow with opengl pipeline
341                 if (rh == RenderingHints.VALUE_INTERPOLATION_BICUBIC)
342                     g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
343                             RenderingHints.VALUE_INTERPOLATION_BILINEAR);
344                 g.translate(imageBounds.getMinX(), imageBounds.getMinY());
345                 g.scale(1/resolution, 1/resolution);
346 //                g.translate(-widMargin, -heiMargin);
347                 g.drawImage(image, 0, 0, null);
348             } finally {
349                 g.setTransform(af);
350                 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, rh);
351             }
352         }
353
354         public void release() {
355             image = null;
356         }
357     }
358
359 }