/*
* Licensed Materials - Property of Rogue Wave Software, Inc.
* © Copyright Rogue Wave Software, Inc. 2014, 2017
* © Copyright IBM Corp. 2009, 2014
* © Copyright ILOG 1996, 2009
* All Rights Reserved.
*
* Note to U.S. Government Users Restricted Rights:
* The Software and Documentation were developed at private expense and
* are "Commercial Items" as that term is defined at 48 CFR 2.101,
* consisting of "Commercial Computer Software" and
* "Commercial Computer Software Documentation", as such terms are
* used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
* as applicable.
*/
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ByteLookupTable;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.LookupOp;
import java.awt.image.Raster;
import java.awt.image.RasterOp;
import java.awt.image.WritableRaster;
/**
* An emboss effect that draws a dark grey shadow at the bottom right corner
* of nontransparent pixels, and a light gray shadow at the top left corner
* of nonstransparent pixels.
*/
public class HoverEmbossOp implements BufferedImageOp, RasterOp
{
private RenderingHints hints;
private LookupOp lop;
private Rectangle area;
private int thickness;
/**
* Constructs a hover emboss operation.
* @param hints The rendering hints
* @param thickness The thickness of the emboss in [1,4]
*/
public HoverEmbossOp(RenderingHints hints, int thickness) {
this.hints = hints;
this.thickness = thickness;
// just to save some code for createCompatibleDestImage, we use this delegate
lop = new LookupOp(new ByteLookupTable(0,new byte[] { 0, 1, 2 }), hints);
}
/**
* Sets the area of interest.
* This can be set before filter. Only the area of interest is treated
* by the operation.
*/
public void setOperationArea(Rectangle r)
{
area = r;
}
/**
* Performs a single-input/single-output operation on a
* <CODE>BufferedImage</CODE>.
* If the color models for the two images do not match, a color
* conversion into the destination color model is performed.
* If the destination image is null,
* a <CODE>BufferedImage</CODE> with an appropriate <CODE>ColorModel</CODE>
* is created.
* <p>
* An <CODE>IllegalArgumentException</CODE> may be thrown if the source
* and/or destination image is incompatible with the types of images
* allowed by the class implementing this filter.
*
* @param src The <CODE>BufferedImage</CODE> to be filtered
* @param dest The <CODE>BufferedImage</CODE> in which to store the results
* @return The filtered <CODE>BufferedImage</CODE>.
*
* @throws IllegalArgumentException If the source and/or destination
* image is not compatible with the types of images allowed by the class
* implementing this filter.
*/
Override
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
{
ColorModel srcCM = src.getColorModel();
// int numBands = srcCM.getNumColorComponents();
ColorModel dstCM;
if (srcCM instanceof IndexColorModel) {
throw new IllegalArgumentException("HooverEmbossOp cannot be "+
"performed on an indexed image");
}
boolean needToConvert = false;
int width = src.getWidth();
int height = src.getHeight();
BufferedImage origDst = dst;
if (dst == null) {
dst = origDst = createCompatibleDestImage(src, null);
dstCM = srcCM;
}
else {
if (height != dst.getHeight() || width != dst.getWidth()) {
throw new
IllegalArgumentException ("Width or height of images do not match");
}
dstCM = dst.getColorModel();
if (srcCM.getColorSpace().getType() != dstCM.getColorSpace().getType())
{
needToConvert = true;
dst = createCompatibleDestImage(src, null);
}
}
filter(src.getRaster(), dst.getRaster());
if (needToConvert) {
// ColorModels are not the same
ColorConvertOp ccop = new ColorConvertOp(hints);
ccop.filter(dst, origDst);
}
return origDst;
}
/**
* Performs a lookup operation on a <code>Raster</code>.
* If the destination <code>Raster</code> is <code>null</code>,
* a new <code>Raster</code> will be created.
* The <code>IllegalArgumentException</code> might be thrown
* if the source <code>Raster</code> and the destination
* <code>Raster</code> do not have the same
* number of bands or if the number of arrays in the
* <code>LookupTable</code> does not meet the
* restrictions stated in the class comment above.
* @param src the source <code>Raster</code> to filter
* @param dst the destination <code>WritableRaster</code> for the
* filtered <code>src</code>
* @return the filtered <code>WritableRaster</code>.
* @throws IllegalArgumentException if the source and destinations
* rasters do not have the same number of bands, or the
* number of arrays in the <code>LookupTable</code> does
* not meet the restrictions described in the class comments.
*
*/
Override
public final WritableRaster filter(Raster src, WritableRaster dst)
{
int numBands = src.getNumBands();
int height = src.getHeight();
int width = src.getWidth();
int srcPix[] = new int[numBands];
int alphaBand = 3;
int LIGHT=230;
int DARK=25;
// Create a new destination Raster, if needed
if (dst == null) {
dst = createCompatibleDestRaster(src);
}
else if (height != dst.getHeight() || width != dst.getWidth()) {
throw new
IllegalArgumentException ("Width or height of Rasters do not match");
}
int sminX = src.getMinX();
int sminY = src.getMinY();
int dminX = dst.getMinX();
int dminY = dst.getMinY();
int minx = 0;
int miny = 0;
int maxx = width;
int maxy = height;
int t = thickness;
if (t < 1) t = 1;
if (t > 4) t = 4;
if (area != null) {
minx = Math.max(0, area.x);
miny = Math.max(0, area.y);
maxx = Math.min(width, area.x + area.width);
maxy = Math.min(height, area.y + area.height);
}
for (int y = miny; y < maxy; y++) {
for (int x = minx; x < maxx; x++) {
src.getPixel(sminX + x, sminY + y, srcPix);
dst.setPixel(dminX + x, dminY + y, srcPix);
}
}
boolean prvTransparent = true;
boolean actTransparent = true;
for (int y = miny+1; y < maxy; y++) {
for (int x = minx+t; x < maxx-t; x++) {
src.getPixel(sminX + x, sminY + y, srcPix);
if (srcPix[alphaBand] == 0)
actTransparent = true;
else
actTransparent = false;
if (actTransparent != prvTransparent) {
if (prvTransparent) {
for (int i = 0; i < srcPix.length; i++)
srcPix[i] = LIGHT;
srcPix[alphaBand] = 255;
for (int i = 0; i < t; i++)
dst.setPixel(dminX + x - 1 - i, dminY + y, srcPix);
} else {
for (int i = 0; i < srcPix.length; i++)
srcPix[i] = DARK;
srcPix[alphaBand] = 255;
for (int i = 0; i < t; i++)
dst.setPixel(dminX + x + i, dminY + y, srcPix);
}
}
prvTransparent = actTransparent;
}
}
prvTransparent = true;
actTransparent = true;
for (int x = minx+1; x < maxx; x++) {
for (int y = miny+t; y < maxy-t; y++) {
src.getPixel(sminX + x, sminY + y, srcPix);
if (srcPix[alphaBand] == 0)
actTransparent = true;
else
actTransparent = false;
if (actTransparent != prvTransparent) {
if (prvTransparent) {
for (int i = 0; i < srcPix.length; i++)
srcPix[i] = LIGHT;
srcPix[alphaBand] = 255;
for (int i = 0; i < t; i++)
dst.setPixel(dminX + x, dminY + y - 1 - i, srcPix);
} else {
for (int i = 0; i < srcPix.length; i++)
srcPix[i] = DARK;
srcPix[alphaBand] = 255;
for (int i = 0; i < t; i++)
dst.setPixel(dminX + x, dminY + y + i, srcPix);
}
}
prvTransparent = actTransparent;
}
}
return dst;
}
/**
* Returns the bounding box of the filtered destination image. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param src the <code>BufferedImage</code> to be filtered
* @return the bounds of the filtered definition image.
*/
Override
public final Rectangle2D getBounds2D (BufferedImage src) {
return getBounds2D(src.getRaster());
}
/**
* Returns the bounding box of the filtered destination Raster. Since
* this is not a geometric operation, the bounding box does not
* change.
* @param src the <code>Raster</code> to be filtered
* @return the bounds of the filtered definition <code>Raster</code>.
*/
Override
public final Rectangle2D getBounds2D (Raster src) {
return src.getBounds();
}
/**
* Creates a zeroed destination image with the correct size and number of
* bands. If destCM is <code>null</code>, an appropriate
* <code>ColorModel</code> will be used.
* @param src Source image for the filter operation.
* @param destCM the destination's <code>ColorModel</code>, which
* can be <code>null</code>.
* @return a filtered destination <code>BufferedImage</code>.
*/
Override
public BufferedImage createCompatibleDestImage(BufferedImage src,
ColorModel destCM)
{
return lop.createCompatibleDestImage(src, destCM);
}
/**
* Creates a zeroed-destination <code>Raster</code> with the
* correct size and number of bands, given this source.
* @param src the <code>Raster</code> to be transformed
* @return the zeroed-destination <code>Raster</code>.
*/
Override
public WritableRaster createCompatibleDestRaster(Raster src)
{
return src.createCompatibleWritableRaster();
}
/**
* Returns the location of the destination point given a
* point in the source. If <code>dstPt</code> is not
* <code>null</code>, it will be used to hold the return value.
* Since this is not a geometric operation, the <code>srcPt</code>
* will equal the <code>dstPt</code>.
* @param srcPt a <code>Point2D</code> that represents a point
* in the source image
* @param dstPt a <code>Point2D</code>that represents the location
* in the destination
* @return the <code>Point2D</code> in the destination that
* corresponds to the specified point in the source.
*/
Override
public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt)
{
return lop.getPoint2D(srcPt, dstPt);
}
/**
* Returns the rendering hints for this op.
* @return the <code>RenderingHints</code> object associated
* with this op.
*/
Override
public final RenderingHints getRenderingHints()
{
return hints;
}
}