/*
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.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: Vincent Hardy Version: $Id: TileRable8Bit.java 1733416 2016-03-03 07:07:13Z gadams $
/**
* 8 bit TileRable implementation
*
* @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
* @version $Id: TileRable8Bit.java 1733416 2016-03-03 07:07:13Z gadams $
*/
public class TileRable8Bit
extends AbstractColorInterpolationRable
implements TileRable{
Tile region
/**
* Tile region
*/
private Rectangle2D tileRegion;
Tiled region
/**
* Tiled region
*/
private Rectangle2D tiledRegion;
Controls whether the tileRegion clips the source
or not
/**
* Controls whether the tileRegion clips the source
* or not
*/
private boolean overflow;
Returns the tile region
/**
* Returns the tile region
*/
public Rectangle2D getTileRegion(){
return tileRegion;
}
Sets the tile region
/**
* Sets the tile region
*/
public void setTileRegion(Rectangle2D tileRegion){
if(tileRegion == null){
throw new IllegalArgumentException();
}
touch();
this.tileRegion = tileRegion;
}
Returns the tiled region
/**
* Returns the tiled region
*/
public Rectangle2D getTiledRegion(){
return tiledRegion;
}
Sets the tiled region
/**
* Sets the tiled region
*/
public void setTiledRegion(Rectangle2D tiledRegion){
if(tiledRegion == null){
throw new IllegalArgumentException();
}
touch();
this.tiledRegion = tiledRegion;
}
Returns the overflow strategy
/**
* Returns the overflow strategy
*/
public boolean isOverflow(){
return overflow;
}
Sets the overflow strategy
/**
* Sets the overflow strategy
*/
public void setOverflow(boolean overflow){
touch();
this.overflow = overflow;
}
Default constructor
/**
* Default constructor
*/
public TileRable8Bit(Filter source,
Rectangle2D tiledRegion,
Rectangle2D tileRegion,
boolean overflow){
super(source);
setTileRegion(tileRegion);
setTiledRegion(tiledRegion);
setOverflow(overflow);
}
Sets the filter source
/**
* Sets the filter source
*/
public void setSource(Filter src){
init(src);
}
Return's the tile source
/**
* Return's the tile source
*/
public Filter getSource(){
return (Filter)srcs.get(0);
}
Returns this filter's bounds
/**
* 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) )
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.
/**
* 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){
AffineTransform usr2dev = rc.getTransform();
// Hints
RenderingHints rcHints = rc.getRenderingHints();
RenderingHints hints = new RenderingHints(null);
if(rcHints != null){
hints.add(rcHints);
}
// 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) )
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;
}
}