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.g2d.image.impl;
14 import java.awt.Color;
15 import java.awt.Graphics2D;
16 import java.awt.GraphicsConfiguration;
17 import java.awt.Shape;
18 import java.awt.Transparency;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.Rectangle2D;
21 import java.awt.image.VolatileImage;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.EnumSet;
25 import java.util.HashMap;
26 import java.util.List;
29 import org.simantics.g2d.image.Image;
30 import org.simantics.scenegraph.Node;
31 import org.simantics.scenegraph.g2d.G2DParentNode;
32 import org.simantics.scenegraph.utils.QualityHints;
35 * Video-ram cache suitable for rasterized PaintableSymbols scalable vector graphics.
37 * This implementation rasterizes the same symbol from different mip map levels.
40 * @author Toni Kalajainen
42 public class MipMapVRamBufferedImage extends ImageProxy implements Image {
44 /** Extra margin to the bounds reported by batik*/
45 public static final double MARGIN_PERCENT = 3;
47 public static final double MAX_DIMENSION = 800;
48 public static final double MIN_DIMENSION = 4;
50 final GraphicsConfiguration gc;
53 double [] resolutions;
54 Map<Double, Raster> rasters = new HashMap<Double, Raster>();
55 double minResolution, maxResolution;
56 EnumSet<Feature> caps;
58 public MipMapVRamBufferedImage(Image original, GraphicsConfiguration gc)
62 throw new IllegalArgumentException("Argument is null.");
63 this.source = original;
66 // Init rasters - they are built on-demand
67 double maxResolution = maxResolution();
68 double minResolution = minResolution();
69 double resolution = maxResolution;
70 List<Double> resolutions = new ArrayList<Double>();
71 while (resolution>minResolution)
73 Raster r = new Raster(resolution);
74 rasters.put(resolution, r);
75 resolutions.add(resolution);
80 this.resolutions = new double[resolutions.size()];
81 for (int i=0; i<resolutions.size(); i++)
82 this.resolutions[i] = resolutions.get(resolutions.size()-1-i);
83 this.minResolution = this.resolutions[0];
84 this.maxResolution = this.resolutions[this.resolutions.length-1];
86 if (original.getFeatures().contains(Feature.Volatile))
87 caps = EnumSet.noneOf(Feature.class);
89 caps = EnumSet.of(Feature.Vector);
92 private double maxResolution()
94 Rectangle2D bounds = source.getBounds();
95 double wid = bounds.getWidth();
96 double hei = bounds.getHeight();
97 return MAX_DIMENSION/Math.sqrt(wid*hei);
100 private double minResolution()
102 Rectangle2D bounds = source.getBounds();
103 double wid = bounds.getWidth();
104 double hei = bounds.getHeight();
105 return MIN_DIMENSION/Math.sqrt(wid*hei);
108 private double requiredResolution(AffineTransform at)
110 double m00 = at.getScaleX();
111 double m11 = at.getScaleY();
112 double m10 = at.getShearY();
113 double m01 = at.getShearX();
114 // Project unit vector to canvas
115 double sx = Math.sqrt( m00*m00+m10*m10 );
116 double sy = Math.sqrt( m01*m01+m11*m11 );
117 return Math.sqrt(sx*sx+sy*sy);
120 private double findClosestResolution(double resolution)
122 int index = Arrays.binarySearch(resolutions, resolution);
123 if (index>=0) return resolutions[index];
126 if (index>=resolutions.length) index = resolutions.length-1;
127 if (index<0) index = 0;
128 return resolutions[index];
132 public Node init(G2DParentNode parent) {
134 // Graphics2D g = gc.getGraphics2D();
135 // // Quality rendering requested, do not render from cache
136 // if (g.getRenderingHint(RenderingHints.KEY_RENDERING) == RenderingHints.VALUE_RENDER_QUALITY)
142 // double requiredResolution = requiredResolution(g.getTransform());
143 // if (requiredResolution > maxResolution)
145 // g = gc.createClone();
146 // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
147 // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
148 // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
149 // g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
150 // g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
151 // g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
152 // gc = new GraphicsContextImpl(g, gc.getBounds(), gc.getNode());
157 // double closestResolution = findClosestResolution(requiredResolution);
159 // Raster raster = rasters.get(closestResolution);
161 // Object origInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
162 // if (origInterpolationHint==null)
163 // origInterpolationHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
164 // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
166 // raster.paint( gc );
168 // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, origInterpolationHint);
172 public Image getOriginalPaintableSymbol()
177 synchronized void releaseRaster() {
178 for (Raster r : rasters.values())
184 protected void notifyChanged() {
186 super.notifyChanged();
189 class Raster implements Comparable<Raster> {
192 int widMargin, heiMargin;
195 Raster(double resolution) {
196 Rectangle2D bounds = source.getBounds();
197 this.resolution = resolution;
198 double wid = bounds.getWidth();
199 double hei = bounds.getHeight();
200 this.wid = (int) (wid * resolution);
201 this.hei = (int) (hei * resolution);
202 widMargin = (int) (wid * resolution * (MARGIN_PERCENT/100)) +1;
203 heiMargin = (int) (hei * resolution * (MARGIN_PERCENT/100)) +1;
206 synchronized VolatileImage restore()
208 Rectangle2D bounds = source.getBounds();
210 image = gc.createCompatibleVolatileImage(
213 Transparency.TRANSLUCENT);
215 int validateResult = image.validate(gc);
216 if (validateResult == VolatileImage.IMAGE_INCOMPATIBLE)
219 if (validateResult == VolatileImage.IMAGE_OK)
222 if (validateResult == VolatileImage.IMAGE_RESTORED /*raster.contentsLost()*/) {
223 Graphics2D target = image.createGraphics();
224 target.setBackground(new Color(255,255,255,0));
225 target.clearRect(0, 0, image.getWidth(), image.getHeight());
226 QualityHints.HIGH_QUALITY_HINTS.setQuality(target);
227 target.translate(widMargin, heiMargin);
228 target.scale(resolution, resolution);
229 target.translate(-bounds.getMinX(), -bounds.getMinY());
231 // new GraphicsContextImpl(new Rectangle2D.Double(0,0, image.getWidth(), image.getHeight()), null)
239 // public void paint(GraphicsContext gc) {
240 // Rectangle2D bounds = source.getBounds();
241 // VolatileImage image = restore();
244 // QualityHints.LOW_QUALITY_HINTS.setQuality(gc.getGraphics2D());
248 // Graphics2D g = gc.getGraphics2D();
249 // AffineTransform af = g.getTransform();
250 // Object rh = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
252 // /// Bicubic interpolation is very slow with opengl pipeline
253 // if (rh == RenderingHints.VALUE_INTERPOLATION_BICUBIC)
254 // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
255 // RenderingHints.VALUE_INTERPOLATION_BILINEAR);
256 // g.translate(bounds.getMinX(), bounds.getMinY());
257 // g.scale(1/resolution, 1/resolution);
258 // g.translate(-widMargin, -heiMargin);
259 // g.drawImage(image, 0, 0, null);
261 // g.setTransform(af);
262 // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, rh);
267 public int compareTo(Raster other) {
268 if (other.resolution<resolution)
270 if (other.resolution>resolution)
277 public int hashCode() {
278 return gc.hashCode() ^original.hashCode();
282 public boolean equals(Object obj) {
283 if (!(obj instanceof VRamBufferedImage)) return false;
284 VRamBufferedImage o = (VRamBufferedImage) obj;
285 return o.gc == gc && o.original.equals(original);
289 public EnumSet<Feature> getFeatures() {
293 public Image getSource() {
297 public GraphicsConfiguration getGraphicsConfiguration() {