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>½</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