/*******************************************************************************
* 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 java.util.Objects;
import org.simantics.scenegraph.g2d.G2DRenderingHints;
import org.simantics.scenegraph.g2d.color.ColorFilter;
import org.simantics.scenegraph.g2d.color.Graphics2DWithColorFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 {
private static final Logger LOGGER = LoggerFactory.getLogger(MipMapBufferedImage.class);
/** 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() {
if (LOGGER.isDebugEnabled())
LOGGER.debug("initializeRasters({}): diagram={}, referenceSize={}, imageBounds={}, maxres={}, minres={}", this.toString(), source, referenceSize, imageBounds, maxResolution(), minResolution());
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;
}
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("initializeRasters({}): resolutions={}", this.toString(), resolutions);
// 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) {
ColorFilter colorFilter = (ColorFilter) g.getRenderingHint(G2DRenderingHints.KEY_COLOR_FILTER);
// 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 {
if (colorFilter != null) {
source.render(new Graphics2DWithColorFilter(g, colorFilter));
} else {
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 {
if (colorFilter != null) {
source.render(new Graphics2DWithColorFilter(g, colorFilter));
} else {
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();
} 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;
private ColorFilter previousColorFilter = null;
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(ColorFilter colorFilter)
{
if (!Objects.equals(colorFilter, previousColorFilter)) {
previousColorFilter = colorFilter;
image = null;
}
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 {
if (colorFilter != null) {
source.render(new Graphics2DWithColorFilter(target, colorFilter));
} else {
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) {
ColorFilter colorFilter = (ColorFilter) g.getRenderingHint(G2DRenderingHints.KEY_COLOR_FILTER);
java.awt.image.BufferedImage image = getOrCreate(colorFilter);
if (image==null)
{
try {
source.render(new Graphics2DWithColorFilter(g, colorFilter));
} 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;
}
}
}