/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.ext.awt.image.renderable;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderContext;
import org.apache.batik.ext.awt.RenderingHintsKeyExt;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.rendered.AffineRed;
import org.apache.batik.ext.awt.image.rendered.BufferedImageCachableRed;
import org.apache.batik.ext.awt.image.rendered.CachableRed;
import org.apache.batik.ext.awt.image.rendered.TileRed;
/**
* 8 bit TileRable implementation
*
* @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
* @version $Id: TileRable8Bit.java,v 1.6 2003/04/11 13:57:48 vhardy Exp $
*/
public class TileRable8Bit
extends AbstractColorInterpolationRable
implements TileRable{
/**
* Tile region
*/
private Rectangle2D tileRegion;
/**
* Tiled region
*/
private Rectangle2D tiledRegion;
/**
* Controls whether the tileRegion clips the source
* or not
*/
private boolean overflow;
/**
* Returns the tile region
*/
public Rectangle2D getTileRegion(){
return tileRegion;
}
/**
* Sets the tile region
*/
public void setTileRegion(Rectangle2D tileRegion){
if(tileRegion == null){
throw new IllegalArgumentException();
}
touch();
this.tileRegion = tileRegion;
}
/**
* Returns the tiled region
*/
public Rectangle2D getTiledRegion(){
return tiledRegion;
}
/**
* Sets the tiled region
*/
public void setTiledRegion(Rectangle2D tiledRegion){
if(tiledRegion == null){
throw new IllegalArgumentException();
}
touch();
this.tiledRegion = tiledRegion;
}
/**
* Returns the overflow strategy
*/
public boolean isOverflow(){
return overflow;
}
/**
* Sets the overflow strategy
*/
public void setOverflow(boolean overflow){
touch();
this.overflow = overflow;
}
/**
* Default constructor
*/
public TileRable8Bit(Filter source,
Rectangle2D tiledRegion,
Rectangle2D tileRegion,
boolean overflow){
super(source);
setTileRegion(tileRegion);
setTiledRegion(tiledRegion);
setOverflow(overflow);
}
/**
* Sets the filter source
*/
public void setSource(Filter src){
init(src);
}
/**
* Return's the tile source
*/
public Filter getSource(){
return (Filter)srcs.get(0);
}
/**
* Returns this filter's bounds
*/
public Rectangle2D getBounds2D(){
return (Rectangle2D)tiledRegion.clone();
}
public RenderedImage createRendering(RenderContext rc){
// Just copy over the rendering hints.
RenderingHints rh = rc.getRenderingHints();
if (rh == null) rh = new RenderingHints(null);
// update the current affine transform
AffineTransform at = rc.getTransform();
double sx = at.getScaleX();
double sy = at.getScaleY();
double shx = at.getShearX();
double shy = at.getShearY();
double tx = at.getTranslateX();
double ty = at.getTranslateY();
// The Scale is the "hypotonose" of the matrix vectors.
double scaleX = Math.sqrt(sx*sx + shy*shy);
double scaleY = Math.sqrt(sy*sy + shx*shx);
// System.out.println("AT: " + at);
// System.out.println("Scale: " + scaleX + "x" + scaleY);
//
// Compute the actual tiled area (intersection of AOI
// and bounds) and the actual tile (anchored in the
// upper left corner of the tiled area
//
// tiledRect
Rectangle2D tiledRect = getBounds2D();
Rectangle2D aoiRect;
Shape aoiShape = rc.getAreaOfInterest();
if (aoiShape == null)
aoiRect = tiledRect;
else {
aoiRect = aoiShape.getBounds2D();
if (tiledRect.intersects(aoiRect) == false)
return null;
Rectangle2D.intersect(tiledRect, aoiRect, tiledRect);
}
// tileRect
Rectangle2D tileRect = tileRegion;
// Adjust the scale so that the tiling happens on pixel
// boundaries on both axis.
// Desired pixel rect width
int dw = (int)(Math.ceil(tileRect.getWidth() *scaleX));
int dh = (int)(Math.ceil(tileRect.getHeight()*scaleY));
double tileScaleX = dw/tileRect.getWidth();
double tileScaleY = dh/tileRect.getHeight();
// System.out.println("scaleX/scaleY : " + scaleX + " / " + scaleY);
// System.out.println("tileScaleX/tileScaleY : " + tileScaleX + " / " + tileScaleY);
// Adjust the translation so that the tile's origin falls on
// pixel boundary
int dx = (int)Math.floor(tileRect.getX()*tileScaleX);
int dy = (int)Math.floor(tileRect.getY()*tileScaleY);
double ttx = dx - (tileRect.getX()*tileScaleX);
double tty = dy - (tileRect.getY()*tileScaleY);
// System.out.println("ttx/tty : " + ttx + " / " + tty);
// Get result unsheared or rotated
AffineTransform tileAt;
// tileAt = AffineTransform.getScaleInstance(tileScaleX, tileScaleY);
// tileAt.translate(ttx, tty);
// System.out.println("Pt: " + tileAt.transform
// (new Point2D.Double(aoiRect.getX(),
// aoiRect.getY()), null));
tileAt = AffineTransform.getTranslateInstance(ttx, tty);
tileAt.scale(tileScaleX, tileScaleY);
// System.out.println("Pt: " + tileAt.transform
// (new Point2D.Double(aoiRect.getX(),
// aoiRect.getY()), null));
// System.out.println("tileRect in userSpace : " + tileRect);
// System.out.println("tileRect in deviceSpace : " +
// tileAt.createTransformedShape(tileRect).
// getBounds2D());
Filter source = getSource();
Rectangle2D srcRect;
if (overflow)
srcRect = source.getBounds2D();
else
srcRect = tileRect;
// System.out.println("SrcRect: " + srcRect);
RenderContext tileRc = new RenderContext(tileAt, srcRect, rh);
// RenderedImage tileRed = new DemandRed(source, tileRc);
RenderedImage tileRed = source.createRendering(tileRc);
// System.out.println("TileRed: " +
// GraphicsUtil.wrap(tileRed).getBounds());
// RenderedImage tileRed = createTile(tileRc);
// System.out.println("tileRed : " + tileRed.getMinX() + "/" + tileRed.getMinY() + "/"
// + tileRed.getWidth() + "/" + tileRed.getHeight());
if(tileRed == null)
return null;
// System.out.println("aoiRect: " + aoiRect);
Rectangle tiledArea = tileAt.createTransformedShape
(aoiRect).getBounds();
// Serious hack alert!!!
// In some cases the bounds are set to cover the whole area.
// when they get scaled up sometimes the lower bounds go
// to Integer.MIN_VALUE, and width/height to Integer.MAX_VALUE,
// but this only covers the negative quarter of the canvas!!!
// So if width and height are MAX_VALUE then we assume this
// clipping has happened and we recenter the range.
// Yes this is a serious hack and I appologies for it.
// I wouldn't need to do this if PatternPaintContext knew
// what it's bounds were going to be....
if ((tiledArea.width == Integer.MAX_VALUE)||
(tiledArea.height == Integer.MAX_VALUE)) {
tiledArea = new Rectangle(Integer.MIN_VALUE/4,
Integer.MIN_VALUE/4,
Integer.MAX_VALUE/2,
Integer.MAX_VALUE/2);
}
// System.out.println("tiledArea: " + tiledArea);
tileRed = convertSourceCS(tileRed);
TileRed tiledRed = new TileRed(tileRed, tiledArea, dw, dh);
// org.apache.batik.test.gvt.ImageDisplay.showImage("Tile", tiledRed);
// System.out.println("TileR: " + tiledRed.getBounds());
// Return sheared/rotated tiled image
AffineTransform shearAt =
new AffineTransform(sx/scaleX, shy/scaleX,
shx/scaleY, sy/scaleY,
tx, ty);
shearAt.scale(scaleX/tileScaleX, scaleY/tileScaleY);
shearAt.translate(-ttx, -tty);
CachableRed cr = tiledRed;
if(!shearAt.isIdentity())
cr = new AffineRed(tiledRed, shearAt, rh);
// System.out.println("AffineR: " + cr.getBounds());
return cr;
}
public Rectangle2D getActualTileBounds(Rectangle2D tiledRect){
// Get the tile rectangle in user space
Rectangle2D tileRect = (Rectangle2D)tileRegion.clone();
// System.out.println("tileRect : " + tileRect);
// System.out.println("tiledRect: " + tiledRect);
if ((tileRect.getWidth() <= 0)
|| (tileRect.getHeight() <= 0)
|| (tiledRect.getWidth() <= 0)
|| (tiledRect.getHeight() <= 0))
return null;
double tileWidth = tileRect.getWidth();
double tileHeight = tileRect.getHeight();
double tiledWidth = tiledRect.getWidth();
double tiledHeight = tiledRect.getHeight();
double w = Math.min(tileWidth, tiledWidth);
double h = Math.min(tileHeight, tiledHeight);
Rectangle2D realTileRect
= new Rectangle2D.Double(tileRect.getX(),
tileRect.getY(),
w, h);
return realTileRect;
}
/**
* Computes the tile to use for the tiling operation.
*
* The tile has its origin in the upper left
* corner of the tiled region. That tile is separated
* into 4 areas: top-left, top-right, bottom-left and
* bottom-right. Each of these areas is mapped to
* some input area from the source.
* If the source is smaller than the tiled area, then
* a single rendering is requested from the source.
* If the source's width or height is bigger than that
* of the tiled area, then separate renderings are
* requested from the source.
*
*/
public RenderedImage createTile(RenderContext rc){
// Rendered result
RenderedImage result = null;
AffineTransform usr2dev = rc.getTransform();
// Hints
RenderingHints hints = rc.getRenderingHints();
if(hints == null){
hints = new RenderingHints(null);
} else {
hints = new RenderingHints(hints);
}
// The region actually tiles is the intersection
// of the tiledRegion and the area of interest
Rectangle2D tiledRect = getBounds2D();
Shape aoiShape = rc.getAreaOfInterest();
Rectangle2D aoiRect = aoiShape.getBounds2D();
if (tiledRect.intersects(aoiRect) == false)
return null;
Rectangle2D.intersect(tiledRect, aoiRect, tiledRect);
// Get the tile rectangle in user space
Rectangle2D tileRect = (Rectangle2D)tileRegion.clone();
// System.out.println("tileRect : " + tileRect);
// System.out.println("tiledRect: " + tiledRect);
if ((tileRect.getWidth() <= 0)
|| (tileRect.getHeight() <= 0)
|| (tiledRect.getWidth() <= 0)
|| (tiledRect.getHeight() <= 0))
return null;
//
// (tiledX, tiledY)
// <------- min(tileWidth, tiledWidth) ----------->
// ^ +------+-------------------------------------+
// | + A' + B' +
// | +------+-------------------------------------+
// min(tileHeight, | + + +
// tiledHeight) | + + +
// | + C' + D' +
// | + + +
// ^ +------+-------------------------------------+
//
// Maps to, in the tile:
//
// (tileX, tileY)
//
// <----------- tileWidth --------------->
// ^ +-----------------------------+------+-------+
// | + + + |
// tiledHeight | + + + |
// | + D + + C |
// | + + + |
// | +-----------------------------+------+-------|
// | + | | |
// | + | | |
// | +-----------------------------+------+-------+
// | | B + + A |
// ^ +-----------------------------+------+-------+
// w = min(tileWidth, tiledWidth)
// h = min(tileHeight, tiledHeight)
// dx = tileWidth - (tiledX - tileX)%tileWidth;
// dy = tileHeight - (tiledY - tileY)%tileHeight;
//
// A = (tileX + tileWidth - dx, tileY + tileHeight - dy, dx, dy)
// B = (tileX, tileY + tileHeight - dy, w - dx, dy)
// C = (tileX + tileWidth - dx, tileY, dx, h - dy)
// D = (tileX, tileY, w - dx, h - dy)
double tileX = tileRect.getX();
double tileY = tileRect.getY();
double tileWidth = tileRect.getWidth();
double tileHeight = tileRect.getHeight();
double tiledX = tiledRect.getX();
double tiledY = tiledRect.getY();
double tiledWidth = tiledRect.getWidth();
double tiledHeight = tiledRect.getHeight();
double w = Math.min(tileWidth, tiledWidth);
double h = Math.min(tileHeight, tiledHeight);
double dx = (tiledX - tileX)%tileWidth;
double dy = (tiledY - tileY)%tileHeight;
if(dx > 0){
dx = tileWidth - dx;
}
else{
dx *= -1;
}
if(dy > 0){
dy = tileHeight - dy;
}
else{
dy *= -1;
}
//
// Adjust dx and dy so that they fall on a pixel boundary
//
double scaleX = usr2dev.getScaleX();
double scaleY = usr2dev.getScaleY();
double tdx = Math.floor(scaleX*dx);
double tdy = Math.floor(scaleY*dy);
dx = tdx/scaleX;
dy = tdy/scaleY;
// System.out.println("dx / dy / w / h : " + dx + " / " + dy + " / " + w + " / " + h);
Rectangle2D.Double A = new Rectangle2D.Double
(tileX + tileWidth - dx, tileY + tileHeight - dy, dx, dy);
Rectangle2D.Double B = new Rectangle2D.Double
(tileX, tileY + tileHeight - dy, w - dx, dy);
Rectangle2D.Double C = new Rectangle2D.Double
(tileX + tileWidth - dx, tileY, dx, h - dy);
Rectangle2D.Double D = new Rectangle2D.Double
(tileX, tileY, w - dx, h - dy);
Rectangle2D realTileRect
= new Rectangle2D.Double(tiledRect.getX(),
tiledRect.getY(),
w, h);
// System.out.println("A rect : " + A);
// System.out.println("B rect : " + B);
// System.out.println("C rect : " + C);
// System.out.println("D rect : " + D);
// System.out.println("realTileR : " + realTileRect);
// A, B, C and D are the four user space are that make the
// tile that will be used. We create a rendering for each of
// these areas that i s not empty (i.e., with either width or
// height equal to zero)
RenderedImage ARed = null, BRed = null, CRed = null, DRed = null;
Filter source = getSource();
if (A.getWidth() > 0 && A.getHeight() > 0){
// System.out.println("Rendering A");
Rectangle devA = usr2dev.createTransformedShape(A).getBounds();
if(devA.width > 0 && devA.height > 0){
AffineTransform ATxf = new AffineTransform(usr2dev);
ATxf.translate(-A.x + tiledX,
-A.y + tiledY);
Shape aoi = A;
if(overflow){
aoi = new Rectangle2D.Double(A.x,
A.y,
tiledWidth,
tiledHeight);
}
hints.put(RenderingHintsKeyExt.KEY_AREA_OF_INTEREST,
aoi);
RenderContext arc
= new RenderContext(ATxf, aoi, hints);
ARed = source.createRendering(arc);
//System.out.println("ARed : " + ARed.getMinX() + " / " +
// ARed.getMinY() + " / " +
// ARed.getWidth() + " / " +
// ARed.getHeight());
}
}
if(B.getWidth() > 0 && B.getHeight() > 0){
// System.out.println("Rendering B");
Rectangle devB = usr2dev.createTransformedShape(B).getBounds();
if(devB.width > 0 && devB.height > 0){
AffineTransform BTxf = new AffineTransform(usr2dev);
BTxf.translate(-B.x + (tiledX + dx),
-B.y + tiledY);
Shape aoi = B;
if(overflow){
aoi = new Rectangle2D.Double(B.x - tiledWidth + w - dx,
B.y,
tiledWidth,
tiledHeight);
}
hints.put(RenderingHintsKeyExt.KEY_AREA_OF_INTEREST,
aoi);
RenderContext brc
= new RenderContext(BTxf, aoi, hints);
BRed = source.createRendering(brc);
// System.out.println("BRed : " + BRed.getMinX() + " / " + BRed.getMinY() + " / " + BRed.getWidth() + " / " + BRed.getHeight());
}
}
if(C.getWidth() > 0 && C.getHeight() > 0){
// System.out.println("Rendering C");
Rectangle devC = usr2dev.createTransformedShape(C).getBounds();
if(devC.width > 0 && devC.height > 0){
AffineTransform CTxf = new AffineTransform(usr2dev);
CTxf.translate(-C.x + tiledX,
-C.y + (tiledY + dy));
Shape aoi = C;
if(overflow){
aoi = new Rectangle2D.Double(C.x,
C.y - tileHeight + h - dy,
tiledWidth,
tiledHeight);
}
hints.put(RenderingHintsKeyExt.KEY_AREA_OF_INTEREST,
aoi);
RenderContext crc
= new RenderContext(CTxf, aoi, hints);
CRed = source.createRendering(crc);
// System.out.println("CRed : " + CRed.getMinX() + " / " + CRed.getMinY() + " / " + CRed.getWidth() + " / " + CRed.getHeight());
}
}
if(D.getWidth() > 0 && D.getHeight() > 0){
// System.out.println("Rendering D");
Rectangle devD = usr2dev.createTransformedShape(D).getBounds();
if(devD.width > 0 && devD.height > 0){
AffineTransform DTxf = new AffineTransform(usr2dev);
DTxf.translate(-D.x + (tiledX + dx),
-D.y + (tiledY + dy));
Shape aoi = D;
if(overflow){
aoi = new Rectangle2D.Double(D.x - tileWidth + w - dx,
D.y - tileHeight + h - dy,
tiledWidth,
tiledHeight);
}
hints.put(RenderingHintsKeyExt.KEY_AREA_OF_INTEREST,
aoi);
RenderContext drc
= new RenderContext(DTxf, aoi, hints);
DRed = source.createRendering(drc);
// System.out.println("DRed : " + DRed.getMinX() + " / " + DRed.getMinY() + " / " + DRed.getWidth() + " / " + DRed.getHeight());
}
}
//
// Now, combine ARed, BRed, CRed and DRed into a single
// RenderedImage that will be tiled
//
final Rectangle realTileRectDev
= usr2dev.createTransformedShape(realTileRect).getBounds();
if(realTileRectDev.width == 0 || realTileRectDev.height == 0){
return null;
}
BufferedImage realTileBI
= new BufferedImage(realTileRectDev.width,
realTileRectDev.height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = GraphicsUtil.createGraphics(realTileBI,
rc.getRenderingHints());
// g.setPaint(new java.awt.Color(0, 255, 0, 64));
// g.fillRect(0, 0, realTileBI.getWidth(), realTileBI.getHeight());
g.translate(-realTileRectDev.x,
-realTileRectDev.y);
// System.out.println("realTileRectDev " + realTileRectDev);
AffineTransform redTxf = new AffineTransform();
Point2D.Double redVec = new Point2D.Double();
RenderedImage refRed = null;
if(ARed != null){
// System.out.println("Drawing A");
g.drawRenderedImage(ARed, redTxf);
refRed = ARed;
}
if(BRed != null){
// System.out.println("Drawing B");
if(refRed == null){
refRed = BRed;
}
// Adjust B's coordinates
redVec.x = dx;
redVec.y = 0;
usr2dev.deltaTransform(redVec, redVec);
redVec.x = Math.floor(redVec.x) - (BRed.getMinX() - refRed.getMinX());
redVec.y = Math.floor(redVec.y) - (BRed.getMinY() - refRed.getMinY());
// System.out.println("BRed adjust : " + redVec);
// redTxf.setToTranslation(redVec.x, redVec.y);
g.drawRenderedImage(BRed, redTxf);
}
if(CRed != null){
// System.out.println("Drawing C");
if(refRed == null){
refRed = CRed;
}
// Adjust C's coordinates
redVec.x = 0;
redVec.y = dy;
usr2dev.deltaTransform(redVec, redVec);
redVec.x = Math.floor(redVec.x) - (CRed.getMinX() - refRed.getMinX());
redVec.y = Math.floor(redVec.y) - (CRed.getMinY() - refRed.getMinY());
// System.out.println("CRed adjust : " + redVec);
// redTxf.setToTranslation(redVec.x, redVec.y);
g.drawRenderedImage(CRed, redTxf);
}
if(DRed != null){
// System.out.println("Drawing D");
if(refRed == null){
refRed = DRed;
}
// Adjust D's coordinates
redVec.x = dx;
redVec.y = dy;
usr2dev.deltaTransform(redVec, redVec);
redVec.x = Math.floor(redVec.x) - (DRed.getMinX() - refRed.getMinX());
redVec.y = Math.floor(redVec.y) - (DRed.getMinY() - refRed.getMinY());
// System.out.println("DRed adjust : " + redVec);
// redTxf.setToTranslation(redVec.x, redVec.y);
g.drawRenderedImage(DRed, redTxf);
}
CachableRed realTile;
realTile = new BufferedImageCachableRed(realTileBI,
realTileRectDev.x,
realTileRectDev.y);
return realTile;
}
}