1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.utils;
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;
26 import com.kitfox.svg.SVGDiagram;
29 * Video-ram cache suitable for rasterized PaintableSymbols scalable vector graphics.
31 * This implementation rasterizes the same symbol from different mip map levels.
34 * @author Toni Kalajainen
36 public class MipMapBufferedImage extends BufferedImage {
38 /** Extra margin to the bounds reported by batik */
39 public static final double MARGIN_PERCENT = 3;
41 // Was 800 in VRam.. ?
42 public static final double MAX_DIMENSION = 600;
43 public static final double MIN_DIMENSION = 4;
47 Map<Double, IRaster> rasters = new HashMap<Double, IRaster>();
49 double minResolution, maxResolution;
54 * @param referenceSize
56 * FIXME: shouldn't be SVG dependent
58 public MipMapBufferedImage(SVGDiagram original, Rectangle2D imageBounds, Point referenceSize) {
59 super(original, imageBounds, referenceSize);
63 private void initializeRasters() {
64 List<Double> resolutions = new ArrayList<Double>();
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;
73 double next = resolution * 2;
74 if (next > maxResolution)
78 while (resolution > minResolution) {
79 IRaster r = createRaster(resolution);
80 rasters.put(resolution, r);
81 resolutions.add(resolution);
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);
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));
106 protected IRaster createRaster(double resolution) {
107 return new BufferedRaster(resolution);
110 private double fitResolution(Point p)
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);
119 private double maxResolution()
121 double wid = imageBounds.getWidth();
122 double hei = imageBounds.getHeight();
123 return MAX_DIMENSION/Math.sqrt(wid*hei);
126 private double minResolution()
128 double wid = imageBounds.getWidth();
129 double hei = imageBounds.getHeight();
130 return MIN_DIMENSION/Math.sqrt(wid*hei);
133 protected double requiredResolution(AffineTransform at)
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);
146 protected double findClosestResolution(double resolution)
148 int index = Arrays.binarySearch(resolutions, resolution);
149 if (index>=0) return resolutions[index];
152 if (index>=resolutions.length) index = resolutions.length-1;
153 if (index<0) index = 0;
154 return resolutions[index];
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)
165 } catch (Exception e) {
166 // NOTE: Catching Exception instead of SVGException due to an
167 // NPE when encountering invalid color SVG definitions (e.g.
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);
181 if (requiredResolution > getRasterRenderingThresholdResolution()) {
182 Graphics2D g2d = (Graphics2D) g.create();
183 setupSourceRender(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.
195 Object origInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
196 if (origInterpolationHint==null)
197 origInterpolationHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
199 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
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);
208 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, origInterpolationHint);
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);
222 protected double getRasterRenderingThresholdResolution() {
223 return maxResolution;
227 public synchronized void releaseRaster() {
228 for (IRaster r : rasters.values())
232 static interface IRaster extends Comparable<IRaster> {
233 double getResolution();
234 void paint(Graphics2D g);
238 static abstract class Raster implements IRaster {
239 protected final double resolution;
241 public Raster(double resolution) {
242 this.resolution = resolution;
245 public double getResolution() {
249 public int compareTo(IRaster o) {
250 double r = getResolution();
251 double or = o.getResolution();
260 class BufferedRaster extends Raster {
261 java.awt.image.BufferedImage image;
262 //int widMargin, heiMargin;
265 BufferedRaster(double 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;
275 synchronized java.awt.image.BufferedImage getOrCreate()
277 if (image!=null) return image;
278 image = new java.awt.image.BufferedImage(
281 java.awt.image.BufferedImage.TYPE_INT_ARGB);
283 Graphics2D target = image.createGraphics();
284 target.setBackground(new Color(255,255,255,0));
285 target.clearRect(0, 0, image.getWidth(), image.getHeight());
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);
292 // target.translate(widMargin, heiMargin);
293 target.scale(resolution, resolution);
294 target.translate(-imageBounds.getMinX(), -imageBounds.getMinY());
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.
305 // new GraphicsContextImpl(target, new Rectangle2D.Double(0,0, image.getWidth(), image.getHeight()), null)
312 public void paint(Graphics2D g) {
313 java.awt.image.BufferedImage image = getOrCreate();
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.
327 AffineTransform af = g.getTransform();
328 Object rh = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
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);
340 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, rh);
344 public void release() {