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