/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* Licensed 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 it.geosolutions.jaiext.vectorbin;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Arrays;
import java.util.Map;
import javax.media.jai.ImageLayout;
import javax.media.jai.RasterFactory;
import javax.media.jai.SourcelessOpImage;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.TopologyException;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jtsexample.geom.ExtendedCoordinate;
import com.vividsolutions.jtsexample.geom.ExtendedCoordinateSequence;
import it.geosolutions.jaiext.utilities.shape.LiteShape;
/**
* Creates a binary image based on tests of pixel inclusion in a polygonal {@code Geometry}. See {@link VectorBinarizeDescriptor} for details.
*
* @author Michael Bedward
* @author Andrea Aime
*/
public class VectorBinarizeOpImage extends SourcelessOpImage {
private final PreparedGeometry geom;
private final Shape shape;
private Raster solidTile;
private Raster blankTile;
/** Default setting for anti-aliasing (false). */
public static final boolean DEFAULT_ANTIALIASING = false;
private boolean antiAliasing = DEFAULT_ANTIALIASING;
private GeometryFactory gf = new GeometryFactory();
/**
* Constructor.
*
* @param sm the {@code SampleModel} used to create tiles
* @param configuration rendering hints
* @param minX origin X ordinate
* @param minY origin Y ordinate
* @param width image width
* @param height image height
* @param geom reference polygonal geometry
* @param antiAliasing whether to use anti-aliasing when rendering the reference geometry
*/
public VectorBinarizeOpImage(SampleModel sm, Map configuration, int minX, int minY, int width,
int height, PreparedGeometry geom, boolean antiAliasing) {
super(buildLayout(minX, minY, width, height, sm), configuration, sm, minX, minY, width,
height);
this.geom = geom;
this.shape = new LiteShape(geom.getGeometry());
this.antiAliasing = antiAliasing;
}
/**
* Builds an {@code ImageLayout} for this image. The {@code width} and {@code height} arguments are requested tile dimensions which will only be
* used if they are smaller than this operator's default tile dimension.
*
* @param minX origin X ordinate
* @param minY origin Y ordinate
* @param width requested tile width
* @param height requested tile height
* @param sm sample model
*
* @return the {@code ImageLayout} object
*/
static ImageLayout buildLayout(int minX, int minY, int width, int height, SampleModel sm) {
// build a sample model for the single tile
ImageLayout il = new ImageLayout();
il.setMinX(minX);
il.setMinY(minY);
il.setWidth(width);
il.setHeight(height);
il.setTileWidth(sm.getWidth());
il.setTileHeight(sm.getHeight());
il.setSampleModel(sm);
if (!il.isValid(ImageLayout.TILE_GRID_X_OFFSET_MASK)) {
il.setTileGridXOffset(il.getMinX(null));
}
if (!il.isValid(ImageLayout.TILE_GRID_Y_OFFSET_MASK)) {
il.setTileGridYOffset(il.getMinY(null));
}
return il;
}
/**
* Returns the specified tile.
*
* @param tileX tile X index
* @param tileY tile Y index
*
* @return the requested tile
*/
@Override
public Raster computeTile(int tileX, int tileY) {
final int x = tileXToX(tileX);
final int y = tileYToY(tileY);
// get the raster tile
Raster tile = getTileRaster(x, y);
// create a read only child in the right location
Raster result = tile.createChild(0, 0, tileWidth, tileHeight, x, y, null);
return result;
}
/**
* Gets the data for the requested tile. If the tile is either completely within or outside of the reference {@code PreparedGeometry} a cached
* constant {@code Raster} with 1 or 0 values is returned. Otherwise tile pixels are checked for inclusion and set individually.
*
* @param minX origin X ordinate
* @param minY origin Y ordinate
*
* @return the requested tile
*/
protected Raster getTileRaster(int minX, int minY) {
// check relationship between geometry and the tile we're computing
Polygon testRect = getTestRect(minX, minY);
try {
// RasterOp need to be thread safe
synchronized (geom) {
if (geom.contains(testRect)) {
return getSolidTile();
} else if (geom.disjoint(testRect)) {
return getBlankTile();
}
}
} catch (TopologyException tpe) {
// In case a Topology Exception have been raised,
// use the standard rasterization instead of leveraging
// on the shared tiles
}
return drawGeometry(minX, minY);
}
/**
* Draw the geometry using Java2D
*
* @return the binarized geometry
*/
private Raster drawGeometry(final int minX, final int minY) {
final int offset = antiAliasing ? 2 : 0;
SampleModel tileSampleModel = sampleModel
.createCompatibleSampleModel(tileWidth, tileHeight);
WritableRaster raster = RasterFactory.createWritableRaster(tileSampleModel,
new java.awt.Point(0, 0));
BufferedImage bi = new BufferedImage(colorModel, raster, false, null);
Graphics2D graphics = null;
try {
graphics = bi.createGraphics();
graphics.setClip(-offset, -offset, tileWidth + offset * 2, tileHeight + offset * 2);
graphics.translate(-minX, -minY);
if (antiAliasing) {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
// draw the shape
graphics.setColor(Color.WHITE);
graphics.fill(shape);
} finally {
if (graphics != null) {
graphics.dispose();
}
}
return raster;
}
/**
* Returns (creating and caching if the first call) a constant tile with 1 values
*
* @return the constant tile
*/
private Raster getSolidTile() {
if (solidTile == null) {
solidTile = constantTile(1);
}
return solidTile;
}
/**
* Returns (creating and caching if the first call) a constant tile with 0 values
*
* @return the constant tile
*/
private Raster getBlankTile() {
if (blankTile == null) {
blankTile = constantTile(0);
}
return blankTile;
}
/**
* Builds a tile with constant value
*
* @param value the constant value
*
* @return the new tile
*/
private Raster constantTile(int value) {
// build the raster
WritableRaster raster = RasterFactory.createWritableRaster(sampleModel, new java.awt.Point(
0, 0));
// sanity checks
int dataType = sampleModel.getTransferType();
int numBands = sampleModel.getNumBands();
if (dataType != DataBuffer.TYPE_BYTE) {
throw new IllegalArgumentException(
"The code works only if the sample model data type is BYTE");
}
if (numBands != 1) {
throw new IllegalArgumentException("The code works only for single band rasters!");
}
// flood fill
int w = sampleModel.getWidth();
int h = sampleModel.getHeight();
int[] data = new int[w * h];
Arrays.fill(data, value);
raster.setSamples(0, 0, w, h, 0, data);
return raster;
}
/**
* Builds the bounds of the rectangle used to test inclusion in the reference {@code PreparedGeometry}.
*
* @param x origin X ordinate
* @param y origin Y ordinate
*/
private Polygon getTestRect(int x, int y) {
ExtendedCoordinate[] copyCoords = new ExtendedCoordinate[5];
for (int i = 0; i < 5; i++) {
int xx = x;
int yy = y;
if (i == 1 || i == 2) {
yy = y + tileHeight;
}
if (i == 2 || i == 3) {
xx = x + tileWidth;
}
copyCoords[i] = new ExtendedCoordinate(xx, yy, 0, 0);
}
ExtendedCoordinateSequence seq = new ExtendedCoordinateSequence(copyCoords);
return gf.createPolygon(gf.createLinearRing(seq), null);
}
}