/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.scenegraph.utils; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import com.kitfox.svg.SVGDiagram; /** * Video-ram cache suitable for rasterized PaintableSymbols scalable vector graphics. *

* This implementation rasterizes the same symbol from different mip map levels. * * @see VRamImage * @author Toni Kalajainen */ public class MipMapBufferedImage extends BufferedImage { /** Extra margin to the bounds reported by batik */ public static final double MARGIN_PERCENT = 3; // Was 800 in VRam.. ? public static final double MAX_DIMENSION = 600; public static final double MIN_DIMENSION = 4; //Shape outline; Map rasters = new HashMap(); double[] resolutions; double minResolution, maxResolution; /** * @param original * @param imageBounds * @param referenceSize * * FIXME: shouldn't be SVG dependent */ public MipMapBufferedImage(SVGDiagram original, Rectangle2D imageBounds, Point referenceSize) { super(original, imageBounds, referenceSize); initializeRasters(); } private void initializeRasters() { List resolutions = new ArrayList(); if (referenceSize != null && !imageBounds.isEmpty()) { // Init rasters - they are built on-demand double maxResolution = maxResolution(); double minResolution = minResolution(); double fitResolution = fitResolution(referenceSize); double resolution = fitResolution; while (true) { double next = resolution * 2; if (next > maxResolution) break; resolution = next; } while (resolution > minResolution) { IRaster r = createRaster(resolution); rasters.put(resolution, r); resolutions.add(resolution); resolution /= 2; } } else { // Init rasters - they are built on-demand double maxResolution = maxResolution(); double minResolution = minResolution(); double resolution = maxResolution; while (resolution > minResolution) { IRaster r = createRaster(resolution); rasters.put(resolution, r); resolutions.add(resolution); resolution /= 2; } } // arraylist -> array this.resolutions = new double[resolutions.size()]; for (int i=0; i=0) return resolutions[index]; index = -(index+1); if (index>=resolutions.length) index = resolutions.length-1; if (index<0) index = 0; return resolutions[index]; } @Override public void paint(Graphics2D g) { // Quality rendering requested, do not render from cache //QualityHints.HIGH_QUALITY_HINTS.setQuality(g); if (g.getRenderingHint(RenderingHints.KEY_RENDERING) == RenderingHints.VALUE_RENDER_QUALITY) { try { source.render(g); } catch (Exception e) { // NOTE: Catching Exception instead of SVGException due to an // NPE when encountering invalid color SVG definitions (e.g. // rgb(256,-1,0)) e.printStackTrace(); } return; } double requiredResolution = requiredResolution(g.getTransform()); // This scale makes the mipmapped painting use a mipmap that is smaller // than the requested image pixel size in cases where the required // resolution only slightly exceeds the size of an available mipmap. requiredResolution *= 0.95; //System.out.println("required resolution: " + requiredResolution); if (requiredResolution > getRasterRenderingThresholdResolution()) { Graphics2D g2d = (Graphics2D) g.create(); setupSourceRender(g2d); try { source.render(g2d); } catch (Exception e) { // NOTE: Catching Exception instead of SVGException due to an // NPE when encountering invalid color SVG definitions (e.g. // rgb(256,-1,0)) e.printStackTrace(); } finally { g2d.dispose(); } } else { Object origInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); if (origInterpolationHint==null) origInterpolationHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); double closestResolution = findClosestResolution(requiredResolution); //System.out.println(" resolutions: " + Arrays.toString(resolutions)); //System.out.println(" closest resolution: " + closestResolution); IRaster raster = rasters.get(closestResolution); try { raster.paint( g ); } finally { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, origInterpolationHint); } } } protected void setupSourceRender(Graphics2D g2d) { g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); } protected double getRasterRenderingThresholdResolution() { return maxResolution; } @Override public synchronized void releaseRaster() { for (IRaster r : rasters.values()) r.release(); } static interface IRaster extends Comparable { double getResolution(); void paint(Graphics2D g); void release(); } static abstract class Raster implements IRaster { protected final double resolution; public Raster(double resolution) { this.resolution = resolution; } public double getResolution() { return resolution; } public int compareTo(IRaster o) { double r = getResolution(); double or = o.getResolution(); if (or < r) return -1; if (or > r) return 1; return 0; } } class BufferedRaster extends Raster { java.awt.image.BufferedImage image; //int widMargin, heiMargin; int wid, hei; BufferedRaster(double resolution) { super(resolution); double wid = imageBounds.getWidth(); double hei = imageBounds.getHeight(); this.wid = (int) (wid * resolution); this.hei = (int) (hei * resolution); // widMargin = (int) (wid * resolution * (MARGIN_PERCENT/100)) +1; // heiMargin = (int) (hei * resolution * (MARGIN_PERCENT/100)) +1; } synchronized java.awt.image.BufferedImage getOrCreate() { if (image!=null) return image; image = new java.awt.image.BufferedImage( (wid+0*2+1), (hei+0*2+1), java.awt.image.BufferedImage.TYPE_INT_ARGB); Graphics2D target = image.createGraphics(); target.setBackground(new Color(255,255,255,0)); target.clearRect(0, 0, image.getWidth(), image.getHeight()); target.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); target.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); target.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); target.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); // target.translate(widMargin, heiMargin); target.scale(resolution, resolution); target.translate(-imageBounds.getMinX(), -imageBounds.getMinY()); try { source.render(target); } catch (Exception e) { // TODO Auto-generated catch block // NOTE: Catching Exception instead of SVGException due to an // NPE when encountering invalid color SVG definitions (e.g. // rgb(256,-1,0)) e.printStackTrace(); } // source.paint( // new GraphicsContextImpl(target, new Rectangle2D.Double(0,0, image.getWidth(), image.getHeight()), null) // ); target.dispose(); return image; } public void paint(Graphics2D g) { java.awt.image.BufferedImage image = getOrCreate(); if (image==null) { try { source.render(g); } catch (Exception e) { // TODO Auto-generated catch block // NOTE: Catching Exception instead of SVGException due to an // NPE when encountering invalid color SVG definitions (e.g. // rgb(256,-1,0)) e.printStackTrace(); } return; } AffineTransform af = g.getTransform(); Object rh = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); try { /// Bicubic interpolation is very slow with opengl pipeline if (rh == RenderingHints.VALUE_INTERPOLATION_BICUBIC) g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.translate(imageBounds.getMinX(), imageBounds.getMinY()); g.scale(1/resolution, 1/resolution); // g.translate(-widMargin, -heiMargin); g.drawImage(image, 0, 0, null); } finally { g.setTransform(af); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, rh); } } public void release() { image = null; } } }