Merged gcj-eclipse branch to trunk.
From-SVN: r120621
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* AffineTransformOp.java -- This class performs affine
|
||||
transformation between two images or rasters in 2 dimensions.
|
||||
Copyright (C) 2004 Free Software Foundation
|
||||
Copyright (C) 2004, 2006 Free Software Foundation
|
||||
|
||||
This file is part of GNU Classpath.
|
||||
|
||||
@@ -39,6 +39,7 @@ exception statement from your version. */
|
||||
package java.awt.image;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.AffineTransform;
|
||||
@@ -48,10 +49,14 @@ import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class performs affine transformation between two images or
|
||||
* rasters in 2 dimensions.
|
||||
* AffineTransformOp performs matrix-based transformations (translations,
|
||||
* scales, flips, rotations, and shears).
|
||||
*
|
||||
* If interpolation is required, nearest neighbour, bilinear, and bicubic
|
||||
* methods are available.
|
||||
*
|
||||
* @author Olga Rodimina (rodimina@redhat.com)
|
||||
* @author Francis Kung (fkung@redhat.com)
|
||||
*/
|
||||
public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
{
|
||||
@@ -74,6 +79,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
*
|
||||
* @param xform AffineTransform that will applied to the source image
|
||||
* @param interpolationType type of interpolation used
|
||||
* @throws ImagingOpException if the transform matrix is noninvertible
|
||||
*/
|
||||
public AffineTransformOp (AffineTransform xform, int interpolationType)
|
||||
{
|
||||
@@ -102,6 +108,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
*
|
||||
* @param xform AffineTransform that will applied to the source image
|
||||
* @param hints rendering hints that will be used during transformation
|
||||
* @throws ImagingOpException if the transform matrix is noninvertible
|
||||
*/
|
||||
public AffineTransformOp (AffineTransform xform, RenderingHints hints)
|
||||
{
|
||||
@@ -112,185 +119,165 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates empty BufferedImage with the size equal to that of the
|
||||
* transformed image and correct number of bands. The newly created
|
||||
* Creates a new BufferedImage with the size equal to that of the
|
||||
* transformed image and the correct number of bands. The newly created
|
||||
* image is created with the specified ColorModel.
|
||||
* If the ColorModel is equal to null, then image is created
|
||||
* with the ColorModel of the source image.
|
||||
* If a ColorModel is not specified, an appropriate ColorModel is used.
|
||||
*
|
||||
* @param src source image
|
||||
* @param destCM color model for the destination image
|
||||
* @return new compatible destination image
|
||||
* @param src the source image.
|
||||
* @param destCM color model for the destination image (can be null).
|
||||
* @return a new compatible destination image.
|
||||
*/
|
||||
public BufferedImage createCompatibleDestImage (BufferedImage src,
|
||||
ColorModel destCM)
|
||||
{
|
||||
if (destCM != null)
|
||||
return new BufferedImage(destCM,
|
||||
createCompatibleDestRaster(src.getRaster()),
|
||||
src.isAlphaPremultiplied(), null);
|
||||
|
||||
// if destCm is not specified, use color model of the source image
|
||||
|
||||
if (destCM == null)
|
||||
destCM = src.getColorModel ();
|
||||
|
||||
return new BufferedImage (destCM,
|
||||
createCompatibleDestRaster (src.getRaster ()),
|
||||
src.isAlphaPremultiplied (),
|
||||
null);
|
||||
// This behaviour was determined by Mauve testcases, and is compatible
|
||||
// with the reference implementation
|
||||
if (src.getType() == BufferedImage.TYPE_INT_ARGB_PRE
|
||||
|| src.getType() == BufferedImage.TYPE_4BYTE_ABGR
|
||||
|| src.getType() == BufferedImage.TYPE_4BYTE_ABGR_PRE)
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
|
||||
|
||||
else
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates empty WritableRaster with the size equal to the transformed
|
||||
* source raster and correct number of bands
|
||||
* Creates a new WritableRaster with the size equal to the transformed
|
||||
* source raster and correct number of bands .
|
||||
*
|
||||
* @param src source raster
|
||||
* @throws RasterFormatException if resulting width or height of raster is 0
|
||||
* @return new compatible raster
|
||||
* @param src the source raster.
|
||||
* @throws RasterFormatException if resulting width or height of raster is 0.
|
||||
* @return a new compatible raster.
|
||||
*/
|
||||
public WritableRaster createCompatibleDestRaster (Raster src)
|
||||
{
|
||||
Rectangle rect = (Rectangle) getBounds2D (src);
|
||||
Rectangle2D rect = getBounds2D(src);
|
||||
|
||||
// throw RasterFormatException if resulting width or height of the
|
||||
// transformed raster is 0
|
||||
|
||||
if (rect.getWidth () == 0 || rect.getHeight () == 0)
|
||||
if (rect.getWidth() == 0 || rect.getHeight() == 0)
|
||||
throw new RasterFormatException("width or height is 0");
|
||||
|
||||
return src.createCompatibleWritableRaster ((int) rect.getWidth (),
|
||||
(int) rect.getHeight ());
|
||||
return src.createCompatibleWritableRaster((int) rect.getWidth(),
|
||||
(int) rect.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms source image using transform specified at the constructor.
|
||||
* The resulting transformed image is stored in the destination image.
|
||||
* The resulting transformed image is stored in the destination image if one
|
||||
* is provided; otherwise a new BufferedImage is created and returned.
|
||||
*
|
||||
* @param src source image
|
||||
* @param dst destination image
|
||||
* @return transformed source image
|
||||
* @throws IllegalArgumentException if the source and destination image are
|
||||
* the same
|
||||
* @return transformed source image.
|
||||
*/
|
||||
public final BufferedImage filter (BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
|
||||
if (dst == src)
|
||||
throw new IllegalArgumentException ("src image cannot be the same as the dst image");
|
||||
|
||||
// If the destination image is null, then BufferedImage is
|
||||
// created with ColorModel of the source image
|
||||
throw new IllegalArgumentException("src image cannot be the same as "
|
||||
+ "the dst image");
|
||||
|
||||
// If the destination image is null, then use a compatible BufferedImage
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestImage(src, src.getColorModel ());
|
||||
dst = createCompatibleDestImage(src, null);
|
||||
|
||||
// FIXME: Must check if color models of src and dst images are the same.
|
||||
// If it is not, then source image should be converted to color model
|
||||
// of the destination image
|
||||
|
||||
Graphics2D gr = (Graphics2D) dst.createGraphics ();
|
||||
gr.setRenderingHints (hints);
|
||||
gr.drawImage (src, transform, null);
|
||||
Graphics2D gr = (Graphics2D) dst.createGraphics();
|
||||
gr.setRenderingHints(hints);
|
||||
gr.drawImage(src, transform, null);
|
||||
return dst;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms source raster using transform specified at the constructor.
|
||||
* The resulting raster is stored in the destination raster.
|
||||
* The resulting raster is stored in the destination raster if it is not
|
||||
* null, otherwise a new raster is created and returned.
|
||||
*
|
||||
* @param src source raster
|
||||
* @param dst destination raster
|
||||
* @return transformed raster
|
||||
* @throws IllegalArgumentException if the source and destination are not
|
||||
* compatible
|
||||
* @return transformed raster.
|
||||
*/
|
||||
public final WritableRaster filter (Raster src, WritableRaster dst)
|
||||
public final WritableRaster filter(Raster src, WritableRaster dst)
|
||||
{
|
||||
// Initial checks
|
||||
if (dst == src)
|
||||
throw new IllegalArgumentException("src image cannot be the same as"
|
||||
+ " the dst image");
|
||||
+ " the dst image");
|
||||
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestRaster(src);
|
||||
|
||||
if (src.getNumBands() != dst.getNumBands())
|
||||
throw new IllegalArgumentException("src and dst must have same number"
|
||||
+ " of bands");
|
||||
+ " of bands");
|
||||
|
||||
double[] dpts = new double[dst.getWidth() * 2];
|
||||
double[] pts = new double[dst.getWidth() * 2];
|
||||
for (int x = 0; x < dst.getWidth(); x++)
|
||||
{
|
||||
dpts[2 * x] = x + dst.getMinX();
|
||||
dpts[2 * x + 1] = x;
|
||||
}
|
||||
Rectangle srcbounds = src.getBounds();
|
||||
if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
|
||||
{
|
||||
for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
|
||||
{
|
||||
try {
|
||||
transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
|
||||
} catch (NoninvertibleTransformException e) {
|
||||
// Can't happen since the constructor traps this
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (int x = 0; x < dst.getWidth(); x++)
|
||||
{
|
||||
if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
|
||||
continue;
|
||||
dst.setDataElements(x + dst.getMinX(), y,
|
||||
src.getDataElements((int)pts[2 * x],
|
||||
(int)pts[2 * x + 1],
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
|
||||
{
|
||||
double[] tmp = new double[4 * src.getNumBands()];
|
||||
for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
|
||||
// Optimization for rasters that can be represented in the RGB colormodel:
|
||||
// wrap the rasters in images, and let Cairo do the transformation
|
||||
if (ColorModel.getRGBdefault().isCompatibleSampleModel(src.getSampleModel())
|
||||
&& ColorModel.getRGBdefault().isCompatibleSampleModel(dst.getSampleModel()))
|
||||
{
|
||||
try {
|
||||
transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
|
||||
} catch (NoninvertibleTransformException e) {
|
||||
// Can't happen since the constructor traps this
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (int x = 0; x < dst.getWidth(); x++)
|
||||
{
|
||||
if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
|
||||
continue;
|
||||
int xx = (int)pts[2 * x];
|
||||
int yy = (int)pts[2 * x + 1];
|
||||
double dx = (pts[2 * x] - xx);
|
||||
double dy = (pts[2 * x + 1] - yy);
|
||||
|
||||
// TODO write this more intelligently
|
||||
if (xx == src.getMinX() + src.getWidth() - 1 ||
|
||||
yy == src.getMinY() + src.getHeight() - 1)
|
||||
{
|
||||
// bottom or right edge
|
||||
Arrays.fill(tmp, 0);
|
||||
src.getPixel(xx, yy, tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal case
|
||||
src.getPixels(xx, yy, 2, 2, tmp);
|
||||
for (int b = 0; b < src.getNumBands(); b++)
|
||||
tmp[b] = dx * dy * tmp[b]
|
||||
+ (1 - dx) * dy * tmp[b + src.getNumBands()]
|
||||
+ dx * (1 - dy) * tmp[b + 2 * src.getNumBands()]
|
||||
+ (1 - dx) * (1 - dy) * tmp[b + 3 * src.getNumBands()];
|
||||
}
|
||||
dst.setPixel(x, y, tmp);
|
||||
}
|
||||
WritableRaster src2 = Raster.createWritableRaster(src.getSampleModel(),
|
||||
src.getDataBuffer(),
|
||||
new Point(src.getMinX(),
|
||||
src.getMinY()));
|
||||
BufferedImage iSrc = new BufferedImage(ColorModel.getRGBdefault(),
|
||||
src2, false, null);
|
||||
BufferedImage iDst = new BufferedImage(ColorModel.getRGBdefault(), dst,
|
||||
false, null);
|
||||
|
||||
return filter(iSrc, iDst).getRaster();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bicubic
|
||||
throw new UnsupportedOperationException("not implemented yet");
|
||||
}
|
||||
|
||||
// Otherwise, we need to do the transformation in java code...
|
||||
// Create arrays to hold all the points
|
||||
double[] dstPts = new double[dst.getHeight() * dst.getWidth() * 2];
|
||||
double[] srcPts = new double[dst.getHeight() * dst.getWidth() * 2];
|
||||
|
||||
// Populate array with all points in the *destination* raster
|
||||
int i = 0;
|
||||
for (int x = 0; x < dst.getWidth(); x++)
|
||||
{
|
||||
for (int y = 0; y < dst.getHeight(); y++)
|
||||
{
|
||||
dstPts[i++] = x;
|
||||
dstPts[i++] = y;
|
||||
}
|
||||
}
|
||||
Rectangle srcbounds = src.getBounds();
|
||||
|
||||
// Use an inverse transform to map each point in the destination to
|
||||
// a point in the source. Note that, while all points in the destination
|
||||
// matrix are integers, this is not necessarily true for points in the
|
||||
// source (hence why interpolation is required)
|
||||
try
|
||||
{
|
||||
AffineTransform inverseTx = transform.createInverse();
|
||||
inverseTx.transform(dstPts, 0, srcPts, 0, dstPts.length / 2);
|
||||
}
|
||||
catch (NoninvertibleTransformException e)
|
||||
{
|
||||
// Shouldn't happen since the constructor traps this
|
||||
throw new ImagingOpException(e.getMessage());
|
||||
}
|
||||
|
||||
// Different interpolation methods...
|
||||
if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
|
||||
filterNearest(src, dst, dstPts, srcPts);
|
||||
|
||||
else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
|
||||
filterBilinear(src, dst, dstPts, srcPts);
|
||||
|
||||
else // bicubic
|
||||
filterBicubic(src, dst, dstPts, srcPts);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -314,27 +301,22 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
*/
|
||||
public final Rectangle2D getBounds2D (Raster src)
|
||||
{
|
||||
// determine new size for the transformed raster.
|
||||
// Need to calculate transformed coordinates of the lower right
|
||||
// corner of the raster. The upper left corner is always (0,0)
|
||||
|
||||
double x2 = (double) src.getWidth () + src.getMinX ();
|
||||
double y2 = (double) src.getHeight () + src.getMinY ();
|
||||
Point2D p2 = getPoint2D (new Point2D.Double (x2,y2), null);
|
||||
|
||||
Rectangle2D rect = new Rectangle (0, 0, (int) p2.getX (), (int) p2.getY ());
|
||||
return rect.getBounds ();
|
||||
return transform.createTransformedShape(src.getBounds()).getBounds2D();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns interpolation type used during transformations
|
||||
* Returns interpolation type used during transformations.
|
||||
*
|
||||
* @return interpolation type
|
||||
*/
|
||||
public final int getInterpolationType ()
|
||||
{
|
||||
if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR))
|
||||
if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
|
||||
return TYPE_BILINEAR;
|
||||
|
||||
else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
|
||||
return TYPE_BICUBIC;
|
||||
|
||||
else
|
||||
return TYPE_NEAREST_NEIGHBOR;
|
||||
}
|
||||
@@ -355,7 +337,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
/**
|
||||
* Returns rendering hints that are used during transformation.
|
||||
*
|
||||
* @return rendering hints
|
||||
* @return the rendering hints used in this Op.
|
||||
*/
|
||||
public final RenderingHints getRenderingHints ()
|
||||
{
|
||||
@@ -366,10 +348,261 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
|
||||
* Returns transform used in transformation between source and destination
|
||||
* image.
|
||||
*
|
||||
* @return transform
|
||||
* @return the transform used in this Op.
|
||||
*/
|
||||
public final AffineTransform getTransform ()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform nearest-neighbour filtering
|
||||
*
|
||||
* @param src the source raster
|
||||
* @param dst the destination raster
|
||||
* @param dpts array of points on the destination raster
|
||||
* @param pts array of corresponding points on the source raster
|
||||
*/
|
||||
private void filterNearest(Raster src, WritableRaster dst, double[] dpts,
|
||||
double[] pts)
|
||||
{
|
||||
Rectangle srcbounds = src.getBounds();
|
||||
|
||||
// For all points on the destination raster, copy the value from the
|
||||
// corrosponding (rounded) source point
|
||||
for (int i = 0; i < dpts.length; i += 2)
|
||||
{
|
||||
int srcX = (int) Math.round(pts[i]) + src.getMinX();
|
||||
int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
|
||||
|
||||
if (srcbounds.contains(srcX, srcY))
|
||||
dst.setDataElements((int) dpts[i] + dst.getMinX(),
|
||||
(int) dpts[i + 1] + dst.getMinY(),
|
||||
src.getDataElements(srcX, srcY, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bilinear filtering
|
||||
*
|
||||
* @param src the source raster
|
||||
* @param dst the destination raster
|
||||
* @param dpts array of points on the destination raster
|
||||
* @param pts array of corresponding points on the source raster
|
||||
*/
|
||||
private void filterBilinear(Raster src, WritableRaster dst, double[] dpts,
|
||||
double[] pts)
|
||||
{
|
||||
Rectangle srcbounds = src.getBounds();
|
||||
|
||||
Object xyarr = null;
|
||||
Object xp1arr = null;
|
||||
Object yp1arr = null;
|
||||
Object xyp1arr = null;
|
||||
|
||||
double xy;
|
||||
double xp1;
|
||||
double yp1;
|
||||
double xyp1;
|
||||
|
||||
double[] result = new double[src.getNumBands()];
|
||||
|
||||
// For all points in the destination raster, use bilinear interpolation
|
||||
// to find the value from the corrosponding source points
|
||||
for (int i = 0; i < dpts.length; i += 2)
|
||||
{
|
||||
int srcX = (int) Math.round(pts[i]) + src.getMinX();
|
||||
int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
|
||||
|
||||
if (srcbounds.contains(srcX, srcY))
|
||||
{
|
||||
// Corner case at the bottom or right edge; use nearest neighbour
|
||||
if (pts[i] >= src.getWidth() - 1
|
||||
|| pts[i + 1] >= src.getHeight() - 1)
|
||||
dst.setDataElements((int) dpts[i] + dst.getMinX(),
|
||||
(int) dpts[i + 1] + dst.getMinY(),
|
||||
src.getDataElements(srcX, srcY, null));
|
||||
|
||||
// Standard case, apply the bilinear formula
|
||||
else
|
||||
{
|
||||
int x = (int) Math.floor(pts[i] + src.getMinX());
|
||||
int y = (int) Math.floor(pts[i + 1] + src.getMinY());
|
||||
double xdiff = pts[i] + src.getMinX() - x;
|
||||
double ydiff = pts[i + 1] + src.getMinY() - y;
|
||||
|
||||
// Get surrounding pixels used in interpolation... optimized
|
||||
// to use the smallest datatype possible.
|
||||
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|
||||
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
|
||||
{
|
||||
xyarr = src.getPixel(x, y, (double[])xyarr);
|
||||
xp1arr = src.getPixel(x+1, y, (double[])xp1arr);
|
||||
yp1arr = src.getPixel(x, y+1, (double[])yp1arr);
|
||||
xyp1arr = src.getPixel(x+1, y+1, (double[])xyp1arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
xyarr = src.getPixel(x, y, (int[])xyarr);
|
||||
xp1arr = src.getPixel(x+1, y, (int[])xp1arr);
|
||||
yp1arr = src.getPixel(x, y+1, (int[])yp1arr);
|
||||
xyp1arr = src.getPixel(x+1, y+1, (int[])xyp1arr);
|
||||
}
|
||||
// using
|
||||
// array[] pixels = src.getPixels(x, y, 2, 2, pixels);
|
||||
// instead of doing four individual src.getPixel() calls
|
||||
// should be faster, but benchmarking shows that it's not...
|
||||
|
||||
// Run interpolation for each band
|
||||
for (int j = 0; j < src.getNumBands(); j++)
|
||||
{
|
||||
// Pull individual sample values out of array
|
||||
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|
||||
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
|
||||
{
|
||||
xy = ((double[])xyarr)[j];
|
||||
xp1 = ((double[])xp1arr)[j];
|
||||
yp1 = ((double[])yp1arr)[j];
|
||||
xyp1 = ((double[])xyp1arr)[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
xy = ((int[])xyarr)[j];
|
||||
xp1 = ((int[])xp1arr)[j];
|
||||
yp1 = ((int[])yp1arr)[j];
|
||||
xyp1 = ((int[])xyp1arr)[j];
|
||||
}
|
||||
|
||||
// If all four samples are identical, there's no need to
|
||||
// calculate anything
|
||||
if (xy == xp1 && xy == yp1 && xy == xyp1)
|
||||
result[j] = xy;
|
||||
|
||||
// Run bilinear interpolation formula
|
||||
else
|
||||
result[j] = (xy * (1-xdiff) + xp1 * xdiff)
|
||||
* (1-ydiff)
|
||||
+ (yp1 * (1-xdiff) + xyp1 * xdiff)
|
||||
* ydiff;
|
||||
}
|
||||
|
||||
dst.setPixel((int)dpts[i] + dst.getMinX(),
|
||||
(int)dpts[i+1] + dst.getMinY(),
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bicubic filtering
|
||||
* based on http://local.wasp.uwa.edu.au/~pbourke/colour/bicubic/
|
||||
*
|
||||
* @param src the source raster
|
||||
* @param dst the destination raster
|
||||
* @param dpts array of points on the destination raster
|
||||
* @param pts array of corresponding points on the source raster
|
||||
*/
|
||||
private void filterBicubic(Raster src, WritableRaster dst, double[] dpts,
|
||||
double[] pts)
|
||||
{
|
||||
Rectangle srcbounds = src.getBounds();
|
||||
double[] result = new double[src.getNumBands()];
|
||||
Object pixels = null;
|
||||
|
||||
// For all points on the destination raster, perform bicubic interpolation
|
||||
// from corrosponding source points
|
||||
for (int i = 0; i < dpts.length; i += 2)
|
||||
{
|
||||
if (srcbounds.contains((int) Math.round(pts[i]) + src.getMinX(),
|
||||
(int) Math.round(pts[i + 1]) + src.getMinY()))
|
||||
{
|
||||
int x = (int) Math.floor(pts[i] + src.getMinX());
|
||||
int y = (int) Math.floor(pts[i + 1] + src.getMinY());
|
||||
double dx = pts[i] + src.getMinX() - x;
|
||||
double dy = pts[i + 1] + src.getMinY() - y;
|
||||
Arrays.fill(result, 0);
|
||||
|
||||
for (int m = - 1; m < 3; m++)
|
||||
for (int n = - 1; n < 3; n++)
|
||||
{
|
||||
// R(x) = ( P(x+2)^3 - 4 P(x+1)^3 + 6 P(x)^3 - 4 P(x-1)^3 ) / 6
|
||||
double r1 = 0;
|
||||
double r2 = 0;
|
||||
|
||||
// Calculate R(m - dx)
|
||||
double rx = m - dx + 2;
|
||||
r1 += rx * rx * rx;
|
||||
|
||||
rx = m - dx + 1;
|
||||
if (rx > 0)
|
||||
r1 -= 4 * rx * rx * rx;
|
||||
|
||||
rx = m - dx;
|
||||
if (rx > 0)
|
||||
r1 += 6 * rx * rx * rx;
|
||||
|
||||
rx = m - dx - 1;
|
||||
if (rx > 0)
|
||||
r1 -= 4 * rx * rx * rx;
|
||||
|
||||
r1 /= 6;
|
||||
|
||||
// Calculate R(dy - n);
|
||||
rx = dy - n + 2;
|
||||
if (rx > 0)
|
||||
r2 += rx * rx * rx;
|
||||
|
||||
rx = dy - n + 1;
|
||||
if (rx > 0)
|
||||
r2 -= 4 * rx * rx * rx;
|
||||
|
||||
rx = dy - n;
|
||||
if (rx > 0)
|
||||
r2 += 6 * rx * rx * rx;
|
||||
|
||||
rx = dy - n - 1;
|
||||
if (rx > 0)
|
||||
r2 -= 4 * rx * rx * rx;
|
||||
|
||||
r2 /= 6;
|
||||
|
||||
// Calculate F(i+m, j+n) R(m - dx) R(dy - n)
|
||||
// Check corner cases
|
||||
int srcX = x + m;
|
||||
if (srcX >= src.getMinX() + src.getWidth())
|
||||
srcX = src.getMinX() + src.getWidth() - 1;
|
||||
else if (srcX < src.getMinX())
|
||||
srcX = src.getMinX();
|
||||
|
||||
int srcY = y + n;
|
||||
if (srcY >= src.getMinY() + src.getHeight())
|
||||
srcY = src.getMinY() + src.getHeight() - 1;
|
||||
else if (srcY < src.getMinY())
|
||||
srcY = src.getMinY();
|
||||
|
||||
// Calculate once for each band, using the smallest
|
||||
// datatype possible
|
||||
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|
||||
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
|
||||
{
|
||||
pixels = src.getPixel(srcX, srcY, (double[])pixels);
|
||||
for (int j = 0; j < result.length; j++)
|
||||
result[j] += ((double[])pixels)[j] * r1 * r2;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixels = src.getPixel(srcX, srcY, (int[])pixels);
|
||||
for (int j = 0; j < result.length; j++)
|
||||
result[j] += ((int[])pixels)[j] * r1 * r2;
|
||||
}
|
||||
}
|
||||
|
||||
// Put it all together
|
||||
dst.setPixel((int)dpts[i] + dst.getMinX(),
|
||||
(int)dpts[i+1] + dst.getMinY(),
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2004 Free Software Foundation
|
||||
/* BandCombineOp.java - perform a combination on the bands of a raster
|
||||
Copyright (C) 2004, 2006 Free Software Foundation
|
||||
|
||||
This file is part of GNU Classpath.
|
||||
|
||||
@@ -36,10 +37,10 @@ exception statement from your version. */
|
||||
|
||||
package java.awt.image;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Filter Raster pixels by applying a matrix.
|
||||
@@ -53,6 +54,9 @@ import java.awt.geom.Rectangle2D;
|
||||
* for the destination. Therefore the destination Raster must contain the
|
||||
* same number of bands as the number of rows in the filter matrix.
|
||||
*
|
||||
* This Op assumes that samples are integers; floating point sample types will
|
||||
* be rounded to their nearest integer value during filtering.
|
||||
*
|
||||
* @author Jerry Quinn (jlquinn@optonline.net)
|
||||
*/
|
||||
public class BandCombineOp implements RasterOp
|
||||
@@ -65,52 +69,74 @@ public class BandCombineOp implements RasterOp
|
||||
*
|
||||
* @param matrix The matrix to filter pixels with.
|
||||
* @param hints Rendering hints to apply. Ignored.
|
||||
* @throws ArrayIndexOutOfBoundsException if the matrix is invalid
|
||||
*/
|
||||
public BandCombineOp(float[][] matrix, RenderingHints hints)
|
||||
{
|
||||
this.matrix = matrix;
|
||||
this.matrix = new float[matrix.length][];
|
||||
int width = matrix[0].length;
|
||||
for (int i = 0; i < matrix.length; i++)
|
||||
{
|
||||
this.matrix[i] = new float[width + 1];
|
||||
for (int j = 0; j < width; j++)
|
||||
this.matrix[i][j] = matrix[i][j];
|
||||
|
||||
// The reference implementation pads the array with a trailing zero...
|
||||
this.matrix[i][width] = 0;
|
||||
}
|
||||
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Raster pixels through a matrix.
|
||||
*
|
||||
* Applies the Op matrix to source pixes to produce dest pixels. Each row
|
||||
* of the matrix is multiplied by the src pixel components to produce the
|
||||
* dest pixel. If matrix is one more than the number of bands in the src,
|
||||
* the last element is implicitly multiplied by 1, i.e. added to the sum
|
||||
* for that dest component.
|
||||
*
|
||||
* If dest is null, a suitable Raster is created. This implementation uses
|
||||
* createCompatibleDestRaster.
|
||||
* Filter Raster pixels through a matrix. Applies the Op matrix to source
|
||||
* pixes to produce dest pixels. Each row of the matrix is multiplied by the
|
||||
* src pixel components to produce the dest pixel. If matrix is one more than
|
||||
* the number of bands in the src, the last element is implicitly multiplied
|
||||
* by 1, i.e. added to the sum for that dest component. If dest is null, a
|
||||
* suitable Raster is created. This implementation uses
|
||||
* createCompatibleDestRaster.
|
||||
*
|
||||
* @param src The source Raster.
|
||||
* @param dest The destination Raster, or null.
|
||||
* @returns The destination Raster or an allocated Raster.
|
||||
* @param dest The destination Raster, or null.
|
||||
* @throws IllegalArgumentException if the destination raster is incompatible
|
||||
* with the source raster.
|
||||
* @return The filtered Raster.
|
||||
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
|
||||
*java.awt.image.WritableRaster)
|
||||
* java.awt.image.WritableRaster)
|
||||
*/
|
||||
public WritableRaster filter(Raster src, WritableRaster dest) {
|
||||
if (dest == null)
|
||||
dest = createCompatibleDestRaster(src);
|
||||
|
||||
else if (dest.getNumBands() != src.getNumBands()
|
||||
|| dest.getTransferType() != src.getTransferType())
|
||||
throw new IllegalArgumentException("Destination raster is incompatible with source raster");
|
||||
|
||||
// Filter the pixels
|
||||
float[] spix = new float[matrix[0].length];
|
||||
float[] dpix = new float[matrix.length];
|
||||
int[] spix = new int[matrix[0].length - 1];
|
||||
int[] spix2 = new int[matrix[0].length - 1];
|
||||
int[] dpix = new int[matrix.length];
|
||||
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
|
||||
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
|
||||
{
|
||||
// In case matrix rows have implicit translation
|
||||
spix[spix.length - 1] = 1.0f;
|
||||
src.getPixel(x, y, spix);
|
||||
for (int i = 0; i < matrix.length; i++)
|
||||
{
|
||||
dpix[i] = 0;
|
||||
for (int j = 0; j < matrix[0].length; j++)
|
||||
dpix[i] += spix[j] * matrix[i][j];
|
||||
// In case matrix rows have implicit translation
|
||||
spix[spix.length - 1] = 1;
|
||||
src.getPixel(x, y, spix);
|
||||
|
||||
// Do not re-calculate if pixel is identical to the last one
|
||||
// (ie, blocks of the same colour)
|
||||
if (!Arrays.equals(spix, spix2))
|
||||
{
|
||||
System.arraycopy(spix, 0, spix2, 0, spix.length);
|
||||
for (int i = 0; i < matrix.length; i++)
|
||||
{
|
||||
dpix[i] = 0;
|
||||
for (int j = 0; j < matrix[0].length - 1; j++)
|
||||
dpix[i] += spix[j] * (int)matrix[i][j];
|
||||
}
|
||||
}
|
||||
dest.setPixel(x, y, dpix);
|
||||
}
|
||||
dest.setPixel(x, y, dpix);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
@@ -125,28 +151,48 @@ public class BandCombineOp implements RasterOp
|
||||
|
||||
/**
|
||||
* Creates a new WritableRaster that can be used as the destination for this
|
||||
* Op. This implementation creates a Banded Raster with data type FLOAT.
|
||||
* @see
|
||||
*java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
|
||||
* Op. The number of bands in the source raster must equal the number of rows
|
||||
* in the op matrix, which must also be equal to either the number of columns
|
||||
* or (columns - 1) in the matrix.
|
||||
*
|
||||
* @param src The source raster.
|
||||
* @return A compatible raster.
|
||||
* @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
|
||||
* @throws IllegalArgumentException if the raster is incompatible with the
|
||||
* matrix.
|
||||
*/
|
||||
public WritableRaster createCompatibleDestRaster(Raster src)
|
||||
{
|
||||
return Raster.createBandedRaster(DataBuffer.TYPE_FLOAT, src.getWidth(),
|
||||
src.getHeight(), matrix.length,
|
||||
new Point(src.getMinX(), src.getMinY()));
|
||||
// Destination raster must have same number of bands as source
|
||||
if (src.getNumBands() != matrix.length)
|
||||
throw new IllegalArgumentException("Number of rows in matrix specifies an "
|
||||
+ "incompatible number of bands");
|
||||
|
||||
// We use -1 and -2 because we previously padded the rows with a trailing 0
|
||||
if (src.getNumBands() != matrix[0].length - 1
|
||||
&& src.getNumBands() != matrix[0].length - 2)
|
||||
throw new IllegalArgumentException("Incompatible number of bands: "
|
||||
+ "the number of bands in the raster must equal the number of "
|
||||
+ "columns in the matrix, optionally minus one");
|
||||
|
||||
return src.createCompatibleWritableRaster();
|
||||
}
|
||||
|
||||
/** Return corresponding destination point for source point.
|
||||
/**
|
||||
* Return corresponding destination point for source point. Because this is
|
||||
* not a geometric operation, it simply returns a copy of the source.
|
||||
*
|
||||
* LookupOp will return the value of src unchanged.
|
||||
* @param src The source point.
|
||||
* @param dst The destination point.
|
||||
* @return dst The destination point.
|
||||
* @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D,
|
||||
*java.awt.geom.Point2D)
|
||||
*/
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst)
|
||||
{
|
||||
if (dst == null) return (Point2D)src.clone();
|
||||
if (dst == null)
|
||||
return (Point2D)src.clone();
|
||||
|
||||
dst.setLocation(src);
|
||||
return dst;
|
||||
}
|
||||
@@ -159,7 +205,11 @@ public class BandCombineOp implements RasterOp
|
||||
return hints;
|
||||
}
|
||||
|
||||
/** Return the matrix for this Op. */
|
||||
/**
|
||||
* Return the matrix used in this operation.
|
||||
*
|
||||
* @return The matrix used in this operation.
|
||||
*/
|
||||
public final float[][] getMatrix()
|
||||
{
|
||||
return matrix;
|
||||
|
||||
@@ -38,6 +38,7 @@ exception statement from your version. */
|
||||
|
||||
package java.awt.image;
|
||||
|
||||
import gnu.java.awt.Buffers;
|
||||
import gnu.java.awt.ComponentDataBlitOp;
|
||||
|
||||
import java.awt.Graphics;
|
||||
@@ -79,26 +80,36 @@ public class BufferedImage extends Image
|
||||
TYPE_BYTE_BINARY = 12,
|
||||
TYPE_BYTE_INDEXED = 13;
|
||||
|
||||
static final int[] bits3 = { 8, 8, 8 };
|
||||
static final int[] bits4 = { 8, 8, 8, 8 };
|
||||
static final int[] bits1byte = { 8 };
|
||||
static final int[] bits1ushort = { 16 };
|
||||
/**
|
||||
* Vector of TileObservers (or null)
|
||||
*/
|
||||
Vector tileObservers;
|
||||
|
||||
static final int[] masks_int = { 0x00ff0000,
|
||||
0x0000ff00,
|
||||
0x000000ff,
|
||||
DataBuffer.TYPE_INT };
|
||||
static final int[] masks_565 = { 0xf800,
|
||||
0x07e0,
|
||||
0x001f,
|
||||
DataBuffer.TYPE_USHORT};
|
||||
static final int[] masks_555 = { 0x7c00,
|
||||
0x03e0,
|
||||
0x001f,
|
||||
DataBuffer.TYPE_USHORT};
|
||||
/**
|
||||
* The image's WritableRaster
|
||||
*/
|
||||
WritableRaster raster;
|
||||
|
||||
/**
|
||||
* The associated ColorModel
|
||||
*/
|
||||
ColorModel colorModel;
|
||||
|
||||
/**
|
||||
* The image's properties (or null)
|
||||
*/
|
||||
Hashtable properties;
|
||||
|
||||
/**
|
||||
* Whether alpha is premultiplied
|
||||
*/
|
||||
boolean isPremultiplied;
|
||||
|
||||
/**
|
||||
* The predefined type, if any.
|
||||
*/
|
||||
int type;
|
||||
|
||||
Vector observers;
|
||||
|
||||
/**
|
||||
* Creates a new <code>BufferedImage</code> with the specified width, height
|
||||
* and type. Valid <code>type</code> values are:
|
||||
@@ -119,155 +130,181 @@ public class BufferedImage extends Image
|
||||
* <li>{@link #TYPE_BYTE_INDEXED}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param w the width (must be > 0).
|
||||
* @param h the height (must be > 0).
|
||||
* @param width the width (must be > 0).
|
||||
* @param height the height (must be > 0).
|
||||
* @param type the image type (see the list of valid types above).
|
||||
*
|
||||
* @throws IllegalArgumentException if <code>w</code> or <code>h</code> is
|
||||
* less than or equal to zero.
|
||||
* @throws IllegalArgumentException if <code>width</code> or
|
||||
* <code>height</code> is less than or equal to zero.
|
||||
* @throws IllegalArgumentException if <code>type</code> is not one of the
|
||||
* specified values.
|
||||
*/
|
||||
public BufferedImage(int w, int h, int type)
|
||||
public BufferedImage(int width, int height, int type)
|
||||
{
|
||||
SampleModel sm = null;
|
||||
ColorModel cm = null;
|
||||
|
||||
boolean alpha = false;
|
||||
boolean premultiplied = false;
|
||||
switch (type)
|
||||
boolean premultiplied = (type == BufferedImage.TYPE_INT_ARGB_PRE ||
|
||||
type == BufferedImage.TYPE_4BYTE_ABGR_PRE);
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case TYPE_4BYTE_ABGR_PRE:
|
||||
case TYPE_INT_ARGB_PRE:
|
||||
premultiplied = true;
|
||||
// fall through
|
||||
case TYPE_INT_ARGB:
|
||||
case TYPE_4BYTE_ABGR:
|
||||
alpha = true;
|
||||
}
|
||||
|
||||
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
switch (type)
|
||||
{
|
||||
case TYPE_INT_RGB:
|
||||
case TYPE_INT_ARGB:
|
||||
case TYPE_INT_ARGB_PRE:
|
||||
case TYPE_USHORT_565_RGB:
|
||||
case TYPE_USHORT_555_RGB:
|
||||
int[] masks = null;
|
||||
switch (type)
|
||||
{
|
||||
case TYPE_INT_RGB:
|
||||
case TYPE_INT_ARGB:
|
||||
case TYPE_INT_ARGB_PRE:
|
||||
masks = masks_int;
|
||||
break;
|
||||
case TYPE_USHORT_565_RGB:
|
||||
masks = masks_565;
|
||||
break;
|
||||
case TYPE_USHORT_555_RGB:
|
||||
masks = masks_555;
|
||||
break;
|
||||
}
|
||||
|
||||
cm = new DirectColorModel(cs,
|
||||
32, // 32 bits in an int
|
||||
masks[0], // r
|
||||
masks[1], // g
|
||||
masks[2], // b
|
||||
alpha ? 0xff000000 : 0,
|
||||
premultiplied,
|
||||
masks[3] // data type
|
||||
);
|
||||
case BufferedImage.TYPE_INT_RGB:
|
||||
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
|
||||
width, height,
|
||||
new int[]{ 0x00FF0000,
|
||||
0x0000FF00,
|
||||
0x000000FF } ) ;
|
||||
cm = new DirectColorModel( 24, 0xff0000, 0xff00, 0xff );
|
||||
break;
|
||||
|
||||
case TYPE_INT_BGR:
|
||||
String msg =
|
||||
"FIXME: Programmer is confused. Why (and how) does a " +
|
||||
"TYPE_INT_BGR image use ComponentColorModel to store " +
|
||||
"8-bit values? Is data type TYPE_INT or TYPE_BYTE. What " +
|
||||
"is the difference between TYPE_INT_BGR and TYPE_3BYTE_BGR?";
|
||||
throw new UnsupportedOperationException(msg);
|
||||
|
||||
case TYPE_3BYTE_BGR:
|
||||
case TYPE_4BYTE_ABGR:
|
||||
case TYPE_4BYTE_ABGR_PRE:
|
||||
case TYPE_BYTE_GRAY:
|
||||
case TYPE_USHORT_GRAY:
|
||||
int[] bits = null;
|
||||
int dataType = DataBuffer.TYPE_BYTE;
|
||||
switch (type) {
|
||||
case TYPE_3BYTE_BGR:
|
||||
bits = bits3;
|
||||
break;
|
||||
case TYPE_4BYTE_ABGR:
|
||||
case TYPE_4BYTE_ABGR_PRE:
|
||||
bits = bits4;
|
||||
break;
|
||||
case TYPE_BYTE_GRAY:
|
||||
bits = bits1byte;
|
||||
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
break;
|
||||
case TYPE_USHORT_GRAY:
|
||||
bits = bits1ushort;
|
||||
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
dataType = DataBuffer.TYPE_USHORT;
|
||||
break;
|
||||
}
|
||||
cm = new ComponentColorModel(cs, bits, alpha, premultiplied,
|
||||
alpha ?
|
||||
Transparency.TRANSLUCENT:
|
||||
Transparency.OPAQUE,
|
||||
dataType);
|
||||
case BufferedImage.TYPE_3BYTE_BGR:
|
||||
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
|
||||
width, height,
|
||||
3, width * 3,
|
||||
new int[]{ 2, 1, 0 } );
|
||||
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
false, false,
|
||||
BufferedImage.OPAQUE,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
break;
|
||||
|
||||
case BufferedImage.TYPE_INT_ARGB:
|
||||
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
|
||||
width, height,
|
||||
new int[]{ 0x00FF0000,
|
||||
0x0000FF00,
|
||||
0x000000FF,
|
||||
0xFF000000 } );
|
||||
if (premultiplied)
|
||||
cm = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
32, 0xff0000, 0xff00, 0xff, 0xff000000,
|
||||
true,
|
||||
Buffers.smallestAppropriateTransferType(32));
|
||||
else
|
||||
cm = new DirectColorModel( 32, 0xff0000, 0xff00, 0xff, 0xff000000 );
|
||||
break;
|
||||
case TYPE_BYTE_BINARY:
|
||||
byte[] vals = { 0, (byte) 0xff };
|
||||
cm = new IndexColorModel(8, 2, vals, vals, vals);
|
||||
|
||||
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||
sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
|
||||
width, height,
|
||||
4, 4*width,
|
||||
new int[]{3, 2, 1, 0});
|
||||
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
true, premultiplied,
|
||||
BufferedImage.TRANSLUCENT,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
break;
|
||||
case TYPE_BYTE_INDEXED:
|
||||
String msg2 = "type not implemented yet";
|
||||
throw new UnsupportedOperationException(msg2);
|
||||
// FIXME: build color-cube and create color model
|
||||
|
||||
case BufferedImage.TYPE_INT_BGR:
|
||||
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
|
||||
width, height,
|
||||
new int[]{ 0x000000FF,
|
||||
0x0000FF00,
|
||||
0x00FF0000 } ) ;
|
||||
cm = new DirectColorModel( 24, 0xff, 0xff00, 0xff0000 );
|
||||
break;
|
||||
|
||||
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
|
||||
width, height,
|
||||
new int[]{ 0xF800,
|
||||
0x7E0,
|
||||
0x1F } ) ;
|
||||
cm = new DirectColorModel( 16, 0xF800, 0x7E0, 0x1F );
|
||||
break;
|
||||
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
|
||||
width, height,
|
||||
new int[]{ 0x7C00,
|
||||
0x3E0,
|
||||
0x1F } ) ;
|
||||
cm = new DirectColorModel( 15, 0x7C00, 0x3E0, 0x1F );
|
||||
break;
|
||||
|
||||
case BufferedImage.TYPE_BYTE_INDEXED:
|
||||
cm = createDefaultIndexedColorModel( false );
|
||||
|
||||
case BufferedImage.TYPE_BYTE_GRAY:
|
||||
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
|
||||
width, height,
|
||||
1, width, new int[]{ 0 } );
|
||||
break;
|
||||
|
||||
case BufferedImage.TYPE_USHORT_GRAY:
|
||||
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_USHORT,
|
||||
width, height,
|
||||
1, width, new int[]{ 0 } );
|
||||
break;
|
||||
|
||||
case BufferedImage.TYPE_BYTE_BINARY:
|
||||
cm = createDefaultIndexedColorModel( true );
|
||||
sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
|
||||
width, height, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown image type " + type);
|
||||
sm = null;
|
||||
}
|
||||
|
||||
if( sm == null )
|
||||
throw new IllegalArgumentException("Unknown predefined image type.");
|
||||
|
||||
init(cm,
|
||||
cm.createCompatibleWritableRaster(w, h),
|
||||
premultiplied,
|
||||
null, // no properties
|
||||
type
|
||||
);
|
||||
if( cm == null ) // only for the grayscale types
|
||||
{
|
||||
int buftype;
|
||||
int[] bits = new int[1];
|
||||
if( type == BufferedImage.TYPE_BYTE_GRAY )
|
||||
{
|
||||
buftype = DataBuffer.TYPE_BYTE;
|
||||
bits[0] = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
buftype = DataBuffer.TYPE_USHORT;
|
||||
bits[0] = 16;
|
||||
}
|
||||
ColorSpace graySpace = ColorSpace.getInstance( ColorSpace.CS_GRAY );
|
||||
|
||||
cm = new ComponentColorModel( graySpace, bits, false, false,
|
||||
Transparency.OPAQUE, buftype );
|
||||
}
|
||||
|
||||
init( cm,
|
||||
Raster.createWritableRaster(sm, new Point( 0, 0 ) ),
|
||||
premultiplied,
|
||||
null, // no properties
|
||||
type );
|
||||
}
|
||||
|
||||
public BufferedImage(int w, int h, int type,
|
||||
IndexColorModel indexcolormodel)
|
||||
{
|
||||
if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED))
|
||||
throw new IllegalArgumentException("type must be binary or indexed");
|
||||
throw new IllegalArgumentException("Type must be TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED");
|
||||
if( indexcolormodel.getMapSize() > 16 && type == TYPE_BYTE_BINARY )
|
||||
throw new IllegalArgumentException("Type TYPE_BYTE_BINARY cannot have a larger than 16-color palette.");
|
||||
if( indexcolormodel.getMapSize() > 256 )
|
||||
throw new IllegalArgumentException("Byte type cannot have a larger than 256-color palette.");
|
||||
|
||||
init(indexcolormodel,
|
||||
indexcolormodel.createCompatibleWritableRaster(w, h),
|
||||
false, // not premultiplied (guess)
|
||||
null, // no properties
|
||||
type);
|
||||
init( indexcolormodel,
|
||||
indexcolormodel.createCompatibleWritableRaster(w, h),
|
||||
indexcolormodel.isAlphaPremultiplied(),
|
||||
null, // no properties
|
||||
type );
|
||||
}
|
||||
|
||||
public BufferedImage(ColorModel colormodel,
|
||||
WritableRaster writableraster,
|
||||
boolean premultiplied,
|
||||
Hashtable properties)
|
||||
Hashtable<?,?> properties)
|
||||
{
|
||||
init(colormodel, writableraster, premultiplied, properties,
|
||||
TYPE_CUSTOM);
|
||||
// TODO: perhaps try to identify type?
|
||||
}
|
||||
|
||||
WritableRaster raster;
|
||||
ColorModel colorModel;
|
||||
Hashtable properties;
|
||||
boolean isPremultiplied;
|
||||
int type;
|
||||
|
||||
|
||||
private void init(ColorModel cm,
|
||||
WritableRaster writableraster,
|
||||
boolean premultiplied,
|
||||
@@ -280,12 +317,48 @@ public class BufferedImage extends Image
|
||||
isPremultiplied = premultiplied;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
//public void addTileObserver(TileObserver tileobserver) {}
|
||||
|
||||
/**
|
||||
* Creates the default palettes for the predefined indexed color types
|
||||
* (256-color or black-and-white)
|
||||
*
|
||||
* @param binary - If <code>true</code>, a black and white palette,
|
||||
* otherwise a default 256-color palette is returned.
|
||||
*/
|
||||
private IndexColorModel createDefaultIndexedColorModel( boolean binary )
|
||||
{
|
||||
if( binary )
|
||||
{
|
||||
byte[] t = new byte[]{ 0, (byte)255 };
|
||||
return new IndexColorModel( 1, 2, t, t, t );
|
||||
}
|
||||
|
||||
byte[] r = new byte[256];
|
||||
byte[] g = new byte[256];
|
||||
byte[] b = new byte[256];
|
||||
int index = 0;
|
||||
for( int i = 0; i < 6; i++ )
|
||||
for( int j = 0; j < 6; j++ )
|
||||
for( int k = 0; k < 6; k++ )
|
||||
{
|
||||
r[ index ] = (byte)(i * 51);
|
||||
g[ index ] = (byte)(j * 51);
|
||||
b[ index ] = (byte)(k * 51);
|
||||
index++;
|
||||
}
|
||||
while( index < 256 )
|
||||
{
|
||||
r[ index ] = g[ index ] = b[ index ] =
|
||||
(byte)(18 + (index - 216) * 6);
|
||||
index++;
|
||||
}
|
||||
return new IndexColorModel( 8, 256, r, g, b );
|
||||
}
|
||||
|
||||
public void coerceData(boolean premultiplied)
|
||||
{
|
||||
colorModel = colorModel.coerceData(raster, premultiplied);
|
||||
isPremultiplied = premultiplied;
|
||||
}
|
||||
|
||||
public WritableRaster copyData(WritableRaster dest)
|
||||
@@ -555,7 +628,7 @@ public class BufferedImage extends Image
|
||||
};
|
||||
}
|
||||
|
||||
public Vector getSources()
|
||||
public Vector<RenderedImage> getSources()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -726,10 +799,10 @@ public class BufferedImage extends Image
|
||||
*/
|
||||
public void addTileObserver (TileObserver to)
|
||||
{
|
||||
if (observers == null)
|
||||
observers = new Vector ();
|
||||
if (tileObservers == null)
|
||||
tileObservers = new Vector ();
|
||||
|
||||
observers.add (to);
|
||||
tileObservers.add (to);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -741,10 +814,10 @@ public class BufferedImage extends Image
|
||||
*/
|
||||
public void removeTileObserver (TileObserver to)
|
||||
{
|
||||
if (observers == null)
|
||||
if (tileObservers == null)
|
||||
return;
|
||||
|
||||
observers.remove (to);
|
||||
tileObservers.remove (to);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,10 @@ exception statement from your version. */
|
||||
|
||||
package java.awt.image;
|
||||
|
||||
import gnu.java.awt.Buffers;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
@@ -47,9 +50,9 @@ import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
/**
|
||||
* ColorConvertOp is a filter for converting an image from one colorspace to
|
||||
* another colorspace. The filter can convert the image through a sequence
|
||||
* of colorspaces or just from source to destination.
|
||||
* ColorConvertOp is a filter for converting images or rasters between
|
||||
* colorspaces, either through a sequence of colorspaces or just from source to
|
||||
* destination.
|
||||
*
|
||||
* Color conversion is done on the color components without alpha. Thus
|
||||
* if a BufferedImage has alpha premultiplied, this is divided out before
|
||||
@@ -63,24 +66,22 @@ import java.awt.geom.Rectangle2D;
|
||||
*/
|
||||
public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
{
|
||||
private ColorSpace srccs;
|
||||
private ColorSpace dstcs;
|
||||
private RenderingHints hints;
|
||||
private ICC_Profile[] profiles;
|
||||
private ICC_Profile[] profiles = null;
|
||||
private ColorSpace[] spaces;
|
||||
private boolean rasterValid;
|
||||
|
||||
|
||||
/**
|
||||
* Convert BufferedImage through a ColorSpace.
|
||||
* Convert a BufferedImage through a ColorSpace.
|
||||
*
|
||||
* This filter version is only valid for BufferedImages. The source image
|
||||
* is converted to cspace. If the destination is not null, it is then
|
||||
* converted to the destination colorspace. Normally this filter will only
|
||||
* be used with a null destination.
|
||||
* Objects created with this constructor can be used to convert
|
||||
* BufferedImage's to a destination ColorSpace. Attempts to convert Rasters
|
||||
* with this constructor will result in an IllegalArgumentException when the
|
||||
* filter(Raster, WritableRaster) method is called.
|
||||
*
|
||||
* @param cspace The target color space.
|
||||
* @param hints Rendering hints to use in conversion, or null.
|
||||
* @param hints Rendering hints to use in conversion, if any (may be null)
|
||||
* @throws NullPointerException if the ColorSpace is null.
|
||||
*/
|
||||
public ColorConvertOp(ColorSpace cspace, RenderingHints hints)
|
||||
{
|
||||
@@ -88,9 +89,27 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
throw new NullPointerException();
|
||||
spaces = new ColorSpace[]{cspace};
|
||||
this.hints = hints;
|
||||
rasterValid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a source colorspace to a destination colorspace.
|
||||
*
|
||||
* This constructor takes two ColorSpace arguments as the source and
|
||||
* destination color spaces. It is usually used with the
|
||||
* filter(Raster, WritableRaster) method, in which case the source colorspace
|
||||
* is assumed to correspond to the source Raster, and the destination
|
||||
* colorspace with the destination Raster.
|
||||
*
|
||||
* If used with BufferedImages that do not match the source or destination
|
||||
* colorspaces specified here, there is an implicit conversion from the
|
||||
* source image to the source ColorSpace, or the destination ColorSpace to
|
||||
* the destination image.
|
||||
*
|
||||
* @param srcCspace The source ColorSpace.
|
||||
* @param dstCspace The destination ColorSpace.
|
||||
* @param hints Rendering hints to use in conversion, if any (may be null).
|
||||
* @throws NullPointerException if any ColorSpace is null.
|
||||
*/
|
||||
public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
|
||||
RenderingHints hints)
|
||||
{
|
||||
@@ -101,61 +120,77 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a source image destination image color space.
|
||||
* Convert from a source colorspace to a destinatino colorspace.
|
||||
*
|
||||
* This constructor builds a ColorConvertOp from an array of ICC_Profiles.
|
||||
* The source image will be converted through the sequence of color spaces
|
||||
* The source will be converted through the sequence of color spaces
|
||||
* defined by the profiles. If the sequence of profiles doesn't give a
|
||||
* well-defined conversion, throws IllegalArgumentException.
|
||||
* well-defined conversion, an IllegalArgumentException is thrown.
|
||||
*
|
||||
* NOTE: Sun's docs don't clearly define what a well-defined conversion is
|
||||
* - or perhaps someone smarter can come along and sort it out.
|
||||
*
|
||||
* For BufferedImages, when the first and last profiles match the
|
||||
* requirements of the source and destination color space respectively, the
|
||||
* corresponding conversion is unnecessary. TODO: code this up. I don't
|
||||
* yet understand how you determine this.
|
||||
* If used with BufferedImages that do not match the source or destination
|
||||
* colorspaces specified here, there is an implicit conversion from the
|
||||
* source image to the source ColorSpace, or the destination ColorSpace to
|
||||
* the destination image.
|
||||
*
|
||||
* For Rasters, the first and last profiles must have the same number of
|
||||
* bands as the source and destination Rasters, respectively. If this is
|
||||
* not the case, or there fewer than 2 profiles, an IllegalArgumentException
|
||||
* will be thrown.
|
||||
*
|
||||
* @param profiles
|
||||
* @param hints
|
||||
* @param profiles An array of ICC_Profile's to convert through.
|
||||
* @param hints Rendering hints to use in conversion, if any (may be null).
|
||||
* @throws NullPointerException if the profile array is null.
|
||||
* @throws IllegalArgumentException if the array is not a well-defined
|
||||
* conversion.
|
||||
*/
|
||||
public ColorConvertOp(ICC_Profile[] profiles, RenderingHints hints)
|
||||
{
|
||||
if (profiles == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.hints = hints;
|
||||
this.profiles = profiles;
|
||||
// TODO: Determine if this is well-defined.
|
||||
|
||||
// Create colorspace array with space for src and dest colorspace
|
||||
// Note that the ICC_ColorSpace constructor will throw an
|
||||
// IllegalArgumentException if the profile is invalid; thus we check
|
||||
// for a "well defined conversion"
|
||||
spaces = new ColorSpace[profiles.length];
|
||||
for (int i = 0; i < profiles.length; i++)
|
||||
spaces[i] = new ICC_ColorSpace(profiles[i]);
|
||||
}
|
||||
|
||||
/** Convert from source image color space to destination image color space.
|
||||
/**
|
||||
* Convert from source color space to destination color space.
|
||||
*
|
||||
* Only valid for BufferedImage objects, this Op converts from the source
|
||||
* color space to the destination color space. The destination can't be
|
||||
* null for this operation.
|
||||
* image's color space to the destination image's color space.
|
||||
*
|
||||
* @param hints Rendering hints to use during conversion, or null.
|
||||
* The destination in the filter(BufferedImage, BufferedImage) method cannot
|
||||
* be null for this operation, and it also cannot be used with the
|
||||
* filter(Raster, WritableRaster) method.
|
||||
*
|
||||
* @param hints Rendering hints to use in conversion, if any (may be null).
|
||||
*/
|
||||
public ColorConvertOp(RenderingHints hints)
|
||||
{
|
||||
this.hints = hints;
|
||||
srccs = null;
|
||||
dstcs = null;
|
||||
rasterValid = false;
|
||||
this.hints = hints;
|
||||
spaces = new ColorSpace[0];
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
|
||||
java.awt.image.BufferedImage)
|
||||
/**
|
||||
* Converts the source image using the conversion path specified in the
|
||||
* constructor. The resulting image is stored in the destination image if one
|
||||
* is provided; otherwise a new BufferedImage is created and returned.
|
||||
*
|
||||
* The source and destination BufferedImage (if one is supplied) must have
|
||||
* the same dimensions.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dst The destination image.
|
||||
* @throws IllegalArgumentException if the rasters and/or color spaces are
|
||||
* incompatible.
|
||||
* @return The transformed image.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
@@ -163,129 +198,241 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
// For now we just suck it up and create intermediate buffers.
|
||||
|
||||
if (dst == null && spaces.length == 0)
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Not enough color space information "
|
||||
+ "to complete conversion.");
|
||||
|
||||
if (dst != null
|
||||
&& (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth()))
|
||||
throw new IllegalArgumentException("Source and destination images have "
|
||||
+ "different dimensions");
|
||||
|
||||
// Make sure input isn't premultiplied by alpha
|
||||
if (src.isAlphaPremultiplied())
|
||||
{
|
||||
BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel());
|
||||
copyimage(src, tmp);
|
||||
tmp.coerceData(false);
|
||||
src = tmp;
|
||||
}
|
||||
{
|
||||
BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel());
|
||||
copyimage(src, tmp);
|
||||
tmp.coerceData(false);
|
||||
src = tmp;
|
||||
}
|
||||
|
||||
ColorModel scm = src.getColorModel();
|
||||
// Convert through defined intermediate conversions
|
||||
BufferedImage tmp;
|
||||
for (int i = 0; i < spaces.length; i++)
|
||||
{
|
||||
BufferedImage tmp = createCompatibleDestImage(src, scm);
|
||||
copyimage(src, tmp);
|
||||
src = tmp;
|
||||
}
|
||||
{
|
||||
if (src.getColorModel().getColorSpace().getType() != spaces[i].getType())
|
||||
{
|
||||
tmp = createCompatibleDestImage(src,
|
||||
createCompatibleColorModel(src,
|
||||
spaces[i]));
|
||||
copyimage(src, tmp);
|
||||
src = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Intermediate conversions leave result in src
|
||||
// No implicit conversion to destination type needed; return result from the
|
||||
// last intermediate conversions (which was left in src)
|
||||
if (dst == null)
|
||||
return src;
|
||||
|
||||
// Apply final conversion
|
||||
copyimage(src, dst);
|
||||
|
||||
dst = src;
|
||||
|
||||
// Implicit conversion to destination image's color space
|
||||
else
|
||||
copyimage(src, dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel)
|
||||
/**
|
||||
* Converts the source raster using the conversion path specified in the
|
||||
* constructor. The resulting raster is stored in the destination raster if
|
||||
* one is provided; otherwise a new WritableRaster is created and returned.
|
||||
*
|
||||
* This operation is not valid with every constructor of this class; see
|
||||
* the constructors for details. Further, the source raster must have the
|
||||
* same number of bands as the source ColorSpace, and the destination raster
|
||||
* must have the same number of bands as the destination ColorSpace.
|
||||
*
|
||||
* The source and destination raster (if one is supplied) must also have the
|
||||
* same dimensions.
|
||||
*
|
||||
* @param src The source raster.
|
||||
* @param dest The destination raster.
|
||||
* @throws IllegalArgumentException if the rasters and/or color spaces are
|
||||
* incompatible.
|
||||
* @return The transformed raster.
|
||||
*/
|
||||
public final WritableRaster filter(Raster src, WritableRaster dest)
|
||||
{
|
||||
// Various checks to ensure that the rasters and color spaces are compatible
|
||||
if (spaces.length < 2)
|
||||
throw new IllegalArgumentException("Not enough information about " +
|
||||
"source and destination colorspaces.");
|
||||
|
||||
if (spaces[0].getNumComponents() != src.getNumBands()
|
||||
|| (dest != null && spaces[spaces.length - 1].getNumComponents() != dest.getNumBands()))
|
||||
throw new IllegalArgumentException("Source or destination raster " +
|
||||
"contains the wrong number of bands.");
|
||||
|
||||
if (dest != null
|
||||
&& (src.getHeight() != dest.getHeight() || src.getWidth() != dest.getWidth()))
|
||||
throw new IllegalArgumentException("Source and destination rasters " +
|
||||
"have different dimensions");
|
||||
|
||||
// Need to iterate through each color space.
|
||||
// spaces[0] corresponds to the ColorSpace of the source raster, and
|
||||
// spaces[spaces.length - 1] corresponds to the ColorSpace of the
|
||||
// destination, with any number (or zero) of intermediate conversions.
|
||||
|
||||
for (int i = 0; i < spaces.length - 2; i++)
|
||||
{
|
||||
WritableRaster tmp = createCompatibleDestRaster(src, spaces[i + 1],
|
||||
false,
|
||||
src.getTransferType());
|
||||
copyraster(src, spaces[i], tmp, spaces[i + 1]);
|
||||
src = tmp;
|
||||
}
|
||||
|
||||
// The last conversion is done outside of the loop so that we can
|
||||
// use the dest raster supplied, instead of creating our own temp raster
|
||||
if (dest == null)
|
||||
dest = createCompatibleDestRaster(src, spaces[spaces.length - 1], false,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
copyraster(src, spaces[spaces.length - 2], dest, spaces[spaces.length - 1]);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty BufferedImage with the size equal to the source and the
|
||||
* correct number of bands for the conversion defined in this Op. The newly
|
||||
* created image is created with the specified ColorModel, or if no ColorModel
|
||||
* is supplied, an appropriate one is chosen.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dstCM A color model for the destination image (may be null).
|
||||
* @throws IllegalArgumentException if an appropriate colormodel cannot be
|
||||
* chosen with the information given.
|
||||
* @return The new compatible destination image.
|
||||
*/
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src,
|
||||
ColorModel dstCM)
|
||||
ColorModel dstCM)
|
||||
{
|
||||
// FIXME: set properties to those in src
|
||||
if (dstCM == null && spaces.length == 0)
|
||||
throw new IllegalArgumentException("Don't know the destination " +
|
||||
"colormodel");
|
||||
|
||||
if (dstCM == null)
|
||||
{
|
||||
dstCM = createCompatibleColorModel(src, spaces[spaces.length - 1]);
|
||||
}
|
||||
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isPremultiplied,
|
||||
null);
|
||||
createCompatibleDestRaster(src.getRaster(),
|
||||
dstCM.getColorSpace(),
|
||||
src.getColorModel().hasAlpha,
|
||||
dstCM.getTransferType()),
|
||||
src.isPremultiplied, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WritableRaster with the size equal to the source and the
|
||||
* correct number of bands.
|
||||
*
|
||||
* Note, the new Raster will always use a BYTE storage size, regardless of
|
||||
* the color model or defined destination; this is for compatibility with
|
||||
* the reference implementation.
|
||||
*
|
||||
* @param src The source Raster.
|
||||
* @throws IllegalArgumentException if there isn't enough colorspace
|
||||
* information to create a compatible Raster.
|
||||
* @return The new compatible destination raster.
|
||||
*/
|
||||
public WritableRaster createCompatibleDestRaster(Raster src)
|
||||
{
|
||||
if (spaces.length < 2)
|
||||
throw new IllegalArgumentException("Not enough destination colorspace " +
|
||||
"information");
|
||||
|
||||
// Create a new raster with the last ColorSpace in the conversion
|
||||
// chain, and with no alpha (implied)
|
||||
return createCompatibleDestRaster(src, spaces[spaces.length-1], false,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of ICC_Profiles used to create this Op, or null if the
|
||||
* Op was created using ColorSpace arguments.
|
||||
*
|
||||
* @return The array of ICC_Profiles, or null.
|
||||
*/
|
||||
public final ICC_Profile[] getICC_Profiles()
|
||||
{
|
||||
return profiles;
|
||||
}
|
||||
|
||||
/** Return the rendering hints for this op. */
|
||||
/**
|
||||
* Returns the rendering hints for this op.
|
||||
*
|
||||
* @return The rendering hints for this Op, or null.
|
||||
*/
|
||||
public final RenderingHints getRenderingHints()
|
||||
{
|
||||
return hints;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
|
||||
*/
|
||||
public final WritableRaster filter(Raster src, WritableRaster dest)
|
||||
{
|
||||
if (!rasterValid)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
// Need to iterate through each color space - there must be at least 2
|
||||
for (int i = 1; i < spaces.length - 1; i++)
|
||||
{
|
||||
// FIXME: this is wrong. tmp needs to have the same number of bands as
|
||||
// spaces[i] has.
|
||||
WritableRaster tmp = createCompatibleDestRaster(src);
|
||||
copyraster(src, spaces[i - 1], tmp, spaces[i]);
|
||||
src = tmp;
|
||||
}
|
||||
|
||||
// FIXME: this is wrong. dst needs to have the same number of bands as
|
||||
// spaces[i] has.
|
||||
if (dest == null)
|
||||
dest = createCompatibleDestRaster(src);
|
||||
copyraster(src, spaces[spaces.length - 2],
|
||||
dest, spaces[spaces.length - 1]);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
|
||||
*/
|
||||
public WritableRaster createCompatibleDestRaster(Raster src)
|
||||
{
|
||||
return src.createCompatibleWritableRaster();
|
||||
}
|
||||
|
||||
/** Return corresponding destination point for source point.
|
||||
/**
|
||||
* Returns the corresponding destination point for a source point.
|
||||
* Because this is not a geometric operation, the destination and source
|
||||
* points will be identical.
|
||||
*
|
||||
* LookupOp will return the value of src unchanged.
|
||||
* @param src The source point.
|
||||
* @param dst The destination point.
|
||||
* @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
|
||||
* @param dst The transformed destination point.
|
||||
* @return The transformed destination point.
|
||||
*/
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst)
|
||||
{
|
||||
if (dst == null) return (Point2D)src.clone();
|
||||
if (dst == null)
|
||||
return (Point2D)src.clone();
|
||||
|
||||
dst.setLocation(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
|
||||
/**
|
||||
* Returns the corresponding destination boundary of a source boundary.
|
||||
* Because this is not a geometric operation, the destination and source
|
||||
* boundaries will be identical.
|
||||
*
|
||||
* @param src The source boundary.
|
||||
* @return The boundaries of the destination.
|
||||
*/
|
||||
public final Rectangle2D getBounds2D(BufferedImage src)
|
||||
{
|
||||
return src.getRaster().getBounds();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
|
||||
/**
|
||||
* Returns the corresponding destination boundary of a source boundary.
|
||||
* Because this is not a geometric operation, the destination and source
|
||||
* boundaries will be identical.
|
||||
*
|
||||
* @param src The source boundary.
|
||||
* @return The boundaries of the destination.
|
||||
*/
|
||||
public final Rectangle2D getBounds2D(Raster src)
|
||||
{
|
||||
return src.getBounds();
|
||||
}
|
||||
|
||||
// According to Sven de Marothy, we need to copy the src into the dest
|
||||
// using Graphics2D, in order to use the rendering hints.
|
||||
|
||||
/**
|
||||
* Copy a source image to a destination image, respecting their colorspaces
|
||||
* and performing colorspace conversions if necessary.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dst The destination image.
|
||||
*/
|
||||
private void copyimage(BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
// This is done using Graphics2D in order to respect the rendering hints.
|
||||
Graphics2D gg = dst.createGraphics();
|
||||
|
||||
// If no hints are set there is no need to call
|
||||
@@ -297,13 +444,23 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
gg.dispose();
|
||||
}
|
||||
|
||||
private void copyraster(Raster src, ColorSpace scs, WritableRaster dst,
|
||||
ColorSpace dcs)
|
||||
/**
|
||||
* Copy a source raster to a destination raster, performing a colorspace
|
||||
* conversion between the two. The conversion will respect the
|
||||
* KEY_COLOR_RENDERING rendering hint if one is present.
|
||||
*
|
||||
* @param src The source raster.
|
||||
* @param scs The colorspace of the source raster.
|
||||
* @dst The destination raster.
|
||||
* @dcs The colorspace of the destination raster.
|
||||
*/
|
||||
private void copyraster(Raster src, ColorSpace scs, WritableRaster dst, ColorSpace dcs)
|
||||
{
|
||||
float[] sbuf = new float[src.getNumBands()];
|
||||
|
||||
if (hints.get(RenderingHints.KEY_COLOR_RENDERING) ==
|
||||
RenderingHints.VALUE_COLOR_RENDER_QUALITY)
|
||||
if (hints != null
|
||||
&& hints.get(RenderingHints.KEY_COLOR_RENDERING) ==
|
||||
RenderingHints.VALUE_COLOR_RENDER_QUALITY)
|
||||
{
|
||||
// use cie for accuracy
|
||||
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
|
||||
@@ -321,4 +478,60 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a color model with the same colorspace and alpha
|
||||
* settings as the source image. The created color model will always be a
|
||||
* ComponentColorModel and have a BYTE transfer type.
|
||||
*
|
||||
* @param img The source image.
|
||||
* @param cs The ColorSpace to use.
|
||||
* @return A color model compatible with the source image.
|
||||
*/
|
||||
private ColorModel createCompatibleColorModel(BufferedImage img, ColorSpace cs)
|
||||
{
|
||||
// The choice of ComponentColorModel and DataBuffer.TYPE_BYTE is based on
|
||||
// Mauve testing of the reference implementation.
|
||||
return new ComponentColorModel(cs,
|
||||
img.getColorModel().hasAlpha(),
|
||||
img.isAlphaPremultiplied(),
|
||||
img.getColorModel().getTransparency(),
|
||||
DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a compatible Raster, given a source raster, colorspace,
|
||||
* alpha value, and transfer type.
|
||||
*
|
||||
* @param src The source raster.
|
||||
* @param cs The ColorSpace to use.
|
||||
* @param hasAlpha Whether the raster should include a component for an alpha.
|
||||
* @param transferType The size of a single data element.
|
||||
* @return A compatible WritableRaster.
|
||||
*/
|
||||
private WritableRaster createCompatibleDestRaster(Raster src, ColorSpace cs,
|
||||
boolean hasAlpha,
|
||||
int transferType)
|
||||
{
|
||||
// The use of a PixelInterleavedSampleModel weas determined using mauve
|
||||
// tests, based on the reference implementation
|
||||
|
||||
int numComponents = cs.getNumComponents();
|
||||
if (hasAlpha)
|
||||
numComponents++;
|
||||
|
||||
int[] offsets = new int[numComponents];
|
||||
for (int i = 0; i < offsets.length; i++)
|
||||
offsets[i] = i;
|
||||
|
||||
DataBuffer db = Buffers.createBuffer(transferType,
|
||||
src.getWidth() * src.getHeight() * numComponents,
|
||||
1);
|
||||
return new WritableRaster(new PixelInterleavedSampleModel(transferType,
|
||||
src.getWidth(),
|
||||
src.getHeight(),
|
||||
numComponents,
|
||||
numComponents * src.getWidth(),
|
||||
offsets),
|
||||
db, new Point(src.getMinX(), src.getMinY()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,40 +624,40 @@ public abstract class ColorModel implements Transparency
|
||||
return cspace;
|
||||
}
|
||||
|
||||
// Typically overridden
|
||||
public ColorModel coerceData(WritableRaster raster,
|
||||
boolean isAlphaPremultiplied)
|
||||
boolean isAlphaPremultiplied)
|
||||
{
|
||||
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
|
||||
return this;
|
||||
// This method should always be overridden, but is not abstract.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected void coerceDataWorker(WritableRaster raster,
|
||||
boolean isAlphaPremultiplied)
|
||||
{
|
||||
int w = raster.getWidth();
|
||||
int h = raster.getHeight();
|
||||
int x = raster.getMinX();
|
||||
int y = raster.getMinY();
|
||||
int size = w*h;
|
||||
int size = w * h;
|
||||
int numColors = getNumColorComponents();
|
||||
int numComponents = getNumComponents();
|
||||
int alphaScale = (1<<getComponentSize(numColors)) - 1;
|
||||
int alphaScale = (1 << getComponentSize(numColors)) - 1;
|
||||
double[] pixels = raster.getPixels(x, y, w, h, (double[]) null);
|
||||
|
||||
for (int i=0; i<size; i++)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
double alpha = pixels[i*numComponents+numColors]*alphaScale;
|
||||
for (int c=0; c<numColors; c++)
|
||||
{
|
||||
int offset = i*numComponents+c;
|
||||
if (isAlphaPremultiplied)
|
||||
pixels[offset] = pixels[offset]/alpha;
|
||||
else
|
||||
pixels[offset] = pixels[offset]*alpha;
|
||||
}
|
||||
double alpha = pixels[i * numComponents + numColors] / alphaScale;
|
||||
for (int c = 0; c < numColors; c++)
|
||||
{
|
||||
int offset = i * numComponents + c;
|
||||
if (isAlphaPremultiplied)
|
||||
pixels[offset] = Math.round(pixels[offset] * alpha);
|
||||
else
|
||||
pixels[offset] = Math.round(pixels[offset] / alpha);
|
||||
}
|
||||
}
|
||||
|
||||
raster.setPixels(0, 0, w, h, pixels);
|
||||
|
||||
// FIXME: what can we return?
|
||||
return null;
|
||||
raster.setPixels(0, 0, w, h, pixels);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,9 +42,11 @@ import gnu.java.awt.Buffers;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ComponentColorModel extends ColorModel
|
||||
{
|
||||
// Find sum of all elements of the array.
|
||||
private static int sum(int[] values)
|
||||
{
|
||||
int sum = 0;
|
||||
@@ -52,6 +54,22 @@ public class ComponentColorModel extends ColorModel
|
||||
sum += values[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Create an appropriate array of bits, given a colorspace (ie, number of
|
||||
// bands), size of the storage data type, and presence of an alpha band.
|
||||
private static int[] findBits(ColorSpace colorSpace, int transferType,
|
||||
boolean hasAlpha)
|
||||
{
|
||||
int[] bits;
|
||||
if (hasAlpha)
|
||||
bits = new int[colorSpace.getNumComponents()+1];
|
||||
else
|
||||
bits = new int[colorSpace.getNumComponents()];
|
||||
|
||||
Arrays.fill(bits, DataBuffer.getDataTypeSize(transferType));
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public ComponentColorModel(ColorSpace colorSpace, int[] bits,
|
||||
boolean hasAlpha,
|
||||
@@ -84,8 +102,8 @@ public class ComponentColorModel extends ColorModel
|
||||
boolean isAlphaPremultiplied,
|
||||
int transparency, int transferType)
|
||||
{
|
||||
this(colorSpace, null, hasAlpha, isAlphaPremultiplied,
|
||||
transparency, transferType);
|
||||
this(colorSpace, findBits(colorSpace, transferType, hasAlpha), hasAlpha,
|
||||
isAlphaPremultiplied, transparency, transferType);
|
||||
}
|
||||
|
||||
public int getRed(int pixel)
|
||||
@@ -288,17 +306,16 @@ public class ComponentColorModel extends ColorModel
|
||||
|
||||
public ColorModel coerceData(WritableRaster raster,
|
||||
boolean isAlphaPremultiplied) {
|
||||
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
|
||||
if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
|
||||
return this;
|
||||
|
||||
/* TODO: provide better implementation based on the
|
||||
assumptions we can make due to the specific type of the
|
||||
color model. */
|
||||
super.coerceData(raster, isAlphaPremultiplied);
|
||||
super.coerceDataWorker(raster, isAlphaPremultiplied);
|
||||
|
||||
return new ComponentColorModel(cspace, bits, hasAlpha(),
|
||||
isAlphaPremultiplied, // argument
|
||||
transparency, transferType);
|
||||
return new ComponentColorModel(cspace, hasAlpha, isAlphaPremultiplied,
|
||||
transparency, transferType);
|
||||
}
|
||||
|
||||
public boolean isCompatibleRaster(Raster raster)
|
||||
|
||||
@@ -38,7 +38,6 @@ exception statement from your version. */
|
||||
|
||||
package java.awt.image;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
@@ -51,11 +50,13 @@ import java.awt.geom.Rectangle2D;
|
||||
* with elements in the kernel to compute a new pixel.
|
||||
*
|
||||
* Each band in a Raster is convolved and copied to the destination Raster.
|
||||
* For BufferedImages, convolution is applied to all components. Color
|
||||
* conversion will be applied if needed.
|
||||
*
|
||||
* For BufferedImages, convolution is applied to all components. If the
|
||||
* source is not premultiplied, the data will be premultiplied before
|
||||
* convolving. Premultiplication will be undone if the destination is not
|
||||
* premultiplied. Color conversion will be applied if needed.
|
||||
* Note that this filter ignores whether the source or destination is alpha
|
||||
* premultiplied. The reference spec states that data will be premultiplied
|
||||
* prior to convolving and divided back out afterwards (if needed), but testing
|
||||
* has shown that this is not the case with their implementation.
|
||||
*
|
||||
* @author jlquinn@optonline.net
|
||||
*/
|
||||
@@ -104,59 +105,83 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
hints = null;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
|
||||
* java.awt.image.BufferedImage)
|
||||
/**
|
||||
* Converts the source image using the kernel specified in the
|
||||
* constructor. The resulting image is stored in the destination image if one
|
||||
* is provided; otherwise a new BufferedImage is created and returned.
|
||||
*
|
||||
* The source and destination BufferedImage (if one is supplied) must have
|
||||
* the same dimensions.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dst The destination image.
|
||||
* @throws IllegalArgumentException if the rasters and/or color spaces are
|
||||
* incompatible.
|
||||
* @return The convolved image.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
if (src == dst)
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Source and destination images " +
|
||||
"cannot be the same.");
|
||||
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||
|
||||
// Make sure source image is premultiplied
|
||||
BufferedImage src1 = src;
|
||||
if (!src.isPremultiplied)
|
||||
// The spec says we should do this, but mauve testing shows that Sun's
|
||||
// implementation does not check this.
|
||||
/*
|
||||
if (!src.isAlphaPremultiplied())
|
||||
{
|
||||
src1 = createCompatibleDestImage(src, src.getColorModel());
|
||||
src.copyData(src1.getRaster());
|
||||
src1.coerceData(true);
|
||||
}
|
||||
*/
|
||||
|
||||
BufferedImage dst1 = dst;
|
||||
if (!src.getColorModel().equals(dst.getColorModel()))
|
||||
if (src1.getColorModel().getColorSpace().getType() != dst.getColorModel().getColorSpace().getType())
|
||||
dst1 = createCompatibleDestImage(src, src.getColorModel());
|
||||
|
||||
filter(src1.getRaster(), dst1.getRaster());
|
||||
|
||||
// Since we don't coerceData above, we don't need to divide it back out.
|
||||
// This is wrong (one mauve test specifically tests converting a non-
|
||||
// premultiplied image to a premultiplied image, and it shows that Sun
|
||||
// simply ignores the premultipled flag, contrary to the spec), but we
|
||||
// mimic it for compatibility.
|
||||
/*
|
||||
if (! dst.isAlphaPremultiplied())
|
||||
dst1.coerceData(false);
|
||||
*/
|
||||
|
||||
// Convert between color models if needed
|
||||
if (dst1 != dst)
|
||||
{
|
||||
// Convert between color models.
|
||||
// TODO Check that premultiplied alpha is handled correctly here.
|
||||
Graphics2D gg = dst.createGraphics();
|
||||
gg.setRenderingHints(hints);
|
||||
gg.drawImage(dst1, 0, 0, null);
|
||||
gg.dispose();
|
||||
}
|
||||
|
||||
new ColorConvertOp(hints).filter(dst1, dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see
|
||||
* java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
|
||||
* java.awt.image.ColorModel)
|
||||
/**
|
||||
* Creates an empty BufferedImage with the size equal to the source and the
|
||||
* correct number of bands. The new image is created with the specified
|
||||
* ColorModel, or if no ColorModel is supplied, an appropriate one is chosen.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dstCM A color model for the destination image (may be null).
|
||||
* @return The new compatible destination image.
|
||||
*/
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src,
|
||||
ColorModel dstCM)
|
||||
ColorModel dstCM)
|
||||
{
|
||||
// FIXME: set properties to those in src
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isPremultiplied, null);
|
||||
if (dstCM != null)
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isAlphaPremultiplied(), null);
|
||||
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -168,6 +193,8 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edge condition for this Op.
|
||||
*
|
||||
* @return The edge condition.
|
||||
*/
|
||||
public int getEdgeCondition()
|
||||
@@ -185,9 +212,22 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
return (Kernel) kernel.clone();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
|
||||
* java.awt.image.WritableRaster)
|
||||
/**
|
||||
* Converts the source raster using the kernel specified in the constructor.
|
||||
* The resulting raster is stored in the destination raster if one is
|
||||
* provided; otherwise a new WritableRaster is created and returned.
|
||||
*
|
||||
* If the convolved value for a sample is outside the range of [0-255], it
|
||||
* will be clipped.
|
||||
*
|
||||
* The source and destination raster (if one is supplied) cannot be the same,
|
||||
* and must also have the same dimensions.
|
||||
*
|
||||
* @param src The source raster.
|
||||
* @param dest The destination raster.
|
||||
* @throws IllegalArgumentException if the rasters identical.
|
||||
* @throws ImagingOpException if the convolution is not possible.
|
||||
* @return The transformed raster.
|
||||
*/
|
||||
public final WritableRaster filter(Raster src, WritableRaster dest)
|
||||
{
|
||||
@@ -209,6 +249,11 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
int top = kernel.getYOrigin();
|
||||
int bottom = Math.max(kHeight - top - 1, 0);
|
||||
|
||||
// Calculate max sample values for clipping
|
||||
int[] maxValue = src.getSampleModel().getSampleSize();
|
||||
for (int i = 0; i < maxValue.length; i++)
|
||||
maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1;
|
||||
|
||||
// process the region that is reachable...
|
||||
int regionW = src.width - left - right;
|
||||
int regionH = src.height - top - bottom;
|
||||
@@ -228,7 +273,14 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
v += tmp[tmp.length - i - 1] * kvals[i];
|
||||
// FIXME: in the above line, I've had to reverse the order of
|
||||
// the samples array to make the tests pass. I haven't worked
|
||||
// out why this is necessary.
|
||||
// out why this is necessary.
|
||||
|
||||
// This clipping is is undocumented, but determined by testing.
|
||||
if (v > maxValue[b])
|
||||
v = maxValue[b];
|
||||
else if (v < 0)
|
||||
v = 0;
|
||||
|
||||
dest.setSample(x + kernel.getXOrigin(), y + kernel.getYOrigin(),
|
||||
b, v);
|
||||
}
|
||||
@@ -310,13 +362,14 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
|
||||
return src.getBounds();
|
||||
}
|
||||
|
||||
/** Return corresponding destination point for source point.
|
||||
/**
|
||||
* Returns the corresponding destination point for a source point. Because
|
||||
* this is not a geometric operation, the destination and source points will
|
||||
* be identical.
|
||||
*
|
||||
* ConvolveOp will return the value of src unchanged.
|
||||
* @param src The source point.
|
||||
* @param dst The destination point.
|
||||
* @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D,
|
||||
* java.awt.geom.Point2D)
|
||||
* @param dst The transformed destination point.
|
||||
* @return The transformed destination point.
|
||||
*/
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst)
|
||||
{
|
||||
|
||||
@@ -91,11 +91,12 @@ public class CropImageFilter extends ImageFilter
|
||||
*
|
||||
* @param props the list of properties associated with this image
|
||||
*/
|
||||
public void setProperties(Hashtable props)
|
||||
public void setProperties(Hashtable<?, ?> props)
|
||||
{
|
||||
props.put("filters", "CropImageFilter");
|
||||
if (consumer != null)
|
||||
consumer.setProperties(props);
|
||||
Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props;
|
||||
prop2.put("filters", "CropImageFilter");
|
||||
if (consumer != null)
|
||||
consumer.setProperties(prop2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -393,20 +393,20 @@ public class DirectColorModel extends PackedColorModel
|
||||
return Buffers.getData(buffer);
|
||||
}
|
||||
|
||||
public final ColorModel coerceData (WritableRaster raster,
|
||||
boolean isAlphaPremultiplied)
|
||||
public ColorModel coerceData (WritableRaster raster,
|
||||
boolean isAlphaPremultiplied)
|
||||
{
|
||||
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
|
||||
if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
|
||||
return this;
|
||||
|
||||
/* TODO: provide better implementation based on the
|
||||
assumptions we can make due to the specific type of the
|
||||
color model. */
|
||||
super.coerceData(raster, isAlphaPremultiplied);
|
||||
|
||||
return new ComponentColorModel(cspace, bits, hasAlpha(),
|
||||
isAlphaPremultiplied, // argument
|
||||
transparency, transferType);
|
||||
super.coerceDataWorker(raster, isAlphaPremultiplied);
|
||||
|
||||
return new DirectColorModel(cspace, pixel_bits, getRedMask(),
|
||||
getGreenMask(), getBlueMask(), getAlphaMask(),
|
||||
isAlphaPremultiplied, transferType);
|
||||
}
|
||||
|
||||
public boolean isCompatibleRaster(Raster raster)
|
||||
|
||||
@@ -136,7 +136,7 @@ public interface ImageConsumer
|
||||
*
|
||||
* @param props the list of properties associated with this image
|
||||
*/
|
||||
void setProperties(Hashtable props);
|
||||
void setProperties(Hashtable<?,?> props);
|
||||
|
||||
/**
|
||||
* This <code>ColorModel</code> should indicate the model used by
|
||||
|
||||
@@ -49,180 +49,178 @@ import java.util.Hashtable;
|
||||
*/
|
||||
public class ImageFilter implements ImageConsumer, Cloneable
|
||||
{
|
||||
/**
|
||||
* The consumer this filter is filtering an image data stream for.
|
||||
* It is initialized in the method <code>getFilterInstance</code>.
|
||||
*/
|
||||
protected ImageConsumer consumer = null;
|
||||
/**
|
||||
* The consumer this filter is filtering an image data stream for.
|
||||
* It is initialized in the method <code>getFilterInstance</code>.
|
||||
*/
|
||||
protected ImageConsumer consumer = null;
|
||||
|
||||
/**
|
||||
* The <code>ImageConsumer</code> can use this method to request
|
||||
* the pixels be delivered in top-down, left-right order.
|
||||
* <br>
|
||||
* The filter can respond in three different ways.
|
||||
* <ul>
|
||||
* <li>The default behavior is to forward the request to the
|
||||
* <code>ImageProducer</code>
|
||||
* using the method <code>requestTopDownLeftRightResend</code>
|
||||
* and using the filter as the consumer.</li>
|
||||
* <li>The filter has the pixels and can retransmit them in the
|
||||
* top-down, left-right order.</li>
|
||||
* <li>The filter can do nothing when this method is called.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void resendTopDownLeftRight(ImageProducer ip)
|
||||
{
|
||||
ip.requestTopDownLeftRightResend(this);
|
||||
}
|
||||
/**
|
||||
* The <code>ImageConsumer</code> can use this method to request
|
||||
* the pixels be delivered in top-down, left-right order.
|
||||
* <br>
|
||||
* The filter can respond in three different ways.
|
||||
* <ul>
|
||||
* <li>The default behavior is to forward the request to the
|
||||
* <code>ImageProducer</code>
|
||||
* using the method <code>requestTopDownLeftRightResend</code>
|
||||
* and using the filter as the consumer.</li>
|
||||
* <li>The filter has the pixels and can retransmit them in the
|
||||
* top-down, left-right order.</li>
|
||||
* <li>The filter can do nothing when this method is called.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void resendTopDownLeftRight(ImageProducer ip)
|
||||
{
|
||||
ip.requestTopDownLeftRightResend(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, returns a shallow copy of the object created by
|
||||
* <code>Object.clone()</code>
|
||||
*
|
||||
* @see java.lang.Object#clone ()
|
||||
*/
|
||||
public Object clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
return super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e)
|
||||
{
|
||||
// This should never happen as this class implements the
|
||||
// Cloneable interface.
|
||||
throw new InternalError ();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* By default, returns a shallow copy of the object created by
|
||||
* <code>Object.clone()</code>
|
||||
*
|
||||
* @see java.lang.Object#clone ()
|
||||
*/
|
||||
public Object clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
return super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e)
|
||||
{
|
||||
// This should never happen as this class implements the
|
||||
// Cloneable interface.
|
||||
throw new InternalError ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the only method which can set the
|
||||
* <code>ImageConsumer</code> for this filter. By default a clone
|
||||
* of this filter with the appropriate consumer set is returned.
|
||||
*
|
||||
* @see #clone ()
|
||||
*/
|
||||
public ImageFilter getFilterInstance(ImageConsumer ic)
|
||||
{
|
||||
if ( ic == null )
|
||||
throw new IllegalArgumentException("null argument for ImageFilter.getFilterInstance(ImageConsumer)");
|
||||
/**
|
||||
* This is the only method which can set the
|
||||
* <code>ImageConsumer</code> for this filter. By default a clone
|
||||
* of this filter with the appropriate consumer set is returned.
|
||||
*
|
||||
* @see #clone ()
|
||||
*/
|
||||
public ImageFilter getFilterInstance(ImageConsumer ic)
|
||||
{
|
||||
ImageFilter f = (ImageFilter)clone();
|
||||
f.consumer = ic;
|
||||
return f;
|
||||
}
|
||||
|
||||
consumer = ic;
|
||||
ImageFilter f = (ImageFilter)clone();
|
||||
consumer = null;
|
||||
return f;
|
||||
}
|
||||
/**
|
||||
* An <code>ImageProducer</code> indicates the size of the image
|
||||
* being produced using this method. A filter can override this
|
||||
* method to intercept these calls from the producer in order to
|
||||
* change either the width or the height before in turn calling
|
||||
* the consumer's <code>setDimensions</code> method.
|
||||
*
|
||||
* @param width the width of the image
|
||||
* @param height the height of the image
|
||||
*/
|
||||
public void setDimensions(int width, int height)
|
||||
{
|
||||
consumer.setDimensions(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* An <code>ImageProducer</code> indicates the size of the image
|
||||
* being produced using this method. A filter can override this
|
||||
* method to intercept these calls from the producer in order to
|
||||
* change either the width or the height before in turn calling
|
||||
* the consumer's <code>setDimensions</code> method.
|
||||
*
|
||||
* @param width the width of the image
|
||||
* @param height the height of the image
|
||||
*/
|
||||
public void setDimensions(int width, int height)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setDimensions(width, height);
|
||||
}
|
||||
/**
|
||||
* An <code>ImageProducer</code> can set a list of properties
|
||||
* associated with this image by using this method.
|
||||
*
|
||||
* @param props the list of properties associated with this image
|
||||
*/
|
||||
public void setProperties(Hashtable<?,?> props)
|
||||
{
|
||||
Hashtable copy = (Hashtable) props.clone();
|
||||
Object o = copy.get("filters");
|
||||
if (o == null)
|
||||
copy.put("filters", toString());
|
||||
else if (o instanceof String)
|
||||
copy.put("filters", ((String) o) + toString());
|
||||
|
||||
/**
|
||||
* An <code>ImageProducer</code> can set a list of properties
|
||||
* associated with this image by using this method.
|
||||
*
|
||||
* @param props the list of properties associated with this image
|
||||
*/
|
||||
public void setProperties(Hashtable props)
|
||||
{
|
||||
props.put("filters", "ImageFilter");
|
||||
if (consumer != null)
|
||||
consumer.setProperties(props);
|
||||
}
|
||||
consumer.setProperties(copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to process calls to this method from the
|
||||
* <code>ImageProducer</code>. By default the <code>setColorModel</code>
|
||||
* method of the consumer is called with the specified <code>model</code>.
|
||||
*
|
||||
* @param model the color model to be used most often by setPixels
|
||||
* @see ColorModel */
|
||||
public void setColorModel(ColorModel model)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setColorModel(model);
|
||||
}
|
||||
/**
|
||||
* Override this method to process calls to this method from the
|
||||
* <code>ImageProducer</code>. By default the <code>setColorModel</code>
|
||||
* method of the consumer is called with the specified <code>model</code>.
|
||||
*
|
||||
* @param model the color model to be used most often by setPixels
|
||||
*
|
||||
* @see ColorModel
|
||||
*/
|
||||
public void setColorModel(ColorModel model)
|
||||
{
|
||||
consumer.setColorModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>ImageProducer</code> should call this method with a
|
||||
* bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
|
||||
* <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
|
||||
* <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
|
||||
* <code>ImageConsumer</code> interface.
|
||||
*
|
||||
* @param flags a bit mask of hints
|
||||
* @see ImageConsumer
|
||||
*/
|
||||
public void setHints(int flags)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setHints(flags);
|
||||
}
|
||||
/**
|
||||
* The <code>ImageProducer</code> should call this method with a
|
||||
* bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
|
||||
* <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
|
||||
* <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
|
||||
* <code>ImageConsumer</code> interface.
|
||||
*
|
||||
* @param flags a bit mask of hints
|
||||
* @see ImageConsumer
|
||||
*/
|
||||
public void setHints(int flags)
|
||||
{
|
||||
consumer.setHints(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as a <code>byte</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, byte[] pixels, int offset, int scansize)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
|
||||
}
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as a <code>byte</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, byte[] pixels, int offset,
|
||||
int scansize)
|
||||
{
|
||||
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as an <code>int</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, int[] pixels, int offset, int scansize)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
|
||||
}
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as an <code>int</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, int[] pixels, int offset,
|
||||
int scansize)
|
||||
{
|
||||
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>ImageProducer</code> calls this method to indicate a
|
||||
* single frame or the entire image is complete. The method is
|
||||
* also used to indicate an error in loading or producing the
|
||||
* image.
|
||||
*/
|
||||
public void imageComplete(int status)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.imageComplete(status);
|
||||
}
|
||||
/**
|
||||
* The <code>ImageProducer</code> calls this method to indicate a
|
||||
* single frame or the entire image is complete. The method is
|
||||
* also used to indicate an error in loading or producing the
|
||||
* image.
|
||||
*/
|
||||
public void imageComplete(int status)
|
||||
{
|
||||
consumer.imageComplete(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,10 +134,6 @@ public class IndexColorModel extends ColorModel
|
||||
if (size < 1)
|
||||
throw new IllegalArgumentException("size < 1");
|
||||
map_size = size;
|
||||
if (0 <= trans && trans < size) {
|
||||
this.trans = trans;
|
||||
transparency = BITMASK;
|
||||
}
|
||||
rgb = new int[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
@@ -146,6 +142,9 @@ public class IndexColorModel extends ColorModel
|
||||
| ((greens[i] & 0xff) << 8)
|
||||
| (blues[i] & 0xff));
|
||||
}
|
||||
|
||||
setTransparentPixel(trans);
|
||||
|
||||
// Generate a bigint with 1's for every pixel
|
||||
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
|
||||
}
|
||||
@@ -275,8 +274,6 @@ public class IndexColorModel extends ColorModel
|
||||
throw new IllegalArgumentException("size < 1");
|
||||
map_size = size;
|
||||
opaque = !hasAlpha;
|
||||
if (0 <= trans && trans < size)
|
||||
this.trans = trans;
|
||||
|
||||
rgb = new int[size];
|
||||
if (hasAlpha)
|
||||
@@ -318,6 +315,8 @@ public class IndexColorModel extends ColorModel
|
||||
transparency = BITMASK;
|
||||
}
|
||||
|
||||
setTransparentPixel(trans);
|
||||
|
||||
// Generate a bigint with 1's for every pixel
|
||||
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
|
||||
}
|
||||
@@ -361,9 +360,6 @@ public class IndexColorModel extends ColorModel
|
||||
throw new IllegalArgumentException("size < 1");
|
||||
map_size = size;
|
||||
opaque = !hasAlpha;
|
||||
if (0 <= trans && trans < size)
|
||||
this.trans = trans;
|
||||
|
||||
rgb = new int[size];
|
||||
if (!hasAlpha)
|
||||
for (int i = 0; i < size; i++)
|
||||
@@ -371,6 +367,8 @@ public class IndexColorModel extends ColorModel
|
||||
else
|
||||
System.arraycopy(cmap, start, rgb, 0, size);
|
||||
|
||||
setTransparentPixel(trans);
|
||||
|
||||
// Generate a bigint with 1's for every pixel
|
||||
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
|
||||
}
|
||||
@@ -584,12 +582,7 @@ public class IndexColorModel extends ColorModel
|
||||
*/
|
||||
public final int getAlpha(int pixel)
|
||||
{
|
||||
if (opaque && pixel != trans)
|
||||
return 255;
|
||||
if ((pixel == trans && trans != -1) || pixel >= map_size)
|
||||
return 0;
|
||||
|
||||
return (0xFF000000 & rgb[pixel]) >> 24;
|
||||
return (rgb[pixel] >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,4 +687,43 @@ public class IndexColorModel extends ColorModel
|
||||
|
||||
return im;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SampleModel} that is compatible to this color model.
|
||||
* This will be a {@link MultiPixelPackedSampleModel} for bits/pixel of
|
||||
* 1, 2 or 4, or a {@link ComponentColorModel} for the other cases.
|
||||
*
|
||||
* @param w the width of the sample model to create
|
||||
* @param h the height of the sample model to create
|
||||
*
|
||||
* @return a compatible sample model
|
||||
*/
|
||||
public SampleModel createCompatibleSampleModel(int w, int h)
|
||||
{
|
||||
SampleModel sm;
|
||||
if (pixel_bits == 1 || pixel_bits == 2 || pixel_bits == 4)
|
||||
sm = new MultiPixelPackedSampleModel(transferType, w, h, pixel_bits);
|
||||
else
|
||||
sm = new ComponentSampleModel(transferType, w, h, 1, w, new int[]{0});
|
||||
return sm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transparent pixel. This is called by the various constructors.
|
||||
*
|
||||
* @param t the transparent pixel
|
||||
*/
|
||||
private void setTransparentPixel(int t)
|
||||
{
|
||||
if (t >= 0 && t < map_size)
|
||||
{
|
||||
rgb[t] &= 0xffffff; // Make the value transparent.
|
||||
trans = t;
|
||||
if (transparency == OPAQUE)
|
||||
{
|
||||
transparency = BITMASK;
|
||||
hasAlpha = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ exception statement from your version. */
|
||||
|
||||
package java.awt.image;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
@@ -67,7 +66,8 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
private LookupTable lut;
|
||||
private RenderingHints hints;
|
||||
|
||||
/** Construct a new LookupOp.
|
||||
/**
|
||||
* Construct a new LookupOp using the given LookupTable.
|
||||
*
|
||||
* @param lookup LookupTable to use.
|
||||
* @param hints Rendering hints (can be null).
|
||||
@@ -78,16 +78,40 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage)
|
||||
/**
|
||||
* Converts the source image using the lookup table specified in the
|
||||
* constructor. The resulting image is stored in the destination image if one
|
||||
* is provided; otherwise a new BufferedImage is created and returned.
|
||||
*
|
||||
* The source image cannot use an IndexColorModel, and the destination image
|
||||
* (if one is provided) must have the same size.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dst The destination image.
|
||||
* @throws IllegalArgumentException if the rasters and/or color spaces are
|
||||
* incompatible.
|
||||
* @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
|
||||
* contained in the LookupTable.
|
||||
* @return The convolved image.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
if (src.getColorModel() instanceof IndexColorModel)
|
||||
throw new IllegalArgumentException("LookupOp.filter: IndexColorModel "
|
||||
+ "not allowed");
|
||||
|
||||
if (lut.getNumComponents() != 1
|
||||
&& lut.getNumComponents() != src.getColorModel().getNumComponents()
|
||||
&& lut.getNumComponents() != src.getColorModel().getNumColorComponents())
|
||||
throw new IllegalArgumentException("LookupOp.filter: Incompatible " +
|
||||
"lookup table and source image");
|
||||
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||
dst = createCompatibleDestImage(src, null);
|
||||
|
||||
else if (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth())
|
||||
throw new IllegalArgumentException("Source and destination images are " +
|
||||
"different sizes.");
|
||||
|
||||
// Set up for potential colormodel mismatch
|
||||
BufferedImage tgt;
|
||||
@@ -116,33 +140,35 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
sr.getPixel(x, y, dbuf);
|
||||
System.arraycopy(dbuf, 0, tmp, 0, tmpBands);
|
||||
dr.setPixel(x, y, lut.lookupPixel(tmp, dbuf));
|
||||
|
||||
/* The reference implementation does not use LookupTable.lookupPixel,
|
||||
* but rather it seems to copy the table into a native array. The
|
||||
* effect of this (a probable bug in their implementation) is that
|
||||
* an out-of-bounds lookup on a ByteLookupTable will *not* throw an
|
||||
* out of bounds exception, but will instead return random garbage.
|
||||
* A bad lookup on a ShortLookupTable, however, will throw an
|
||||
* exception.
|
||||
*
|
||||
* Instead of mimicing this behaviour, we always throw an
|
||||
* ArrayOutofBoundsException by virtue of using
|
||||
* LookupTable.lookupPixle.
|
||||
*/
|
||||
}
|
||||
}
|
||||
else if (lut.getNumComponents() != 1
|
||||
&&
|
||||
lut.getNumComponents() != src.getColorModel().getNumComponents())
|
||||
throw new IllegalArgumentException("LookupOp.filter: "
|
||||
+ "Incompatible lookup "
|
||||
+ "table and source image");
|
||||
|
||||
// No alpha to ignore
|
||||
int[] dbuf = new int[src.getColorModel().getNumComponents()];
|
||||
|
||||
// Filter the pixels
|
||||
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
|
||||
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
|
||||
dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf));
|
||||
|
||||
if (tgt != dst)
|
||||
else
|
||||
{
|
||||
// Convert between color models.
|
||||
// TODO Check that premultiplied alpha is handled correctly here.
|
||||
Graphics2D gg = dst.createGraphics();
|
||||
gg.setRenderingHints(hints);
|
||||
gg.drawImage(tgt, 0, 0, null);
|
||||
gg.dispose();
|
||||
// No alpha to ignore
|
||||
int[] dbuf = new int[src.getColorModel().getNumComponents()];
|
||||
|
||||
// Filter the pixels
|
||||
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
|
||||
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
|
||||
dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf));
|
||||
}
|
||||
|
||||
if (tgt != dst)
|
||||
new ColorConvertOp(hints).filter(tgt, dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -160,18 +186,27 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src,
|
||||
ColorModel dstCM)
|
||||
{
|
||||
// FIXME: set properties to those in src
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isPremultiplied, null);
|
||||
if (dstCM != null)
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isAlphaPremultiplied(), null);
|
||||
|
||||
// This is a strange exception, done for compatibility with the reference
|
||||
// (as demonstrated by a mauve testcase)
|
||||
int imgType = src.getType();
|
||||
if (imgType == BufferedImage.TYPE_USHORT_GRAY)
|
||||
imgType = BufferedImage.TYPE_BYTE_GRAY;
|
||||
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(), imgType);
|
||||
}
|
||||
|
||||
/** Return corresponding destination point for source point.
|
||||
/**
|
||||
* Returns the corresponding destination point for a given source point.
|
||||
*
|
||||
* This Op will return the source point unchanged.
|
||||
*
|
||||
* LookupOp will return the value of src unchanged.
|
||||
* @param src The source point.
|
||||
* @param dst The destination point.
|
||||
* @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
|
||||
*/
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst)
|
||||
{
|
||||
@@ -182,7 +217,11 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
return dst;
|
||||
}
|
||||
|
||||
/** Return the LookupTable for this op. */
|
||||
/**
|
||||
* Return the LookupTable for this op.
|
||||
*
|
||||
* @return The lookup table.
|
||||
*/
|
||||
public final LookupTable getTable()
|
||||
{
|
||||
return lut;
|
||||
@@ -196,7 +235,8 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
return hints;
|
||||
}
|
||||
|
||||
/** Filter a raster through a lookup table.
|
||||
/**
|
||||
* Filter a raster through a lookup table.
|
||||
*
|
||||
* Applies the lookup table for this Rasterop to each pixel of src and
|
||||
* puts the results in dest. If dest is null, a new Raster is created and
|
||||
@@ -206,8 +246,9 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
* @param dest The destination raster.
|
||||
* @return The WritableRaster with the filtered pixels.
|
||||
* @throws IllegalArgumentException if lookup table has more than one
|
||||
* component but not the same as src and dest.
|
||||
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
|
||||
* component but not the same as src and dest.
|
||||
* @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
|
||||
* contained in the LookupTable.
|
||||
*/
|
||||
public final WritableRaster filter(Raster src, WritableRaster dest)
|
||||
{
|
||||
@@ -216,12 +257,13 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
dest = createCompatibleDestRaster(src);
|
||||
else
|
||||
if (src.getNumBands() != dest.getNumBands())
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (lut.getNumComponents() != 1
|
||||
&& lut.getNumComponents() != src.getNumBands())
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Source and destination rasters " +
|
||||
"are incompatible.");
|
||||
|
||||
if (lut.getNumComponents() != 1
|
||||
&& lut.getNumComponents() != src.getNumBands())
|
||||
throw new IllegalArgumentException("Lookup table is incompatible with " +
|
||||
"this raster.");
|
||||
|
||||
// Allocate pixel storage.
|
||||
int[] tmp = new int[src.getNumBands()];
|
||||
@@ -230,6 +272,19 @@ public class LookupOp implements BufferedImageOp, RasterOp
|
||||
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
|
||||
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
|
||||
dest.setPixel(x, y, lut.lookupPixel(src.getPixel(x, y, tmp), tmp));
|
||||
|
||||
/* The reference implementation does not use LookupTable.lookupPixel,
|
||||
* but rather it seems to copy the table into a native array. The
|
||||
* effect of this (a probable bug in their implementation) is that
|
||||
* an out-of-bounds lookup on a ByteLookupTable will *not* throw an
|
||||
* out of bounds exception, but will instead return random garbage.
|
||||
* A bad lookup on a ShortLookupTable, however, will throw an
|
||||
* exception.
|
||||
*
|
||||
* Instead of mimicing this behaviour, we always throw an
|
||||
* ArrayOutofBoundsException by virtue of using
|
||||
* LookupTable.lookupPixle.
|
||||
*/
|
||||
return dest;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* MemoryImageSource.java -- Java class for providing image data
|
||||
Copyright (C) 1999, 2004 Free Software Foundation, Inc.
|
||||
Copyright (C) 1999, 2004, 2006, Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Classpath.
|
||||
|
||||
@@ -41,6 +41,9 @@ package java.awt.image;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* An image producer that delivers image data from an array.
|
||||
*/
|
||||
public class MemoryImageSource implements ImageProducer
|
||||
{
|
||||
private boolean animated = false;
|
||||
@@ -73,10 +76,19 @@ public class MemoryImageSource implements ImageProducer
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ImageProducer from memory
|
||||
* Constructs an ImageProducer from memory.
|
||||
*
|
||||
* @param w the image width.
|
||||
* @param h the image height.
|
||||
* @param cm the color model.
|
||||
* @param pix the image data.
|
||||
* @param off the offset to the first pixel in the array.
|
||||
* @param scan the number of array elements from a pixel on one row to the
|
||||
* corresponding pixel on the next row.
|
||||
* @param props image properties (<code>null</code> permitted).
|
||||
*/
|
||||
public MemoryImageSource(int w, int h, ColorModel cm, byte[] pix, int off,
|
||||
int scan, Hashtable props)
|
||||
int scan, Hashtable<?,?> props)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
@@ -106,10 +118,19 @@ public class MemoryImageSource implements ImageProducer
|
||||
}
|
||||
|
||||
/**
|
||||
Constructs an ImageProducer from memory
|
||||
*/
|
||||
* Constructs an ImageProducer from memory
|
||||
*
|
||||
* @param w the image width.
|
||||
* @param h the image height.
|
||||
* @param cm the color model.
|
||||
* @param pix the image data.
|
||||
* @param off the offset to the first pixel in the array.
|
||||
* @param scan the number of array elements from a pixel on one row to the
|
||||
* corresponding pixel on the next row.
|
||||
* @param props image properties (<code>null</code> permitted).
|
||||
*/
|
||||
public MemoryImageSource(int w, int h, ColorModel cm, int[] pix, int off,
|
||||
int scan, Hashtable props)
|
||||
int scan, Hashtable<?,?> props)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
@@ -122,16 +143,32 @@ public class MemoryImageSource implements ImageProducer
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ImageProducer from memory using the default RGB ColorModel
|
||||
* Constructs an ImageProducer from memory using the default RGB ColorModel.
|
||||
*
|
||||
* @param w the image width.
|
||||
* @param h the image height.
|
||||
* @param pix the image data.
|
||||
* @param off the offset to the first pixel in the array.
|
||||
* @param scan the number of array elements from a pixel on one row to the
|
||||
* corresponding pixel on the next row.
|
||||
* @param props image properties (<code>null</code> permitted).
|
||||
|
||||
*/
|
||||
public MemoryImageSource(int w, int h, int[] pix, int off, int scan,
|
||||
Hashtable props)
|
||||
Hashtable<?,?> props)
|
||||
{
|
||||
this(w, h, ColorModel.getRGBdefault(), pix, off, scan, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ImageProducer from memory using the default RGB ColorModel
|
||||
* Constructs an ImageProducer from memory using the default RGB ColorModel.
|
||||
*
|
||||
* @param w the image width.
|
||||
* @param h the image height.
|
||||
* @param pix the image data.
|
||||
* @param off the offset to the first pixel in the array.
|
||||
* @param scan the number of array elements from a pixel on one row to the
|
||||
* corresponding pixel on the next row.
|
||||
*/
|
||||
public MemoryImageSource(int w, int h, int[] pix, int off, int scan)
|
||||
{
|
||||
@@ -141,6 +178,8 @@ public class MemoryImageSource implements ImageProducer
|
||||
/**
|
||||
* Used to register an <code>ImageConsumer</code> with this
|
||||
* <code>ImageProducer</code>.
|
||||
*
|
||||
* @param ic the image consumer.
|
||||
*/
|
||||
public synchronized void addConsumer(ImageConsumer ic)
|
||||
{
|
||||
@@ -153,6 +192,8 @@ public class MemoryImageSource implements ImageProducer
|
||||
/**
|
||||
* Used to determine if the given <code>ImageConsumer</code> is
|
||||
* already registered with this <code>ImageProducer</code>.
|
||||
*
|
||||
* @param ic the image consumer.
|
||||
*/
|
||||
public synchronized boolean isConsumer(ImageConsumer ic)
|
||||
{
|
||||
@@ -164,6 +205,8 @@ public class MemoryImageSource implements ImageProducer
|
||||
/**
|
||||
* Used to remove an <code>ImageConsumer</code> from the list of
|
||||
* registered consumers for this <code>ImageProducer</code>.
|
||||
*
|
||||
* @param ic the image consumer.
|
||||
*/
|
||||
public synchronized void removeConsumer(ImageConsumer ic)
|
||||
{
|
||||
@@ -197,6 +240,8 @@ public class MemoryImageSource implements ImageProducer
|
||||
* Used to register an <code>ImageConsumer</code> with this
|
||||
* <code>ImageProducer</code> and then request that this producer
|
||||
* resend the image data in the order top-down, left-right.
|
||||
*
|
||||
* @param ic the image consumer.
|
||||
*/
|
||||
public void requestTopDownLeftRightResend(ImageConsumer ic)
|
||||
{
|
||||
@@ -219,7 +264,7 @@ public class MemoryImageSource implements ImageProducer
|
||||
* sending animation. If this flag is set then full buffers are sent
|
||||
* in the newPixels methods instead of just regions.
|
||||
*
|
||||
* @param fullbuffers - a flag indicating whether to send the full buffers
|
||||
* @param fullbuffers a flag indicating whether to send the full buffers
|
||||
*/
|
||||
public synchronized void setFullBufferUpdates(boolean fullbuffers)
|
||||
{
|
||||
@@ -260,6 +305,11 @@ public class MemoryImageSource implements ImageProducer
|
||||
/**
|
||||
* Send an animation frame to the image consumers containing the specified
|
||||
* pixels unless setFullBufferUpdates is set.
|
||||
*
|
||||
* @param x the x-coordinate.
|
||||
* @param y the y-coordinate.
|
||||
* @param w the width.
|
||||
* @param h the height.
|
||||
*/
|
||||
public synchronized void newPixels(int x, int y, int w, int h)
|
||||
{
|
||||
@@ -306,6 +356,12 @@ public class MemoryImageSource implements ImageProducer
|
||||
*
|
||||
* If framenotify is set then a notification is sent when the frame
|
||||
* is sent otherwise no status is sent.
|
||||
*
|
||||
* @param x the x-coordinate.
|
||||
* @param y the y-coordinate.
|
||||
* @param w the width.
|
||||
* @param h the height.
|
||||
* @param framenotify send notification?
|
||||
*/
|
||||
public synchronized void newPixels(int x, int y, int w, int h,
|
||||
boolean framenotify)
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PixelGrabber implements ImageConsumer
|
||||
|
||||
ColorModel model = ColorModel.getRGBdefault();
|
||||
int hints;
|
||||
Hashtable props;
|
||||
Hashtable<?,?> props;
|
||||
|
||||
int int_pixel_buffer[];
|
||||
boolean ints_delivered = false;
|
||||
@@ -442,7 +442,7 @@ public class PixelGrabber implements ImageConsumer
|
||||
* @param props a list of properties associated with the image being
|
||||
* produced
|
||||
*/
|
||||
public synchronized void setProperties(Hashtable props)
|
||||
public synchronized void setProperties(Hashtable<?,?> props)
|
||||
{
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
@@ -46,228 +46,220 @@ package java.awt.image;
|
||||
*/
|
||||
public abstract class RGBImageFilter extends ImageFilter
|
||||
{
|
||||
protected ColorModel origmodel;
|
||||
protected ColorModel origmodel;
|
||||
|
||||
protected ColorModel newmodel;
|
||||
protected ColorModel newmodel;
|
||||
|
||||
/**
|
||||
Specifies whether to apply the filter to the index entries of the
|
||||
IndexColorModel. Subclasses should set this to true if the filter
|
||||
does not depend on the pixel's coordinate.
|
||||
*/
|
||||
protected boolean canFilterIndexColorModel = false;
|
||||
/**
|
||||
* Specifies whether to apply the filter to the index entries of the
|
||||
* IndexColorModel. Subclasses should set this to true if the filter
|
||||
* does not depend on the pixel's coordinate.
|
||||
*/
|
||||
protected boolean canFilterIndexColorModel = false;
|
||||
|
||||
/**
|
||||
Construct new RGBImageFilter.
|
||||
*/
|
||||
public RGBImageFilter()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Construct new RGBImageFilter.
|
||||
*/
|
||||
public RGBImageFilter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ColorModel used to filter with. If the specified ColorModel is IndexColorModel
|
||||
* and canFilterIndexColorModel is true, we subsitute the ColorModel for a filtered one
|
||||
* here and in setPixels whenever the original one appears. Otherwise overrides the default
|
||||
* ColorModel of ImageProducer and specifies the default RGBColorModel
|
||||
*
|
||||
* @param model the color model to be used most often by setPixels
|
||||
* @see ColorModel */
|
||||
public void setColorModel(ColorModel model)
|
||||
{
|
||||
origmodel = model;
|
||||
newmodel = model;
|
||||
/**
|
||||
* Sets the ColorModel used to filter with. If the specified ColorModel is
|
||||
* IndexColorModel and canFilterIndexColorModel is true, we subsitute the
|
||||
* ColorModel for a filtered one here and in setPixels whenever the original
|
||||
* one appears. Otherwise overrides the default ColorModel of ImageProducer
|
||||
* and specifies the default RGBColorModel
|
||||
*
|
||||
* @param model the color model to be used most often by setPixels
|
||||
*
|
||||
* @see ColorModel
|
||||
*/
|
||||
public void setColorModel(ColorModel model)
|
||||
{
|
||||
if ((model instanceof IndexColorModel) && canFilterIndexColorModel)
|
||||
{
|
||||
ColorModel newCM = filterIndexColorModel((IndexColorModel) model);
|
||||
substituteColorModel(model, newCM);
|
||||
consumer.setColorModel(newmodel);
|
||||
}
|
||||
else
|
||||
{
|
||||
consumer.setColorModel(ColorModel.getRGBdefault());
|
||||
}
|
||||
}
|
||||
|
||||
if( ( model instanceof IndexColorModel) && canFilterIndexColorModel ) {
|
||||
newmodel = filterIndexColorModel( (IndexColorModel) model );
|
||||
if (consumer != null)
|
||||
consumer.setColorModel(newmodel);
|
||||
}
|
||||
else {
|
||||
if (consumer != null)
|
||||
consumer.setColorModel(ColorModel.getRGBdefault());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a new ColorModel to subsitute for the old ColorModel when
|
||||
setPixels encounters the a pixel with the old ColorModel. The pixel
|
||||
remains unchanged except for a new ColorModel.
|
||||
|
||||
@param oldcm the old ColorModel
|
||||
@param newcm the new ColorModel
|
||||
*/
|
||||
public void substituteColorModel(ColorModel oldcm,
|
||||
ColorModel newcm)
|
||||
{
|
||||
origmodel = oldcm;
|
||||
newmodel = newcm;
|
||||
}
|
||||
/**
|
||||
* Registers a new ColorModel to subsitute for the old ColorModel when
|
||||
* setPixels encounters the a pixel with the old ColorModel. The pixel
|
||||
* remains unchanged except for a new ColorModel.
|
||||
*
|
||||
* @param oldcm the old ColorModel
|
||||
* @param newcm the new ColorModel
|
||||
*/
|
||||
public void substituteColorModel(ColorModel oldcm, ColorModel newcm)
|
||||
{
|
||||
origmodel = oldcm;
|
||||
newmodel = newcm;
|
||||
}
|
||||
|
||||
/**
|
||||
Filters an IndexColorModel through the filterRGB function. Uses
|
||||
coordinates of -1 to indicate its filtering an index and not a pixel.
|
||||
/**
|
||||
* Filters an IndexColorModel through the filterRGB function. Uses
|
||||
* coordinates of -1 to indicate its filtering an index and not a pixel.
|
||||
*
|
||||
* @param icm an IndexColorModel to filter
|
||||
*/
|
||||
public IndexColorModel filterIndexColorModel(IndexColorModel icm)
|
||||
{
|
||||
int len = icm.getMapSize();
|
||||
byte[] reds = new byte[len];
|
||||
byte[] greens = new byte[len];
|
||||
byte[] blues = new byte[len];
|
||||
byte[] alphas = new byte[len];
|
||||
|
||||
@param icm an IndexColorModel to filter
|
||||
*/
|
||||
public IndexColorModel filterIndexColorModel(IndexColorModel icm)
|
||||
{
|
||||
int len = icm.getMapSize(), rgb;
|
||||
byte reds[] = new byte[len], greens[] = new byte[len], blues[] = new byte[len], alphas[] = new byte[len];
|
||||
|
||||
icm.getAlphas( alphas );
|
||||
icm.getReds( reds );
|
||||
icm.getGreens( greens );
|
||||
icm.getBlues( blues );
|
||||
icm.getAlphas( alphas );
|
||||
icm.getReds( reds );
|
||||
icm.getGreens( greens );
|
||||
icm.getBlues( blues );
|
||||
|
||||
for( int i = 0; i < len; i++ )
|
||||
{
|
||||
rgb = filterRGB( -1, -1, makeColor ( alphas[i], reds[i], greens[i], blues[i] ) );
|
||||
alphas[i] = (byte)(( 0xff000000 & rgb ) >> 24);
|
||||
reds[i] = (byte)(( 0xff0000 & rgb ) >> 16);
|
||||
greens[i] = (byte)(( 0xff00 & rgb ) >> 8);
|
||||
blues[i] = (byte)(0xff & rgb);
|
||||
}
|
||||
return new IndexColorModel( icm.getPixelSize(), len, reds, greens, blues, alphas );
|
||||
}
|
||||
int transparent = icm.getTransparentPixel();
|
||||
boolean needAlpha = false;
|
||||
for( int i = 0; i < len; i++ )
|
||||
{
|
||||
int rgb = filterRGB(-1, -1, icm.getRGB(i));
|
||||
alphas[i] = (byte) (rgb >> 24);
|
||||
if (alphas[i] != ((byte) 0xff) && i != transparent)
|
||||
needAlpha = true;
|
||||
reds[i] = (byte) (rgb >> 16);
|
||||
greens[i] = (byte) (rgb >> 8);
|
||||
blues[i] = (byte) (rgb);
|
||||
}
|
||||
IndexColorModel newIcm;
|
||||
if (needAlpha)
|
||||
newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
|
||||
blues, alphas);
|
||||
else
|
||||
newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
|
||||
blues, transparent);
|
||||
return newIcm;
|
||||
}
|
||||
|
||||
private int makeColor( byte a, byte r, byte g, byte b )
|
||||
{
|
||||
return ( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
|
||||
}
|
||||
/**
|
||||
* This functions filters a set of RGB pixels through filterRGB.
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the
|
||||
* <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the
|
||||
* <code>pixels</code> array
|
||||
*/
|
||||
public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
|
||||
int offset, int scansize)
|
||||
{
|
||||
int index = offset;
|
||||
for (int yp = 0; yp < h; yp++)
|
||||
{
|
||||
for (int xp = 0; xp < w; xp++)
|
||||
{
|
||||
pixels[index] = filterRGB(xp + x, yp + y, pixels[index]);
|
||||
index++;
|
||||
}
|
||||
index += scansize - w;
|
||||
}
|
||||
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset,
|
||||
scansize);
|
||||
}
|
||||
|
||||
/**
|
||||
This functions filters a set of RGB pixels through filterRGB.
|
||||
/**
|
||||
* If the ColorModel is the same ColorModel which as already converted
|
||||
* then it converts it the converted ColorModel. Otherwise it passes the
|
||||
* array of pixels through filterRGBpixels.
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code>
|
||||
* array
|
||||
* @param scansize the width to use in extracting pixels from the
|
||||
* <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h, ColorModel model,
|
||||
byte[] pixels, int offset, int scansize)
|
||||
{
|
||||
if (model == origmodel)
|
||||
{
|
||||
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] filtered = new int[w];
|
||||
int index = offset;
|
||||
for (int yp = 0; yp < h; yp++)
|
||||
{
|
||||
for (int xp = 0; xp < w; xp++)
|
||||
{
|
||||
filtered[xp] = model.getRGB((pixels[index] & 0xff));
|
||||
index++;
|
||||
}
|
||||
index += scansize - w;
|
||||
filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@param x the x coordinate of the rectangle
|
||||
@param y the y coordinate of the rectangle
|
||||
@param w the width of the rectangle
|
||||
@param h the height of the rectangle
|
||||
@param pixels the array of pixel values
|
||||
@param offset the index of the first pixels in the <code>pixels</code> array
|
||||
@param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
|
||||
int offset, int scansize)
|
||||
{
|
||||
for (int yp = 0; yp < h; yp++)
|
||||
{
|
||||
for (int xp = 0; xp < w; xp++)
|
||||
{
|
||||
pixels[offset + xp] = filterRGB(xp + x, yp + y, pixels[offset + xp]);
|
||||
}
|
||||
offset += scansize;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as an <code>int</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code>
|
||||
* array
|
||||
* @param scansize the width to use in extracting pixels from the
|
||||
* <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h, ColorModel model,
|
||||
int[] pixels, int offset, int scansize)
|
||||
{
|
||||
if (model == origmodel)
|
||||
{
|
||||
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] filtered = new int[w];
|
||||
int index = offset;
|
||||
for (int yp = 0; yp < h; yp++)
|
||||
{
|
||||
for (int xp = 0; xp < w; xp++)
|
||||
{
|
||||
filtered[xp] = model.getRGB((pixels[index] & 0xff));
|
||||
index++;
|
||||
}
|
||||
index += scansize - w;
|
||||
filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the ColorModel is the same ColorModel which as already converted
|
||||
* then it converts it the converted ColorModel. Otherwise it passes the
|
||||
* array of pixels through filterRGBpixels.
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, byte[] pixels,
|
||||
int offset, int scansize)
|
||||
{
|
||||
if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
|
||||
}
|
||||
else
|
||||
{
|
||||
int intPixels[] =
|
||||
convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
|
||||
filterRGBPixels( x, y, w, h, intPixels, offset, scansize );
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), intPixels, offset, scansize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function delivers a rectangle of pixels where any
|
||||
* pixel(m,n) is stored in the array as an <code>int</code> at
|
||||
* index (n * scansize + m + offset).
|
||||
*
|
||||
* @param x the x coordinate of the rectangle
|
||||
* @param y the y coordinate of the rectangle
|
||||
* @param w the width of the rectangle
|
||||
* @param h the height of the rectangle
|
||||
* @param model the <code>ColorModel</code> used to translate the pixels
|
||||
* @param pixels the array of pixel values
|
||||
* @param offset the index of the first pixels in the <code>pixels</code> array
|
||||
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
|
||||
*/
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, int[] pixels,
|
||||
int offset, int scansize)
|
||||
{
|
||||
if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
|
||||
{
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
|
||||
}
|
||||
else
|
||||
{
|
||||
//FIXME: Store the filtered pixels in a separate temporary buffer?
|
||||
convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
|
||||
filterRGBPixels( x, y, w, h, pixels, offset, scansize );
|
||||
if (consumer != null)
|
||||
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset, scansize);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] convertColorModelToDefault(int x, int y, int w, int h,
|
||||
ColorModel model, byte pixels[],
|
||||
int offset, int scansize)
|
||||
{
|
||||
int intPixels[] = new int[pixels.length];
|
||||
for (int i = 0; i < pixels.length; i++)
|
||||
intPixels[i] = makeColorbyDefaultCM(model, pixels[i]);
|
||||
return intPixels;
|
||||
}
|
||||
|
||||
private void convertColorModelToDefault(int x, int y, int w, int h,
|
||||
ColorModel model, int pixels[],
|
||||
int offset, int scansize)
|
||||
{
|
||||
for (int i = 0; i < pixels.length; i++)
|
||||
pixels[i] = makeColorbyDefaultCM(model, pixels[i]);
|
||||
}
|
||||
|
||||
private int makeColorbyDefaultCM(ColorModel model, byte rgb)
|
||||
{
|
||||
return makeColor( model.getAlpha( rgb ) * 4, model.getRed( rgb ) * 4, model.getGreen( rgb ) * 4, model.getBlue( rgb ) * 4 );
|
||||
}
|
||||
|
||||
private int makeColorbyDefaultCM(ColorModel model, int rgb)
|
||||
{
|
||||
return makeColor( model.getAlpha( rgb ), model.getRed( rgb ), model.getGreen( rgb ), model.getBlue( rgb ) );
|
||||
}
|
||||
|
||||
private int makeColor( int a, int r, int g, int b )
|
||||
{
|
||||
return (int)( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Filters a single pixel from the default ColorModel.
|
||||
|
||||
@param x x-coordinate
|
||||
@param y y-coordinate
|
||||
@param rgb color
|
||||
*/
|
||||
public abstract int filterRGB(int x,
|
||||
int y,
|
||||
int rgb);
|
||||
/**
|
||||
* Filters a single pixel from the default ColorModel.
|
||||
*
|
||||
* @param x x-coordinate
|
||||
* @param y y-coordinate
|
||||
* @param rgb color
|
||||
*/
|
||||
public abstract int filterRGB(int x, int y, int rgb);
|
||||
}
|
||||
|
||||
@@ -511,9 +511,10 @@ public class Raster
|
||||
int height, int childMinX, int childMinY,
|
||||
int[] bandList)
|
||||
{
|
||||
/* FIXME: Throw RasterFormatException if child bounds extends
|
||||
beyond the bounds of this raster. */
|
||||
|
||||
if (parentX < minX || parentX + width > minX + this.width
|
||||
|| parentY < minY || parentY + height > minY + this.height)
|
||||
throw new RasterFormatException("Child raster extends beyond parent");
|
||||
|
||||
SampleModel sm = (bandList == null) ?
|
||||
sampleModel :
|
||||
sampleModel.createSubsetSampleModel(bandList);
|
||||
|
||||
@@ -46,7 +46,7 @@ import java.util.Vector;
|
||||
*/
|
||||
public interface RenderedImage
|
||||
{
|
||||
Vector getSources();
|
||||
Vector<RenderedImage> getSources();
|
||||
Object getProperty(String name);
|
||||
String[] getPropertyNames();
|
||||
ColorModel getColorModel();
|
||||
|
||||
@@ -46,6 +46,7 @@ import java.util.Hashtable;
|
||||
* exact method is not defined by Sun but some sort of fast Box filter should
|
||||
* probably be correct.
|
||||
* <br>
|
||||
* Currently this filter does nothing and needs to be implemented.
|
||||
*
|
||||
* @author C. Brian Jones (cbj@gnu.org)
|
||||
*/
|
||||
@@ -116,11 +117,11 @@ public class ReplicateScaleFilter extends ImageFilter
|
||||
}
|
||||
else if (destWidth < 0)
|
||||
{
|
||||
destWidth = (int) (width * ((double) destHeight / srcHeight));
|
||||
destWidth = width * destHeight / srcHeight;
|
||||
}
|
||||
else if (destHeight < 0)
|
||||
{
|
||||
destHeight = (int) (height * ((double) destWidth / srcWidth));
|
||||
destHeight = height * destWidth / srcWidth;
|
||||
}
|
||||
|
||||
if (consumer != null)
|
||||
@@ -133,11 +134,12 @@ public class ReplicateScaleFilter extends ImageFilter
|
||||
*
|
||||
* @param props the list of properties associated with this image
|
||||
*/
|
||||
public void setProperties(Hashtable props)
|
||||
public void setProperties(Hashtable<?, ?> props)
|
||||
{
|
||||
props.put("filters", "ReplicateScaleFilter");
|
||||
if (consumer != null)
|
||||
consumer.setProperties(props);
|
||||
Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props;
|
||||
prop2.put("filters", "ReplicateScaleFilter");
|
||||
if (consumer != null)
|
||||
consumer.setProperties(prop2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,19 +159,35 @@ public class ReplicateScaleFilter extends ImageFilter
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, byte[] pixels, int offset, int scansize)
|
||||
{
|
||||
double rx = ((double) srcWidth) / destWidth;
|
||||
double ry = ((double) srcHeight) / destHeight;
|
||||
|
||||
int destScansize = (int) Math.round(scansize / rx);
|
||||
|
||||
byte[] destPixels = replicatePixels(x, y, w, h,
|
||||
model, pixels, offset, scansize,
|
||||
rx, ry, destScansize);
|
||||
|
||||
if (consumer != null)
|
||||
consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
|
||||
(int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
|
||||
model, destPixels, 0, destScansize);
|
||||
if (srcrows == null || srccols == null)
|
||||
setupSources();
|
||||
int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
|
||||
int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
|
||||
byte[] pix;
|
||||
if (outpixbuf != null && outpixbuf instanceof byte[])
|
||||
{
|
||||
pix = (byte[]) outpixbuf;
|
||||
}
|
||||
else
|
||||
{
|
||||
pix = new byte[destWidth];
|
||||
outpixbuf = pix;
|
||||
}
|
||||
int sy, sx;
|
||||
for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
|
||||
{
|
||||
int offs = offset + scansize * (sy - y);
|
||||
int xx;
|
||||
for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
|
||||
{
|
||||
pix[xx] = pixels[offs + sx - x];
|
||||
}
|
||||
if (xx > dx1)
|
||||
{
|
||||
consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
|
||||
destWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,59 +207,52 @@ public class ReplicateScaleFilter extends ImageFilter
|
||||
public void setPixels(int x, int y, int w, int h,
|
||||
ColorModel model, int[] pixels, int offset, int scansize)
|
||||
{
|
||||
double rx = ((double) srcWidth) / destWidth;
|
||||
double ry = ((double) srcHeight) / destHeight;
|
||||
|
||||
int destScansize = (int) Math.round(scansize / rx);
|
||||
|
||||
int[] destPixels = replicatePixels(x, y, w, h,
|
||||
model, pixels, offset, scansize,
|
||||
rx, ry, destScansize);
|
||||
|
||||
if (consumer != null)
|
||||
consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
|
||||
(int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
|
||||
model, destPixels, 0, destScansize);
|
||||
if (srcrows == null || srccols == null)
|
||||
setupSources();
|
||||
int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
|
||||
int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
|
||||
int[] pix;
|
||||
if (outpixbuf != null && outpixbuf instanceof int[])
|
||||
{
|
||||
pix = (int[]) outpixbuf;
|
||||
}
|
||||
else
|
||||
{
|
||||
pix = new int[destWidth];
|
||||
outpixbuf = pix;
|
||||
}
|
||||
int sy, sx;
|
||||
for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
|
||||
{
|
||||
int offs = offset + scansize * (sy - y);
|
||||
int xx;
|
||||
for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
|
||||
{
|
||||
pix[xx] = pixels[offs + sx - x];
|
||||
}
|
||||
if (xx > dx1)
|
||||
{
|
||||
consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
|
||||
destWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] replicatePixels(int srcx, int srcy, int srcw, int srch,
|
||||
ColorModel model, byte[] srcPixels,
|
||||
int srcOffset, int srcScansize,
|
||||
double rx, double ry, int destScansize)
|
||||
{
|
||||
byte[] destPixels =
|
||||
new byte[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
|
||||
|
||||
int a, b;
|
||||
for (int i = 0; i < destPixels.length; i++)
|
||||
{
|
||||
a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
|
||||
b = (int) ((i % destScansize) * rx);
|
||||
if ((a + b + srcOffset) < srcPixels.length)
|
||||
destPixels[i] = srcPixels[a + b + srcOffset];
|
||||
}
|
||||
|
||||
return destPixels;
|
||||
}
|
||||
|
||||
private int[] replicatePixels(int srcx, int srcy, int srcw, int srch,
|
||||
ColorModel model, int[] srcPixels,
|
||||
int srcOffset, int srcScansize,
|
||||
double rx, double ry, int destScansize)
|
||||
{
|
||||
int[] destPixels =
|
||||
new int[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
|
||||
|
||||
int a, b;
|
||||
for (int i = 0; i < destPixels.length; i++)
|
||||
{
|
||||
a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
|
||||
b = (int) ((i % destScansize) * rx);
|
||||
if ((a + b + srcOffset) < srcPixels.length)
|
||||
destPixels[i] = srcPixels[a + b + srcOffset];
|
||||
}
|
||||
|
||||
return destPixels;
|
||||
}
|
||||
/**
|
||||
* Sets up the srcrows and srccols arrays.
|
||||
*/
|
||||
private void setupSources()
|
||||
{
|
||||
srcrows = new int[destHeight + 1];
|
||||
for (int y = 0; y <= destHeight; y++)
|
||||
{
|
||||
srcrows[y] = (2 * y * srcHeight + srcHeight) / (2 * destHeight);
|
||||
}
|
||||
srccols = new int[destWidth + 1];
|
||||
for (int x = 0; x <= destWidth; x++)
|
||||
{
|
||||
srccols[x] = (2 * x * srcWidth + srcWidth) / (2 * destWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2004 Free Software Foundation
|
||||
/* Copyright (C) 2004, 2006 Free Software Foundation
|
||||
|
||||
This file is part of GNU Classpath.
|
||||
|
||||
@@ -43,7 +43,23 @@ import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* RescaleOp is a filter that changes each pixel by a scaling factor and offset.
|
||||
*
|
||||
* For filtering Rasters, either one scaling factor and offset can be specified,
|
||||
* which will be applied to all bands; or a scaling factor and offset can be
|
||||
* specified for each band.
|
||||
*
|
||||
* For BufferedImages, the scaling may apply to both color and alpha components.
|
||||
* If only one scaling factor is provided, or if the number of factors provided
|
||||
* equals the number of color components, the scaling is performed on all color
|
||||
* components. Otherwise, the scaling is performed on all components including
|
||||
* alpha. Alpha premultiplication is ignored.
|
||||
*
|
||||
* After filtering, if color conversion is necessary, the conversion happens,
|
||||
* taking alpha premultiplication into account.
|
||||
*
|
||||
* @author Jerry Quinn (jlquinn@optonline.net)
|
||||
* @author Francis Kung (fkung@redhat.com)
|
||||
*/
|
||||
public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
{
|
||||
@@ -51,15 +67,43 @@ public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
private float[] offsets;
|
||||
private RenderingHints hints = null;
|
||||
|
||||
/**
|
||||
* Create a new RescaleOp object using the given scale factors and offsets.
|
||||
*
|
||||
* The length of the arrays must be equal to the number of bands (or number of
|
||||
* data or color components) of the raster/image that this Op will be used on,
|
||||
* otherwise an IllegalArgumentException will be thrown when calling the
|
||||
* filter method.
|
||||
*
|
||||
* @param scaleFactors an array of scale factors.
|
||||
* @param offsets an array of offsets.
|
||||
* @param hints any rendering hints to use (can be null).
|
||||
* @throws NullPointerException if the scaleFactors or offsets array is null.
|
||||
*/
|
||||
public RescaleOp(float[] scaleFactors,
|
||||
float[] offsets,
|
||||
RenderingHints hints)
|
||||
{
|
||||
this.scale = scaleFactors;
|
||||
this.offsets = offsets;
|
||||
int length = Math.min(scaleFactors.length, offsets.length);
|
||||
|
||||
scale = new float[length];
|
||||
System.arraycopy(scaleFactors, 0, this.scale, 0, length);
|
||||
|
||||
this.offsets = new float[length];
|
||||
System.arraycopy(offsets, 0, this.offsets, 0, length);
|
||||
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RescaleOp object using the given scale factor and offset.
|
||||
*
|
||||
* The same scale factor and offset will be used on all bands/components.
|
||||
*
|
||||
* @param scaleFactor the scale factor to use.
|
||||
* @param offset the offset to use.
|
||||
* @param hints any rendering hints to use (can be null).
|
||||
*/
|
||||
public RescaleOp(float scaleFactor,
|
||||
float offset,
|
||||
RenderingHints hints)
|
||||
@@ -69,22 +113,47 @@ public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scaling factors. This method accepts an optional array, which
|
||||
* will be used to store the factors if not null (this avoids allocating a
|
||||
* new array). If this array is too small to hold all the scaling factors,
|
||||
* the array will be filled and the remaining factors discarded.
|
||||
*
|
||||
* @param scaleFactors array to store the scaling factors in (can be null).
|
||||
* @return an array of scaling factors.
|
||||
*/
|
||||
public final float[] getScaleFactors(float[] scaleFactors)
|
||||
{
|
||||
if (scaleFactors == null)
|
||||
scaleFactors = new float[scale.length];
|
||||
System.arraycopy(scale, 0, scaleFactors, 0, scale.length);
|
||||
System.arraycopy(scale, 0, scaleFactors, 0, Math.min(scale.length,
|
||||
scaleFactors.length));
|
||||
return scaleFactors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offsets. This method accepts an optional array, which
|
||||
* will be used to store the offsets if not null (this avoids allocating a
|
||||
* new array). If this array is too small to hold all the offsets, the array
|
||||
* will be filled and the remaining factors discarded.
|
||||
*
|
||||
* @param offsets array to store the offsets in (can be null).
|
||||
* @return an array of offsets.
|
||||
*/
|
||||
public final float[] getOffsets(float[] offsets)
|
||||
{
|
||||
if (offsets == null)
|
||||
offsets = new float[this.offsets.length];
|
||||
System.arraycopy(this.offsets, 0, offsets, 0, this.offsets.length);
|
||||
System.arraycopy(this.offsets, 0, offsets, 0, Math.min(this.offsets.length,
|
||||
offsets.length));
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of scaling factors / offsets.
|
||||
*
|
||||
* @return the number of scaling factors / offsets.
|
||||
*/
|
||||
public final int getNumFactors()
|
||||
{
|
||||
return scale.length;
|
||||
@@ -98,36 +167,74 @@ public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
return hints;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage)
|
||||
/**
|
||||
* Converts the source image using the scale factors and offsets specified in
|
||||
* the constructor. The resulting image is stored in the destination image if
|
||||
* one is provided; otherwise a new BufferedImage is created and returned.
|
||||
*
|
||||
* The source image cannot use an IndexColorModel, and the destination image
|
||||
* (if one is provided) must have the same size.
|
||||
*
|
||||
* If the final value of a sample is beyond the range of the color model, it
|
||||
* will be clipped to the appropriate maximum / minimum.
|
||||
*
|
||||
* @param src The source image.
|
||||
* @param dst The destination image.
|
||||
* @throws IllegalArgumentException if the rasters and/or color spaces are
|
||||
* incompatible.
|
||||
* @return The rescaled image.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
|
||||
{
|
||||
// TODO Make sure premultiplied alpha is handled correctly.
|
||||
// TODO See that color conversion is handled.
|
||||
// TODO figure out how to use rendering hints.
|
||||
if (scale.length != offsets.length)
|
||||
throw new IllegalArgumentException();
|
||||
// Initial checks
|
||||
if (scale.length != 1
|
||||
&& scale.length != src.getColorModel().getNumComponents()
|
||||
&& (scale.length != src.getColorModel().getNumColorComponents()))
|
||||
throw new IllegalArgumentException("Source image has wrong number of "
|
||||
+ "bands for these scaling factors.");
|
||||
|
||||
ColorModel scm = src.getColorModel();
|
||||
if (dst == null) dst = createCompatibleDestImage(src, null);
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestImage(src, null);
|
||||
else if (src.getHeight() != dst.getHeight()
|
||||
|| src.getWidth() != dst.getWidth())
|
||||
throw new IllegalArgumentException("Source and destination images are "
|
||||
+ "different sizes.");
|
||||
|
||||
WritableRaster wsrc = src.getRaster();
|
||||
WritableRaster wdst = dst.getRaster();
|
||||
|
||||
// Share constant across colors except alpha
|
||||
if (scale.length == 1 || scale.length == scm.getNumColorComponents())
|
||||
// Prepare for possible colorspace conversion
|
||||
BufferedImage dst2 = dst;
|
||||
if (dst.getColorModel().getColorSpace().getType() != src.getColorModel().getColorSpace().getType())
|
||||
dst2 = createCompatibleDestImage(src, src.getColorModel());
|
||||
|
||||
// Figure out how many bands to scale
|
||||
int numBands = scale.length;
|
||||
if (scale.length == 1)
|
||||
numBands = src.getColorModel().getNumColorComponents();
|
||||
boolean[] bands = new boolean[numBands];
|
||||
// this assumes the alpha, if present, is the last band
|
||||
Arrays.fill(bands, true);
|
||||
|
||||
// Perform rescaling
|
||||
filter(src.getRaster(), dst2.getRaster(), bands);
|
||||
|
||||
// Copy alpha band if needed (ie if it exists and wasn't scaled)
|
||||
// NOTE: This assumes the alpha component is the last band!
|
||||
if (src.getColorModel().hasAlpha()
|
||||
&& numBands == src.getColorModel().getNumColorComponents())
|
||||
{
|
||||
// Construct a raster that doesn't include an alpha band.
|
||||
int[] subbands = new int[scm.getNumColorComponents()];
|
||||
for (int i=0; i < subbands.length; i++) subbands[i] = i;
|
||||
wsrc =
|
||||
wsrc.createWritableChild(wsrc.minX, wsrc.minY, wsrc.width, wsrc.height,
|
||||
wsrc.minX, wsrc.minY, subbands);
|
||||
}
|
||||
// else all color bands
|
||||
|
||||
filter(wsrc, wdst);
|
||||
dst2.getRaster().setSamples(0, 0, src.getWidth(), src.getHeight(),
|
||||
numBands,
|
||||
src.getRaster().getSamples(0, 0,
|
||||
src.getWidth(),
|
||||
src.getHeight(),
|
||||
numBands,
|
||||
(int[]) null));
|
||||
}
|
||||
|
||||
// Perform colorspace conversion if needed
|
||||
if (dst != dst2)
|
||||
new ColorConvertOp(hints).filter(dst2, dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -136,50 +243,106 @@ public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
*/
|
||||
public final WritableRaster filter(Raster src, WritableRaster dest)
|
||||
{
|
||||
if (dest == null) dest = src.createCompatibleWritableRaster();
|
||||
|
||||
// Required sanity checks
|
||||
if (src.numBands != dest.numBands || scale.length != offsets.length)
|
||||
throw new IllegalArgumentException();
|
||||
if (scale.length != 1 && scale.length != src.numBands)
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Number of rasters is incompatible "
|
||||
+ "with the number of scaling "
|
||||
+ "factors provided.");
|
||||
|
||||
// Create scaling arrays if needed
|
||||
float[] lscale = scale;
|
||||
float[] loff = offsets;
|
||||
if (scale.length == 1)
|
||||
{
|
||||
lscale = new float[src.numBands];
|
||||
Arrays.fill(lscale, scale[0]);
|
||||
loff = new float[src.numBands];
|
||||
Arrays.fill(loff, offsets[0]);
|
||||
}
|
||||
if (dest == null)
|
||||
dest = src.createCompatibleWritableRaster();
|
||||
else if (src.getHeight() != dest.getHeight()
|
||||
|| src.getWidth() != dest.getWidth())
|
||||
throw new IllegalArgumentException("Source and destination rasters are "
|
||||
+ "different sizes.");
|
||||
else if (src.numBands != dest.numBands)
|
||||
throw new IllegalArgumentException("Source and destination rasters "
|
||||
+ "are incompatible.");
|
||||
|
||||
// TODO The efficiency here can be improved for various data storage
|
||||
// patterns, aka SampleModels.
|
||||
float[] pixel = new float[src.numBands];
|
||||
for (int y = src.minY; y < src.height + src.minY; y++)
|
||||
for (int x = src.minX; x < src.width + src.minX; x++)
|
||||
{
|
||||
src.getPixel(x, y, pixel);
|
||||
for (int b = 0; b < src.numBands; b++)
|
||||
pixel[b] = pixel[b] * lscale[b] + loff[b];
|
||||
dest.setPixel(x, y, pixel);
|
||||
}
|
||||
// Filter all bands
|
||||
boolean[] bands = new boolean[src.getNumBands()];
|
||||
Arrays.fill(bands, true);
|
||||
return filter(src, dest, bands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform raster-based filtering on a selected number of bands.
|
||||
*
|
||||
* The length of the bands array should equal the number of bands; a true
|
||||
* element indicates filtering should happen on the corresponding band, while
|
||||
* a false element will skip the band.
|
||||
*
|
||||
* The rasters are assumed to be compatible and non-null.
|
||||
*
|
||||
* @param src the source raster.
|
||||
* @param dest the destination raster.
|
||||
* @param bands an array indicating which bands to filter.
|
||||
* @throws NullPointerException if any parameter is null.
|
||||
* @throws ArrayIndexOutOfBoundsException if the bands array is too small.
|
||||
* @return the destination raster.
|
||||
*/
|
||||
private WritableRaster filter(Raster src, WritableRaster dest, boolean[] bands)
|
||||
{
|
||||
int[] values = new int[src.getHeight() * src.getWidth()];
|
||||
float scaleFactor, offset;
|
||||
|
||||
// Find max sample value, to be used for clipping later
|
||||
int[] maxValue = src.getSampleModel().getSampleSize();
|
||||
for (int i = 0; i < maxValue.length; i++)
|
||||
maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1;
|
||||
|
||||
// TODO: can this be optimized further?
|
||||
// Filter all samples of all requested bands
|
||||
for (int band = 0; band < bands.length; band++)
|
||||
if (bands[band])
|
||||
{
|
||||
values = src.getSamples(src.getMinX(), src.getMinY(), src.getWidth(),
|
||||
src.getHeight(), band, values);
|
||||
|
||||
if (scale.length == 1)
|
||||
{
|
||||
scaleFactor = scale[0];
|
||||
offset = offsets[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
scaleFactor = scale[band];
|
||||
offset = offsets[band];
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
values[i] = (int) (values[i] * scaleFactor + offset);
|
||||
|
||||
// Clip if needed
|
||||
if (values[i] < 0)
|
||||
values[i] = 0;
|
||||
if (values[i] > maxValue[band])
|
||||
values[i] = maxValue[band];
|
||||
}
|
||||
|
||||
dest.setSamples(dest.getMinX(), dest.getMinY(), dest.getWidth(),
|
||||
dest.getHeight(), band, values);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
|
||||
* java.awt.image.ColorModel)
|
||||
*/
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src,
|
||||
ColorModel dstCM)
|
||||
{
|
||||
if (dstCM == null) dstCM = src.getColorModel();
|
||||
WritableRaster wr = src.getRaster().createCompatibleWritableRaster();
|
||||
BufferedImage image
|
||||
= new BufferedImage(dstCM, wr, src.isPremultiplied, null);
|
||||
return image;
|
||||
if (dstCM == null)
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
|
||||
|
||||
return new BufferedImage(dstCM,
|
||||
src.getRaster().createCompatibleWritableRaster(),
|
||||
src.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -209,9 +372,13 @@ public class RescaleOp implements BufferedImageOp, RasterOp
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
|
||||
*/
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst) {
|
||||
if (dst == null) dst = (Point2D) src.clone();
|
||||
else dst.setLocation(src);
|
||||
public final Point2D getPoint2D(Point2D src, Point2D dst)
|
||||
{
|
||||
if (dst == null)
|
||||
dst = (Point2D) src.clone();
|
||||
else
|
||||
dst.setLocation(src);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,9 +246,7 @@ public abstract class SampleModel
|
||||
public void setDataElements(int x, int y, int w, int h,
|
||||
Object obj, DataBuffer data)
|
||||
{
|
||||
int size = w * h;
|
||||
int numDataElements = getNumDataElements();
|
||||
int dataSize = numDataElements * size;
|
||||
|
||||
Object pixelData;
|
||||
switch (getTransferType())
|
||||
@@ -257,25 +255,34 @@ public abstract class SampleModel
|
||||
pixelData = new byte[numDataElements];
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
pixelData = new short[numDataElements];
|
||||
break;
|
||||
case DataBuffer.TYPE_INT:
|
||||
pixelData = new int[numDataElements];
|
||||
break;
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
pixelData = new float[numDataElements];
|
||||
break;
|
||||
case DataBuffer.TYPE_DOUBLE:
|
||||
pixelData = new double[numDataElements];
|
||||
break;
|
||||
default:
|
||||
// Seems like the only sensible thing to do.
|
||||
throw new ClassCastException();
|
||||
// The RI silently igores invalid types.
|
||||
pixelData = null;
|
||||
}
|
||||
int inOffset = 0;
|
||||
|
||||
for (int yy = y; yy < (y + h); yy++)
|
||||
int inOffset = 0;
|
||||
if (pixelData != null)
|
||||
{
|
||||
for (int xx = x; xx < (x + w); xx++)
|
||||
for (int yy=y; yy<(y+h); yy++)
|
||||
{
|
||||
System.arraycopy(obj, inOffset, pixelData, 0,
|
||||
numDataElements);
|
||||
setDataElements(xx, yy, pixelData, data);
|
||||
inOffset += numDataElements;
|
||||
for (int xx=x; xx<(x+w); xx++)
|
||||
{
|
||||
System.arraycopy(obj, inOffset, pixelData, 0, numDataElements);
|
||||
setDataElements(xx, yy, pixelData, data);
|
||||
inOffset += numDataElements;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,110 +412,31 @@ public class SinglePixelPackedSampleModel extends SampleModel
|
||||
return (samples & bitMasks[b]) >>> bitOffsets[b];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method implements a more efficient way to set data elements than the
|
||||
* default implementation of the super class. It sets the data elements line
|
||||
* by line instead of pixel by pixel.
|
||||
*
|
||||
* @param x The x-coordinate of the data elements in <code>obj</code>.
|
||||
* @param y The y-coordinate of the data elements in <code>obj</code>.
|
||||
* @param w The width of the data elements in <code>obj</code>.
|
||||
* @param h The height of the data elements in <code>obj</code>.
|
||||
* @param obj The primitive array containing the data elements to set.
|
||||
* @param data The DataBuffer to store the data elements into.
|
||||
* @see java.awt.image.SampleModel#setDataElements(int, int, int, int,
|
||||
* java.lang.Object, java.awt.image.DataBuffer)
|
||||
*/
|
||||
public void setDataElements(int x, int y, int w, int h,
|
||||
Object obj, DataBuffer data)
|
||||
{
|
||||
|
||||
Object pixelData;
|
||||
switch (getTransferType())
|
||||
{
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
pixelData = ((DataBufferByte) data).getData();
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
pixelData = ((DataBufferUShort) data).getData();
|
||||
break;
|
||||
case DataBuffer.TYPE_INT:
|
||||
pixelData = ((DataBufferInt) data).getData();
|
||||
break;
|
||||
default:
|
||||
// Seems like the only sensible thing to do.
|
||||
throw new ClassCastException();
|
||||
}
|
||||
|
||||
int inOffset = 0;
|
||||
int dataOffset = scanlineStride*y + x + data.getOffset();
|
||||
for (int yy=y; yy<(y+h); yy++)
|
||||
{
|
||||
System.arraycopy(obj,inOffset,pixelData,dataOffset,w);
|
||||
dataOffset += scanlineStride;
|
||||
inOffset += w;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setDataElements(int x, int y, Object obj, DataBuffer data)
|
||||
{
|
||||
int offset = scanlineStride*y + x + data.getOffset();
|
||||
|
||||
int transferType = getTransferType();
|
||||
if (getTransferType() != data.getDataType())
|
||||
{
|
||||
throw new IllegalArgumentException("transfer type ("+
|
||||
getTransferType()+"), "+
|
||||
"does not match data "+
|
||||
"buffer type (" +
|
||||
data.getDataType() +
|
||||
").");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (transferType)
|
||||
{
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
{
|
||||
DataBufferByte out = (DataBufferByte) data;
|
||||
byte[] in = (byte[]) obj;
|
||||
out.getData()[offset] = in[0];
|
||||
return;
|
||||
}
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
{
|
||||
DataBufferUShort out = (DataBufferUShort) data;
|
||||
short[] in = (short[]) obj;
|
||||
out.getData()[offset] = in[0];
|
||||
return;
|
||||
}
|
||||
case DataBuffer.TYPE_INT:
|
||||
{
|
||||
DataBufferInt out = (DataBufferInt) data;
|
||||
int[] in = (int[]) obj;
|
||||
out.getData()[offset] = in[0];
|
||||
return;
|
||||
}
|
||||
// FIXME: Fill in the other possible types.
|
||||
default:
|
||||
throw new InternalError();
|
||||
}
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException aioobe)
|
||||
{
|
||||
String msg = "While writing data elements" +
|
||||
", x="+x+", y="+y+
|
||||
", width="+width+", height="+height+
|
||||
", scanlineStride="+scanlineStride+
|
||||
", offset="+offset+
|
||||
", data.getSize()="+data.getSize()+
|
||||
", data.getOffset()="+data.getOffset()+
|
||||
": " +
|
||||
aioobe;
|
||||
throw new ArrayIndexOutOfBoundsException(msg);
|
||||
}
|
||||
switch (transferType)
|
||||
{
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
{
|
||||
byte[] in = (byte[]) obj;
|
||||
data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xff);
|
||||
break;
|
||||
}
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
{
|
||||
short[] in = (short[]) obj;
|
||||
data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xffff);
|
||||
break;
|
||||
}
|
||||
case DataBuffer.TYPE_INT:
|
||||
{
|
||||
int[] in = (int[]) obj;
|
||||
data.setElem(y * scanlineStride + x, in[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -136,8 +136,9 @@ public class WritableRaster extends Raster
|
||||
{
|
||||
// This mirrors the code from the super class
|
||||
|
||||
// FIXME: Throw RasterFormatException if child bounds extends
|
||||
// beyond the bounds of this raster.
|
||||
if (parentX < minX || parentX + w > minX + width
|
||||
|| parentY < minY || parentY + h > minY + height)
|
||||
throw new RasterFormatException("Child raster extends beyond parent");
|
||||
|
||||
SampleModel sm = (bandList == null) ?
|
||||
sampleModel :
|
||||
@@ -149,6 +150,25 @@ public class WritableRaster extends Raster
|
||||
sampleModelTranslateY + childMinY - parentY),
|
||||
this);
|
||||
}
|
||||
|
||||
public Raster createChild(int parentX, int parentY, int width,
|
||||
int height, int childMinX, int childMinY,
|
||||
int[] bandList)
|
||||
{
|
||||
if (parentX < minX || parentX + width > minX + this.width
|
||||
|| parentY < minY || parentY + height > minY + this.height)
|
||||
throw new RasterFormatException("Child raster extends beyond parent");
|
||||
|
||||
SampleModel sm = (bandList == null) ?
|
||||
sampleModel :
|
||||
sampleModel.createSubsetSampleModel(bandList);
|
||||
|
||||
return new WritableRaster(sm, dataBuffer,
|
||||
new Rectangle(childMinX, childMinY, width, height),
|
||||
new Point(sampleModelTranslateX + childMinX - parentX,
|
||||
sampleModelTranslateY + childMinY - parentY),
|
||||
this);
|
||||
}
|
||||
|
||||
public void setDataElements(int x, int y, Object inData)
|
||||
{
|
||||
|
||||
@@ -45,20 +45,20 @@ import java.util.Vector;
|
||||
public class ParameterBlock implements Cloneable, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -7577115551785240750L;
|
||||
protected Vector sources;
|
||||
protected Vector parameters;
|
||||
protected Vector<Object> sources;
|
||||
protected Vector<Object> parameters;
|
||||
|
||||
public ParameterBlock()
|
||||
{
|
||||
this(new Vector(), new Vector());
|
||||
this(new Vector<Object>(), new Vector<Object>());
|
||||
}
|
||||
|
||||
public ParameterBlock(Vector sources)
|
||||
public ParameterBlock(Vector<Object> sources)
|
||||
{
|
||||
this(sources, new Vector());
|
||||
this(sources, new Vector<Object>());
|
||||
}
|
||||
|
||||
public ParameterBlock(Vector sources, Vector parameters)
|
||||
public ParameterBlock(Vector<Object> sources, Vector<Object> parameters)
|
||||
{
|
||||
this.sources = sources;
|
||||
this.parameters = parameters;
|
||||
@@ -80,9 +80,9 @@ public class ParameterBlock implements Cloneable, Serializable
|
||||
{
|
||||
ParameterBlock pb = (ParameterBlock) shallowClone();
|
||||
if (sources != null)
|
||||
pb.sources = (Vector) sources.clone();
|
||||
pb.sources = (Vector<Object>) sources.clone();
|
||||
if (parameters != null)
|
||||
pb.parameters = (Vector) parameters.clone();
|
||||
pb.parameters = (Vector<Object>) parameters.clone();
|
||||
return pb;
|
||||
}
|
||||
|
||||
@@ -119,12 +119,12 @@ public class ParameterBlock implements Cloneable, Serializable
|
||||
return sources.size();
|
||||
}
|
||||
|
||||
public Vector getSources()
|
||||
public Vector<Object> getSources()
|
||||
{
|
||||
return sources;
|
||||
}
|
||||
|
||||
public void setSources(Vector sources)
|
||||
public void setSources(Vector<Object> sources)
|
||||
{
|
||||
this.sources = sources;
|
||||
}
|
||||
@@ -140,12 +140,12 @@ public class ParameterBlock implements Cloneable, Serializable
|
||||
return parameters.size();
|
||||
}
|
||||
|
||||
public Vector getParameters()
|
||||
public Vector<Object> getParameters()
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(Vector parameters)
|
||||
public void setParameters(Vector<Object> parameters)
|
||||
{
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface RenderableImage
|
||||
{
|
||||
String HINTS_OBSERVED = "HINTS_OBSERVED";
|
||||
|
||||
Vector getSources();
|
||||
Vector<RenderableImage> getSources();
|
||||
Object getProperty(String name);
|
||||
String[] getPropertyNames();
|
||||
boolean isDynamic();
|
||||
|
||||
@@ -55,7 +55,7 @@ public class RenderableImageOp implements RenderableImage
|
||||
this.block = (ParameterBlock) block.clone();
|
||||
}
|
||||
|
||||
public Vector getSources()
|
||||
public Vector<RenderableImage> getSources()
|
||||
{
|
||||
if (block.sources == null)
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user