001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Oleg V. Khaschansky, Denis M. Kishenko
019: * @version $Revision$
020: */package java.awt.image;
021:
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.Rectangle2D;
024: import java.awt.geom.Point2D;
025: import java.awt.geom.NoninvertibleTransformException;
026: import java.awt.*;
027: import java.util.Arrays;
028:
029: import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
030: import org.apache.harmony.awt.internal.nls.Messages;
031:
032: public class AffineTransformOp implements BufferedImageOp, RasterOp {
033: public static final int TYPE_NEAREST_NEIGHBOR = 1;
034: public static final int TYPE_BILINEAR = 2;
035: public static final int TYPE_BICUBIC = 3;
036:
037: private int iType; // interpolation type
038: private AffineTransform at;
039: private RenderingHints hints;
040:
041: static {
042: // TODO - uncomment
043: //System.loadLibrary("imageops");
044: }
045:
046: public AffineTransformOp(AffineTransform xform, RenderingHints hints) {
047: this (xform, TYPE_NEAREST_NEIGHBOR);
048: this .hints = hints;
049:
050: if (hints != null) {
051: Object hint = hints.get(RenderingHints.KEY_INTERPOLATION);
052: if (hint != null) {
053: // Nearest neighbor is default
054: if (hint == RenderingHints.VALUE_INTERPOLATION_BILINEAR) {
055: this .iType = TYPE_BILINEAR;
056: } else if (hint == RenderingHints.VALUE_INTERPOLATION_BICUBIC) {
057: this .iType = TYPE_BICUBIC;
058: }
059: } else {
060: hint = hints.get(RenderingHints.KEY_RENDERING);
061: // Determine from rendering quality
062: if (hint == RenderingHints.VALUE_RENDER_QUALITY) {
063: this .iType = TYPE_BILINEAR;
064: // For speed use nearest neighbor
065: }
066: }
067: }
068: }
069:
070: public AffineTransformOp(AffineTransform xform, int interp) {
071: if (Math.abs(xform.getDeterminant()) <= Double.MIN_VALUE) {
072: // awt.24F=Unable to invert transform {0}
073: throw new ImagingOpException(Messages.getString(
074: "awt.24F", xform)); //$NON-NLS-1$
075: }
076:
077: this .at = (AffineTransform) xform.clone();
078:
079: if (interp != TYPE_NEAREST_NEIGHBOR && interp != TYPE_BILINEAR
080: && interp != TYPE_BICUBIC) {
081: // awt.250=Unknown interpolation type: {0}
082: throw new IllegalArgumentException(Messages.getString(
083: "awt.250", interp)); //$NON-NLS-1$
084: }
085:
086: this .iType = interp;
087: }
088:
089: public final int getInterpolationType() {
090: return iType;
091: }
092:
093: public final RenderingHints getRenderingHints() {
094: if (hints == null) {
095: Object value = null;
096:
097: switch (iType) {
098: case TYPE_NEAREST_NEIGHBOR:
099: value = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
100: break;
101: case TYPE_BILINEAR:
102: value = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
103: break;
104: case TYPE_BICUBIC:
105: value = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
106: break;
107: default:
108: value = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
109: }
110:
111: hints = new RenderingHints(
112: RenderingHints.KEY_INTERPOLATION, value);
113: }
114:
115: return hints;
116: }
117:
118: public final AffineTransform getTransform() {
119: return (AffineTransform) at.clone();
120: }
121:
122: public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
123: return at.transform(srcPt, dstPt);
124: }
125:
126: public final Rectangle2D getBounds2D(BufferedImage src) {
127: return getBounds2D(src.getRaster());
128: }
129:
130: public final Rectangle2D getBounds2D(Raster src) {
131: // We position source raster to (0,0) even if it is translated child raster.
132: // This means that we need only width and height of the src
133: int width = src.getWidth();
134: int height = src.getHeight();
135:
136: float[] corners = { 0, 0, width, 0, width, height, 0, height };
137:
138: at.transform(corners, 0, corners, 0, 4);
139:
140: Rectangle2D.Float bounds = new Rectangle2D.Float(corners[0],
141: corners[1], 0, 0);
142: bounds.add(corners[2], corners[3]);
143: bounds.add(corners[4], corners[5]);
144: bounds.add(corners[6], corners[7]);
145:
146: return bounds;
147: }
148:
149: public BufferedImage createCompatibleDestImage(BufferedImage src,
150: ColorModel destCM) {
151: Rectangle2D newBounds = getBounds2D(src);
152:
153: // Destination image should include (0,0) + positive part
154: // of the area bounded by newBounds (in source coordinate system).
155: double dstWidth = newBounds.getX() + newBounds.getWidth();
156: double dstHeight = newBounds.getY() + newBounds.getHeight();
157:
158: if (dstWidth <= 0 || dstHeight <= 0) {
159: // awt.251=Transformed width ({0}) and height ({1}) should be greater than 0
160: throw new RasterFormatException(Messages.getString(
161: "awt.251", dstWidth, dstHeight)); //$NON-NLS-1$
162: }
163:
164: if (destCM != null) {
165: return new BufferedImage(destCM, destCM
166: .createCompatibleWritableRaster((int) dstWidth,
167: (int) dstHeight), destCM
168: .isAlphaPremultiplied(), null);
169: }
170:
171: ColorModel cm = src.getColorModel();
172:
173: // Interpolation other than NN doesn't make any sense for index color
174: if (iType != TYPE_NEAREST_NEIGHBOR
175: && cm instanceof IndexColorModel) {
176: return new BufferedImage((int) dstWidth, (int) dstHeight,
177: BufferedImage.TYPE_INT_ARGB);
178: }
179:
180: // OK, we can get source color model
181: return new BufferedImage(cm, src.getRaster()
182: .createCompatibleWritableRaster((int) dstWidth,
183: (int) dstHeight), cm.isAlphaPremultiplied(),
184: null);
185: }
186:
187: public WritableRaster createCompatibleDestRaster(Raster src) {
188: // Here approach is other then in createCompatibleDestImage -
189: // destination should include only
190: // transformed image, but not (0,0) in source coordinate system
191:
192: Rectangle2D newBounds = getBounds2D(src);
193: return src.createCompatibleWritableRaster((int) newBounds
194: .getX(), (int) newBounds.getY(), (int) newBounds
195: .getWidth(), (int) newBounds.getHeight());
196: }
197:
198: public final BufferedImage filter(BufferedImage src,
199: BufferedImage dst) {
200: if (src == dst) {
201: // awt.252=Source can't be same as the destination
202: throw new IllegalArgumentException(Messages
203: .getString("awt.252")); //$NON-NLS-1$
204: }
205:
206: ColorModel srcCM = src.getColorModel();
207: BufferedImage finalDst = null;
208:
209: if (srcCM instanceof IndexColorModel
210: && (iType != TYPE_NEAREST_NEIGHBOR || srcCM
211: .getPixelSize() % 8 != 0)) {
212: src = ((IndexColorModel) srcCM).convertToIntDiscrete(src
213: .getRaster(), true);
214: srcCM = src.getColorModel();
215: }
216:
217: if (dst == null) {
218: dst = createCompatibleDestImage(src, srcCM);
219: } else {
220: if (!srcCM.equals(dst.getColorModel())) {
221: // Treat BufferedImage.TYPE_INT_RGB and BufferedImage.TYPE_INT_ARGB as same
222: if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src
223: .getType() == BufferedImage.TYPE_INT_ARGB) && (dst
224: .getType() == BufferedImage.TYPE_INT_RGB || dst
225: .getType() == BufferedImage.TYPE_INT_ARGB))) {
226: finalDst = dst;
227: dst = createCompatibleDestImage(src, srcCM);
228: }
229: }
230: }
231:
232: // Skip alpha channel for TYPE_INT_RGB images
233: if (slowFilter(src.getRaster(), dst.getRaster()) != 0) {
234: // awt.21F=Unable to transform source
235: throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
236: // TODO - uncomment
237: //if (ippFilter(src.getRaster(), dst.getRaster(), src.getType()) != 0)
238: //throw new ImagingOpException ("Unable to transform source");
239: }
240:
241: if (finalDst != null) {
242: Graphics2D g = finalDst.createGraphics();
243: g.setComposite(AlphaComposite.Src);
244: g.drawImage(dst, 0, 0, null);
245: } else {
246: finalDst = dst;
247: }
248:
249: return finalDst;
250: }
251:
252: public final WritableRaster filter(Raster src, WritableRaster dst) {
253: if (src == dst) {
254: // awt.252=Source can't be same as the destination
255: throw new IllegalArgumentException(Messages
256: .getString("awt.252")); //$NON-NLS-1$
257: }
258:
259: if (dst == null) {
260: dst = createCompatibleDestRaster(src);
261: } else if (src.getNumBands() != dst.getNumBands()) {
262: // awt.253=Different number of bands in source and destination
263: throw new IllegalArgumentException(Messages
264: .getString("awt.253")); //$NON-NLS-1$
265: }
266:
267: if (slowFilter(src, dst) != 0) {
268: // awt.21F=Unable to transform source
269: throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
270: // TODO - uncomment
271: //if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM) != 0)
272: // throw new ImagingOpException("Unable to transform source");
273: }
274:
275: return dst;
276: }
277:
278: // TODO remove when method is used
279: @SuppressWarnings("unused")
280: private int ippFilter(Raster src, WritableRaster dst, int imageType) {
281: int srcStride, dstStride;
282: boolean skipChannel = false;
283: int channels;
284: int offsets[] = null;
285:
286: switch (imageType) {
287: case BufferedImage.TYPE_INT_RGB:
288: case BufferedImage.TYPE_INT_BGR: {
289: channels = 4;
290: srcStride = src.getWidth() * 4;
291: dstStride = dst.getWidth() * 4;
292: skipChannel = true;
293: break;
294: }
295:
296: case BufferedImage.TYPE_INT_ARGB:
297: case BufferedImage.TYPE_INT_ARGB_PRE:
298: case BufferedImage.TYPE_4BYTE_ABGR:
299: case BufferedImage.TYPE_4BYTE_ABGR_PRE: {
300: channels = 4;
301: srcStride = src.getWidth() * 4;
302: dstStride = dst.getWidth() * 4;
303: break;
304: }
305:
306: case BufferedImage.TYPE_BYTE_GRAY:
307: case BufferedImage.TYPE_BYTE_INDEXED: {
308: channels = 1;
309: srcStride = src.getWidth();
310: dstStride = dst.getWidth();
311: break;
312: }
313:
314: case BufferedImage.TYPE_3BYTE_BGR: {
315: channels = 3;
316: srcStride = src.getWidth() * 3;
317: dstStride = dst.getWidth() * 3;
318: break;
319: }
320:
321: case BufferedImage.TYPE_USHORT_GRAY: // TODO - could be done in native code?
322: case BufferedImage.TYPE_USHORT_565_RGB:
323: case BufferedImage.TYPE_USHORT_555_RGB:
324: case BufferedImage.TYPE_BYTE_BINARY: {
325: return slowFilter(src, dst);
326: }
327:
328: default: {
329: SampleModel srcSM = src.getSampleModel();
330: SampleModel dstSM = dst.getSampleModel();
331:
332: if (srcSM instanceof PixelInterleavedSampleModel
333: && dstSM instanceof PixelInterleavedSampleModel) {
334: // Check PixelInterleavedSampleModel
335: if (srcSM.getDataType() != DataBuffer.TYPE_BYTE
336: || dstSM.getDataType() != DataBuffer.TYPE_BYTE) {
337: return slowFilter(src, dst);
338: }
339:
340: channels = srcSM.getNumBands(); // Have IPP functions for 1, 3 and 4 channels
341: if (channels != 1 && channels != 3 && channels != 4) {
342: return slowFilter(src, dst);
343: }
344:
345: int dataTypeSize = DataBuffer.getDataTypeSize(srcSM
346: .getDataType()) / 8;
347:
348: srcStride = ((ComponentSampleModel) srcSM)
349: .getScanlineStride()
350: * dataTypeSize;
351: dstStride = ((ComponentSampleModel) dstSM)
352: .getScanlineStride()
353: * dataTypeSize;
354: } else if (srcSM instanceof SinglePixelPackedSampleModel
355: && dstSM instanceof SinglePixelPackedSampleModel) {
356: // Check SinglePixelPackedSampleModel
357: SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel) srcSM;
358: SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel) dstSM;
359:
360: // No IPP function for this type
361: if (sppsm1.getDataType() == DataBuffer.TYPE_USHORT) {
362: return slowFilter(src, dst);
363: }
364:
365: channels = sppsm1.getNumBands();
366: // Have IPP functions for 1, 3 and 4 channels
367: if (channels != 1 && channels != 3 && channels != 4) {
368: return slowFilter(src, dst);
369: }
370:
371: // Check compatibility of sample models
372: if (sppsm1.getDataType() != sppsm2.getDataType()
373: || !Arrays.equals(sppsm1.getBitOffsets(),
374: sppsm2.getBitOffsets())
375: || !Arrays.equals(sppsm1.getBitMasks(), sppsm2
376: .getBitMasks())) {
377: return slowFilter(src, dst);
378: }
379:
380: for (int i = 0; i < channels; i++) {
381: if (sppsm1.getSampleSize(i) != 8) {
382: return slowFilter(src, dst);
383: }
384: }
385:
386: if (channels == 3) {
387: channels = 4;
388: }
389:
390: int dataTypeSize = DataBuffer.getDataTypeSize(sppsm1
391: .getDataType()) / 8;
392:
393: srcStride = sppsm1.getScanlineStride() * dataTypeSize;
394: dstStride = sppsm2.getScanlineStride() * dataTypeSize;
395: } else {
396: return slowFilter(src, dst);
397: }
398:
399: // Fill offsets if there's a child raster
400: if (src.getParent() != null || dst.getParent() != null) {
401: if (src.getSampleModelTranslateX() != 0
402: || src.getSampleModelTranslateY() != 0
403: || dst.getSampleModelTranslateX() != 0
404: || dst.getSampleModelTranslateY() != 0) {
405: offsets = new int[4];
406: offsets[0] = -src.getSampleModelTranslateX()
407: + src.getMinX();
408: offsets[1] = -src.getSampleModelTranslateY()
409: + src.getMinY();
410: offsets[2] = -dst.getSampleModelTranslateX()
411: + dst.getMinX();
412: offsets[3] = -dst.getSampleModelTranslateY()
413: + dst.getMinY();
414: }
415: }
416: }
417: }
418:
419: double m00 = at.getScaleX();
420: double m01 = at.getShearX();
421: double m02 = at.getTranslateX();
422: double m10 = at.getShearY();
423: double m11 = at.getScaleY();
424: double m12 = at.getTranslateY();
425:
426: Object srcData, dstData;
427: AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor
428: .getInstance();
429: try {
430: srcData = dbAccess.getData(src.getDataBuffer());
431: dstData = dbAccess.getData(dst.getDataBuffer());
432: } catch (IllegalArgumentException e) {
433: return -1; // Unknown data buffer type
434: }
435:
436: return ippAffineTransform(m00, m01, m02, m10, m11, m12,
437: srcData, src.getWidth(), src.getHeight(), srcStride,
438: dstData, dst.getWidth(), dst.getHeight(), dstStride,
439: iType, channels, skipChannel, offsets);
440: }
441:
442: private int slowFilter(Raster src, WritableRaster dst) {
443: // TODO: make correct interpolation
444: // TODO: what if there are different data types?
445:
446: Rectangle srcBounds = src.getBounds();
447: Rectangle dstBounds = dst.getBounds();
448: Rectangle normDstBounds = new Rectangle(0, 0, dstBounds.width,
449: dstBounds.height);
450: Rectangle bounds = getBounds2D(src).getBounds().intersection(
451: normDstBounds);
452:
453: AffineTransform inv = null;
454: try {
455: inv = at.createInverse();
456: } catch (NoninvertibleTransformException e) {
457: return -1;
458: }
459:
460: double[] m = new double[6];
461: inv.getMatrix(m);
462:
463: int minSrcX = srcBounds.x;
464: int minSrcY = srcBounds.y;
465: int maxSrcX = srcBounds.x + srcBounds.width;
466: int maxSrcY = srcBounds.y + srcBounds.height;
467:
468: int minX = bounds.x + dstBounds.x;
469: int minY = bounds.y + dstBounds.y;
470: int maxX = minX + bounds.width;
471: int maxY = minY + bounds.height;
472:
473: int hx = (int) (m[0] * 256);
474: int hy = (int) (m[1] * 256);
475: int vx = (int) (m[2] * 256);
476: int vy = (int) (m[3] * 256);
477: int sx = (int) (m[4] * 256) + hx * bounds.x + vx * bounds.y
478: + (srcBounds.x) * 256;
479: int sy = (int) (m[5] * 256) + hy * bounds.x + vy * bounds.y
480: + (srcBounds.y) * 256;
481:
482: vx -= hx * bounds.width;
483: vy -= hy * bounds.width;
484:
485: if (src.getTransferType() == dst.getTransferType()) {
486: for (int y = minY; y < maxY; y++) {
487: for (int x = minX; x < maxX; x++) {
488: int px = sx >> 8;
489: int py = sy >> 8;
490: if (px >= minSrcX && py >= minSrcY && px < maxSrcX
491: && py < maxSrcY) {
492: Object val = src.getDataElements(px, py, null);
493: dst.setDataElements(x, y, val);
494: }
495: sx += hx;
496: sy += hy;
497: }
498: sx += vx;
499: sy += vy;
500: }
501: } else {
502: float pixel[] = null;
503: for (int y = minY; y < maxY; y++) {
504: for (int x = minX; x < maxX; x++) {
505: int px = sx >> 8;
506: int py = sy >> 8;
507: if (px >= minSrcX && py >= minSrcY && px < maxSrcX
508: && py < maxSrcY) {
509: pixel = src.getPixel(px, py, pixel);
510: dst.setPixel(x, y, pixel);
511: }
512: sx += hx;
513: sy += hy;
514: }
515: sx += vx;
516: sy += vy;
517: }
518: }
519:
520: return 0;
521: }
522:
523: private native int ippAffineTransform(double m00, double m01,
524: double m02, double m10, double m11, double m12, Object src,
525: int srcWidth, int srcHeight, int srcStride, Object dst,
526: int dstWidth, int dstHeight, int dstStride, int iType,
527: int channels, boolean skipChannel, int offsets[]);
528: }
|