/** Tests the {@link #rescaleToBytes()} operation. */
  @Test
  public void rescaleToBytes() {

    assertTrue("Assertions should be enabled.", ImageWorker.class.desiredAssertionStatus());

    // set up synthetic images for testing
    final RenderedImage test1 =
        ConstantDescriptor.create(128.0f, 128.0f, new Double[] {20000.0}, null);
    final RenderedImage test2 =
        ConstantDescriptor.create(128.0f, 128.0f, new Double[] {255.0}, null);
    final RenderedImage test3 = getSynthetic(20000);
    final RenderedImage test4 = getSynthetic(255);

    // starting to check the results

    // single band value exceed the byte upper bound and is constant
    final ImageWorker test1I = new ImageWorker(test1).rescaleToBytes();
    Assert.assertEquals("Format", test1I.getRenderedOperation().getOperationName());
    final double[] maximums1 = test1I.getMaximums();
    Assert.assertTrue(maximums1.length == 1);
    Assert.assertEquals(255.0, maximums1[0], 1E-10);
    final double[] minimums1 = test1I.getMinimums();
    Assert.assertTrue(minimums1.length == 1);
    Assert.assertEquals(255.0, minimums1[0], 1E-10);

    // single band value does not exceed the byte upper bound and is constant
    final ImageWorker test2I = new ImageWorker(test2).rescaleToBytes();
    Assert.assertEquals("Format", test2I.getRenderedOperation().getOperationName());
    final double[] maximums2 = test1I.getMaximums();
    Assert.assertTrue(maximums2.length == 1);
    Assert.assertEquals(255.0, maximums2[0], 1E-10);
    final double[] minimums2 = test1I.getMinimums();
    Assert.assertTrue(minimums2.length == 1);
    Assert.assertEquals(255.0, minimums2[0], 1E-10);

    // single band value exceed the byte upper bound
    ImageWorker test3I = new ImageWorker(test3);
    final double[] maximums3a = test3I.getMaximums();
    final double[] minimums3a = test3I.getMinimums();
    test3I.rescaleToBytes();
    Assert.assertEquals("Rescale", test3I.getRenderedOperation().getOperationName());
    final double[] maximums3b = test3I.getMaximums();
    final double[] minimums3b = test3I.getMinimums();

    if (maximums3a[0] > 255) {
      Assert.assertTrue(Math.abs(maximums3a[0] - maximums3b[0]) > 1E-10);
      Assert.assertTrue(Math.abs(255.0 - maximums3b[0]) >= 0);
    }

    if (minimums3a[0] < 0) {
      Assert.assertTrue(minimums3b[0] >= 0);
    }

    // single band value does not exceed the byte upper bound
    ImageWorker test4I = new ImageWorker(test4);
    final double[] maximums4a = test4I.getMaximums();
    final double[] minimums4a = test4I.getMinimums();
    test4I.rescaleToBytes();
    Assert.assertEquals("Format", test4I.getRenderedOperation().getOperationName());
    final double[] maximums4b = test4I.getMaximums();
    final double[] minimums4b = test4I.getMinimums();
    Assert.assertEquals(maximums4a[0], maximums4b[0], 1E-10);
    Assert.assertEquals(minimums4a[0], minimums4b[0], 1E-10);

    // now test multibands case
    final RenderedImage multiband = BandMergeDescriptor.create(test2, test3, null);
    ImageWorker testmultibandI = new ImageWorker(multiband);
    final double[] maximums5a = testmultibandI.getMaximums();
    final double[] minimums5a = testmultibandI.getMinimums();
    testmultibandI.rescaleToBytes();
    final double[] maximums5b = testmultibandI.getMaximums();
    final double[] minimums5b = testmultibandI.getMinimums();
    Assert.assertEquals(maximums5a[0], maximums5b[0], 1E-10);
    Assert.assertEquals(minimums5a[0], minimums5b[0], 1E-10);

    Assert.assertTrue(Math.abs(maximums5a[1] - maximums5b[1]) > 1E-10);
    Assert.assertTrue(Math.abs(minimums5a[1] - minimums5b[1]) > 1E-10);
  }
  /**
   * Returns the specified property.
   *
   * @param name Property name.
   * @param opNode Operation node.
   */
  public Object getProperty(String name, Object opNode) {
    validate(name, opNode);

    if (opNode instanceof RenderedOp && name.equalsIgnoreCase("roi")) {
      RenderedOp op = (RenderedOp) opNode;

      ParameterBlock pb = op.getParameterBlock();

      // Retrieve the rendered source image and its ROI.
      RenderedImage src = pb.getRenderedSource(0);
      Object property = src.getProperty("ROI");
      if (property == null
          || property.equals(java.awt.Image.UndefinedProperty)
          || !(property instanceof ROI)) {
        return java.awt.Image.UndefinedProperty;
      }

      // Return undefined also if source ROI is empty.
      ROI srcROI = (ROI) property;
      if (srcROI.getBounds().isEmpty()) {
        return java.awt.Image.UndefinedProperty;
      }

      // Retrieve the Interpolation object.
      Interpolation interp = (Interpolation) pb.getObjectParameter(1);

      // Determine the effective source bounds.
      Rectangle srcBounds = null;
      PlanarImage dst = op.getRendering();
      if (dst instanceof GeometricOpImage && ((GeometricOpImage) dst).getBorderExtender() == null) {
        srcBounds =
            new Rectangle(
                src.getMinX() + interp.getLeftPadding(),
                src.getMinY() + interp.getTopPadding(),
                src.getWidth() - interp.getWidth() + 1,
                src.getHeight() - interp.getHeight() + 1);
      } else {
        srcBounds = new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(), src.getHeight());
      }

      // If necessary, clip the ROI to the effective source bounds.
      if (!srcBounds.contains(srcROI.getBounds())) {
        srcROI = srcROI.intersect(new ROIShape(srcBounds));
      }

      // Retrieve the Warp object.
      Warp warp = (Warp) pb.getObjectParameter(0);

      // Setting constant image to be warped as a ROI
      Rectangle dstBounds = op.getBounds();

      // Setting layout of the constant image
      ImageLayout2 layout = new ImageLayout2();
      int minx = (int) srcBounds.getMinX();
      int miny = (int) srcBounds.getMinY();
      int w = (int) srcBounds.getWidth();
      int h = (int) srcBounds.getHeight();
      layout.setMinX(minx);
      layout.setMinY(miny);
      layout.setWidth(w);
      layout.setHeight(h);
      RenderingHints hints = op.getRenderingHints();
      hints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout));

      final PlanarImage constantImage =
          ConstantDescriptor.create(new Float(w), new Float(h), new Byte[] {(byte) 255}, hints);

      PlanarImage roiImage = null;

      // Make sure to specify tileCache, tileScheduler, tileRecyclier, by cloning hints.
      RenderingHints warpingHints = op.getRenderingHints();
      warpingHints.remove(JAI.KEY_IMAGE_LAYOUT);

      // Creating warped roi by the same way (Warp, Interpolation, source ROI) we warped the
      // input image.
      final ParameterBlock paramBlk = new ParameterBlock();
      paramBlk.addSource(constantImage);
      paramBlk.add(warp);
      paramBlk.add(interp);
      paramBlk.add(null);
      paramBlk.add(srcROI);

      // force in the image layout, this way we get exactly the same
      // as the affine we're eliminating
      Hints localHints = new Hints(op.getRenderingHints());
      localHints.remove(JAI.KEY_IMAGE_LAYOUT);
      ImageLayout il = new ImageLayout();
      il.setMinX(dstBounds.x);
      il.setMinY(dstBounds.y);
      il.setWidth(dstBounds.width);
      il.setHeight(dstBounds.height);
      localHints.put(JAI.KEY_IMAGE_LAYOUT, il);
      roiImage = JAI.create("Warp", paramBlk, localHints);
      ROI dstROI = new ROI(roiImage, 1);

      // If necessary, clip the warped ROI to the destination bounds.
      if (!dstBounds.contains(dstROI.getBounds())) {
        dstROI = dstROI.intersect(new ROIShape(dstBounds));
      }

      // Return the warped and possibly clipped ROI.
      return dstROI;
    }

    return java.awt.Image.UndefinedProperty;
  }