546 lines
19 KiB
Java
546 lines
19 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* @author Oleg V. Khaschansky
|
|
* @version $Revision$
|
|
*
|
|
* @date: Sep 29, 2005
|
|
*/
|
|
|
|
package java.awt.image;
|
|
|
|
import java.awt.*;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.util.Arrays;
|
|
|
|
import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
|
|
import org.apache.harmony.awt.internal.nls.Messages;
|
|
|
|
/**
|
|
* The ConvolveOp class convolves from the source data to the destination using
|
|
* a convolution kernel. Each output pixel is represented as the result of
|
|
* multiplying the kernel and the surround of the input pixel.
|
|
*
|
|
* @since Android 1.0
|
|
*/
|
|
public class ConvolveOp implements BufferedImageOp, RasterOp {
|
|
|
|
/**
|
|
* The Constant EDGE_ZERO_FILL indicates that pixels at the edge of the
|
|
* destination image are set to zero.
|
|
*/
|
|
public static final int EDGE_ZERO_FILL = 0;
|
|
|
|
/**
|
|
* The Constant EDGE_NO_OP indicates that pixels at the edge of the source
|
|
* image are converted to the edge pixels in the destination without
|
|
* modification.
|
|
*/
|
|
public static final int EDGE_NO_OP = 1;
|
|
|
|
/**
|
|
* The kernel.
|
|
*/
|
|
private Kernel kernel;
|
|
|
|
/**
|
|
* The edge cond.
|
|
*/
|
|
private int edgeCond;
|
|
|
|
/**
|
|
* The rhs.
|
|
*/
|
|
private RenderingHints rhs = null;
|
|
|
|
static {
|
|
// TODO
|
|
// System.loadLibrary("imageops");
|
|
}
|
|
|
|
/**
|
|
* Instantiates a new ConvolveOp object with the specified Kernel and
|
|
* specified edges condition.
|
|
*
|
|
* @param kernel
|
|
* the specified Kernel.
|
|
* @param edgeCondition
|
|
* the specified edge condition.
|
|
* @param hints
|
|
* the RenderingHints object, or null.
|
|
*/
|
|
public ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints) {
|
|
this.kernel = kernel;
|
|
this.edgeCond = edgeCondition;
|
|
this.rhs = hints;
|
|
}
|
|
|
|
/**
|
|
* Instantiates a new ConvolveOp object with the specified Kernel and
|
|
* EDGE_ZERO_FILL edge condition.
|
|
*
|
|
* @param kernel
|
|
* the specified Kernel.
|
|
*/
|
|
public ConvolveOp(Kernel kernel) {
|
|
this.kernel = kernel;
|
|
this.edgeCond = EDGE_ZERO_FILL;
|
|
}
|
|
|
|
/**
|
|
* Gets the Kernel object of this ConvolveOp.
|
|
*
|
|
* @return the Kernel object of this ConvolveOp.
|
|
*/
|
|
public final Kernel getKernel() {
|
|
return (Kernel)kernel.clone();
|
|
}
|
|
|
|
public final RenderingHints getRenderingHints() {
|
|
return rhs;
|
|
}
|
|
|
|
/**
|
|
* Gets the edge condition of this ConvolveOp.
|
|
*
|
|
* @return the edge condition: EDGE_NO_OP or EDGE_ZERO_FILL.
|
|
*/
|
|
public int getEdgeCondition() {
|
|
return edgeCond;
|
|
}
|
|
|
|
public final Rectangle2D getBounds2D(Raster src) {
|
|
return src.getBounds();
|
|
}
|
|
|
|
public final Rectangle2D getBounds2D(BufferedImage src) {
|
|
return getBounds2D(src.getRaster());
|
|
}
|
|
|
|
public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
|
if (dstPt == null) {
|
|
dstPt = new Point2D.Float();
|
|
}
|
|
|
|
dstPt.setLocation(srcPt);
|
|
return dstPt;
|
|
}
|
|
|
|
public WritableRaster createCompatibleDestRaster(Raster src) {
|
|
return src.createCompatibleWritableRaster();
|
|
}
|
|
|
|
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
|
|
if (dstCM == null) {
|
|
dstCM = src.getColorModel();
|
|
}
|
|
|
|
if (dstCM instanceof IndexColorModel) {
|
|
dstCM = ColorModel.getRGBdefault();
|
|
}
|
|
|
|
WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster()
|
|
.createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM
|
|
.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
|
|
|
|
return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null);
|
|
}
|
|
|
|
public final WritableRaster filter(Raster src, WritableRaster dst) {
|
|
if (src == null) { // Should throw according to spec
|
|
// awt.256=Source raster is null
|
|
throw new NullPointerException(Messages.getString("awt.256")); //$NON-NLS-1$
|
|
}
|
|
|
|
if (src == dst) {
|
|
// awt.257=Source raster is equal to destination
|
|
throw new IllegalArgumentException(Messages.getString("awt.257")); //$NON-NLS-1$
|
|
}
|
|
|
|
if (dst == null) {
|
|
dst = createCompatibleDestRaster(src);
|
|
} else if (src.getNumBands() != dst.getNumBands()) {
|
|
// awt.258=Number of source bands ({0}) is not equal to number of
|
|
// destination bands ({1})
|
|
throw new IllegalArgumentException(Messages.getString(
|
|
"awt.258", src.getNumBands(), dst.getNumBands())); //$NON-NLS-1$
|
|
}
|
|
|
|
// TODO
|
|
// if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM) != 0)
|
|
if (slowFilter(src, dst) != 0) {
|
|
// awt.21F=Unable to transform source
|
|
throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Slow filter.
|
|
*
|
|
* @param src
|
|
* the src.
|
|
* @param dst
|
|
* the dst.
|
|
* @return the int.
|
|
*/
|
|
private int slowFilter(Raster src, WritableRaster dst) {
|
|
try {
|
|
SampleModel sm = src.getSampleModel();
|
|
|
|
int numBands = src.getNumBands();
|
|
int srcHeight = src.getHeight();
|
|
int srcWidth = src.getWidth();
|
|
|
|
int xOrigin = kernel.getXOrigin();
|
|
int yOrigin = kernel.getYOrigin();
|
|
int kWidth = kernel.getWidth();
|
|
int kHeight = kernel.getHeight();
|
|
float[] data = kernel.getKernelData(null);
|
|
|
|
int srcMinX = src.getMinX();
|
|
int srcMinY = src.getMinY();
|
|
int dstMinX = dst.getMinX();
|
|
int dstMinY = dst.getMinY();
|
|
|
|
int srcConvMaxX = srcWidth - (kWidth - xOrigin - 1);
|
|
int srcConvMaxY = srcHeight - (kHeight - yOrigin - 1);
|
|
|
|
int[] maxValues = new int[numBands];
|
|
int[] masks = new int[numBands];
|
|
int[] sampleSizes = sm.getSampleSize();
|
|
|
|
for (int i = 0; i < numBands; i++) {
|
|
maxValues[i] = (1 << sampleSizes[i]) - 1;
|
|
masks[i] = ~(maxValues[i]);
|
|
}
|
|
|
|
// Processing bounds
|
|
float[] pixels = null;
|
|
pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels);
|
|
float[] newPixels = new float[pixels.length];
|
|
int rowLength = srcWidth * numBands;
|
|
if (this.edgeCond == ConvolveOp.EDGE_NO_OP) {
|
|
// top
|
|
int start = 0;
|
|
int length = yOrigin * rowLength;
|
|
System.arraycopy(pixels, start, newPixels, start, length);
|
|
// bottom
|
|
start = (srcHeight - (kHeight - yOrigin - 1)) * rowLength;
|
|
length = (kHeight - yOrigin - 1) * rowLength;
|
|
System.arraycopy(pixels, start, newPixels, start, length);
|
|
// middle
|
|
length = xOrigin * numBands;
|
|
int length1 = (kWidth - xOrigin - 1) * numBands;
|
|
start = yOrigin * rowLength;
|
|
int start1 = (yOrigin + 1) * rowLength - length1;
|
|
for (int i = yOrigin; i < (srcHeight - (kHeight - yOrigin - 1)); i++) {
|
|
System.arraycopy(pixels, start, newPixels, start, length);
|
|
System.arraycopy(pixels, start1, newPixels, start1, length1);
|
|
start += rowLength;
|
|
start1 += rowLength;
|
|
}
|
|
|
|
}
|
|
|
|
// Cycle over pixels to be calculated
|
|
for (int i = yOrigin; i < srcConvMaxY; i++) {
|
|
for (int j = xOrigin; j < srcConvMaxX; j++) {
|
|
|
|
// Take kernel data in backward direction, convolution
|
|
int kernelIdx = data.length - 1;
|
|
|
|
int pixelIndex = i * rowLength + j * numBands;
|
|
for (int hIdx = 0, rasterHIdx = i - yOrigin; hIdx < kHeight; hIdx++, rasterHIdx++) {
|
|
for (int wIdx = 0, rasterWIdx = j - xOrigin; wIdx < kWidth; wIdx++, rasterWIdx++) {
|
|
int curIndex = rasterHIdx * rowLength + rasterWIdx * numBands;
|
|
for (int idx = 0; idx < numBands; idx++) {
|
|
newPixels[pixelIndex + idx] += data[kernelIdx]
|
|
* pixels[curIndex + idx];
|
|
}
|
|
kernelIdx--;
|
|
}
|
|
}
|
|
|
|
// Check for overflow now
|
|
for (int idx = 0; idx < numBands; idx++) {
|
|
if (((int)newPixels[pixelIndex + idx] & masks[idx]) != 0) {
|
|
if (newPixels[pixelIndex + idx] < 0) {
|
|
newPixels[pixelIndex + idx] = 0;
|
|
} else {
|
|
newPixels[pixelIndex + idx] = maxValues[idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, newPixels);
|
|
} catch (Exception e) { // Something goes wrong, signal error
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
|
|
if (src == null) {
|
|
// awt.259=Source image is null
|
|
throw new NullPointerException(Messages.getString("awt.259")); //$NON-NLS-1$
|
|
}
|
|
|
|
if (src == dst) {
|
|
// awt.25A=Source equals to destination
|
|
throw new IllegalArgumentException(Messages.getString("awt.25A")); //$NON-NLS-1$
|
|
}
|
|
|
|
ColorModel srcCM = src.getColorModel();
|
|
BufferedImage finalDst = null;
|
|
|
|
if (srcCM instanceof IndexColorModel) {
|
|
src = ((IndexColorModel)srcCM).convertToIntDiscrete(src.getRaster(), true);
|
|
srcCM = src.getColorModel();
|
|
}
|
|
|
|
if (dst == null) {
|
|
dst = createCompatibleDestImage(src, srcCM);
|
|
} else {
|
|
if (!srcCM.equals(dst.getColorModel())) {
|
|
// Treat BufferedImage.TYPE_INT_RGB and
|
|
// BufferedImage.TYPE_INT_ARGB as same
|
|
if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst
|
|
.getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) {
|
|
finalDst = dst;
|
|
dst = createCompatibleDestImage(src, srcCM);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip alpha channel for TYPE_INT_RGB images
|
|
// TODO
|
|
// if (ippFilter(src.getRaster(), dst.getRaster(), src.getType()) != 0)
|
|
if (slowFilter(src.getRaster(), dst.getRaster()) != 0) {
|
|
// awt.21F=Unable to transform source
|
|
throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
|
|
}
|
|
|
|
if (finalDst != null) {
|
|
Graphics2D g = finalDst.createGraphics();
|
|
g.setComposite(AlphaComposite.Src);
|
|
g.drawImage(dst, 0, 0, null);
|
|
} else {
|
|
finalDst = dst;
|
|
}
|
|
|
|
return finalDst;
|
|
}
|
|
|
|
// TODO remove when this method is used
|
|
/**
|
|
* Ipp filter.
|
|
*
|
|
* @param src
|
|
* the src.
|
|
* @param dst
|
|
* the dst.
|
|
* @param imageType
|
|
* the image type.
|
|
* @return the int.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private int ippFilter(Raster src, WritableRaster dst, int imageType) {
|
|
int srcStride, dstStride;
|
|
boolean skipChannel = false;
|
|
int channels;
|
|
int offsets[] = null;
|
|
|
|
switch (imageType) {
|
|
case BufferedImage.TYPE_INT_RGB:
|
|
case BufferedImage.TYPE_INT_BGR: {
|
|
channels = 4;
|
|
srcStride = src.getWidth() * 4;
|
|
dstStride = dst.getWidth() * 4;
|
|
skipChannel = true;
|
|
break;
|
|
}
|
|
|
|
case BufferedImage.TYPE_INT_ARGB:
|
|
case BufferedImage.TYPE_INT_ARGB_PRE:
|
|
case BufferedImage.TYPE_4BYTE_ABGR:
|
|
case BufferedImage.TYPE_4BYTE_ABGR_PRE: {
|
|
channels = 4;
|
|
srcStride = src.getWidth() * 4;
|
|
dstStride = dst.getWidth() * 4;
|
|
break;
|
|
}
|
|
|
|
case BufferedImage.TYPE_BYTE_GRAY: {
|
|
channels = 1;
|
|
srcStride = src.getWidth();
|
|
dstStride = dst.getWidth();
|
|
break;
|
|
}
|
|
|
|
case BufferedImage.TYPE_3BYTE_BGR: {
|
|
channels = 3;
|
|
srcStride = src.getWidth() * 3;
|
|
dstStride = dst.getWidth() * 3;
|
|
break;
|
|
}
|
|
|
|
case BufferedImage.TYPE_USHORT_GRAY: // TODO - could be done in
|
|
// native code?
|
|
case BufferedImage.TYPE_USHORT_565_RGB:
|
|
case BufferedImage.TYPE_USHORT_555_RGB:
|
|
case BufferedImage.TYPE_BYTE_BINARY: {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
default: {
|
|
SampleModel srcSM = src.getSampleModel();
|
|
SampleModel dstSM = dst.getSampleModel();
|
|
|
|
if (srcSM instanceof PixelInterleavedSampleModel
|
|
&& dstSM instanceof PixelInterleavedSampleModel) {
|
|
// Check PixelInterleavedSampleModel
|
|
if (srcSM.getDataType() != DataBuffer.TYPE_BYTE
|
|
|| dstSM.getDataType() != DataBuffer.TYPE_BYTE) {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
channels = srcSM.getNumBands(); // Have IPP functions for 1,
|
|
// 3 and 4 channels
|
|
if (!(channels == 1 || channels == 3 || channels == 4)) {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
srcStride = ((ComponentSampleModel)srcSM).getScanlineStride();
|
|
dstStride = ((ComponentSampleModel)dstSM).getScanlineStride();
|
|
} else if (srcSM instanceof SinglePixelPackedSampleModel
|
|
&& dstSM instanceof SinglePixelPackedSampleModel) {
|
|
// Check SinglePixelPackedSampleModel
|
|
SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM;
|
|
SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM;
|
|
|
|
channels = sppsm1.getNumBands();
|
|
|
|
// TYPE_INT_RGB, TYPE_INT_ARGB...
|
|
if (sppsm1.getDataType() != DataBuffer.TYPE_INT
|
|
|| sppsm2.getDataType() != DataBuffer.TYPE_INT
|
|
|| !(channels == 3 || channels == 4)) {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
// Check compatibility of sample models
|
|
if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets())
|
|
|| !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
for (int i = 0; i < channels; i++) {
|
|
if (sppsm1.getSampleSize(i) != 8) {
|
|
return slowFilter(src, dst);
|
|
}
|
|
}
|
|
|
|
if (channels == 3) { // Cannot skip channel, don't know
|
|
// which is alpha...
|
|
channels = 4;
|
|
}
|
|
|
|
srcStride = sppsm1.getScanlineStride() * 4;
|
|
dstStride = sppsm2.getScanlineStride() * 4;
|
|
} else {
|
|
return slowFilter(src, dst);
|
|
}
|
|
|
|
// Fill offsets if there's a child raster
|
|
if (src.getParent() != null || dst.getParent() != null) {
|
|
if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0
|
|
|| dst.getSampleModelTranslateX() != 0
|
|
|| dst.getSampleModelTranslateY() != 0) {
|
|
offsets = new int[4];
|
|
offsets[0] = -src.getSampleModelTranslateX() + src.getMinX();
|
|
offsets[1] = -src.getSampleModelTranslateY() + src.getMinY();
|
|
offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX();
|
|
offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Object srcData, dstData;
|
|
AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
|
|
try {
|
|
srcData = dbAccess.getData(src.getDataBuffer());
|
|
dstData = dbAccess.getData(dst.getDataBuffer());
|
|
} catch (IllegalArgumentException e) {
|
|
return -1; // Unknown data buffer type
|
|
}
|
|
|
|
return ippFilter32f(kernel.data, kernel.getWidth(), kernel.getHeight(),
|
|
kernel.getXOrigin(), kernel.getYOrigin(), edgeCond, srcData, src.getWidth(), src
|
|
.getHeight(), srcStride, dstData, dst.getWidth(), dst.getHeight(),
|
|
dstStride, channels, skipChannel, offsets);
|
|
}
|
|
|
|
/**
|
|
* Ipp filter32f.
|
|
*
|
|
* @param kernel
|
|
* the kernel.
|
|
* @param kWidth
|
|
* the k width.
|
|
* @param kHeight
|
|
* the k height.
|
|
* @param anchorX
|
|
* the anchor x.
|
|
* @param anchorY
|
|
* the anchor y.
|
|
* @param borderType
|
|
* the border type.
|
|
* @param src
|
|
* the src.
|
|
* @param srcWidth
|
|
* the src width.
|
|
* @param srcHeight
|
|
* the src height.
|
|
* @param srcStride
|
|
* the src stride.
|
|
* @param dst
|
|
* the dst.
|
|
* @param dstWidth
|
|
* the dst width.
|
|
* @param dstHeight
|
|
* the dst height.
|
|
* @param dstStride
|
|
* the dst stride.
|
|
* @param channels
|
|
* the channels.
|
|
* @param skipChannel
|
|
* the skip channel.
|
|
* @param offsets
|
|
* the offsets.
|
|
* @return the int.
|
|
*/
|
|
private native int ippFilter32f(float kernel[], int kWidth, int kHeight, int anchorX,
|
|
int anchorY, int borderType, Object src, int srcWidth, int srcHeight, int srcStride,
|
|
Object dst, int dstWidth, int dstHeight, int dstStride, int channels,
|
|
boolean skipChannel, int offsets[]);
|
|
}
|