1 /*
2  * $RCSfile: FilteredSubsampleDescriptor.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.1 $
9  * $Date: 2005/02/11 04:57:36 $
10  * $State: Exp $
11  */package com.lightcrafts.mediax.jai.operator;
12 
13 import com.lightcrafts.media.jai.util.PropertyGeneratorImpl;
14 import java.awt.Rectangle;
15 import java.awt.RenderingHints;
16 import java.awt.geom.AffineTransform;
17 import java.awt.image.RenderedImage;
18 import java.awt.image.renderable.ParameterBlock;
19 import com.lightcrafts.mediax.jai.Interpolation;
20 import com.lightcrafts.mediax.jai.InterpolationBicubic2;
21 import com.lightcrafts.mediax.jai.InterpolationBicubic;
22 import com.lightcrafts.mediax.jai.InterpolationBilinear;
23 import com.lightcrafts.mediax.jai.InterpolationNearest;
24 import com.lightcrafts.mediax.jai.JAI;
25 import com.lightcrafts.mediax.jai.OpImage;
26 import com.lightcrafts.mediax.jai.OperationDescriptorImpl;
27 import com.lightcrafts.mediax.jai.ParameterBlockJAI;
28 import com.lightcrafts.mediax.jai.PlanarImage;
29 import com.lightcrafts.mediax.jai.ROI;
30 import com.lightcrafts.mediax.jai.ROIShape;
31 import com.lightcrafts.mediax.jai.RenderableOp;
32 import com.lightcrafts.mediax.jai.RenderedOp;
33 import com.lightcrafts.mediax.jai.WarpOpImage;
34 import com.lightcrafts.mediax.jai.registry.RenderedRegistryMode;
35 
36 /**
37  * <p> This property generator computes the properties for the operation
38  * "FilteredSubsample" dynamically.
39  */
40 class FilteredSubsamplePropertyGenerator extends PropertyGeneratorImpl {
41 
42     /** Constructor. */
FilteredSubsamplePropertyGenerator()43     public FilteredSubsamplePropertyGenerator() {
44         super(new String[] {"FilteredSubsample"},
45               new Class[] {boolean.class},
46               new Class[] {RenderedOp.class, RenderableOp.class});
47     }
48 
49     /**
50      * Returns the specified property.
51      *
52      * @param name  Property name.
53      * @param opNode Operation node.
54      */
getProperty(String name, Object opNode)55     public Object getProperty(String name,
56                               Object opNode) {
57         validate(name, opNode);
58 
59         if(opNode instanceof RenderedOp &&
60            name.equalsIgnoreCase("roi")) {
61             RenderedOp op = (RenderedOp)opNode;
62 
63 	    ParameterBlock pb = op.getParameterBlock();
64 
65 	    // Retrieve the rendered source image and its ROI.
66 	    RenderedImage src = pb.getRenderedSource(0);
67 	    Object property = src.getProperty("ROI");
68 	    if (property == null ||
69 		property.equals(java.awt.Image.UndefinedProperty) ||
70 		!(property instanceof ROI)) {
71 		return null;
72 	    }
73 	    ROI srcROI = (ROI)property;
74 
75 	    // Determine the effective source bounds.
76 	    Rectangle srcBounds = null;
77 	    PlanarImage dst = op.getRendering();
78 	    if(dst instanceof WarpOpImage && !((OpImage)dst).hasExtender(0)) {
79 		WarpOpImage warpIm = (WarpOpImage)dst;
80 		srcBounds =
81 		    new Rectangle(src.getMinX() + warpIm.getLeftPadding(),
82 				  src.getMinY() + warpIm.getTopPadding(),
83 				  src.getWidth() - warpIm.getWidth() + 1,
84 				  src.getHeight() - warpIm.getHeight() + 1);
85 	    } else {
86 		srcBounds = new Rectangle(src.getMinX(),
87 					  src.getMinY(),
88 					  src.getWidth(),
89 					  src.getHeight());
90 	    }
91 
92 	    // If necessary, clip the ROI to the effective source bounds.
93 	    if(!srcBounds.contains(srcROI.getBounds())) {
94 		srcROI = srcROI.intersect(new ROIShape(srcBounds));
95 	    }
96 
97 	    // Retrieve the scale factors
98 	    float sx = 1.0F/pb.getIntParameter(1);
99 	    float sy = 1.0F/pb.getIntParameter(2);
100 
101 	    // Create an equivalent transform.
102 	    AffineTransform transform =
103 		new AffineTransform(sx, 0.0, 0.0, sy, 0, 0);
104 
105 	    // Create the scaled ROI.
106 	    ROI dstROI = srcROI.transform(transform);
107 
108 	    // Retrieve the destination bounds.
109 	    Rectangle dstBounds = op.getBounds();
110 
111 	    // If necessary, clip the warped ROI to the destination bounds.
112 	    if(!dstBounds.contains(dstROI.getBounds())) {
113 		dstROI = dstROI.intersect(new ROIShape(dstBounds));
114 	    }
115 
116 	    // Return the warped and possibly clipped ROI.
117 	    return dstROI;
118 	} else {
119 	    return null;
120 	}
121     }
122 
123     /** Returns the valid property names for the operation "FilteredSubsample". */
getPropertyNames()124     public String[] getPropertyNames() {
125         String[] properties = new String[1];
126         properties[0] = "roi";
127         return(properties);
128     }
129 
130 }
131 
132 /**
133  * An <code>OperationDescriptor</code> describing the "FilteredSubsample"
134  * operation.
135  *
136  * <p> The "FilteredSubsample" operation subsamples an image by integral
137  * factors. The furnished scale factors express the ratio of the
138  * source dimensions to the destination dimensions.  The input filter is
139  * symmetric about the center pixel and is specified by values from the
140  * center outward.  Both filter axes use the same input filter values.
141 
142  * <p> When applying scale factors of scaleX, scaleY to a source image
143  * with width of src_width and height of src_height, the resulting image
144  * is defined to have the following bounds:
145  *
146  * <code></pre>
147  *       dst_minX  = round(src_minX  / scaleX) <br>
148  *       dst_minY  = round(src_minY  / scaleY) <br>
149  *       dst_width  =  round(src_width  / scaleX) <br>
150  *       dst_height =  round(src_height / scaleY) <br>
151  * </pre></code>
152  *
153  * <p> The input filter is quadrant symmetric (typically antialias). The
154  * filter is product-separable, quadrant symmetric, and is defined by half of its
155  * span. For example, if the input filter, qsFilter, was of size 3, it would have
156  * width and height 5 and have the symmetric form: <br>
157  *   <code> qs[2] qs[1] qs[0] qs[1] qs[2] </code> <br>
158  *
159  * <p> A fully expanded 5 by 5 kernel has the following format (25 entries
160  * defined by only 3 entries):
161  *
162  *   <code>
163  *   <p align=center> qs[2]*qs[2]  qs[2]*qs[1]  qs[2]*qs[0]  qs[2]*qs[1]  qs[2]*qs[2] <br>
164  *
165  *                    qs[1]*qs[2]  qs[1]*qs[1]  qs[1]*qs[0]  qs[1]*qs[1]  qs[1]*qs[2] <br>
166  *
167  *                    qs[0]*qs[2]  qs[0]*qs[1]  qs[0]*qs[0]  qs[0]*qs[1]  qs[0]*qs[2] <br>
168  *
169  *                    qs[1]*qs[2]  qs[1]*qs[1]  qs[1]*qs[0]  qs[1]*qs[1]  qs[1]*qs[2] <br>
170  *
171  *                    qs[2]*qs[2]  qs[2]*qs[1]  qs[2]*qs[0]  qs[2]*qs[1]  qs[2]*qs[2]
172  *   </p> </code>
173  *
174  * <p> This operator is similar to the image scale operator.  Important
175  * differences are described here.  The coordinate transformation differences
176  * between the FilteredDownsampleOpImage and the ScaleOpImage operators can be
177  * understood by comparing their mapping equations directly.
178  *
179  * <p> For the scale operator, the destination (D) to source (S) mapping
180  * equations are given by
181  *
182  * <code>
183  *   <p> xS = (xD - xTrans)/xScale <br>
184  *       yS = (yD - yTrans)/yScale
185  * </code>
186  *
187  * <p> The scale and translation terms are floating point values in D-frame
188  * pixel units.  For scale this means that one S pixel maps to xScale
189  * by yScale D-frame pixels.  The translation vector, (xTrans, yTrans),
190  * is in D-frame pixel units.
191  *
192  * <p> The FilteredSubsample operator mapping equations are given by
193  *
194  * <code>
195  *   <p> xS = xD*scaleX <br>
196  *   yS = yD*scaleY
197  * </code>
198  *
199  * <p> The scale terms for this operation are integral values in the
200  * S-Frame; there are no translation terms for this operation.
201  *
202  * <p> The downsample terms are restricted to positive integral values.
203  * Geometrically, one D-frame pixel maps to scaleX * scaleY S-frame
204  * pixels.  The combination of downsampling and filtering has performance
205  * benefits over sequential operator usage in part due to the symmetry
206  * constraints imposed by only allowing integer parameters for scaling and
207  * only allowing separable symmetric filters.  With odd scale factors, D-frame
208  * pixels map directly onto S-frame pixel centers.  With even scale factors,
209  * D-frame pixels map squarely between S-frame pixel centers.  Below are
210  * examples of even, odd, and combination cases.
211  *
212  *   <p>  s = S-frame pixel centers <br>
213  *        d = D-frame pixel centers mapped to S-frame
214  *   </p>
215  *   <kbd>
216  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
217  *   <pre>   d       d       d  </pre>
218  *   <pre> s   s   s   s   s   s           s   d   s   s   d   s  </pre>
219  *   <pre>  </pre>
220  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
221  *   <pre>   d       d       d  </pre>
222  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
223  *   <pre>  </pre>
224  *   <pre> s   s   s   s   s   s           s   d   s   s   d   s  </pre>
225  *   <pre>   d       d       d  </pre>
226  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
227  *   <pre>  </pre>
228  *   <pre> Even scaleX/Y factors            Odd scaleX/Y factors  </pre>
229  *   <pre>   </pre>
230  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
231  *   <pre>     d           d    </pre>
232  *   <pre> s   s   s   s   s   s           s d s   s d s   s d s  </pre>
233  *   <pre>   </pre>
234  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
235  *   <pre>     d           d    </pre>
236  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
237  *   <pre>   </pre>
238  *   <pre> s   s   s   s   s   s           s d s   s d s   s d s  </pre>
239  *   <pre>     d           d    </pre>
240  *   <pre> s   s   s   s   s   s           s   s   s   s   s   s  </pre>
241  *   <pre>   </pre>
242  * <pre>  Odd/even scaleX/Y factors      Even/odd scaleX/Y factors  </pre> <br>
243  *   </kbd>
244  *
245  * <p> The convolution kernel is restricted to have quadrant symmetry (qs). This
246  * type of symmetry is also product separable.  The qsFilter is specified by
247  * a floating array.  If qsFilter[0], qsFilter[1], ... ,
248  * qsFilter[qsFilter.length - 1]
249  * is the filter input, then the entire separable kernel is given by <br>
250  * qsFilter[qsFilter.length - 1], ... , qsFilter[0], ... ,
251  * qsFilter[qsFilter.length - 1] <br>
252  *
253  * <p> The restriction of integer parameter constraints allows full product
254  * separablity and symmetry when applying the combined resample and filter
255  * convolution operations.
256  *
257  * <p> If Bilinear or Bicubic interpolation is specified, the source needs
258  * to be extended such that it has the extra pixels needed to compute all
259  * the destination pixels. This extension is performed via the
260  * <code>BorderExtender</code> class. The type of border extension can be
261  * specified as a <code>RenderingHint</code> to the <code>JAI.create</code>
262  * method.
263  *
264  * <p> If no <code>BorderExtender</code> is specified, the source will
265  * not be extended.  The output image size is still calculated
266  * according to the formula specified above. However since there is not
267  * enough source to compute all the destination pixels, only that
268  * subset of the destination image's pixels which can be computed,
269  * will be written in the destination. The rest of the destination
270  * will be set to zeros.
271  *
272  * <p> It should be noted that this operation automatically adds a
273  * value of <code>Boolean.TRUE</code> for the
274  * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> to the given
275  * <code>configuration</code> so that the operation is performed
276  * on the pixel values instead of being performed on the indices into
277  * the color map if the source(s) have an <code>IndexColorModel</code>.
278  * This addition will take place only if a value for the
279  * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been
280  * provided by the user. Note that the <code>configuration</code> Map
281  * is cloned before the new hint is added to it. The operation can be
282  * smart about the value of the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code>
283  * <code>RenderingHints</code>, i.e. while the default value for the
284  * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> is
285  * <code>Boolean.TRUE</code>, in some cases the operator could set the
286  * default.
287  *
288  * <p> "FilteredSubsample" defines a PropertyGenerator that performs an identical
289  * transformation on the "ROI" property of the source image, which can
290  * be retrieved by calling the <code>getProperty</code> method with
291  * "ROI" as the property name.
292  *
293  * <p> One design purpose of this operation is anti-aliasing when
294  * downsampling.  The technique of anti-aliasing applies a good
295  * filter to the area of rendering to generate better results.  Generally,
296  * this filter is required to be (quadrant) symmetric and separable
297  * to obtain good performance.  The widely-used Gaussian filter
298  * satisfies all these requirements.  Thus, the default value for the
299  * parameter "qsFilter" is generated from a Gaussian kernel
300  * based on the following procedure:
301  *
302  * <p> Define the Gaussian function <code>G(x)</code> as
303  * <p>  <code>G(x) = e<sup>-x<sup>2</sup>/(2s<sup>2</sup>)</sup>/(
304  * (2pi)<sup>&#189;</sup>s)</code>,
305  * <p>where <code>s</code> is the standard deviation, and <code>pi</code>
306  * is the ratio of the circumference of a circle to its diameter.
307  *
308  * <p> For a one-dimensional Gaussian kernel with a size of <code>2N+1</code>,
309  *  the standard deviation of the Gaussian function to generate this kernel
310  *  is chosen as <code>N/3</code>.  The
311  *  one-dimensional Gaussian kernel <code>K<sub>N</sub>(1:2N+1)</code> is
312  *  <p><code>(G(-N)/S, G(-N+1)/S, ..., G(0),..., G(N-1)/S, G(N)/S), </code>
313  * <p> where <code>S</code> is the summation of <code>G(-N), G(-N+1), ...,G(0),
314  * ..., G(N-1)</code>, and <code>G(N)</code>.  A two-dimensional Gaussian
315  * kernel with a size of <code>(2N+1) x (2N+1)</code> is constructed as the
316  * outer product of two <code>K<sub>N</sub></code>s:
317  * the <code>(i, j)</code>th element is
318  * <code>K<sub>N</sub>(i)K<sub>N</sub>(j)</code>.
319  * The quadrant symmetric filter corresponding to the
320  * <code>(2N+1) x (2N+1)</code> Gaussian kernel is simply
321  * <p> <code>(G(0)/S, G(1)/S, ..., G(N)/S)</code>, or
322  * <p> <code>(K<sub>N</sub>(N+1), ..., K<sub>N</sub>(2N+1)</code>.
323  *
324  * <p> Denote the maximum of the X and Y subsample factors as <code>M</code>.
325  * If <code>M</code> is even, the default "qsFilter" is the quadrant symmetric
326  * filter derived from the two-dimensional <code>(M+1) x (M+1)</code>
327  * Gaussian kernel. If <code>M</code> is odd, the default
328  * "qsFilter" is the quadrant symmetric filter derived from the
329  * two-dimensional <code>M x M</code> Gaussian kernel.
330  *
331  * <p><table border=1>
332  * <caption>Resource List</caption>
333  * <tr><th>Name</th>        <th>Value</th></tr>
334  * <tr><td>GlobalName</td>  <td>FilteredSubsample</td></tr>
335  * <tr><td>LocalName</td>   <td>FilteredSubsample</td></tr>
336  * <tr><td>Vendor</td>      <td>com.lightcrafts.media.jai</td></tr>
337  * <tr><td>Description</td> <td>Filters and subsamples an image.</td></tr>
338  * <tr><td>DocURL</td>      <td>http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/FilteredSubsample.html</td></tr>
339  * <tr><td>Version</td>     <td>1.1</td></tr>
340  * <tr><td>arg0Desc</td>    <td>The X subsample factor.</td></tr>
341  * <tr><td>arg1Desc</td>    <td>The Y subsample factor.</td></tr>
342  * <tr><td>arg2Desc</td>    <td>Symmetric filter coefficients.</td></tr>
343  * <tr><td>arg3Desc</td>    <td>The interpolation object for
344  *                              resampling.</td></tr>
345  * </table></p>
346  *
347  * <p><table border=1>
348  * <caption>Parameter List</caption>
349  * <tr><th>Name</th>          <th>Class Type</th>
350  *                            <th>Default Value</th></tr>
351  * <tr><td>scaleX</td>        <td>java.lang.Integer</td>
352  *                            <td>2</td>
353  * <tr><td>scaleY</td>        <td>java.lang.Integer</td>
354  *                            <td>2</td>
355  * <tr><td>qsFilter</td>      <td>java.lang.Float []</td>
356  *                            <td>A quadrant symmetric filter
357  *				  generated from a Gaussian kernel
358  *				  as described above.</td>
359  * <tr><td>interpolation</td> <td>com.lightcrafts.mediax.jai.Interpolation</td>
360  *                            <td>InterpolationNearest</td>
361  * </table></p>
362  *
363  * @see com.lightcrafts.mediax.jai.Interpolation
364  * @see com.lightcrafts.mediax.jai.BorderExtender
365  * @see com.lightcrafts.mediax.jai.OperationDescriptor
366  *
367  * @since JAI 1.1
368  */
369 public class FilteredSubsampleDescriptor extends OperationDescriptorImpl {
370 
371     /**
372      * The resource strings that provide the general documentation
373      * and specify the parameter list for this operation.
374      */
375     private static final String[][] resources = {
376         {"GlobalName",  "FilteredSubsample"},
377         {"LocalName",   "FilteredSubsample"},
378         {"Vendor",      "com.lightcrafts.media.jai"},
379         {"Description", JaiI18N.getString("FilteredSubsampleDescriptor0")},
380         {"DocURL",      "http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/FilteredSubsampleDescriptor.html"},
381         {"Version",     "1.0"},
382         {"arg0Desc",    "The X subsample factor."},
383         {"arg1Desc",    "The Y subsample factor."},
384 	{"arg2Desc",    "Symmetric filter coefficients."},
385 	{"arg3Desc",    "Interpolation object."}
386     };
387 
388     /** The parameter class list for this operation. */
389     private static final Class[] paramClasses = {
390         java.lang.Integer.class, java.lang.Integer.class,
391 	float[].class, Interpolation.class
392     };
393 
394     /** The parameter name list for this operation. */
395     private static final String[] paramNames = {
396         "scaleX", "scaleY", "qsFilterArray", "interpolation",
397     };
398 
399     /** The parameter default value list for this operation. */
400     private static final Object[] paramDefaults = {
401         new Integer(2),
402         new Integer(2),
403         null,
404         Interpolation.getInstance(Interpolation.INTERP_NEAREST)
405     };
406 
407     private static final String[] supportedModes = {
408 	"rendered"
409     };
410 
411     /** <p> Constructor. */
FilteredSubsampleDescriptor()412     public FilteredSubsampleDescriptor() {
413         super(resources, supportedModes, 1,
414               paramNames, paramClasses, paramDefaults, null);
415     }
416 
417     /**
418      * Validates the input parameters.
419      *
420      * <p> In addition to the standard checks performed by the
421      * superclass method, this method checks that "scaleX" and "scaleY"
422      * are both greater than 0 and that the interpolation type
423      * is one of 4 standard types: <br>
424      * <p> <code>
425      *    com.lightcrafts.mediax.jai.InterpolationNearest <br>
426      *    com.lightcrafts.mediax.jai.InterpolationBilinear <br>
427      *    com.lightcrafts.mediax.jai.InterpolationBicubic <br>
428      *    com.lightcrafts.mediax.jai.InterpolationBicubic2
429      * </code>
430      */
validateParameters(String modeName, ParameterBlock args, StringBuffer msg)431     protected boolean validateParameters(String modeName,
432 					 ParameterBlock args,
433                                          StringBuffer msg) {
434 	if (!super.validateParameters(modeName, args, msg))
435 	    return false;
436 
437         int scaleX = args.getIntParameter(0);
438         int scaleY = args.getIntParameter(1);
439         if (scaleX < 1 || scaleY < 1) {
440             msg.append(getName() + " " +
441                        JaiI18N.getString("FilteredSubsampleDescriptor1"));
442             return false;
443         }
444 
445 	float[] filter = (float[])args.getObjectParameter(2);
446 
447 	// if this parameter is null, generate the kernel based on the
448 	// procedure described above.
449 	if (filter == null) {
450 	    int m = scaleX > scaleY ? scaleX: scaleY;
451 	    if ((m & 1) == 0)
452 		m++;
453 
454 	    double sigma = (m - 1) / 6.0;
455 
456 	    // when m is 1, sigma is 0; will generate NaN.  Give any number
457 	    // to sigma will generate the correct kernel {1.0}
458 	    if (m == 1)
459 		sigma = 1.0;
460 
461 	    filter = new float[m/2 + 1];
462 	    float sum = 0;
463 
464 	    for (int i = 0; i < filter.length; i++) {
465 		filter[i] = (float)gaussian((double)i, sigma);
466 		if (i == 0)
467 		    sum += filter[i];
468 		else
469 		    sum += filter[i] * 2;
470 	    }
471 
472 	    for (int i = 0; i < filter.length; i++) {
473 		filter[i] /= sum;
474 	    }
475 
476 	    args.set(filter, 2);
477 	}
478 
479         Interpolation interp = (Interpolation)args.getObjectParameter(3);
480 
481         // Determine the interpolation type, if not supported throw exception
482         if (!((interp instanceof InterpolationNearest)  ||
483             (interp instanceof InterpolationBilinear) ||
484             (interp instanceof InterpolationBicubic)  ||
485             (interp instanceof InterpolationBicubic2))) {
486            msg.append(getName() + " " +
487                        JaiI18N.getString("FilteredSubsampleDescriptor2"));
488            return false;
489         }
490         return true;
491 
492     } // validateParameters
493 
494     /** Computes the value of Gaussian function at <code>x</code>.
495      * @param x The coordinate at where the value is computed.
496      * @param sigma The standard deviation for the Gaussian function.
497      */
gaussian(double x, double sigma)498     private double gaussian(double x, double sigma) {
499 	return Math.exp(-x*x/(2 * sigma * sigma)) / sigma / Math.sqrt(2 * Math.PI);
500     }
501 
502 
503 
504     /**
505      * Filters and subsamples an image.
506      *
507      * <p>Creates a <code>ParameterBlockJAI</code> from all
508      * supplied arguments except <code>hints</code> and invokes
509      * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
510      *
511      * @see JAI
512      * @see ParameterBlockJAI
513      * @see RenderedOp
514      *
515      * @param source0 <code>RenderedImage</code> source 0.
516      * @param scaleX The X subsample factor.
517      * May be <code>null</code>.
518      * @param scaleY The Y subsample factor.
519      * May be <code>null</code>.
520      * @param qsFilterArray Symmetric filter coefficients.
521      * May be <code>null</code>.
522      * @param interpolation Interpolation object.
523      * May be <code>null</code>.
524      * @param hints The <code>RenderingHints</code> to use.
525      * May be <code>null</code>.
526      * @return The <code>RenderedOp</code> destination.
527      * @throws IllegalArgumentException if <code>source0</code> is <code>null</code>.
528      */
create(RenderedImage source0, Integer scaleX, Integer scaleY, float[] qsFilterArray, Interpolation interpolation, RenderingHints hints)529     public static RenderedOp create(RenderedImage source0,
530                                     Integer scaleX,
531                                     Integer scaleY,
532                                     float[] qsFilterArray,
533                                     Interpolation interpolation,
534                                     RenderingHints hints)  {
535         ParameterBlockJAI pb =
536             new ParameterBlockJAI("FilteredSubsample",
537                                   RenderedRegistryMode.MODE_NAME);
538 
539         pb.setSource("source0", source0);
540 
541         pb.setParameter("scaleX", scaleX);
542         pb.setParameter("scaleY", scaleY);
543         pb.setParameter("qsFilterArray", qsFilterArray);
544         pb.setParameter("interpolation", interpolation);
545 
546         return JAI.create("FilteredSubsample", pb, hints);
547     }
548 } // FilteredSubsampleDescriptor
549