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