/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © 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; } }