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