0001: /*
0002: * $RCSfile: FilteredSubsampleOpImage.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.1 $
0009: * $Date: 2005/02/11 04:56:26 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.opimage;
0013:
0014: import java.awt.Rectangle;
0015: import java.awt.geom.Point2D;
0016: import java.awt.image.Raster;
0017: import java.awt.image.RenderedImage;
0018: import java.awt.image.WritableRaster;
0019: import java.awt.image.DataBuffer;
0020: import java.awt.image.renderable.ParameterBlock;
0021: import javax.media.jai.ImageLayout;
0022: import java.util.Map;
0023: import javax.media.jai.GeometricOpImage;
0024: import javax.media.jai.BorderExtender;
0025: import javax.media.jai.Interpolation;
0026: import javax.media.jai.InterpolationNearest;
0027: import javax.media.jai.InterpolationBilinear;
0028: import javax.media.jai.InterpolationBicubic;
0029: import javax.media.jai.InterpolationBicubic2;
0030: import javax.media.jai.RasterAccessor;
0031: import javax.media.jai.RasterFormatTag;
0032: import com.sun.media.jai.util.ImageUtil;
0033:
0034: /**
0035: * <p> A class extending <code>GeometricOpImage</code> to
0036: * subsample and antialias filter images. Image scaling operations
0037: * require rectilinear backwards mapping and padding by the resampling
0038: * and filter dimensions.
0039: *
0040: * <p> When applying scale factors of scaleX, scaleY to a source image
0041: * with width of src_width and height of src_height, the resulting image
0042: * is defined to have the following bounds:
0043: *
0044: * <code></pre>
0045: * dst minX = round(src minX / scaleX) <br>
0046: * dst minY = round(src minY / scaleY) <br>
0047: * dst width = round(src width / scaleX) <br>
0048: * dst height = round(src height / scaleY) <br>
0049: * </pre></code>
0050: *
0051: * <p> The applied filter is quadrant symmetric (typically antialias + resample). The
0052: * filter is product-separable, quadrant symmetric, and is defined by half of its
0053: * span. For example, if the input filter, qsFilter, was of size 3, it would have
0054: * width and height 5 and have the symmetric form:
0055: * qs[2] qs[1] qs[0] qs[1] qs[2]
0056: * Because we have chosen to keep the filters in compact form we need
0057: * to keep track of parity.
0058: *
0059: * <p> A fully expanded 5 by 5 kernel has format (25 entries defined by
0060: * only 3 entries):
0061: *
0062: * <code>
0063: * <p align=center> qs[2]*qs[2] qs[2]*qs[1] qs[2]*qs[0] qs[2]*qs[1] qs[2]*qs[2] <br>
0064: *
0065: * qs[1]*qs[2] qs[1]*qs[1] qs[1]*qs[0] qs[1]*qs[1] qs[1]*qs[2] <br>
0066: *
0067: * qs[0]*qs[2] qs[0]*qs[1] qs[0]*qs[0] qs[0]*qs[1] qs[0]*qs[2] <br>
0068: *
0069: * qs[1]*qs[2] qs[1]*qs[1] qs[1]*qs[0] qs[1]*qs[1] qs[1]*qs[2] <br>
0070: *
0071: * qs[2]*qs[2] qs[2]*qs[1] qs[2]*qs[0] qs[2]*qs[1] qs[2]*qs[2]
0072: * </p> </code>
0073: *
0074: * <p> Horizontal and vertical kernels representing convolved resample and qsFilter
0075: * kernels are computed from the input filter, the resample type, and because the
0076: * downsample factors affect resample weights, the downsample scale factors. If the
0077: * scale factors are odd, then the resample kernel is unity. Parity is used to
0078: * signify whether the symmetric kernel has a double center (even parity) or a
0079: * single center value (odd parity).
0080: *
0081: * <p> This operator is similar to the image scale operator. Important
0082: * differences are described here. The coordinate transformation differences
0083: * between the FilteredDownsampleOpImage and the ScaleOpImage operators can be
0084: * understood by comparing their mapping equations directly.
0085: *
0086: * <p> For the scale operator, the destination (D) to source (S) mapping
0087: * equations are given by
0088: *
0089: * <code>
0090: * <p> xS = (xD - xTrans)/xScale <br>
0091: * yS = (yD - yTrans)/yScale
0092: * </code>
0093: *
0094: * <p> The scale and translation terms are floating point values in D-frame
0095: * pixel units. For scale this means that one S pixel maps to xScale
0096: * by yScale D-frame pixels. The translation vector, (xTrans, yTrans),
0097: * is in D-frame pixel units.
0098: *
0099: * <p> The filtered downsample operator mapping equations are given by
0100: *
0101: * <code>
0102: * <p> xS = xD*scaleX + (int)(hKernel.length/2) <br>
0103: * yS = yD*scaleY + (int)(vKernel.length/2)
0104: * </code>
0105: *
0106: * <p> The mapping equations have the intended property that the convolution
0107: * kernel overlays the upper left source pixels for the upper left destination
0108: * pixel.
0109: *
0110: * <p> The downsample terms are restricted to positive integral values.
0111: * Geometrically, one D-frame pixel maps to scaleX by scaleY S-frame
0112: * pixels. The combination of downsampling and filtering has performance
0113: * benefits over sequential operator usage in part due to the symmetry
0114: * constraints imposed by only allowing integer parameters for scaling and
0115: * only allowing separable symmetric filters. With odd scale factors, D-frame
0116: * pixels map directly onto S-frame pixel centers. With even scale factors,
0117: * D-frame pixels map squarely between S-frame pixel centers. Below are
0118: * examples of even, odd, and combination cases.
0119: *
0120: * <p> s = S-frame pixel centers <br>
0121: * d = D-frame pixel centers mapped to S-frame
0122: * </p>
0123: * <kbd>
0124: * <pre> s s s s s s s s s s s s </pre>
0125: * <pre> d d d </pre>
0126: * <pre> s s s s s s s d s s d s </pre>
0127: * <pre> </pre>
0128: * <pre> s s s s s s s s s s s s </pre>
0129: * <pre> d d d </pre>
0130: * <pre> s s s s s s s s s s s s </pre>
0131: * <pre> </pre>
0132: * <pre> s s s s s s s d s s d s </pre>
0133: * <pre> d d d </pre>
0134: * <pre> s s s s s s s s s s s s </pre>
0135: * <pre> </pre>
0136: * <pre> Even scaleX/Y factors Odd scaleX/Y factors </pre>
0137: * <pre> </pre>
0138: * <pre> s s s s s s s s s s s s </pre>
0139: * <pre> d d </pre>
0140: * <pre> s s s s s s s d s s d s s d s </pre>
0141: * <pre> </pre>
0142: * <pre> s s s s s s s s s s s s </pre>
0143: * <pre> d d </pre>
0144: * <pre> s s s s s s s s s s s s </pre>
0145: * <pre> </pre>
0146: * <pre> s s s s s s s d s s d s s d s </pre>
0147: * <pre> d d </pre>
0148: * <pre> s s s s s s s s s s s s </pre>
0149: * <pre> </pre>
0150: * <pre> Odd/even scaleX/Y factors Even/odd scaleX/Y factors </pre> <br>
0151: * </kbd>
0152: *
0153: * <p> The convolution kernel is restricted to have quadrant symmetry (qs). This
0154: * type of symmetry is also product separable. The qsFilter is specified by
0155: * a floating array. If qsFilter[0], qsFilter[1], ... , qsFilter[qsFilter.len() - 1]
0156: * is the filter input, then the entire separable kernel is given by <br>
0157: * qsFilter[qsFilter.len() - 1], ... , qsFilter[0], ... , qsFilter[qsFilter.len() - 1] <br>
0158: *
0159: * <p> The restriction of integer parameter constraints allows full product
0160: * separablity and symmetry when applying the combined resample and filter
0161: * convolution operations.
0162: *
0163: * <p> If Bilinear or Bicubic interpolation is specified, the source needs
0164: * to be extended such that it has the extra pixels needed to compute all
0165: * the destination pixels. This extension is performed via the
0166: * <code>BorderExtender</code> class. The type of border extension can be
0167: * specified as a <code>RenderingHint</code> to the <code>JAI.create</code>
0168: * method.
0169: *
0170: * <p> If no <code>BorderExtender</code> is specified, the source will
0171: * not be extended. The scaled image size is still calculated
0172: * according to the formula specified above. However since there is not
0173: * enough source to compute all the destination pixels, only that
0174: * subset of the destination image's pixels which can be computed,
0175: * will be written in the destination. The rest of the destination
0176: * will be set to zeros.
0177: *
0178: * <p> The current implementation of this operator does not support
0179: * <code>MultiPixelPackedSampleModel</code> source data. The Rendered Image
0180: * Factory for this operator, <code>FilteredSubsampleRIF</code>, will throw an
0181: * <code>IllegalArgumentException</code> for this type of input.
0182: *
0183: * @see GeometricOpImage
0184: */
0185: public class FilteredSubsampleOpImage extends GeometricOpImage {
0186:
0187: /** <p> The horizontal downsample factor. */
0188: protected int scaleX;
0189:
0190: /** <p> The vertical downsample factor. */
0191: protected int scaleY;
0192:
0193: /** <p> Horizontal filter parity. Rules: 0 => even, 1 => odd. See hKernel */
0194: protected int hParity;
0195:
0196: /** <p> Vertical filter parity. Rules: 0 => even, 1 => odd. See vKernel */
0197: protected int vParity;
0198:
0199: /** <p> Compact form of combined resample and antialias filters
0200: * used by computeRect method. hKernel is the horizontal kernel.
0201: *
0202: * <p> The symmetric filter is applied even or odd depending on filter
0203: * parity.
0204: *
0205: * <p> Expanded even kernel example (<code>hParity = 0</code>): <br>
0206: * <code>
0207: * hKernel[2] hKernel[1] hKernel[0] hKernel[0] hKernel[1] hKernel[2]
0208: * </code>
0209: *
0210: * <p> Expanded odd kernel example (<code>hParity = 1</code>): <br>
0211: * <code>
0212: * hKernel[2] hKernel[1] hKernel[0] hKernel[1] hKernel[2]
0213: * </code>
0214: */
0215: protected float[] hKernel;
0216:
0217: /** <p> Compact form of combined resample and antialias filters
0218: * used by computeRect method. vKernel is the vertical kernel.
0219: *
0220: * <p> The symmetric filter is applied even or odd depending on filter
0221: * parity.
0222: *
0223: * <p> Expanded even kernel example (<code>vParity = 0</code>): <br>
0224: * <code>
0225: * vKernel[2] vKernel[1] vKernel[0] vKernel[0] vKernel[1] vKernel[2]
0226: * </code>
0227: *
0228: * <p> Expanded odd kernel example (<code>vParity = 1</code>): <br>
0229: * <code>
0230: * vKernel[2] vKernel[1] vKernel[0] vKernel[1] vKernel[2]
0231: * </code>
0232: */
0233: protected float[] vKernel;
0234:
0235: /** <p> <code>convolveFullKernels</code> -- convolve two kernels and return the
0236: * result in a floating array.
0237: *
0238: * @param a floating kernel array.
0239: * @param b floating kernel array.
0240: * @return floating kernel array representing a*b (full convolution)
0241: */
0242: private static float[] convolveFullKernels(float[] a, float[] b) {
0243: int lenA = a.length;
0244: int lenB = b.length;
0245: float[] c = new float[lenA + lenB - 1];
0246:
0247: for (int k = 0; k < c.length; k++)
0248: for (int j = Math.max(0, k - lenB + 1); j <= Math.min(k,
0249: lenA - 1); j++)
0250: c[k] += a[j] * b[k - j];
0251:
0252: return c;
0253:
0254: } // convolveFullKernels
0255:
0256: /** <p> <code>convolveSymmetricKernels</code> uses a symmetric representation
0257: * (partial kernels) of input and output kernels. For example, with
0258: * aParity 1 (odd) and bParity 0 (even) the passed kernels a and b would
0259: * have form:
0260: * <code>
0261: * a: a[lenA-1] ... a[1] a[0] a[1] ... a[lenA-1]
0262: * b: b[lenB-1] ... b[1] b[0] b[0] b[1] ... b[lenB-1]
0263: * </code>
0264: *
0265: * (i.e., don't send symmetric parts but assumes parity controls filter
0266: * lengths).
0267: *
0268: * <p> It is possible to do this convolution without resorting to full
0269: * kernels but this is messy. @see convolveFullKernels for details.
0270: *
0271: * <p> Further notes:
0272: * 1. The return kernel, <code>c</code>, has parity
0273: * <code>1 + aParity + bParity mod 2</code>
0274: * 2. The reason for setting up the kernels this way is to enforce symmetry
0275: * constraints. (Design choice.)
0276: *
0277: * @param aParity int that is 0 or 1.
0278: * @param bParity int that is 0 or 1.
0279: * @param a floating partial kernel array.
0280: * @param b floating partial kernel array.
0281: * @return symmetric portion of floating array representing a*b (convolution).
0282: */
0283: private static float[] convolveSymmetricKernels(int aParity,
0284: int bParity, float[] a, float[] b) {
0285: int lenA = a.length;
0286: int lenB = b.length;
0287: int lenTmpA = 2 * lenA - aParity;
0288: int lenTmpB = 2 * lenB - bParity;
0289: int lenTmpC = lenTmpA + lenTmpB - 1;
0290: float[] tmpA = new float[lenTmpA];
0291: float[] tmpB = new float[lenTmpB];
0292: float[] tmpC;
0293: float[] c = new float[(lenTmpC + 1) / 2];
0294:
0295: // Construct "full" a
0296: for (int k = 0; k < lenTmpA; k++)
0297: tmpA[k] = a[Math.abs(k - lenA + (aParity - 1) * (k / lenA)
0298: + 1)];
0299:
0300: // Construct "full" b
0301: for (int k = 0; k < lenTmpB; k++)
0302: tmpB[k] = b[Math.abs(k - lenB + (bParity - 1) * (k / lenB)
0303: + 1)];
0304:
0305: // Convolve "full" a with "full" b to get a "full" tempC
0306: tmpC = convolveFullKernels(tmpA, tmpB);
0307:
0308: // Carve out and return the portion of c that holds
0309: int cParity = tmpC.length % 2;
0310: for (int k = 0; k < c.length; k++)
0311: c[k] = tmpC[lenTmpC - c.length - k - 1 + cParity];
0312:
0313: return c;
0314:
0315: } // convolveSymmetricKernels
0316:
0317: /** <p> <code>combineFilters</code> based on <code>resampleType</code> and
0318: * input partial <code>qsFilter</code> (see above for details on qsFilter format).
0319: * Input <code>qsFilter</code> is restricted to have odd parity
0320: * (<code>qsFilter[0]</code> is at the center of the kernel).
0321: *
0322: * @param scaleFactor positive int representing the downsample factor.
0323: * @param resampleType int representing the interpolation type.
0324: * @param qsFilter floating partial kernel array (antialias filter).
0325: * @return floating partial kernel representing combined resample and qsFilter.
0326: */
0327: private static float[] combineFilters(int scaleFactor,
0328: int resampleType, float[] qsFilter) {
0329:
0330: // Odd scale factors imply no resample filter is required
0331: // return pointer to the qsFilter
0332: if ((scaleFactor % 2) == 1)
0333: return (float[]) qsFilter.clone();
0334:
0335: int qsParity = 1;
0336: int resampParity = 0; // Unless nearest neighbor case is selected (ignored)
0337:
0338: switch (resampleType) {
0339: case Interpolation.INTERP_NEAREST: // Return a copy of the qsFilter
0340: return (float[]) qsFilter.clone();
0341: case Interpolation.INTERP_BILINEAR: // 2 by 2 resample filter
0342: float[] bilinearKernel = { 1.0F / 2.0F };
0343: return convolveSymmetricKernels(qsParity, resampParity,
0344: qsFilter, bilinearKernel);
0345: case Interpolation.INTERP_BICUBIC: // 4 by 4 resample filter
0346: float[] bicubicKernel = { 9.0F / 16.0F, -1.0F / 16.0F };
0347: return convolveSymmetricKernels(qsParity, resampParity,
0348: qsFilter, bicubicKernel);
0349: case Interpolation.INTERP_BICUBIC_2: // alternate 4 by 4 resample filter
0350: float[] bicubic2Kernel = { 5.0F / 8.0F, -1.0F / 8.0F };
0351: return convolveSymmetricKernels(qsParity, resampParity,
0352: qsFilter, bicubic2Kernel);
0353: default:
0354: throw new IllegalArgumentException(JaiI18N
0355: .getString("FilteredSubsample0"));
0356: }
0357:
0358: } // combineFilters
0359:
0360: /** <p> <code>filterParity</code> -- Returns combined filter/resample parity.
0361: * This is odd only when we have an odd scale factor or we have nearest neighbor
0362: * filtering (no resample kernel needed). <code>scaleFactor</code> was validated
0363: * by the constructor. Possible return values are 0 or 1.
0364: *
0365: * @param scaleFactor positive int representing the downsample factor.
0366: * @param resampleType int representing the interpolation type.
0367: * @return int representing combined filter parity (0 or 1).
0368: */
0369: private static int filterParity(int scaleFactor, int resampleType) {
0370:
0371: // Test scale factor for oddness or nearest neighbor resampling
0372: if ((scaleFactor % 2 == 1)
0373: || (resampleType == Interpolation.INTERP_NEAREST))
0374: return 1;
0375:
0376: // for all other cases we will be convolving an odd filter with an even
0377: // filter, thus producing an even filter
0378: return 0;
0379:
0380: } // filterParity
0381:
0382: /** <p> <code>layoutHelper</code> validates input and returns an
0383: * <code>ImageLayout</code> object.
0384: *
0385: * @param source a RenderedImage object.
0386: * @param interp an Interpolation object.
0387: * @param scaleX an int downsample factor.
0388: * @param scaleY an int downsample factor.
0389: * @param filterSize an int representing the size of the combined
0390: * filter and resample kernel.
0391: * @param il an ImageLayout object.
0392: * @return validated ImageLayout object.
0393: */
0394: private static final ImageLayout layoutHelper(RenderedImage source,
0395: Interpolation interp, int scaleX, int scaleY,
0396: int filterSize, ImageLayout il) {
0397:
0398: if (scaleX < 1 || scaleY < 1) {
0399: throw new IllegalArgumentException(JaiI18N
0400: .getString("FilteredSubsample1"));
0401: }
0402: if (filterSize < 1) {
0403: throw new IllegalArgumentException(JaiI18N
0404: .getString("FilteredSubsample2"));
0405: }
0406:
0407: // Set the bounds to the scaled source bounds.
0408: Rectangle bounds = forwardMapRect(source.getMinX(), source
0409: .getMinY(), source.getWidth(), source.getHeight(),
0410: scaleX, scaleY);
0411:
0412: // If the user has supplied a layout, use it
0413: ImageLayout layout = (il == null) ? new ImageLayout(bounds.x,
0414: bounds.y, bounds.width, bounds.height)
0415: : (ImageLayout) il.clone();
0416:
0417: // Override dimensions if user passed a hint
0418: if (il != null) {
0419: layout.setWidth(bounds.width);
0420: layout.setHeight(bounds.height);
0421: layout.setMinX(bounds.x);
0422: layout.setMinY(bounds.y);
0423: }
0424:
0425: return layout;
0426:
0427: } // setLayout
0428:
0429: /** <p> <code>FilteredSubsampleOpImage</code> constructs an OpImage representing
0430: * filtered integral subsampling. The scale factors represent the ratio of
0431: * source to destination dimensions.
0432: *
0433: * @param source a RenderedImage.
0434: * @param extender a BorderExtender, or null.
0435: * @param config a Map object possibly holding tile cache information
0436: * @param layout an ImageLayout optionally containing the tile grid layout,
0437: * SampleModel, and ColorModel, or null.
0438: * @param interp a Interpolation object to use for resampling.
0439: * @param scaleX downsample factor along x axis.
0440: * @param scaleY downsample factor along y axis.
0441: * @param qsFilter symmetric filter coefficients (partial kernel).
0442: * @throws IllegalArgumentException if the interp type is not one of:
0443: * INTERP_NEAREST, INTERP_BILINEAR, INTERP_BICUBIC, or INTERP_BICUBIC_2
0444: */
0445: public FilteredSubsampleOpImage(RenderedImage source,
0446: BorderExtender extender, Map config, ImageLayout layout,
0447: int scaleX, int scaleY, float[] qsFilter,
0448: Interpolation interp) {
0449:
0450: // Propagate to GeometricOpImage constructor
0451: super (vectorize(source), layoutHelper(source, interp, scaleX,
0452: scaleY, qsFilter.length, layout), config, // Map object
0453: true, // cobbleSources,
0454: extender, // extender
0455: interp, // Interpolation object
0456: null);
0457:
0458: int resampleType;
0459:
0460: // Determine the interpolation type, if not supported throw exception
0461: if (interp instanceof InterpolationNearest) {
0462: resampleType = Interpolation.INTERP_NEAREST;
0463: } else if (interp instanceof InterpolationBilinear) {
0464: resampleType = Interpolation.INTERP_BILINEAR;
0465: } else if (interp instanceof InterpolationBicubic) {
0466: resampleType = Interpolation.INTERP_BICUBIC;
0467: } else if (interp instanceof InterpolationBicubic2) {
0468: resampleType = Interpolation.INTERP_BICUBIC_2;
0469: } else {
0470: throw new IllegalArgumentException(JaiI18N
0471: .getString("FilteredSubsample3"));
0472: }
0473:
0474: // Construct combined anti-alias and resample kernels.
0475: this .hParity = filterParity(scaleX, resampleType);
0476: this .vParity = filterParity(scaleY, resampleType);
0477: this .hKernel = combineFilters(scaleX, resampleType, qsFilter);
0478: this .vKernel = combineFilters(scaleY, resampleType, qsFilter);
0479:
0480: this .scaleX = scaleX;
0481: this .scaleY = scaleY;
0482:
0483: } // FilteredSubsampleOpImage
0484:
0485: /**
0486: * Computes the source point corresponding to the supplied point.
0487: *
0488: * @param destPt the position in destination image coordinates
0489: * to map to source image coordinates.
0490: *
0491: * @return a <code>Point2D</code> of the same class as
0492: * <code>destPt</code>.
0493: *
0494: * @throws IllegalArgumentException if <code>destPt</code> is
0495: * <code>null</code>.
0496: *
0497: * @since JAI 1.1.2
0498: */
0499: public Point2D mapDestPoint(Point2D destPt) {
0500: if (destPt == null) {
0501: throw new IllegalArgumentException(JaiI18N
0502: .getString("Generic0"));
0503: }
0504:
0505: Point2D pt = (Point2D) destPt.clone();
0506:
0507: pt.setLocation(destPt.getX() * scaleX, destPt.getY() * scaleY);
0508:
0509: return pt;
0510: }
0511:
0512: /**
0513: * Computes the destination point corresponding to the supplied point.
0514: *
0515: * @param sourcePt the position in source image coordinates
0516: * to map to destination image coordinates.
0517: *
0518: * @return a <code>Point2D</code> of the same class as
0519: * <code>sourcePt</code>.
0520: *
0521: * @throws IllegalArgumentException if <code>sourcePt</code> is
0522: * <code>null</code>.
0523: *
0524: * @since JAI 1.1.2
0525: */
0526: public Point2D mapSourcePoint(Point2D sourcePt) {
0527: if (sourcePt == null) {
0528: throw new IllegalArgumentException(JaiI18N
0529: .getString("Generic0"));
0530: }
0531:
0532: Point2D pt = (Point2D) sourcePt.clone();
0533:
0534: pt.setLocation(sourcePt.getX() / scaleX, sourcePt.getY()
0535: / scaleY);
0536:
0537: return pt;
0538: }
0539:
0540: /**
0541: * <p> Returns a conservative estimate of the destination region that
0542: * can potentially be affected by the pixels of a rectangle of a
0543: * given source.
0544: *
0545: * @param sourceRect The <code>Rectangle</code> in source coordinates.
0546: * @param sourceIndex The index of the source image.
0547: *
0548: * @return a <code>Rectangle</code> indicating the potentially
0549: * affected destination region, or <code>null</code> if
0550: * the region is unknown.
0551: *
0552: * @throws IllegalArgumentException if <code>sourceIndex</code> is
0553: * negative or greater than the index of the last source.
0554: * @throws NullPointerException if <code>sourceRect</code> is
0555: * <code>null</code>.
0556: */
0557: public Rectangle mapSourceRect(Rectangle sourceRect, int sourceIndex) {
0558: if (sourceIndex != 0) { // this image only has one source
0559: throw new IllegalArgumentException(JaiI18N
0560: .getString("FilteredSubsample4"));
0561: }
0562:
0563: int xOffset = sourceRect.x + hKernel.length - hParity - scaleX
0564: / 2;
0565: int yOffset = sourceRect.y + vKernel.length - vParity - scaleY
0566: / 2;
0567: int rectWidth = sourceRect.width - 2 * hKernel.length + hParity
0568: + 1;
0569: int rectHeight = sourceRect.height - 2 * vKernel.length
0570: + vParity + 1;
0571: return forwardMapRect(xOffset, yOffset, rectWidth, rectHeight,
0572: scaleX, scaleY);
0573:
0574: } // mapSourceRect
0575:
0576: /** <p> Forward map a source Rectangle into destination space.
0577: *
0578: * @param x source frame coordinate.
0579: * @param y source frame coordinate.
0580: * @param w source frame width.
0581: * @param h source frame height.
0582: * @param scaleX downsample factor.
0583: * @param scaleY downsample factor.
0584: * @return a <code>Rectangle</code> indicating the destination region.
0585: */
0586: private static final Rectangle forwardMapRect(int x, int y, int w,
0587: int h, int scaleX, int scaleY) {
0588: float sx = 1.0F / scaleX;
0589: float sy = 1.0F / scaleY;
0590:
0591: x = Math.round(x * sx);
0592: y = Math.round(y * sy);
0593:
0594: return new Rectangle(x, y, Math.round((x + w) * sx) - x, Math
0595: .round((y + h) * sy)
0596: - y);
0597: } // forwardMapRect
0598:
0599: /** <p> Forward map a source Rectangle into destination space.
0600: * Required by abstract GeometricOpImage
0601: *
0602: * @param srcRect a source Rectangle.
0603: * @param srcIndex int source index (0 for this operator)
0604: * @return a <code>Rectangle</code> indicating the destination region.
0605: */
0606: protected final Rectangle forwardMapRect(Rectangle srcRect,
0607: int srcIndex) {
0608: int x = srcRect.x;
0609: int y = srcRect.y;
0610: int w = srcRect.width;
0611: int h = srcRect.height;
0612: float sx = 1.0F / scaleX;
0613: float sy = 1.0F / scaleY;
0614:
0615: x = Math.round(x * sx);
0616: y = Math.round(y * sy);
0617:
0618: return new Rectangle(x, y, Math.round((x + w) * sx) - x, Math
0619: .round((y + h) * sy)
0620: - y);
0621: } // forwardMapRect
0622:
0623: /** <p> Backward map a destination Rectangle into source space.
0624: *
0625: * @param destRect a destination Rectangle.
0626: * @param srcIndex int source index (0 for this operator)
0627: * @return a <code>Rectangle</code> indicating the source region.
0628: */
0629: protected final Rectangle backwardMapRect(Rectangle destRect,
0630: int srcIncex) {
0631: int x = destRect.x;
0632: int y = destRect.y;
0633: int w = destRect.width;
0634: int h = destRect.height;
0635:
0636: return new Rectangle(x * scaleX, y * scaleY, (x + w) * scaleX
0637: - x, (y + h) * scaleY - y);
0638: } // backwardMapRect
0639:
0640: /**
0641: * <p> Returns a conservative estimate of the region of a specified
0642: * source that is required in order to compute the pixels of a
0643: * given destination rectangle.
0644: *
0645: * @param destRect The <code>Rectangle</code> in destination coordinates.
0646: * @param sourceIndex The index of the source image.
0647: *
0648: * @return a <code>Rectangle</code> indicating the required source region.
0649: *
0650: * @throws IllegalArgumentException if <code>sourceIndex</code> is
0651: * negative or greater than the index of the last source.
0652: * @throws NullPointerException if <code>destRect</code> is
0653: * <code>null</code>.
0654: */
0655: public Rectangle mapDestRect(Rectangle destRect, int sourceIndex) {
0656: if (sourceIndex != 0) { // this image only has one source
0657: throw new IllegalArgumentException(JaiI18N
0658: .getString("FilteredSubsample4"));
0659: }
0660: int xOffset = destRect.x * scaleX - hKernel.length + hParity
0661: + scaleX / 2;
0662: int yOffset = destRect.y * scaleY - vKernel.length + vParity
0663: + scaleY / 2;
0664: int rectWidth = destRect.width * scaleX + 2 * hKernel.length
0665: - hParity - 1;
0666: int rectHeight = destRect.height * scaleY + 2 * vKernel.length
0667: - vParity - 1;
0668: return new Rectangle(xOffset, yOffset, rectWidth, rectHeight);
0669:
0670: } // mapDestRect
0671:
0672: /**
0673: * <p> Performs a combined subsample/filter operation on a specified rectangle.
0674: * The sources are cobbled.
0675: *
0676: * @param sources an array of source Rasters, guaranteed to provide all
0677: * necessary source data for computing the output.
0678: * @param dest a WritableRaster containing the area to be computed.
0679: * @param destRect the rectangle within dest to be processed.
0680: */
0681: public void computeRect(Raster[] sources, WritableRaster dest,
0682: Rectangle destRect) {
0683:
0684: // Get RasterAccessor tags (initialized in OpImage superclass).
0685: RasterFormatTag[] formatTags = getFormatTags();
0686:
0687: // Get destination accessor.
0688: RasterAccessor dst = new RasterAccessor(dest, destRect,
0689: formatTags[1], getColorModel());
0690:
0691: // Get source accessor.
0692: RasterAccessor src = new RasterAccessor(sources[0],
0693: mapDestRect(destRect, 0), formatTags[0],
0694: getSourceImage(0).getColorModel());
0695:
0696: switch (dst.getDataType()) {
0697: case DataBuffer.TYPE_BYTE:
0698: computeRectByte(src, dst);
0699: break;
0700: case DataBuffer.TYPE_USHORT:
0701: computeRectUShort(src, dst);
0702: break;
0703: case DataBuffer.TYPE_SHORT:
0704: computeRectShort(src, dst);
0705: break;
0706: case DataBuffer.TYPE_INT:
0707: computeRectInt(src, dst);
0708: break;
0709: case DataBuffer.TYPE_FLOAT:
0710: computeRectFloat(src, dst);
0711: break;
0712: case DataBuffer.TYPE_DOUBLE:
0713: computeRectDouble(src, dst);
0714: break;
0715: default:
0716: throw new IllegalArgumentException(JaiI18N
0717: .getString("FilteredSubsample5"));
0718: }
0719:
0720: // If the RasterAccessor set up a temporary write buffer for the
0721: // operator, tell it to copy that data to the destination Raster.
0722: if (dst.isDataCopy()) {
0723: dst.clampDataArrays();
0724: dst.copyDataToRaster();
0725: }
0726:
0727: } // computeRect
0728:
0729: /** <code>computeRectByte</code> filter subsamples byte pixel data.
0730: *
0731: * @param src RasterAccessor for source image.
0732: * @param dst RasterAccessor for output image.
0733: */
0734: protected void computeRectByte(RasterAccessor src,
0735: RasterAccessor dst) {
0736:
0737: // Get dimensions.
0738: int dwidth = dst.getWidth();
0739: int dheight = dst.getHeight();
0740: int dnumBands = dst.getNumBands();
0741:
0742: // Get destination data array references and strides.
0743: byte dstDataArrays[][] = dst.getByteDataArrays();
0744: int dstBandOffsets[] = dst.getBandOffsets();
0745: int dstPixelStride = dst.getPixelStride();
0746: int dstScanlineStride = dst.getScanlineStride();
0747:
0748: // Get source data array references and strides.
0749: byte srcDataArrays[][] = src.getByteDataArrays();
0750: int srcBandOffsets[] = src.getBandOffsets();
0751: int srcPixelStride = src.getPixelStride();
0752: int srcScanlineStride = src.getScanlineStride();
0753:
0754: // Compute reused numbers
0755: int kernelNx = 2 * hKernel.length - hParity;
0756: int kernelNy = 2 * vKernel.length - vParity;
0757: int stepDown = (kernelNy - 1) * srcScanlineStride;
0758: int stepRight = (kernelNx - 1) * srcPixelStride;
0759:
0760: int upLeft, upRight, dnLeft, dnRight;
0761: float kk;
0762: float vCtr = vKernel[0];
0763: float hCtr = hKernel[0];
0764: int kInd, xLeft, xRight, xUp, xDown;
0765:
0766: for (int band = 0; band < dnumBands; band++) {
0767: byte dstData[] = dstDataArrays[band];
0768: byte srcData[] = srcDataArrays[band];
0769: int srcScanlineOffset = srcBandOffsets[band];
0770: int dstScanlineOffset = dstBandOffsets[band];
0771:
0772: // Step over source raster coordinates
0773: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
0774: int dInd = dstScanlineOffset;
0775: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
0776:
0777: int upLeft0 = xSrc * srcPixelStride + ySrc
0778: * srcScanlineStride + srcScanlineOffset;
0779: int upRight0 = upLeft0 + stepRight;
0780: int dnLeft0 = upLeft0 + stepDown;
0781: int dnRight0 = upRight0 + stepDown;
0782:
0783: // Exploit 4-fold symmetry
0784: float sum = 0;
0785:
0786: // Make the rectangle squeeze in the vertical direction
0787: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
0788: upLeft = upLeft0;
0789: upRight = upRight0;
0790: dnLeft = dnLeft0;
0791: dnRight = dnRight0;
0792:
0793: // Make the rectangle squeeze in the horizontal direction
0794: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
0795: kk = hKernel[ix] * vKernel[iy];
0796: sum += kk
0797: * ((int) (srcData[upLeft] & 0xff)
0798: + (int) (srcData[upRight] & 0xff)
0799: + (int) (srcData[dnLeft] & 0xff) + (int) (srcData[dnRight] & 0xff));
0800: upLeft += srcPixelStride;
0801: upRight -= srcPixelStride;
0802: dnLeft += srcPixelStride;
0803: dnRight -= srcPixelStride;
0804: } // ix
0805: upLeft0 += srcScanlineStride; // down a row
0806: upRight0 += srcScanlineStride;
0807: dnLeft0 -= srcScanlineStride; // up a row
0808: dnRight0 -= srcScanlineStride;
0809: } // iy
0810:
0811: // Compute the remaining 2-Fold symmetry portions (across and down as needed)
0812:
0813: // Loop down the center (hParity is odd)
0814: if (hParity == 1) {
0815: xUp = (xSrc + hKernel.length - 1)
0816: * srcPixelStride + ySrc
0817: * srcScanlineStride + srcScanlineOffset;
0818: xDown = xUp + stepDown;
0819: kInd = vKernel.length - 1;
0820: while (xUp < xDown) {
0821: kk = hCtr * vKernel[kInd--];
0822: sum += kk
0823: * ((int) (srcData[xUp] & 0xff) + (int) (srcData[xDown] & 0xff));
0824: xUp += srcScanlineStride;
0825: xDown -= srcScanlineStride;
0826: }
0827: } // hParity
0828:
0829: // Loop across the center (vParity is odd), pick up the center if hParity was odd
0830: if (vParity == 1) {
0831: xLeft = xSrc * srcPixelStride
0832: + (ySrc + vKernel.length - 1)
0833: * srcScanlineStride + srcScanlineOffset;
0834: xRight = xLeft + stepRight;
0835: kInd = hKernel.length - 1;
0836: while (xLeft < xRight) {
0837: kk = vCtr * hKernel[kInd--];
0838: sum += kk
0839: * ((int) (srcData[xLeft] & 0xff) + (int) (srcData[xRight] & 0xff));
0840: xLeft += srcPixelStride;
0841: xRight -= srcPixelStride;
0842: } // while xLeft
0843:
0844: // Grab the center pixel if hParity was odd also
0845: if (hParity == 1)
0846: sum += vCtr * hCtr
0847: * (int) (srcData[xLeft] & 0xff);
0848:
0849: } // if vParity
0850:
0851: // Convert the sum to an output pixel
0852: if (sum < 0.0)
0853: sum = 0;
0854: if (sum > 255.0)
0855: sum = 255;
0856:
0857: dstData[dInd] = (byte) (sum + 0.5);
0858:
0859: dInd += dstPixelStride;
0860: } // for xSrc
0861:
0862: dstScanlineOffset += dstScanlineStride;
0863:
0864: } // for ySrc
0865:
0866: } // for band
0867:
0868: return;
0869:
0870: } // computeRectByte
0871:
0872: /** <code>computeRectUShort</code> filter subsamples unsigned short pixel data.
0873: *
0874: * @param src RasterAccessor for source image.
0875: * @param dst RasterAccessor for output image.
0876: */
0877: protected void computeRectUShort(RasterAccessor src,
0878: RasterAccessor dst) {
0879:
0880: // Get dimensions.
0881: int dwidth = dst.getWidth();
0882: int dheight = dst.getHeight();
0883: int dnumBands = dst.getNumBands();
0884:
0885: // Get destination data array references and strides.
0886: short dstDataArrays[][] = dst.getShortDataArrays();
0887: int dstBandOffsets[] = dst.getBandOffsets();
0888: int dstPixelStride = dst.getPixelStride();
0889: int dstScanlineStride = dst.getScanlineStride();
0890:
0891: // Get source data array references and strides.
0892: short srcDataArrays[][] = src.getShortDataArrays();
0893: int srcBandOffsets[] = src.getBandOffsets();
0894: int srcPixelStride = src.getPixelStride();
0895: int srcScanlineStride = src.getScanlineStride();
0896:
0897: // Compute reused numbers
0898: int kernelNx = 2 * hKernel.length - hParity;
0899: int kernelNy = 2 * vKernel.length - vParity;
0900: int stepDown = (kernelNy - 1) * srcScanlineStride;
0901: int stepRight = (kernelNx - 1) * srcPixelStride;
0902:
0903: int upLeft, upRight, dnLeft, dnRight;
0904: float kk;
0905: float vCtr = vKernel[0];
0906: float hCtr = hKernel[0];
0907: int kInd, xLeft, xRight, xUp, xDown;
0908:
0909: for (int band = 0; band < dnumBands; band++) {
0910: short dstData[] = dstDataArrays[band];
0911: short srcData[] = srcDataArrays[band];
0912: int srcScanlineOffset = srcBandOffsets[band];
0913: int dstScanlineOffset = dstBandOffsets[band];
0914:
0915: // Step over source raster coordinates
0916: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
0917: int dInd = dstScanlineOffset;
0918: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
0919:
0920: int upLeft0 = xSrc * srcPixelStride + ySrc
0921: * srcScanlineStride + srcScanlineOffset;
0922: int upRight0 = upLeft0 + stepRight;
0923: int dnLeft0 = upLeft0 + stepDown;
0924: int dnRight0 = upRight0 + stepDown;
0925:
0926: // Exploit 4-fold symmetry
0927: float sum = 0;
0928:
0929: // Make the rectangle squeeze in the vertical direction
0930: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
0931: upLeft = upLeft0;
0932: upRight = upRight0;
0933: dnLeft = dnLeft0;
0934: dnRight = dnRight0;
0935:
0936: // Make the rectangle squeeze in the horizontal direction
0937: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
0938: kk = hKernel[ix] * vKernel[iy];
0939: sum += kk
0940: * ((int) (srcData[upLeft] & 0xffff)
0941: + (int) (srcData[upRight] & 0xffff)
0942: + (int) (srcData[dnLeft] & 0xffff) + (int) (srcData[dnRight] & 0xffff));
0943: upLeft += srcPixelStride;
0944: upRight -= srcPixelStride;
0945: dnLeft += srcPixelStride;
0946: dnRight -= srcPixelStride;
0947: } // ix
0948: upLeft0 += srcScanlineStride; // down a row
0949: upRight0 += srcScanlineStride;
0950: dnLeft0 -= srcScanlineStride; // up a row
0951: dnRight0 -= srcScanlineStride;
0952: } // iy
0953:
0954: // Compute the remaining 2-Fold symmetry portions
0955: // (across and down as needed)
0956:
0957: // Loop down the center (hParity is odd)
0958: if (hParity == 1) {
0959: xUp = (xSrc + hKernel.length - 1)
0960: * srcPixelStride + ySrc
0961: * srcScanlineStride + srcScanlineOffset;
0962: xDown = xUp + stepDown;
0963: kInd = vKernel.length - 1;
0964: while (xUp < xDown) {
0965: kk = hCtr * vKernel[kInd--];
0966: sum += kk
0967: * ((int) (srcData[xUp] & 0xffff) + (int) (srcData[xDown] & 0xffff));
0968: xUp += srcScanlineStride;
0969: xDown -= srcScanlineStride;
0970: }
0971: } // hParity
0972:
0973: // Loop across the center (vParity is odd), pick up the center if hParity was odd
0974: if (vParity == 1) {
0975: xLeft = xSrc * srcPixelStride
0976: + (ySrc + vKernel.length - 1)
0977: * srcScanlineStride + srcScanlineOffset;
0978: xRight = xLeft + stepRight;
0979: kInd = hKernel.length - 1;
0980: while (xLeft < xRight) {
0981: kk = vCtr * hKernel[kInd--];
0982: sum += kk
0983: * ((int) (srcData[xLeft] & 0xffff) + (int) (srcData[xRight] & 0xffff));
0984: xLeft += srcPixelStride;
0985: xRight -= srcPixelStride;
0986: } // while xLeft
0987:
0988: // Grab the center pixel if hParity was odd also
0989: if (hParity == 1)
0990: sum += vCtr * hCtr
0991: * (int) (srcData[xLeft] & 0xffff);
0992:
0993: } // if vParity
0994: int val = (int) (sum + 0.5);
0995: dstData[dInd] = (short) (val > 0xffff ? 0xffff
0996: : (val < 0 ? 0 : val));
0997:
0998: dInd += dstPixelStride;
0999: } // for xSrc
1000:
1001: dstScanlineOffset += dstScanlineStride;
1002:
1003: } // for ySrc
1004:
1005: } // for band
1006:
1007: return;
1008:
1009: } // computeRectUShort
1010:
1011: /** <code>computeRectShort</code> filter subsamples short pixel data.
1012: *
1013: * @param src RasterAccessor for source image.
1014: * @param dst RasterAccessor for output image.
1015: */
1016: protected void computeRectShort(RasterAccessor src,
1017: RasterAccessor dst) {
1018:
1019: // Get dimensions.
1020: int dwidth = dst.getWidth();
1021: int dheight = dst.getHeight();
1022: int dnumBands = dst.getNumBands();
1023:
1024: // Get destination data array references and strides.
1025: short dstDataArrays[][] = dst.getShortDataArrays();
1026: int dstBandOffsets[] = dst.getBandOffsets();
1027: int dstPixelStride = dst.getPixelStride();
1028: int dstScanlineStride = dst.getScanlineStride();
1029:
1030: // Get source data array references and strides.
1031: short srcDataArrays[][] = src.getShortDataArrays();
1032: int srcBandOffsets[] = src.getBandOffsets();
1033: int srcPixelStride = src.getPixelStride();
1034: int srcScanlineStride = src.getScanlineStride();
1035:
1036: // Compute reused numbers
1037: int kernelNx = 2 * hKernel.length - hParity;
1038: int kernelNy = 2 * vKernel.length - vParity;
1039: int stepDown = (kernelNy - 1) * srcScanlineStride;
1040: int stepRight = (kernelNx - 1) * srcPixelStride;
1041:
1042: int upLeft, upRight, dnLeft, dnRight;
1043: float kk;
1044: float vCtr = vKernel[0];
1045: float hCtr = hKernel[0];
1046: int kInd, xLeft, xRight, xUp, xDown;
1047:
1048: for (int band = 0; band < dnumBands; band++) {
1049: short dstData[] = dstDataArrays[band];
1050: short srcData[] = srcDataArrays[band];
1051: int srcScanlineOffset = srcBandOffsets[band];
1052: int dstScanlineOffset = dstBandOffsets[band];
1053:
1054: // Step over source raster coordinates
1055: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
1056: int dInd = dstScanlineOffset;
1057: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
1058:
1059: int upLeft0 = xSrc * srcPixelStride + ySrc
1060: * srcScanlineStride + srcScanlineOffset;
1061: int upRight0 = upLeft0 + stepRight;
1062: int dnLeft0 = upLeft0 + stepDown;
1063: int dnRight0 = upRight0 + stepDown;
1064:
1065: // Exploit 4-fold symmetry
1066: float sum = 0;
1067:
1068: // Make the rectangle squeeze in the vertical direction
1069: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
1070: upLeft = upLeft0;
1071: upRight = upRight0;
1072: dnLeft = dnLeft0;
1073: dnRight = dnRight0;
1074:
1075: // Make the rectangle squeeze in the horizontal direction
1076: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
1077: kk = hKernel[ix] * vKernel[iy];
1078: sum += kk
1079: * ((int) (srcData[upLeft])
1080: + (int) (srcData[upRight])
1081: + (int) (srcData[dnLeft]) + (int) (srcData[dnRight]));
1082: upLeft += srcPixelStride;
1083: upRight -= srcPixelStride;
1084: dnLeft += srcPixelStride;
1085: dnRight -= srcPixelStride;
1086: } // ix
1087: upLeft0 += srcScanlineStride; // down a row
1088: upRight0 += srcScanlineStride;
1089: dnLeft0 -= srcScanlineStride; // up a row
1090: dnRight0 -= srcScanlineStride;
1091: } // iy
1092:
1093: // Compute the remaining 2-Fold symmetry portions
1094: // (across and down as needed)
1095:
1096: // Loop down the center (hParity is odd)
1097: if (hParity == 1) {
1098: xUp = (xSrc + hKernel.length - 1)
1099: * srcPixelStride + ySrc
1100: * srcScanlineStride + srcScanlineOffset;
1101: xDown = xUp + stepDown;
1102: kInd = vKernel.length - 1;
1103: while (xUp < xDown) {
1104: kk = hCtr * vKernel[kInd--];
1105: sum += kk
1106: * ((int) (srcData[xUp]) + (int) (srcData[xDown]));
1107: xUp += srcScanlineStride;
1108: xDown -= srcScanlineStride;
1109: }
1110: } // hParity
1111:
1112: // Loop across the center (vParity is odd), pick up the center if hParity was odd
1113: if (vParity == 1) {
1114: xLeft = xSrc * srcPixelStride
1115: + (ySrc + vKernel.length - 1)
1116: * srcScanlineStride + srcScanlineOffset;
1117: xRight = xLeft + stepRight;
1118: kInd = hKernel.length - 1;
1119: while (xLeft < xRight) {
1120: kk = vCtr * hKernel[kInd--];
1121: sum += kk
1122: * ((int) (srcData[xLeft]) + (int) (srcData[xRight]));
1123: xLeft += srcPixelStride;
1124: xRight -= srcPixelStride;
1125: } // while xLeft
1126:
1127: // Grab the center pixel if hParity was odd also
1128: if (hParity == 1)
1129: sum += vCtr * hCtr * (int) (srcData[xLeft]);
1130:
1131: } // if vParity
1132:
1133: dstData[dInd] = ImageUtil
1134: .clampShort((int) (sum + 0.5));
1135: dInd += dstPixelStride;
1136:
1137: } // for xSrc
1138:
1139: dstScanlineOffset += dstScanlineStride;
1140:
1141: } // for ySrc
1142:
1143: } // for band
1144:
1145: return;
1146:
1147: } // computeRectShort
1148:
1149: /** <code>computeRectInt</code> filter subsamples int pixel data.
1150: *
1151: * @param src RasterAccessor for source image.
1152: * @param dst RasterAccessor for output image.
1153: */
1154: protected void computeRectInt(RasterAccessor src, RasterAccessor dst) {
1155:
1156: // Get dimensions.
1157: int dwidth = dst.getWidth();
1158: int dheight = dst.getHeight();
1159: int dnumBands = dst.getNumBands();
1160:
1161: // Get destination data array references and strides.
1162: int dstDataArrays[][] = dst.getIntDataArrays();
1163: int dstBandOffsets[] = dst.getBandOffsets();
1164: int dstPixelStride = dst.getPixelStride();
1165: int dstScanlineStride = dst.getScanlineStride();
1166:
1167: // Get source data array references and strides.
1168: int srcDataArrays[][] = src.getIntDataArrays();
1169: int srcBandOffsets[] = src.getBandOffsets();
1170: int srcPixelStride = src.getPixelStride();
1171: int srcScanlineStride = src.getScanlineStride();
1172:
1173: // Compute reused numbers
1174: int kernelNx = 2 * hKernel.length - hParity;
1175: int kernelNy = 2 * vKernel.length - vParity;
1176: int stepDown = (kernelNy - 1) * srcScanlineStride;
1177: int stepRight = (kernelNx - 1) * srcPixelStride;
1178:
1179: int upLeft, upRight, dnLeft, dnRight;
1180: double kk;
1181: double vCtr = (double) vKernel[0];
1182: double hCtr = (double) hKernel[0];
1183: int kInd, xLeft, xRight, xUp, xDown;
1184:
1185: for (int band = 0; band < dnumBands; band++) {
1186: int dstData[] = dstDataArrays[band];
1187: int srcData[] = srcDataArrays[band];
1188: int srcScanlineOffset = srcBandOffsets[band];
1189: int dstScanlineOffset = dstBandOffsets[band];
1190:
1191: // Step over source raster coordinates
1192: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
1193: int dInd = dstScanlineOffset;
1194: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
1195:
1196: int upLeft0 = xSrc * srcPixelStride + ySrc
1197: * srcScanlineStride + srcScanlineOffset;
1198: int upRight0 = upLeft0 + stepRight;
1199: int dnLeft0 = upLeft0 + stepDown;
1200: int dnRight0 = upRight0 + stepDown;
1201:
1202: // Exploit 4-fold symmetry
1203: double sum = 0;
1204:
1205: // Make the rectangle squeeze in the vertical direction
1206: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
1207: upLeft = upLeft0;
1208: upRight = upRight0;
1209: dnLeft = dnLeft0;
1210: dnRight = dnRight0;
1211:
1212: // Make the rectangle squeeze in the horizontal direction
1213: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
1214: kk = hKernel[ix] * vKernel[iy];
1215: sum += kk
1216: * ((long) srcData[upLeft]
1217: + (long) srcData[upRight]
1218: + (long) srcData[dnLeft] + (long) srcData[dnRight]);
1219: upLeft += srcPixelStride;
1220: upRight -= srcPixelStride;
1221: dnLeft += srcPixelStride;
1222: dnRight -= srcPixelStride;
1223: } // ix
1224: upLeft0 += srcScanlineStride; // down a row
1225: upRight0 += srcScanlineStride;
1226: dnLeft0 -= srcScanlineStride; // up a row
1227: dnRight0 -= srcScanlineStride;
1228: } // iy
1229:
1230: // Compute the remaining 2-Fold symmetry portions
1231: // (across and down as needed)
1232:
1233: // Loop down the center (hParity is odd)
1234: if (hParity == 1) {
1235: xUp = (xSrc + hKernel.length - 1)
1236: * srcPixelStride + ySrc
1237: * srcScanlineStride + srcScanlineOffset;
1238: xDown = xUp + stepDown;
1239: kInd = vKernel.length - 1;
1240: while (xUp < xDown) {
1241: kk = hCtr * vKernel[kInd--];
1242: sum += kk
1243: * ((long) srcData[xUp] + (long) srcData[xDown]);
1244: xUp += srcScanlineStride;
1245: xDown -= srcScanlineStride;
1246: }
1247: } // hParity
1248:
1249: // Loop across the center (vParity is odd), pick up the center if hParity was odd
1250: if (vParity == 1) {
1251: xLeft = xSrc * srcPixelStride
1252: + (ySrc + vKernel.length - 1)
1253: * srcScanlineStride + srcScanlineOffset;
1254: xRight = xLeft + stepRight;
1255: kInd = hKernel.length - 1;
1256: while (xLeft < xRight) {
1257: kk = vCtr * hKernel[kInd--];
1258: sum += kk
1259: * ((long) (srcData[xLeft]) + (long) (srcData[xRight]));
1260: xLeft += srcPixelStride;
1261: xRight -= srcPixelStride;
1262: } // while xLeft
1263:
1264: // Grab the center pixel if hParity was odd also
1265: if (hParity == 1)
1266: sum += vCtr * hCtr
1267: * ((long) srcData[xLeft]);
1268:
1269: } // if vParity
1270:
1271: dstData[dInd] = ImageUtil
1272: .clampInt((int) (sum + 0.5));
1273:
1274: dInd += dstPixelStride;
1275: } // for xSrc
1276:
1277: dstScanlineOffset += dstScanlineStride;
1278:
1279: } // for ySrc
1280:
1281: } // for band
1282:
1283: return;
1284:
1285: } // computeRectInt
1286:
1287: /** <code>computeRectFloat</code> filter subsamples float pixel data.
1288: *
1289: * @param src RasterAccessor for source image.
1290: * @param dst RasterAccessor for output image.
1291: */
1292: protected void computeRectFloat(RasterAccessor src,
1293: RasterAccessor dst) {
1294:
1295: // Get dimensions.
1296: int dwidth = dst.getWidth();
1297: int dheight = dst.getHeight();
1298: int dnumBands = dst.getNumBands();
1299:
1300: // Get destination data array references and strides.
1301: float dstDataArrays[][] = dst.getFloatDataArrays();
1302: int dstBandOffsets[] = dst.getBandOffsets();
1303: int dstPixelStride = dst.getPixelStride();
1304: int dstScanlineStride = dst.getScanlineStride();
1305:
1306: // Get source data array references and strides.
1307: float srcDataArrays[][] = src.getFloatDataArrays();
1308: int srcBandOffsets[] = src.getBandOffsets();
1309: int srcPixelStride = src.getPixelStride();
1310: int srcScanlineStride = src.getScanlineStride();
1311:
1312: // Compute reused numbers
1313: int kernelNx = 2 * hKernel.length - hParity;
1314: int kernelNy = 2 * vKernel.length - vParity;
1315: int stepDown = (kernelNy - 1) * srcScanlineStride;
1316: int stepRight = (kernelNx - 1) * srcPixelStride;
1317:
1318: int upLeft, upRight, dnLeft, dnRight;
1319: double kk;
1320: double vCtr = (double) vKernel[0];
1321: double hCtr = (double) hKernel[0];
1322: int kInd, xLeft, xRight, xUp, xDown;
1323:
1324: for (int band = 0; band < dnumBands; band++) {
1325: float dstData[] = dstDataArrays[band];
1326: float srcData[] = srcDataArrays[band];
1327: int srcScanlineOffset = srcBandOffsets[band];
1328: int dstScanlineOffset = dstBandOffsets[band];
1329:
1330: // Step over source raster coordinates
1331: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
1332: int dInd = dstScanlineOffset;
1333: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
1334:
1335: int upLeft0 = xSrc * srcPixelStride + ySrc
1336: * srcScanlineStride + srcScanlineOffset;
1337: int upRight0 = upLeft0 + stepRight;
1338: int dnLeft0 = upLeft0 + stepDown;
1339: int dnRight0 = upRight0 + stepDown;
1340:
1341: // Exploit 4-fold symmetry
1342: double sum = 0;
1343:
1344: // Make the rectangle squeeze in the vertical direction
1345: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
1346: upLeft = upLeft0;
1347: upRight = upRight0;
1348: dnLeft = dnLeft0;
1349: dnRight = dnRight0;
1350:
1351: // Make the rectangle squeeze in the horizontal direction
1352: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
1353: kk = hKernel[ix] * vKernel[iy];
1354: sum += kk
1355: * ((double) srcData[upLeft]
1356: + (double) srcData[upRight]
1357: + (double) srcData[dnLeft] + (double) srcData[dnRight]);
1358: upLeft += srcPixelStride;
1359: upRight -= srcPixelStride;
1360: dnLeft += srcPixelStride;
1361: dnRight -= srcPixelStride;
1362: } // ix
1363: upLeft0 += srcScanlineStride; // down a row
1364: upRight0 += srcScanlineStride;
1365: dnLeft0 -= srcScanlineStride; // up a row
1366: dnRight0 -= srcScanlineStride;
1367: } // iy
1368:
1369: // Compute the remaining 2-Fold symmetry portions
1370: // (across and down as needed)
1371:
1372: // Loop down the center (hParity is odd)
1373: if (hParity == 1) {
1374: xUp = (xSrc + hKernel.length - 1)
1375: * srcPixelStride + ySrc
1376: * srcScanlineStride + srcScanlineOffset;
1377: xDown = xUp + stepDown;
1378: kInd = vKernel.length - 1;
1379: while (xUp < xDown) {
1380: kk = hCtr * vKernel[kInd--];
1381: sum += kk
1382: * ((double) srcData[xUp] + (double) srcData[xDown]);
1383: xUp += srcScanlineStride;
1384: xDown -= srcScanlineStride;
1385: }
1386: } // hParity
1387:
1388: // Loop across the center (vParity is odd), pick up the center if hParity was odd
1389: if (vParity == 1) {
1390: xLeft = xSrc * srcPixelStride
1391: + (ySrc + vKernel.length - 1)
1392: * srcScanlineStride + srcScanlineOffset;
1393: xRight = xLeft + stepRight;
1394: kInd = hKernel.length - 1;
1395: while (xLeft < xRight) {
1396: kk = vCtr * hKernel[kInd--];
1397: sum += kk
1398: * ((double) (srcData[xLeft]) + (double) (srcData[xRight]));
1399: xLeft += srcPixelStride;
1400: xRight -= srcPixelStride;
1401: } // while xLeft
1402:
1403: // Grab the center pixel if hParity was odd also
1404: if (hParity == 1)
1405: sum += vCtr * hCtr
1406: * ((double) srcData[xLeft]);
1407:
1408: } // if vParity
1409:
1410: dstData[dInd] = ImageUtil.clampFloat(sum);
1411:
1412: dInd += dstPixelStride;
1413: } // for xSrc
1414:
1415: dstScanlineOffset += dstScanlineStride;
1416:
1417: } // for ySrc
1418:
1419: } // for band
1420:
1421: return;
1422:
1423: } // computeRectFloat
1424:
1425: /** <code>computeRectDouble</code> filter subsamples double pixel data.
1426: *
1427: * @param src RasterAccessor for source image.
1428: * @param dst RasterAccessor for output image.
1429: */
1430: protected void computeRectDouble(RasterAccessor src,
1431: RasterAccessor dst) {
1432:
1433: // Get dimensions.
1434: int dwidth = dst.getWidth();
1435: int dheight = dst.getHeight();
1436: int dnumBands = dst.getNumBands();
1437:
1438: // Get destination data array references and strides.
1439: double dstDataArrays[][] = dst.getDoubleDataArrays();
1440: int dstBandOffsets[] = dst.getBandOffsets();
1441: int dstPixelStride = dst.getPixelStride();
1442: int dstScanlineStride = dst.getScanlineStride();
1443:
1444: // Get source data array references and strides.
1445: double srcDataArrays[][] = src.getDoubleDataArrays();
1446: int srcBandOffsets[] = src.getBandOffsets();
1447: int srcPixelStride = src.getPixelStride();
1448: int srcScanlineStride = src.getScanlineStride();
1449:
1450: // Compute reused numbers
1451: int kernelNx = 2 * hKernel.length - hParity;
1452: int kernelNy = 2 * vKernel.length - vParity;
1453: int stepDown = (kernelNy - 1) * srcScanlineStride;
1454: int stepRight = (kernelNx - 1) * srcPixelStride;
1455:
1456: int upLeft, upRight, dnLeft, dnRight;
1457: double kk;
1458: double vCtr = (double) vKernel[0];
1459: double hCtr = (double) hKernel[0];
1460: int kInd, xLeft, xRight, xUp, xDown;
1461:
1462: for (int band = 0; band < dnumBands; band++) {
1463: double dstData[] = dstDataArrays[band];
1464: double srcData[] = srcDataArrays[band];
1465: int srcScanlineOffset = srcBandOffsets[band];
1466: int dstScanlineOffset = dstBandOffsets[band];
1467:
1468: // Step over source raster coordinates
1469: for (int ySrc = 0; ySrc < scaleY * dheight; ySrc += scaleY) {
1470: int dInd = dstScanlineOffset;
1471: for (int xSrc = 0; xSrc < scaleX * dwidth; xSrc += scaleX) {
1472:
1473: int upLeft0 = xSrc * srcPixelStride + ySrc
1474: * srcScanlineStride + srcScanlineOffset;
1475: int upRight0 = upLeft0 + stepRight;
1476: int dnLeft0 = upLeft0 + stepDown;
1477: int dnRight0 = upRight0 + stepDown;
1478:
1479: // Exploit 4-fold symmetry
1480: double sum = 0;
1481:
1482: // Make the rectangle squeeze in the vertical direction
1483: for (int iy = vKernel.length - 1; iy > vParity - 1; iy--) {
1484: upLeft = upLeft0;
1485: upRight = upRight0;
1486: dnLeft = dnLeft0;
1487: dnRight = dnRight0;
1488:
1489: // Make the rectangle squeeze in the horizontal direction
1490: for (int ix = hKernel.length - 1; ix > hParity - 1; ix--) {
1491: kk = hKernel[ix] * vKernel[iy];
1492: sum += kk
1493: * (srcData[upLeft]
1494: + srcData[upRight]
1495: + srcData[dnLeft] + srcData[dnRight]);
1496: upLeft += srcPixelStride;
1497: upRight -= srcPixelStride;
1498: dnLeft += srcPixelStride;
1499: dnRight -= srcPixelStride;
1500: } // ix
1501: upLeft0 += srcScanlineStride; // down a row
1502: upRight0 += srcScanlineStride;
1503: dnLeft0 -= srcScanlineStride; // up a row
1504: dnRight0 -= srcScanlineStride;
1505: } // iy
1506:
1507: // Compute the remaining 2-Fold symmetry portions
1508: // (across and down as needed)
1509:
1510: // Loop down the center (hParity is odd)
1511: if (hParity == 1) {
1512: xUp = (xSrc + hKernel.length - 1)
1513: * srcPixelStride + ySrc
1514: * srcScanlineStride + srcScanlineOffset;
1515: xDown = xUp + stepDown;
1516: kInd = vKernel.length - 1;
1517: while (xUp < xDown) {
1518: kk = hCtr * vKernel[kInd--];
1519: sum += kk * (srcData[xUp] + srcData[xDown]);
1520: xUp += srcScanlineStride;
1521: xDown -= srcScanlineStride;
1522: }
1523: } // hParity
1524:
1525: // Loop across the center (vParity is odd), pick up the center if hParity was odd
1526: if (vParity == 1) {
1527: xLeft = xSrc * srcPixelStride
1528: + (ySrc + vKernel.length - 1)
1529: * srcScanlineStride + srcScanlineOffset;
1530: xRight = xLeft + stepRight;
1531: kInd = hKernel.length - 1;
1532: while (xLeft < xRight) {
1533: kk = vCtr * hKernel[kInd--];
1534: sum += kk
1535: * (srcData[xLeft] + srcData[xRight]);
1536: xLeft += srcPixelStride;
1537: xRight -= srcPixelStride;
1538: } // while xLeft
1539:
1540: // Grab the center pixel if hParity was odd also
1541: if (hParity == 1)
1542: sum += vCtr * hCtr * srcData[xLeft];
1543:
1544: } // if vParity
1545:
1546: dstData[dInd] = sum;
1547:
1548: dInd += dstPixelStride;
1549: } // for xSrc
1550:
1551: dstScanlineOffset += dstScanlineStride;
1552:
1553: } // for ySrc
1554:
1555: } // for band
1556:
1557: return;
1558:
1559: } // computeRectDouble
1560:
1561: } // class FilteredSubsampleOpImage
|