summaryrefslogtreecommitdiffstats
path: root/awt/java/awt/image/AffineTransformOp.java
diff options
context:
space:
mode:
Diffstat (limited to 'awt/java/awt/image/AffineTransformOp.java')
-rw-r--r--awt/java/awt/image/AffineTransformOp.java618
1 files changed, 618 insertions, 0 deletions
diff --git a/awt/java/awt/image/AffineTransformOp.java b/awt/java/awt/image/AffineTransformOp.java
new file mode 100644
index 0000000..db25e1a
--- /dev/null
+++ b/awt/java/awt/image/AffineTransformOp.java
@@ -0,0 +1,618 @@
+/*
+ * 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, Denis M. Kishenko
+ * @version $Revision$
+ */
+
+package java.awt.image;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.*;
+import java.util.Arrays;
+
+import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
+import org.apache.harmony.awt.internal.nls.Messages;
+
+/**
+ * The AffineTransform class translates coordinates from 2D coordinates in the
+ * source image or Raster to 2D coordinates in the destination image or Raster
+ * using affine transformation. The number of bands in the source Raster should
+ * equal to the number of bands in the destination Raster.
+ *
+ * @since Android 1.0
+ */
+public class AffineTransformOp implements BufferedImageOp, RasterOp {
+
+ /**
+ * The Constant TYPE_NEAREST_NEIGHBOR indicates nearest-neighbor
+ * interpolation type.
+ */
+ public static final int TYPE_NEAREST_NEIGHBOR = 1;
+
+ /**
+ * The Constant TYPE_BILINEAR indicates bilinear interpolation type.
+ */
+ public static final int TYPE_BILINEAR = 2;
+
+ /**
+ * The Constant TYPE_BICUBIC indicates bi-cubic interpolation type.
+ */
+ public static final int TYPE_BICUBIC = 3;
+
+ /**
+ * The i type.
+ */
+ private int iType; // interpolation type
+
+ /**
+ * The at.
+ */
+ private AffineTransform at;
+
+ /**
+ * The hints.
+ */
+ private RenderingHints hints;
+
+ static {
+ // TODO - uncomment
+ // System.loadLibrary("imageops");
+ }
+
+ /**
+ * Instantiates a new AffineTransformOp with the specified AffineTransform
+ * and RenderingHints object which defines the interpolation type.
+ *
+ * @param xform
+ * the AffineTransform.
+ * @param hints
+ * the RenderingHints object which defines the interpolation
+ * type.
+ */
+ public AffineTransformOp(AffineTransform xform, RenderingHints hints) {
+ this(xform, TYPE_NEAREST_NEIGHBOR);
+ this.hints = hints;
+
+ if (hints != null) {
+ Object hint = hints.get(RenderingHints.KEY_INTERPOLATION);
+ if (hint != null) {
+ // Nearest neighbor is default
+ if (hint == RenderingHints.VALUE_INTERPOLATION_BILINEAR) {
+ this.iType = TYPE_BILINEAR;
+ } else if (hint == RenderingHints.VALUE_INTERPOLATION_BICUBIC) {
+ this.iType = TYPE_BICUBIC;
+ }
+ } else {
+ hint = hints.get(RenderingHints.KEY_RENDERING);
+ // Determine from rendering quality
+ if (hint == RenderingHints.VALUE_RENDER_QUALITY) {
+ this.iType = TYPE_BILINEAR;
+ // For speed use nearest neighbor
+ }
+ }
+ }
+ }
+
+ /**
+ * Instantiates a new AffineTransformOp with the specified AffineTransform
+ * and a specified interpolation type from the list of predefined
+ * interpolation types.
+ *
+ * @param xform
+ * the AffineTransform.
+ * @param interp
+ * the one of predefined interpolation types:
+ * TYPE_NEAREST_NEIGHBOR, TYPE_BILINEAR, or TYPE_BICUBIC.
+ */
+ public AffineTransformOp(AffineTransform xform, int interp) {
+ if (Math.abs(xform.getDeterminant()) <= Double.MIN_VALUE) {
+ // awt.24F=Unable to invert transform {0}
+ throw new ImagingOpException(Messages.getString("awt.24F", xform)); //$NON-NLS-1$
+ }
+
+ this.at = (AffineTransform)xform.clone();
+
+ if (interp != TYPE_NEAREST_NEIGHBOR && interp != TYPE_BILINEAR && interp != TYPE_BICUBIC) {
+ // awt.250=Unknown interpolation type: {0}
+ throw new IllegalArgumentException(Messages.getString("awt.250", interp)); //$NON-NLS-1$
+ }
+
+ this.iType = interp;
+ }
+
+ /**
+ * Gets the interpolation type.
+ *
+ * @return the interpolation type.
+ */
+ public final int getInterpolationType() {
+ return iType;
+ }
+
+ public final RenderingHints getRenderingHints() {
+ if (hints == null) {
+ Object value = null;
+
+ switch (iType) {
+ case TYPE_NEAREST_NEIGHBOR:
+ value = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
+ break;
+ case TYPE_BILINEAR:
+ value = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
+ break;
+ case TYPE_BICUBIC:
+ value = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
+ break;
+ default:
+ value = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
+ }
+
+ hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, value);
+ }
+
+ return hints;
+ }
+
+ /**
+ * Gets the affine transform associated with this AffineTransformOp.
+ *
+ * @return the AffineTransform.
+ */
+ public final AffineTransform getTransform() {
+ return (AffineTransform)at.clone();
+ }
+
+ public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
+ return at.transform(srcPt, dstPt);
+ }
+
+ public final Rectangle2D getBounds2D(BufferedImage src) {
+ return getBounds2D(src.getRaster());
+ }
+
+ public final Rectangle2D getBounds2D(Raster src) {
+ // We position source raster to (0,0) even if it is translated child
+ // raster.
+ // This means that we need only width and height of the src
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ float[] corners = {
+ 0, 0, width, 0, width, height, 0, height
+ };
+
+ at.transform(corners, 0, corners, 0, 4);
+
+ Rectangle2D.Float bounds = new Rectangle2D.Float(corners[0], corners[1], 0, 0);
+ bounds.add(corners[2], corners[3]);
+ bounds.add(corners[4], corners[5]);
+ bounds.add(corners[6], corners[7]);
+
+ return bounds;
+ }
+
+ public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
+ Rectangle2D newBounds = getBounds2D(src);
+
+ // Destination image should include (0,0) + positive part
+ // of the area bounded by newBounds (in source coordinate system).
+ double dstWidth = newBounds.getX() + newBounds.getWidth();
+ double dstHeight = newBounds.getY() + newBounds.getHeight();
+
+ if (dstWidth <= 0 || dstHeight <= 0) {
+ // awt.251=Transformed width ({0}) and height ({1}) should be
+ // greater than 0
+ throw new RasterFormatException(Messages.getString("awt.251", dstWidth, dstHeight)); //$NON-NLS-1$
+ }
+
+ if (destCM != null) {
+ return new BufferedImage(destCM, destCM.createCompatibleWritableRaster((int)dstWidth,
+ (int)dstHeight), destCM.isAlphaPremultiplied(), null);
+ }
+
+ ColorModel cm = src.getColorModel();
+
+ // Interpolation other than NN doesn't make any sense for index color
+ if (iType != TYPE_NEAREST_NEIGHBOR && cm instanceof IndexColorModel) {
+ return new BufferedImage((int)dstWidth, (int)dstHeight, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ // OK, we can get source color model
+ return new BufferedImage(cm, src.getRaster().createCompatibleWritableRaster((int)dstWidth,
+ (int)dstHeight), cm.isAlphaPremultiplied(), null);
+ }
+
+ public WritableRaster createCompatibleDestRaster(Raster src) {
+ // Here approach is other then in createCompatibleDestImage -
+ // destination should include only
+ // transformed image, but not (0,0) in source coordinate system
+
+ Rectangle2D newBounds = getBounds2D(src);
+ return src.createCompatibleWritableRaster((int)newBounds.getX(), (int)newBounds.getY(),
+ (int)newBounds.getWidth(), (int)newBounds.getHeight());
+ }
+
+ public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (src == dst) {
+ // awt.252=Source can't be same as the destination
+ throw new IllegalArgumentException(Messages.getString("awt.252")); //$NON-NLS-1$
+ }
+
+ ColorModel srcCM = src.getColorModel();
+ BufferedImage finalDst = null;
+
+ if (srcCM instanceof IndexColorModel
+ && (iType != TYPE_NEAREST_NEIGHBOR || srcCM.getPixelSize() % 8 != 0)) {
+ 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
+ if (slowFilter(src.getRaster(), dst.getRaster()) != 0) {
+ // awt.21F=Unable to transform source
+ throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
+ // TODO - uncomment
+ // if (ippFilter(src.getRaster(), dst.getRaster(), src.getType()) !=
+ // 0)
+ // throw new ImagingOpException ("Unable to transform source");
+ }
+
+ if (finalDst != null) {
+ Graphics2D g = finalDst.createGraphics();
+ g.setComposite(AlphaComposite.Src);
+ g.drawImage(dst, 0, 0, null);
+ } else {
+ finalDst = dst;
+ }
+
+ return finalDst;
+ }
+
+ public final WritableRaster filter(Raster src, WritableRaster dst) {
+ if (src == dst) {
+ // awt.252=Source can't be same as the destination
+ throw new IllegalArgumentException(Messages.getString("awt.252")); //$NON-NLS-1$
+ }
+
+ if (dst == null) {
+ dst = createCompatibleDestRaster(src);
+ } else if (src.getNumBands() != dst.getNumBands()) {
+ // awt.253=Different number of bands in source and destination
+ throw new IllegalArgumentException(Messages.getString("awt.253")); //$NON-NLS-1$
+ }
+
+ if (slowFilter(src, dst) != 0) {
+ // awt.21F=Unable to transform source
+ throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
+ // TODO - uncomment
+ // if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM) != 0)
+ // throw new ImagingOpException("Unable to transform source");
+ }
+
+ return dst;
+ }
+
+ // TODO remove when 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:
+ case BufferedImage.TYPE_BYTE_INDEXED: {
+ 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);
+ }
+
+ int dataTypeSize = DataBuffer.getDataTypeSize(srcSM.getDataType()) / 8;
+
+ srcStride = ((ComponentSampleModel)srcSM).getScanlineStride() * dataTypeSize;
+ dstStride = ((ComponentSampleModel)dstSM).getScanlineStride() * dataTypeSize;
+ } else if (srcSM instanceof SinglePixelPackedSampleModel
+ && dstSM instanceof SinglePixelPackedSampleModel) {
+ // Check SinglePixelPackedSampleModel
+ SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM;
+ SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM;
+
+ // No IPP function for this type
+ if (sppsm1.getDataType() == DataBuffer.TYPE_USHORT) {
+ return slowFilter(src, dst);
+ }
+
+ channels = sppsm1.getNumBands();
+ // Have IPP functions for 1, 3 and 4 channels
+ if (channels != 1 && channels != 3 && channels != 4) {
+ return slowFilter(src, dst);
+ }
+
+ // Check compatibility of sample models
+ if (sppsm1.getDataType() != sppsm2.getDataType()
+ || !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) {
+ channels = 4;
+ }
+
+ int dataTypeSize = DataBuffer.getDataTypeSize(sppsm1.getDataType()) / 8;
+
+ srcStride = sppsm1.getScanlineStride() * dataTypeSize;
+ dstStride = sppsm2.getScanlineStride() * dataTypeSize;
+ } 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();
+ }
+ }
+ }
+ }
+
+ double m00 = at.getScaleX();
+ double m01 = at.getShearX();
+ double m02 = at.getTranslateX();
+ double m10 = at.getShearY();
+ double m11 = at.getScaleY();
+ double m12 = at.getTranslateY();
+
+ 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 ippAffineTransform(m00, m01, m02, m10, m11, m12, srcData, src.getWidth(), src
+ .getHeight(), srcStride, dstData, dst.getWidth(), dst.getHeight(), dstStride,
+ iType, channels, skipChannel, offsets);
+ }
+
+ /**
+ * Slow filter.
+ *
+ * @param src
+ * the src.
+ * @param dst
+ * the dst.
+ * @return the int.
+ */
+ private int slowFilter(Raster src, WritableRaster dst) {
+ // TODO: make correct interpolation
+ // TODO: what if there are different data types?
+
+ Rectangle srcBounds = src.getBounds();
+ Rectangle dstBounds = dst.getBounds();
+ Rectangle normDstBounds = new Rectangle(0, 0, dstBounds.width, dstBounds.height);
+ Rectangle bounds = getBounds2D(src).getBounds().intersection(normDstBounds);
+
+ AffineTransform inv = null;
+ try {
+ inv = at.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ return -1;
+ }
+
+ double[] m = new double[6];
+ inv.getMatrix(m);
+
+ int minSrcX = srcBounds.x;
+ int minSrcY = srcBounds.y;
+ int maxSrcX = srcBounds.x + srcBounds.width;
+ int maxSrcY = srcBounds.y + srcBounds.height;
+
+ int minX = bounds.x + dstBounds.x;
+ int minY = bounds.y + dstBounds.y;
+ int maxX = minX + bounds.width;
+ int maxY = minY + bounds.height;
+
+ int hx = (int)(m[0] * 256);
+ int hy = (int)(m[1] * 256);
+ int vx = (int)(m[2] * 256);
+ int vy = (int)(m[3] * 256);
+ int sx = (int)(m[4] * 256) + hx * bounds.x + vx * bounds.y + (srcBounds.x) * 256;
+ int sy = (int)(m[5] * 256) + hy * bounds.x + vy * bounds.y + (srcBounds.y) * 256;
+
+ vx -= hx * bounds.width;
+ vy -= hy * bounds.width;
+
+ if (src.getTransferType() == dst.getTransferType()) {
+ for (int y = minY; y < maxY; y++) {
+ for (int x = minX; x < maxX; x++) {
+ int px = sx >> 8;
+ int py = sy >> 8;
+ if (px >= minSrcX && py >= minSrcY && px < maxSrcX && py < maxSrcY) {
+ Object val = src.getDataElements(px, py, null);
+ dst.setDataElements(x, y, val);
+ }
+ sx += hx;
+ sy += hy;
+ }
+ sx += vx;
+ sy += vy;
+ }
+ } else {
+ float pixel[] = null;
+ for (int y = minY; y < maxY; y++) {
+ for (int x = minX; x < maxX; x++) {
+ int px = sx >> 8;
+ int py = sy >> 8;
+ if (px >= minSrcX && py >= minSrcY && px < maxSrcX && py < maxSrcY) {
+ pixel = src.getPixel(px, py, pixel);
+ dst.setPixel(x, y, pixel);
+ }
+ sx += hx;
+ sy += hy;
+ }
+ sx += vx;
+ sy += vy;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Ipp affine transform.
+ *
+ * @param m00
+ * the m00.
+ * @param m01
+ * the m01.
+ * @param m02
+ * the m02.
+ * @param m10
+ * the m10.
+ * @param m11
+ * the m11.
+ * @param m12
+ * the m12.
+ * @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 iType
+ * the i type.
+ * @param channels
+ * the channels.
+ * @param skipChannel
+ * the skip channel.
+ * @param offsets
+ * the offsets.
+ * @return the int.
+ */
+ private native int ippAffineTransform(double m00, double m01, double m02, double m10,
+ double m11, double m12, Object src, int srcWidth, int srcHeight, int srcStride,
+ Object dst, int dstWidth, int dstHeight, int dstStride, int iType, int channels,
+ boolean skipChannel, int offsets[]);
+} \ No newline at end of file