diff options
Diffstat (limited to 'awt/java/awt/image/AffineTransformOp.java')
-rw-r--r-- | awt/java/awt/image/AffineTransformOp.java | 618 |
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 |