| /* |
| |
| 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.ext.awt.image.renderable; |
| |
| import java.awt.Composite; |
| import java.awt.Graphics2D; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.color.ColorSpace; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.image.RenderedImage; |
| import java.awt.image.renderable.RenderContext; |
| import java.awt.image.renderable.RenderableImage; |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.util.ListIterator; |
| import java.util.List; |
| |
| import org.apache.batik.ext.awt.image.CompositeRule; |
| import org.apache.batik.ext.awt.image.GraphicsUtil; |
| import org.apache.batik.ext.awt.image.SVGComposite; |
| import org.apache.batik.ext.awt.image.rendered.AffineRed; |
| import org.apache.batik.ext.awt.image.rendered.TileCacheRed; |
| |
| /** |
| * Interface for implementing filter resolution. |
| * |
| * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a> |
| * @version $Id$ |
| */ |
| public class FilterResRable8Bit extends AbstractRable |
| implements FilterResRable, PaintRable { |
| |
| /** |
| * Filter resolution along the x-axis |
| */ |
| private int filterResolutionX = -1; |
| |
| /** |
| * Filter resolution along the y-axis |
| */ |
| private int filterResolutionY = -1; |
| |
| public FilterResRable8Bit() { |
| // System.out.println("Using FilterResRable8bit..."); |
| } |
| |
| |
| public FilterResRable8Bit(Filter src, int filterResX, int filterResY) { |
| init(src, null); |
| setFilterResolutionX(filterResX); |
| setFilterResolutionY(filterResY); |
| } |
| |
| /** |
| * Returns the source to be cropped. |
| */ |
| public Filter getSource() { |
| return (Filter)srcs.get(0); |
| } |
| |
| /** |
| * Sets the source to be cropped |
| * @param src image to offset. |
| */ |
| public void setSource(Filter src){ |
| init(src, null); |
| } |
| |
| /** |
| * Returns the resolution along the X axis. |
| */ |
| public int getFilterResolutionX(){ |
| return filterResolutionX; |
| } |
| |
| /** |
| * Sets the resolution along the X axis, i.e., the maximum |
| * size for intermediate images along that axis. |
| * The value should be greater than zero to have an effect. |
| * Negative values are illegal. |
| */ |
| public void setFilterResolutionX(int filterResolutionX){ |
| if(filterResolutionX < 0){ |
| throw new IllegalArgumentException(); |
| } |
| touch(); |
| this.filterResolutionX = filterResolutionX; |
| } |
| |
| /** |
| * Returns the resolution along the Y axis. |
| */ |
| public int getFilterResolutionY(){ |
| return filterResolutionY; |
| } |
| |
| /** |
| * Sets the resolution along the Y axis, i.e., the maximum |
| * size for intermediate images along that axis. |
| * If the Y-value is less than zero, the scale applied to |
| * the rendered images is computed to preserve the image's aspect ratio |
| */ |
| public void setFilterResolutionY(int filterResolutionY){ |
| touch(); |
| this.filterResolutionY = filterResolutionY; |
| } |
| |
| |
| /** |
| * This returns true if <code>ri</code> and all of <code>ri</code>'s |
| * sources implement the PaintRable interface. This is used to |
| * indicate that the chain has a good potential for bypassing the |
| * filterRes operation entirely. |
| * |
| * Ideally there would be a checkPaintRable method in PaintRable |
| * that could be used to get a definate answer about a filters |
| * ability to draw directly to a Graphics2D (this can sometimes |
| * 'fail' because of the way the Graphics2D is currently |
| * configured). |
| */ |
| public boolean allPaintRable(RenderableImage ri) { |
| if (!(ri instanceof PaintRable)) |
| return false; |
| |
| List v = ri.getSources(); |
| // No sources and we are PaintRable so the chain is PaintRable. |
| if (v == null) return true; |
| |
| for (Object aV : v) { |
| RenderableImage nri = (RenderableImage) aV; |
| // A source is not paintRable so we are not 100% paintRable. |
| if (!allPaintRable(nri)) return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * This function attempts to distribute the filterRes operation |
| * across src. Right now it knows about two operations, pad and |
| * composite. It's main target is the composite but often pad |
| * operations are sprinked in the chain so it needs to know about |
| * them. This list could be extended however if it gets much |
| * longer it should probably be rolled into a new 'helper interface' |
| * like PaintRable. |
| * |
| * NOTE: This is essentially a bad hack, but it is a hack that is |
| * recomended by the SVG specification so I do it. |
| */ |
| public boolean distributeAcross(RenderableImage src, Graphics2D g2d) { |
| boolean ret; |
| if (src instanceof PadRable) { |
| PadRable pad = (PadRable)src; |
| Shape clip = g2d.getClip(); |
| g2d.clip(pad.getPadRect()); |
| ret = distributeAcross(pad.getSource(), g2d); |
| g2d.setClip(clip); |
| return ret; |
| } |
| |
| if (src instanceof CompositeRable) { |
| CompositeRable comp = (CompositeRable)src; |
| if (comp.getCompositeRule() != CompositeRule.OVER) |
| return false; |
| |
| if (false) { |
| // To check colorspaces or to not check colorspaces |
| // _that_ is the question... |
| ColorSpace crCS = comp.getOperationColorSpace(); |
| ColorSpace g2dCS = GraphicsUtil.getDestinationColorSpace(g2d); |
| if ((g2dCS == null) || (g2dCS != crCS)) |
| return false; |
| } |
| |
| List v = comp.getSources(); |
| if (v == null) return true; |
| ListIterator li = v.listIterator(v.size()); |
| while (li.hasPrevious()) { |
| RenderableImage csrc = (RenderableImage)li.previous(); |
| if (!allPaintRable(csrc)) { |
| li.next(); |
| break; |
| } |
| } |
| |
| if (!li.hasPrevious()) { |
| // All inputs are PaintRable so just draw directly to |
| // the graphics ignore filter res all togeather... |
| GraphicsUtil.drawImage(g2d, comp); |
| return true; |
| } |
| |
| if (!li.hasNext()) |
| // None of the trailing inputs are PaintRable so we don't |
| // distribute across this at all. |
| return false; |
| |
| // Now we are in the case where some are paintRable and |
| // some aren't. In this case we create a new |
| // CompositeRable with the first ones, to which we apply |
| // ourselves (limiting the resolution), and after that |
| // we simply draw the remainder... |
| int idx = li.nextIndex(); // index of first PaintRable... |
| Filter f = new CompositeRable8Bit(v.subList(0, idx), |
| comp.getCompositeRule(), |
| comp.isColorSpaceLinear()); |
| f = new FilterResRable8Bit(f, getFilterResolutionX(), |
| getFilterResolutionY()); |
| GraphicsUtil.drawImage(g2d, f); |
| while (li.hasNext()) { |
| PaintRable pr = (PaintRable)li.next(); |
| if (!pr.paintRable(g2d)) { |
| // Ugg it failed to paint so we need to filterRes it... |
| Filter prf = (Filter)pr; |
| prf = new FilterResRable8Bit(prf, getFilterResolutionX(), |
| getFilterResolutionY()); |
| GraphicsUtil.drawImage(g2d, prf); |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Should perform the equivilent action as |
| * createRendering followed by drawing the RenderedImage. |
| * |
| * @param g2d The Graphics2D to draw to. |
| * @return true if the paint call succeeded, false if |
| * for some reason the paint failed (in which |
| * case a createRendering should be used). |
| */ |
| public boolean paintRable(Graphics2D g2d) { |
| // This is a bit of a hack to implement the suggestion of SVG |
| // specification that if the last operation in a filter chain |
| // is a SRC_OVER composite and the source is SourceGraphic it |
| // should be rendered directly to the canvas (by passing |
| // filterRes). We are actually much more aggressive in |
| // implementing this suggestion since we will bypass filterRes |
| // for all the trailing elements in a SRC_OVER composite that |
| // can be drawn directly to the canvas. |
| |
| // System.out.println("Calling FilterResRable paintRable"); |
| |
| // This optimization only apply if we are using |
| // SrcOver. Otherwise things break... |
| Composite c = g2d.getComposite(); |
| if (!SVGComposite.OVER.equals(c)) |
| return false; |
| |
| Filter src = getSource(); |
| return distributeAcross(src, g2d); |
| } |
| |
| /** |
| * Cached Rendered image at filterRes. |
| */ |
| Reference resRed = null; |
| float resScale = 0; |
| |
| private float getResScale() { |
| return resScale; |
| } |
| |
| private RenderedImage getResRed(RenderingHints hints) { |
| Rectangle2D imageRect = getBounds2D(); |
| double resScaleX = getFilterResolutionX()/imageRect.getWidth(); |
| double resScaleY = getFilterResolutionY()/imageRect.getHeight(); |
| |
| |
| // System.out.println("filterRes X " + filterResolutionX + |
| // " Y : " + filterResolutionY); |
| |
| float resScale = (float)Math.min(resScaleX, resScaleY); |
| |
| RenderedImage ret; |
| if (resScale == this.resScale) { |
| // System.out.println("Matched"); |
| ret = (RenderedImage)resRed.get(); |
| if (ret != null) |
| return ret; |
| } |
| |
| AffineTransform resUsr2Dev; |
| resUsr2Dev = AffineTransform.getScaleInstance(resScale, resScale); |
| |
| // |
| // Create a new RenderingContext |
| // |
| RenderContext newRC = new RenderContext(resUsr2Dev, null, hints); |
| |
| ret = getSource().createRendering(newRC); |
| |
| // This is probably justified since the whole reason to use |
| // The filterRes attribute is because the filter chain is |
| // expensive, otherwise you should let it evaluate at |
| // screen resolution always - right? |
| ret = new TileCacheRed(GraphicsUtil.wrap(ret)); |
| this.resScale = resScale; |
| this.resRed = new SoftReference(ret); |
| |
| return ret; |
| } |
| |
| |
| |
| /** |
| * |
| */ |
| public RenderedImage createRendering(RenderContext renderContext) { |
| // Get user space to device space transform |
| AffineTransform usr2dev = renderContext.getTransform(); |
| if(usr2dev == null){ |
| usr2dev = new AffineTransform(); |
| } |
| |
| RenderingHints hints = renderContext.getRenderingHints(); |
| |
| // As per specification, a value of zero for the |
| // x-axis or y-axis causes the filter to produce |
| // nothing. |
| // The processing is done as follows: |
| // + if the x resolution is zero, this is a no-op |
| // else compute the x scale. |
| // + if the y resolution is zero, this is a no-op |
| // else compute the y resolution from the x scale |
| // and compute the corresponding y scale. |
| // + if the y or x scale is less than one, insert |
| // an AffineRable. |
| // Else, return the source as is. |
| int filterResolutionX = getFilterResolutionX(); |
| int filterResolutionY = getFilterResolutionY(); |
| // System.out.println("FilterResRable: " + filterResolutionX + "x" + |
| // filterResolutionY); |
| |
| if ((filterResolutionX <= 0) || (filterResolutionY == 0)) |
| return null; |
| |
| // Find out the renderable area |
| Rectangle2D imageRect = getBounds2D(); |
| Rectangle devRect; |
| devRect = usr2dev.createTransformedShape(imageRect).getBounds(); |
| |
| // Now, compare the devRect with the filter |
| // resolution hints |
| float scaleX = 1; |
| if(filterResolutionX < devRect.width) |
| scaleX = filterResolutionX / (float)devRect.width; |
| |
| float scaleY = 1; |
| if(filterResolutionY < 0) |
| scaleY = scaleX; |
| else if(filterResolutionY < devRect.height) |
| scaleY = filterResolutionY / (float)devRect.height; |
| |
| // Only resample if either scaleX or scaleY is |
| // smaller than 1 |
| if ((scaleX >= 1) && (scaleY >= 1)) |
| return getSource().createRendering(renderContext); |
| |
| // System.out.println("Using Fixed Resolution..."); |
| |
| // Using fixed resolution image since we need an image larger |
| // than this. |
| RenderedImage resRed = getResRed(hints); |
| float resScale = getResScale(); |
| |
| AffineTransform residualAT; |
| residualAT = new AffineTransform(usr2dev.getScaleX()/resScale, |
| usr2dev.getShearY()/resScale, |
| usr2dev.getShearX()/resScale, |
| usr2dev.getScaleY()/resScale, |
| usr2dev.getTranslateX(), |
| usr2dev.getTranslateY()); |
| |
| // org.ImageDisplay.showImage("AT: " + newUsr2Dev, result); |
| |
| return new AffineRed(GraphicsUtil.wrap(resRed), residualAT, hints); |
| } |
| } |
| |