001: /*
002: * $RCSfile: RotateCRIF.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/11/21 22:49:40 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.RenderingHints;
015: import java.awt.geom.AffineTransform;
016: import java.awt.geom.NoninvertibleTransformException;
017: import java.awt.image.DataBuffer;
018: import java.awt.image.MultiPixelPackedSampleModel;
019: import java.awt.image.RenderedImage;
020: import java.awt.image.SampleModel;
021: import java.awt.image.renderable.RenderableImage;
022: import java.awt.image.renderable.RenderableImageOp;
023: import java.awt.image.renderable.RenderContext;
024: import java.awt.image.renderable.ParameterBlock;
025: import java.awt.image.renderable.RenderedImageFactory;
026: import javax.media.jai.CRIFImpl;
027: import javax.media.jai.BorderExtender;
028: import javax.media.jai.ImageLayout;
029: import javax.media.jai.Interpolation;
030: import javax.media.jai.InterpolationBicubic;
031: import javax.media.jai.InterpolationBicubic2;
032: import javax.media.jai.InterpolationBilinear;
033: import javax.media.jai.InterpolationNearest;
034: import javax.media.jai.InterpolationTable;
035: import javax.media.jai.JAI;
036: import javax.media.jai.OpImage;
037: import javax.media.jai.PlanarImage;
038: import javax.media.jai.RenderedOp;
039: import com.sun.media.jai.opimage.PointMapperOpImage;
040: import java.util.Map;
041: import java.awt.geom.Rectangle2D;
042: import java.awt.geom.Point2D;
043:
044: /**
045: * @since EA4
046: * @see AffineOpimage
047: */
048: public class RotateCRIF extends CRIFImpl {
049:
050: /** Constructor. */
051: public RotateCRIF() {
052: super ("rotate");
053: }
054:
055: /**
056: * Creates an rotate operation.
057: */
058: public RenderedImage create(ParameterBlock paramBlock,
059: RenderingHints renderHints) {
060: // Get ImageLayout from renderHints if any.
061: ImageLayout layout = RIFUtil.getImageLayoutHint(renderHints);
062:
063: // Get BorderExtender from renderHints if any.
064: BorderExtender extender = RIFUtil
065: .getBorderExtenderHint(renderHints);
066:
067: RenderedImage source = paramBlock.getRenderedSource(0);
068:
069: float x_center = paramBlock.getFloatParameter(0);
070: float y_center = paramBlock.getFloatParameter(1);
071: float angle = paramBlock.getFloatParameter(2);
072:
073: Object arg1 = paramBlock.getObjectParameter(3);
074: Interpolation interp = (Interpolation) arg1;
075:
076: double[] backgroundValues = (double[]) paramBlock
077: .getObjectParameter(4);
078:
079: SampleModel sm = source.getSampleModel();
080: boolean isBinary = (sm instanceof MultiPixelPackedSampleModel)
081: && (sm.getSampleSize(0) == 1)
082: && (sm.getDataType() == DataBuffer.TYPE_BYTE
083: || sm.getDataType() == DataBuffer.TYPE_USHORT || sm
084: .getDataType() == DataBuffer.TYPE_INT);
085:
086: //
087: // Convert angle to degrees (within some precision) given PI's
088: // transcendental nature. All this, to check if we can call
089: // simpler methods like Copy or Transpose for certain angles
090: // viz., 0, 90, 180, 270, 360, 450, .....
091: //
092: double tmp_angle = (180.0 / Math.PI) * angle;
093: double rnd_angle = Math.round(tmp_angle);
094:
095: //
096: // Represent the angle as an AffineTransform
097: //
098: AffineTransform transform = AffineTransform.getRotateInstance(
099: angle, x_center, y_center);
100:
101: // Check if angle is (nearly) integral
102: if (Math.abs(rnd_angle - tmp_angle) < 0.0001) {
103: int dangle = (int) rnd_angle % 360;
104:
105: // Shift dangle into the range [0..359].
106: if (dangle < 0) {
107: dangle += 360;
108: }
109:
110: //
111: // Do a copy if angle is 0 degrees or
112: // multiple of 360 degrees
113: //
114: if (dangle == 0) {
115: return new CopyOpImage(source, renderHints, layout);
116: }
117:
118: int ix_center = (int) Math.round(x_center);
119: int iy_center = (int) Math.round(y_center);
120:
121: // Do a transpose if angle is mutiple of 270, 180, 90 degrees
122: // and the translation is (nearly) integral.
123: if (((dangle % 90) == 0)
124: && (Math.abs(x_center - ix_center) < 0.0001)
125: && (Math.abs(y_center - iy_center) < 0.0001)) {
126:
127: int transType = -1;
128: int rotMinX = 0;
129: int rotMinY = 0;
130:
131: int sourceMinX = source.getMinX();
132: int sourceMinY = source.getMinY();
133: int sourceMaxX = sourceMinX + source.getWidth();
134: int sourceMaxY = sourceMinY + source.getHeight();
135:
136: if (dangle == 90) {
137: transType = 4;
138: rotMinX = ix_center - (sourceMaxY - iy_center);
139: rotMinY = iy_center - (ix_center - sourceMinX);
140: } else if (dangle == 180) {
141: transType = 5;
142: rotMinX = 2 * ix_center - sourceMaxX;
143: rotMinY = 2 * iy_center - sourceMaxY;
144: } else { // dangle == 270
145: transType = 6;
146: rotMinX = ix_center - (iy_center - sourceMinY);
147: rotMinY = iy_center - (sourceMaxX - ix_center);
148: }
149:
150: RenderedImage trans;
151: if (isBinary) {
152: trans = new TransposeBinaryOpImage(source,
153: renderHints, layout, transType);
154: } else {
155: trans = new TransposeOpImage(source, renderHints,
156: layout, transType);
157: }
158:
159: // Determine current image origin
160: int imMinX = trans.getMinX();
161: int imMinY = trans.getMinY();
162:
163: // TranslateIntOpImage can't deal with ImageLayout hint
164: if (layout == null) {
165: // Translate image and return it
166: OpImage intermediateImage = new TranslateIntOpImage(
167: trans, renderHints, rotMinX - imMinX,
168: rotMinY - imMinY);
169: try {
170: return new PointMapperOpImage(
171: intermediateImage, renderHints,
172: transform);
173: } catch (NoninvertibleTransformException nite) {
174: return intermediateImage;
175: }
176: } else {
177: ParameterBlock pbScale = new ParameterBlock();
178: pbScale.addSource(trans);
179: pbScale.add(0F);
180: pbScale.add(0F);
181: pbScale.add(rotMinX - imMinX);
182: pbScale.add(rotMinY - imMinY);
183: pbScale.add(interp);
184: PlanarImage intermediateImage = JAI.create("scale",
185: pbScale, renderHints).getRendering();
186: try {
187: return new PointMapperOpImage(
188: intermediateImage, renderHints,
189: transform);
190: } catch (NoninvertibleTransformException nite) {
191: return intermediateImage;
192: }
193: }
194: }
195: }
196:
197: //
198: // At this point we know that we cannot call other operations.
199: // Have to do Affine.
200: //
201:
202: //
203: // Do the Affine operation
204: //
205: if (interp instanceof InterpolationNearest) {
206: if (isBinary) {
207: return new AffineNearestBinaryOpImage(source, extender,
208: renderHints, layout, transform, interp,
209: backgroundValues);
210: } else {
211: return new AffineNearestOpImage(source, extender,
212: renderHints, layout, transform, interp,
213: backgroundValues);
214: }
215: } else if (interp instanceof InterpolationBilinear) {
216: return new AffineBilinearOpImage(source, extender,
217: renderHints, layout, transform, interp,
218: backgroundValues);
219: } else if (interp instanceof InterpolationBicubic) {
220: return new AffineBicubicOpImage(source, extender,
221: renderHints, layout, transform, interp,
222: backgroundValues);
223: } else if (interp instanceof InterpolationBicubic2) {
224: return new AffineBicubic2OpImage(source, extender,
225: renderHints, layout, transform, interp,
226: backgroundValues);
227: } else {
228: return new AffineGeneralOpImage(source, extender,
229: renderHints, layout, transform, interp,
230: backgroundValues);
231: }
232: }
233:
234: /**
235: * Creates a new instance of <code>AffineOpImage</code>
236: * in the renderable layer. This method satisfies the
237: * implementation of CRIF.
238: */
239: public RenderedImage create(RenderContext renderContext,
240: ParameterBlock paramBlock) {
241: return paramBlock.getRenderedSource(0);
242: }
243:
244: /**
245: * Maps the output RenderContext into the RenderContext for the ith
246: * source.
247: * This method satisfies the implementation of CRIF.
248: *
249: * @param i The index of the source image.
250: * @param renderContext The renderContext being applied to the operation.
251: * @param paramBlock The ParameterBlock containing the sources
252: * and the translation factors.
253: * @param image The RenderableImageOp from which this method
254: * was called.
255: */
256: public RenderContext mapRenderContext(int i,
257: RenderContext renderContext, ParameterBlock paramBlock,
258: RenderableImage image) {
259: float x_center = paramBlock.getFloatParameter(0);
260: float y_center = paramBlock.getFloatParameter(1);
261: float angle = paramBlock.getFloatParameter(2);
262:
263: AffineTransform rotate = AffineTransform.getRotateInstance(
264: angle, x_center, y_center);
265:
266: RenderContext RC = (RenderContext) renderContext.clone();
267: AffineTransform usr2dev = RC.getTransform();
268: usr2dev.concatenate(rotate);
269: RC.setTransform(usr2dev);
270: return RC;
271: }
272:
273: /**
274: * Gets the bounding box for the output of <code>TranslateOpImage</code>.
275: * This method satisfies the implementation of CRIF.
276: */
277: public Rectangle2D getBounds2D(ParameterBlock paramBlock) {
278: RenderableImage source = paramBlock.getRenderableSource(0);
279:
280: float x_center = paramBlock.getFloatParameter(0);
281: float y_center = paramBlock.getFloatParameter(1);
282: float angle = paramBlock.getFloatParameter(2);
283: Interpolation interp = (Interpolation) paramBlock
284: .getObjectParameter(3);
285:
286: //
287: // Convert angle to degrees (within some precision) given PI's
288: // transcendantal nature. All this, to check if we can call
289: // simpler methods like Copy or Transpose for certain angles
290: // viz., 0, 90, 180, 270, 360, 450, .....
291: //
292: int dangle = 0;
293: double tmp_angle = 180.0F * angle / Math.PI;
294: double rnd_angle = Math.round(tmp_angle);
295:
296: if (Math.abs(rnd_angle - tmp_angle) < 0.0001) {
297: dangle = (int) rnd_angle;
298: } else {
299: dangle = (int) tmp_angle;
300: }
301:
302: //
303: // It's a copy if angle is 0 degrees or multiple of 360 degrees
304: //
305: if (dangle % 360 == 0) {
306: return new Rectangle2D.Float(source.getMinX(), source
307: .getMinY(), source.getWidth(), source.getHeight());
308: }
309:
310: //
311: // It's a transpose if angle is mutiple of 270, 180, 90 degrees
312: //
313: float x0 = (float) source.getMinX();
314: float y0 = (float) source.getMinY();
315: float s_width = (float) source.getWidth();
316: float s_height = (float) source.getHeight();
317: float x1 = x0 + s_width - 1;
318: float y1 = y0 + s_height - 1;
319:
320: float tx0 = 0;
321: float ty0 = 0;
322: float tx1 = 0;
323: float ty1 = 0;
324:
325: if (dangle % 270 == 0) {
326: if (dangle < 0) {
327: // -270 degrees
328: tx0 = s_height - y1 - 1;
329: ty0 = x0;
330: tx1 = s_height - y0 - 1;
331: ty1 = x1;
332: return new Rectangle2D.Float(tx0, ty0, tx1 - tx0 + 1,
333: ty1 - ty0 + 1);
334: } else {
335: // 270 degrees
336: tx0 = y0;
337: ty0 = s_width - x1 - 1;
338: tx1 = y1;
339: ty1 = s_width - x0 - 1;
340: return new Rectangle2D.Float(tx0, ty0, tx1 - tx0 + 1,
341: ty1 - ty0 + 1);
342: }
343: }
344:
345: if (dangle % 180 == 0) {
346: tx0 = s_width - x1 - 1;
347: ty0 = s_height - y1 - 1;
348: tx1 = s_width - x0 - 1;
349: ty1 = s_height - y0 - 1;
350: // 180 degrees
351: return new Rectangle2D.Float(tx0, ty0, tx1 - tx0 + 1, ty1
352: - ty0 + 1);
353: }
354:
355: if (dangle % 90 == 0) {
356: if (dangle < 0) {
357: // -90 degrees
358: tx0 = y0;
359: ty0 = s_width - x1 - 1;
360: tx1 = y1;
361: ty1 = s_width - x0 - 1;
362: return new Rectangle2D.Float(tx0, ty0, tx1 - tx0 + 1,
363: ty1 - ty0 + 1);
364: } else {
365: // 90 degrees
366: tx0 = s_height - y1 - 1;
367: ty0 = x0;
368: tx1 = s_height - y0 - 1;
369: ty1 = x1;
370: return new Rectangle2D.Float(tx0, ty0, tx1 - tx0 + 1,
371: ty1 - ty0 + 1);
372: }
373: }
374:
375: //
376: // It's a Affine
377: //
378: AffineTransform rotate = AffineTransform.getRotateInstance(
379: angle, x_center, y_center);
380:
381: //
382: // Get sx0,sy0 coordinates and width & height of the source
383: //
384: float sx0 = (float) source.getMinX();
385: float sy0 = (float) source.getMinY();
386: float sw = (float) source.getWidth();
387: float sh = (float) source.getHeight();
388:
389: //
390: // The 4 points (clockwise order) are
391: // (sx0, sy0), (sx0+sw, sy0)
392: // (sx0, sy0+sh), (sx0+sw, sy0+sh)
393: //
394: Point2D[] pts = new Point2D[4];
395: pts[0] = new Point2D.Float(sx0, sy0);
396: pts[1] = new Point2D.Float((sx0 + sw), sy0);
397: pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
398: pts[3] = new Point2D.Float(sx0, (sy0 + sh));
399:
400: // Forward map
401: rotate.transform(pts, 0, pts, 0, 4);
402:
403: float dx0 = Float.MAX_VALUE;
404: float dy0 = Float.MAX_VALUE;
405: float dx1 = -Float.MAX_VALUE;
406: float dy1 = -Float.MAX_VALUE;
407: for (int i = 0; i < 4; i++) {
408: float px = (float) pts[i].getX();
409: float py = (float) pts[i].getY();
410:
411: dx0 = Math.min(dx0, px);
412: dy0 = Math.min(dy0, py);
413: dx1 = Math.max(dx1, px);
414: dy1 = Math.max(dy1, py);
415: }
416:
417: //
418: // Get the width & height of the resulting bounding box.
419: // This is set on the layout
420: //
421: float lw = dx1 - dx0;
422: float lh = dy1 - dy0;
423:
424: return new Rectangle2D.Float(dx0, dy0, lw, lh);
425: }
426: }
|