| /* |
| |
| 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 <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a> |
| * @version $Id$ |
| */ |
| 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. |
| */ |
| 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) at = new AffineTransform(); |
| else at = 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. |
| */ |
| public void setTree(GraphicsNode treeRoot) { |
| rootGN = treeRoot; |
| } |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| public AffineTransform getTransform() { |
| return usr2dev; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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). |
| */ |
| 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. |
| * |
| * @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). |
| * |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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 <tt>areas</tt>. |
| * |
| * 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); |
| } |
| } |
| |
| |
| Iterator iter = devRLM.iterator(); |
| while (iter.hasNext()) { |
| Rectangle r = (Rectangle)iter.next(); |
| 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; |
| } |
| } |
| } |