/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.batik.gvt.renderer;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Iterator;
import org.apache.batik.ext.awt.geom.RectListManager;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.HaltingThread;
Simple implementation of the Renderer that supports dynamic updates.
Author: Thierry Kormann Version: $Id: MacRenderer.java 1804130 2017-08-04 14:41:11Z ssteiner $
/**
* Simple implementation of the Renderer that supports dynamic updates.
*
* @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
* @version $Id: MacRenderer.java 1804130 2017-08-04 14:41:11Z ssteiner $
*/
public class MacRenderer implements ImageRenderer {
static final int COPY_OVERHEAD = 1000;
static final int COPY_LINE_OVERHEAD = 10;
static final AffineTransform IDENTITY = new AffineTransform();
protected RenderingHints renderingHints;
protected AffineTransform usr2dev;
protected GraphicsNode rootGN;
protected int offScreenWidth;
protected int offScreenHeight;
protected boolean isDoubleBuffered;
protected BufferedImage currImg;
protected BufferedImage workImg;
protected RectListManager damagedAreas;
public static int IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB_PRE;
public static Color TRANSPARENT_WHITE = new Color(255, 255, 255, 0);
protected static RenderingHints defaultRenderingHints;
static {
defaultRenderingHints = new RenderingHints(null);
defaultRenderingHints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
defaultRenderingHints.put(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
Constructs a new dynamic renderer with the specified buffer image.
/**
* Constructs a new dynamic renderer with the specified buffer image.
*/
public MacRenderer() {
renderingHints = new RenderingHints(null);
renderingHints.add(defaultRenderingHints);
usr2dev = new AffineTransform();
}
public MacRenderer(RenderingHints rh,
AffineTransform at){
renderingHints = new RenderingHints(null);
renderingHints.add(rh);
if (at == null) usr2dev = new AffineTransform();
else usr2dev = new AffineTransform(at);
}
public void dispose() {
rootGN = null;
currImg = null;
workImg = null;
renderingHints = null;
usr2dev = null;
if ( damagedAreas != null ){
damagedAreas.clear();
}
damagedAreas = null;
}
This associates the given GVT Tree with this renderer.
Any previous tree association is forgotten.
Not certain if this should be just GraphicsNode, or CanvasGraphicsNode.
/**
* This associates the given GVT Tree with this renderer.
* Any previous tree association is forgotten.
* Not certain if this should be just GraphicsNode, or CanvasGraphicsNode.
*/
public void setTree(GraphicsNode treeRoot) {
rootGN = treeRoot;
}
Returns the GVT tree associated with this renderer
/**
* Returns the GVT tree associated with this renderer
*/
public GraphicsNode getTree() {
return rootGN;
}
Sets the transform from the current user space (as defined by
the top node of the GVT tree, to the associated device space.
/**
* Sets the transform from the current user space (as defined by
* the top node of the GVT tree, to the associated device space.
*/
public void setTransform(AffineTransform usr2dev) {
if(usr2dev == null)
this.usr2dev = new AffineTransform();
else
this.usr2dev = new AffineTransform(usr2dev);
if (workImg == null) return;
synchronized (workImg) {
Graphics2D g2d = workImg.createGraphics();
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, workImg.getWidth(), workImg.getHeight());
g2d.dispose();
}
damagedAreas = null;
}
Returns the transform from the current user space (as defined
by the top node of the GVT tree) to the device space.
/**
* Returns the transform from the current user space (as defined
* by the top node of the GVT tree) to the device space.
*/
public AffineTransform getTransform() {
return usr2dev;
}
Params: - rh – Set of rendering hints to use for future renderings
/**
* @param rh Set of rendering hints to use for future renderings
*/
public void setRenderingHints(RenderingHints rh) {
this.renderingHints = new RenderingHints(null);
this.renderingHints.add(rh);
damagedAreas = null;
}
Returns: the RenderingHints which the Renderer is using for its
rendering
/**
* @return the RenderingHints which the Renderer is using for its
* rendering
*/
public RenderingHints getRenderingHints() {
return renderingHints;
}
Returns true if the Renderer is currently doubleBuffering is
rendering requests. If it is then getOffscreen will only
return completed renderings (or null if nothing is available).
/**
* Returns true if the Renderer is currently doubleBuffering is
* rendering requests. If it is then getOffscreen will only
* return completed renderings (or null if nothing is available).
*/
public boolean isDoubleBuffered(){
return isDoubleBuffered;
}
Turns on/off double buffering in renderer. Turning off
double buffering makes it possible to see the ongoing results
of a render operation.
Params: - isDoubleBuffered – the new value for double buffering
/**
* Turns on/off double buffering in renderer. Turning off
* double buffering makes it possible to see the ongoing results
* of a render operation.
*
* @param isDoubleBuffered the new value for double buffering
*/
public void setDoubleBuffered(boolean isDoubleBuffered){
if (this.isDoubleBuffered == isDoubleBuffered)
return;
this.isDoubleBuffered = isDoubleBuffered;
if (isDoubleBuffered) {
workImg = null; // start double buffer, split buffers
} else {
// No longer double buffering so delete second offscreen
workImg = currImg;
damagedAreas = null;
}
}
Update the size of the image to be returned by getOffScreen.
Note that this change will not be reflected by calls to
getOffscreen until either clearOffScreen has completed (when
isDoubleBuffered is false) or reapint has completed (when
isDoubleBuffered is true).
/**
* Update the size of the image to be returned by getOffScreen.
* Note that this change will not be reflected by calls to
* getOffscreen until either clearOffScreen has completed (when
* isDoubleBuffered is false) or reapint has completed (when
* isDoubleBuffered is true).
*
*/
public void updateOffScreen(int width, int height) {
offScreenWidth = width;
offScreenHeight = height;
}
Returns the current offscreen image.
The exact symantics of this vary base on the value of
isDoubleBuffered. If isDoubleBuffered is false this will
return the image currently being worked on as soon as it is
available.
if isDoubleBuffered is false this will return the most recently
completed result of repaint.
/**
* Returns the current offscreen image.
*
* The exact symantics of this vary base on the value of
* isDoubleBuffered. If isDoubleBuffered is false this will
* return the image currently being worked on as soon as it is
* available.
*
* if isDoubleBuffered is false this will return the most recently
* completed result of repaint.
*/
public BufferedImage getOffScreen() {
if (rootGN == null)
return null;
return currImg;
}
Sets up and clears the current offscreen buffer.
When not double buffering one should call this method before
calling getOffscreen to get the offscreen being drawn into.
This ensures the buffer is up to date and doesn't contain junk.
When double buffering this call can effectively be skipped,
since getOffscreen will only refect the new rendering after
repaint completes.
/**
* Sets up and clears the current offscreen buffer.
*
* When not double buffering one should call this method before
* calling getOffscreen to get the offscreen being drawn into.
* This ensures the buffer is up to date and doesn't contain junk.
*
* When double buffering this call can effectively be skipped,
* since getOffscreen will only refect the new rendering after
* repaint completes.
*/
public void clearOffScreen() {
// No need to clear in double buffer case people will
// only see it when it is done...
if (isDoubleBuffered)
return;
updateWorkingBuffers();
if (workImg == null) return;
synchronized (workImg) {
Graphics2D g2d = workImg.createGraphics();
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, workImg.getWidth(), workImg.getHeight());
g2d.dispose();
}
damagedAreas = null;
}
public void flush() {
// Since we don't cache we don't need to flush
}
public void flush(Rectangle r) {
// Since we don't cache we don't need to flush
}
Flush a list of rectangles of cached image data.
/**
* Flush a list of rectangles of cached image data.
*/
public void flush(Collection areas) {
// Since we don't cache we don't need to flush
}
protected void updateWorkingBuffers() {
if (rootGN == null) {
currImg = null;
workImg = null;
return;
}
int w = offScreenWidth;
int h = offScreenHeight;
if ((workImg == null) ||
(workImg.getWidth() < w) ||
(workImg.getHeight() < h)) {
workImg = new BufferedImage(w, h, IMAGE_TYPE);
// workImg = new BI(w, h, IMAGE_TYPE);
}
if (!isDoubleBuffered) {
currImg = workImg;
}
}
public void repaint(Shape area) {
if (area == null) return;
RectListManager rlm = new RectListManager();
rlm.add(usr2dev.createTransformedShape(area).getBounds());
repaint(rlm);
}
Repaints the associated GVT tree under the list of areas
.
If double buffered is true and this method completes cleanly it
will set the result of the repaint as the image returned by
getOffscreen otherwise the old image will still be returned.
If double buffered is false it is possible some effects of
the failed rendering will be visible in the image returned
by getOffscreen.
Params: - devRLM – regions to be repainted, in the current
user space coordinate system.
/**
* Repaints the associated GVT tree under the list of <code>areas</code>.
*
* If double buffered is true and this method completes cleanly it
* will set the result of the repaint as the image returned by
* getOffscreen otherwise the old image will still be returned.
* If double buffered is false it is possible some effects of
* the failed rendering will be visible in the image returned
* by getOffscreen.
*
* @param devRLM regions to be repainted, in the current
* user space coordinate system.
*/
// long lastFrame = -1;
public void repaint(RectListManager devRLM) {
if (devRLM == null)
return;
updateWorkingBuffers();
if ((rootGN == null) || (workImg == null))
return;
try {
// Ensure only one thread works on WorkImg at a time...
synchronized (workImg) {
Graphics2D g2d = GraphicsUtil.createGraphics
(workImg, renderingHints);
Rectangle dr;
dr = new Rectangle(0, 0, offScreenWidth, offScreenHeight);
if ((isDoubleBuffered) &&
(currImg != null) &&
(damagedAreas != null)) {
damagedAreas.subtract(devRLM, COPY_OVERHEAD,
COPY_LINE_OVERHEAD);
damagedAreas.mergeRects(COPY_OVERHEAD,
COPY_LINE_OVERHEAD);
Iterator iter = damagedAreas.iterator();
g2d.setComposite(AlphaComposite.Src);
while (iter.hasNext()) {
Rectangle r = (Rectangle)iter.next();
if (!dr.intersects(r)) continue;
r = dr.intersection(r);
g2d.setClip (r.x, r.y, r.width, r.height);
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect (r.x, r.y, r.width, r.height);
g2d.setComposite(AlphaComposite.SrcOver);
g2d.drawImage (currImg, 0, 0, null);
}
}
for (Object aDevRLM : devRLM) {
Rectangle r = (Rectangle) aDevRLM;
if (!dr.intersects(r)) continue;
r = dr.intersection(r);
g2d.setTransform(IDENTITY);
g2d.setClip(r.x, r.y, r.width, r.height);
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(r.x, r.y, r.width, r.height);
g2d.setComposite(AlphaComposite.SrcOver);
g2d.transform(usr2dev);
rootGN.paint(g2d);
}
g2d.dispose();
}
} catch (Throwable t) { t.printStackTrace(); }
if (HaltingThread.hasBeenHalted())
return;
// System.out.println("Dmg: " + damagedAreas);
// System.out.println("Areas: " + devRects);
// Swap the buffers if the rendering completed cleanly.
if (isDoubleBuffered) {
BufferedImage tmpImg = workImg;
workImg = currImg;
currImg = tmpImg;
damagedAreas = devRLM;
}
}
}