Example #1
0
 /**
  * Gets cloned image files.
  *
  * @return the ClonedImageFiles
  * @throws java.io.IOException
  */
 public List<File> getClonedImageFiles() throws IOException {
   if (oimages != null) {
     if (dpiX == 0 || dpiY == 0) {
       if (rect == null || rect.isEmpty()) {
         return ImageIOHelper.createTiffFiles(oimages, index);
       } else {
         // rectangular region
         //                    BufferedImage bi = ((BufferedImage)
         // oimages.get(index).getRenderedImage()).getSubimage(rect.x, rect.y, rect.width,
         // rect.height);
         // On Linux, the standard getSubimage method has generated images that Tesseract does not
         // like.
         BufferedImage bi =
             ImageHelper.getSubImage(
                 (BufferedImage) oimages.get(index).getRenderedImage(),
                 rect.x,
                 rect.y,
                 rect.width,
                 rect.height);
         List<IIOImage> tempList = new ArrayList<IIOImage>();
         tempList.add(new IIOImage(bi, null, null));
         return ImageIOHelper.createTiffFiles(tempList, 0);
       }
     } else {
       // scaling
       if (rect == null || rect.isEmpty()) {
         List<IIOImage> tempList = new ArrayList<IIOImage>();
         for (IIOImage oimage : (index == -1 ? oimages : oimages.subList(index, index + 1))) {
           BufferedImage bi = (BufferedImage) oimage.getRenderedImage();
           Map<String, String> metadata = ImageIOHelper.readImageData(oimage);
           float scale = dpiX / Float.parseFloat(metadata.get("dpiX"));
           bi =
               ImageHelper.getScaledInstance(
                   bi, (int) (bi.getWidth() * scale), (int) (bi.getHeight() * scale));
           tempList.add(new IIOImage(bi, null, null));
         }
         return ImageIOHelper.createTiffFiles(tempList, (index == -1 ? index : 0), dpiX, dpiY);
       } else {
         // rectangular region
         // Cut out the subimage first and rescale that
         BufferedImage bi =
             ((BufferedImage) oimages.get(index).getRenderedImage())
                 .getSubimage(rect.x, rect.y, rect.width, rect.height);
         Map<String, String> metadata = ImageIOHelper.readImageData(oimages.get(index));
         float scale = dpiX / Float.parseFloat(metadata.get("dpiX"));
         bi =
             ImageHelper.getScaledInstance(
                 bi, (int) (bi.getWidth() * scale), (int) (bi.getHeight() * scale));
         List<IIOImage> tempList = new ArrayList<IIOImage>();
         tempList.add(new IIOImage(bi, null, null));
         return ImageIOHelper.createTiffFiles(tempList, 0, dpiX, dpiY);
       }
     }
   } else {
     return ImageIOHelper.createTiffFiles(imageFile, index);
   }
 }
 public static Rectangle[] computeDifference(final Rectangle rect1, final Rectangle rect2) {
   if (rect1 == null || rect1.isEmpty() || rect2 == null || rect2.isEmpty()) {
     return new Rectangle[0];
   }
   Rectangle isection = rect1.intersection(rect2);
   if (isection.isEmpty()) {
     return new Rectangle[0];
   }
   ArrayList reminders = new ArrayList(4);
   substract(rect1, isection, reminders);
   return (Rectangle[]) reminders.toArray(new Rectangle[0]);
 }
  public void setRect(int dx, int dy, Raster srcRaster) {
    Rectangle targetUnclipped =
        new Rectangle(
            srcRaster.getMinX() + dx,
            srcRaster.getMinY() + dy,
            srcRaster.getWidth(),
            srcRaster.getHeight());

    Rectangle target = getBounds().intersection(targetUnclipped);

    if (target.isEmpty()) return;

    int sx = target.x - dx;
    int sy = target.y - dy;

    // FIXME: Do tests on rasters and use get/set data instead.

    /* The JDK documentation seems to imply this implementation.
    (the trucation of higher bits), but an implementation using
    get/setDataElements would be more efficient. None of the
    implementations would do anything sensible when the sample
    models don't match.

    But this is probably not the place to consider such
    optimizations.*/

    int[] pixels = srcRaster.getPixels(sx, sy, target.width, target.height, (int[]) null);

    setPixels(target.x, target.y, target.width, target.height, pixels);
  }
  /**
   * Scrolls the map pane image. We use {@linkplain MapPane#moveImage(int, int)} rather than
   * {@linkplain MapPane#setDisplayArea(<any>)} in this method because it gives much smoother
   * scrolling when the key is held down.
   *
   * @param action scroll direction
   */
  private void scroll(Action action) {
    Rectangle r = ((JComponent) mapPane).getVisibleRect();
    if (!(r == null || r.isEmpty())) {
      int dx = 0;
      int dy = 0;
      switch (action) {
        case SCROLL_LEFT:
          dx = Math.max(1, (int) (r.getWidth() * SCROLL_FRACTION));
          break;

        case SCROLL_RIGHT:
          dx = Math.min(-1, (int) (-r.getWidth() * SCROLL_FRACTION));
          break;

        case SCROLL_UP:
          dy = Math.max(1, (int) (r.getWidth() * SCROLL_FRACTION));
          break;

        case SCROLL_DOWN:
          dy = Math.min(-1, (int) (-r.getWidth() * SCROLL_FRACTION));
          break;

        default:
          throw new IllegalArgumentException("Invalid action argument: " + action);
      }

      mapPane.moveImage(dx, dy);
    }
  }
  /**
   * Sets image to be processed.
   *
   * @param xsize width of image
   * @param ysize height of image
   * @param buf pixel data
   * @param rect the bounding rectangle defines the region of the image to be recognized. A
   *     rectangle of zero dimension or <code>null</code> indicates the whole image.
   * @param bpp bits per pixel, represents the bit depth of the image, with 1 for binary bitmap, 8
   *     for gray, and 24 for color RGB.
   */
  private void setImage(int xsize, int ysize, ByteBuffer buf, Rectangle rect, int bpp) {
    int bytespp = bpp / 8;
    int bytespl = (int) Math.ceil(xsize * bpp / 8.0);
    api.TessBaseAPISetImage(handle, buf, xsize, ysize, bytespp, bytespl);

    if (rect != null && !rect.isEmpty()) {
      api.TessBaseAPISetRectangle(handle, rect.x, rect.y, rect.width, rect.height);
    }
  }
 private boolean isUpdateApplicable(
     RemoteDesktopServerEvent e, RemoteDesktopServerEvent evt, float passRatio) {
   if (e.getUpdateRect() != null && evt.getUpdateRect() != null) {
     Rectangle intersect = e.getUpdateRect().intersection(evt.getUpdateRect());
     if (intersect != null && !intersect.isEmpty()) {
       float ratio =
           100
               * (intersect.width * intersect.height)
               / (e.getUpdateRect().width * e.getUpdateRect().height);
       return ratio >= passRatio;
     }
   }
   return false;
 }
  private void addInvalidArea(Rectangle invalidArea) {
    if (invalidArea.x > getWidth() || (invalidArea.x + invalidArea.width < 0)) return;
    if (invalidArea.y > getHeight() || (invalidArea.y + invalidArea.height < 0)) return;

    int origX = invalidArea.x;
    int origY = invalidArea.y;

    invalidArea.x = Math.max(invalidArea.x, 0);
    invalidArea.y = Math.max(invalidArea.y, 0);
    invalidArea.width = Math.min(origX + invalidArea.width, getWidth()) - invalidArea.x;
    invalidArea.height = Math.min(origY + invalidArea.height, getHeight()) - invalidArea.y;

    if (invalidOffscreenArea.isEmpty()) invalidOffscreenArea.setBounds(invalidArea);
    else invalidOffscreenArea.add(invalidArea);
  }
Example #8
0
  @Override
  public void initialize() throws OperatorException {
    subsetReader = new ProductSubsetBuilder();
    ProductSubsetDef subsetDef = new ProductSubsetDef();
    if (tiePointGridNames != null) {
      subsetDef.addNodeNames(tiePointGridNames);
    } else {
      subsetDef.addNodeNames(sourceProduct.getTiePointGridNames());
    }
    if (bandNames != null) {
      subsetDef.addNodeNames(bandNames);
    } else {
      subsetDef.addNodeNames(sourceProduct.getBandNames());
    }
    String[] nodeNames = subsetDef.getNodeNames();
    if (nodeNames != null) {
      final ArrayList<String> referencedNodeNames = new ArrayList<String>();
      for (String nodeName : nodeNames) {
        collectReferencedRasters(nodeName, referencedNodeNames);
      }
      subsetDef.addNodeNames(referencedNodeNames.toArray(new String[referencedNodeNames.size()]));
    }

    if (geoRegion != null) {
      region = computePixelRegion(sourceProduct, geoRegion, 0);
    }
    if (fullSwath) {
      region = new Rectangle(0, region.y, sourceProduct.getSceneRasterWidth(), region.height);
    }
    if (region != null) {
      if (region.isEmpty()) {
        throw new OperatorException("No intersection with source product boundary.");
      }
      subsetDef.setRegion(region);
    }

    subsetDef.setSubSampling(subSamplingX, subSamplingY);

    if (copyMetadata) {
      subsetDef.setIgnoreMetadata(false);
    }

    try {
      targetProduct = subsetReader.readProductNodes(sourceProduct, subsetDef);
    } catch (Throwable t) {
      throw new OperatorException(t);
    }
  }
Example #9
0
 void flushBuffers() {
   if (isVisible() && !nativeBounds.isEmpty() && !isFullScreenMode) {
     try {
       LWCToolkit.invokeAndWait(
           new Runnable() {
             @Override
             public void run() {
               // Posting an empty to flush the EventQueue without blocking the main thread
             }
           },
           target);
     } catch (InvocationTargetException e) {
       e.printStackTrace();
     }
   }
 }
 boolean isDirty() {
   return !invalidOffscreenArea.isEmpty();
 }
 /**
  * Marks part of the offscreen image as invalid. Invalid area of the image will be updated on next
  * paintComponent(Graphics) invocation.
  *
  * @param dirtyRect the part of the offscreen image to be marked as invalid.
  */
 protected final void invalidateImage(Rectangle invalidArea) {
   if (invalidArea.isEmpty()) return;
   addInvalidArea(invalidArea);
 }
Example #12
0
  /**
   * Applies the band select operation to a grid coverage.
   *
   * @param cropEnvelope the target envelope; always not null
   * @param cropROI the target ROI shape; nullable
   * @param roiTolerance; as read from op's params
   * @param sourceCoverage is the source {@link GridCoverage2D} that we want to crop.
   * @param hints A set of rendering hints, or {@code null} if none.
   * @param sourceGridToWorldTransform is the 2d grid-to-world transform for the source coverage.
   * @return The result as a grid coverage.
   */
  private static GridCoverage2D buildResult(
      final GeneralEnvelope cropEnvelope,
      final Geometry cropROI,
      final double roiTolerance,
      final boolean forceMosaic,
      final Hints hints,
      final GridCoverage2D sourceCoverage,
      final AffineTransform sourceGridToWorldTransform) {

    //
    // Getting the source coverage and its child geolocation objects
    //
    final RenderedImage sourceImage = sourceCoverage.getRenderedImage();
    final GridGeometry2D sourceGridGeometry = ((GridGeometry2D) sourceCoverage.getGridGeometry());
    final GridEnvelope2D sourceGridRange = sourceGridGeometry.getGridRange2D();

    //
    // Now we try to understand if we have a simple scale and translate or a
    // more elaborated grid-to-world transformation n which case a simple
    // crop could not be enough, but we may need a more elaborated chain of
    // operation in order to do a good job. As an instance if we
    // have a rotation which is not multiple of PI/2 we have to use
    // the mosaic with a ROI
    //
    final boolean isSimpleTransform =
        CoverageUtilities.isSimpleGridToWorldTransform(sourceGridToWorldTransform, EPS);

    // Do we need to explode the Palette to RGB(A)?
    //
    int actionTaken = 0;

    // //
    //
    // Layout
    //
    // //
    final RenderingHints targetHints = new RenderingHints(null);
    if (hints != null) targetHints.add(hints);
    final ImageLayout layout = initLayout(sourceImage, targetHints);
    targetHints.put(JAI.KEY_IMAGE_LAYOUT, layout);

    //
    // prepare the processor to use for this operation
    //
    final JAI processor = OperationJAI.getJAI(targetHints);
    final boolean useProvidedProcessor = !processor.equals(JAI.getDefaultInstance());

    try {

      if (cropROI != null) {
        // replace the cropEnvelope with the envelope of the intersection
        // of the ROI and the cropEnvelope.
        // Remember that envelope(intersection(roi,cropEnvelope)) != intersection(cropEnvelope,
        // envelope(roi))
        final Polygon modelSpaceROI = FeatureUtilities.getPolygon(cropEnvelope, GFACTORY);
        Geometry intersection = IntersectUtils.intersection(cropROI, modelSpaceROI);
        Envelope2D e2d =
            JTS.getEnvelope2D(
                intersection.getEnvelopeInternal(), cropEnvelope.getCoordinateReferenceSystem());
        GeneralEnvelope ge = new GeneralEnvelope((org.opengis.geometry.Envelope) e2d);
        cropEnvelope.setEnvelope(ge);
      }

      // //
      //
      // Build the new range by keeping into
      // account translation of grid geometry constructor for respecting
      // OGC PIXEL-IS-CENTER ImageDatum assumption.
      //
      // //
      final AffineTransform sourceWorldToGridTransform = sourceGridToWorldTransform.createInverse();

      // //
      //
      // finalRasterArea will hold the smallest rectangular integer raster area that contains the
      // floating point raster
      // area which we obtain when applying the world-to-grid transform to the cropEnvelope. Note
      // that we need to intersect
      // such an area with the area covered by the source coverage in order to be sure we do not try
      // to crop outside the
      // bounds of the source raster.
      //
      // //
      final Rectangle2D finalRasterAreaDouble =
          XAffineTransform.transform(
              sourceWorldToGridTransform, cropEnvelope.toRectangle2D(), null);
      final Rectangle finalRasterArea = finalRasterAreaDouble.getBounds();

      // intersection with the original range in order to not try to crop outside the image bounds
      Rectangle.intersect(finalRasterArea, sourceGridRange, finalRasterArea);
      if (finalRasterArea.isEmpty())
        throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP));

      // //
      //
      // It is worth to point out that doing a crop the G2W transform
      // should not change while the envelope might change as
      // a consequence of the rounding of the underlying image datum
      // which uses integer factors or in case the G2W is very
      // complex. Note that we will always strive to
      // conserve the original grid-to-world transform.
      //
      // //

      // we do not have to crop in this case (should not really happen at
      // this time)
      if (finalRasterArea.equals(sourceGridRange) && isSimpleTransform && cropROI == null)
        return sourceCoverage;

      // //
      //
      // if I get here I have something to crop
      // using the world-to-grid transform for going from envelope to the
      // new grid range.
      //
      // //
      final double minX = finalRasterArea.getMinX();
      final double minY = finalRasterArea.getMinY();
      final double width = finalRasterArea.getWidth();
      final double height = finalRasterArea.getHeight();

      // //
      //
      // Check if we need to use mosaic or crop
      //
      // //
      final PlanarImage croppedImage;
      final ParameterBlock pbj = new ParameterBlock();
      pbj.addSource(sourceImage);
      java.awt.Polygon rasterSpaceROI = null;
      String operatioName = null;
      if (!isSimpleTransform || cropROI != null) {
        // /////////////////////////////////////////////////////////////////////
        //
        // We don't have a simple scale and translate transform, JAI
        // crop MAY NOT suffice. Let's decide whether or not we'll use
        // the Mosaic.
        //
        // /////////////////////////////////////////////////////////////////////
        Polygon modelSpaceROI = FeatureUtilities.getPolygon(cropEnvelope, GFACTORY);

        // //
        //
        // Now convert this polygon back into a shape for the source
        // raster space.
        //
        // //
        final List<Point2D> points = new ArrayList<Point2D>(5);
        rasterSpaceROI =
            FeatureUtilities.convertPolygonToPointArray(
                modelSpaceROI, ProjectiveTransform.create(sourceWorldToGridTransform), points);
        if (rasterSpaceROI == null || rasterSpaceROI.getBounds().isEmpty())
          if (finalRasterArea.isEmpty())
            throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP));
        final boolean doMosaic =
            forceMosaic
                ? true
                : decideJAIOperation(roiTolerance, rasterSpaceROI.getBounds2D(), points);
        if (doMosaic || cropROI != null) {
          // prepare the params for the mosaic
          final ROI[] roiarr;
          try {
            if (cropROI != null) {
              final LiteShape2 cropRoiLS2 =
                  new LiteShape2(
                      cropROI, ProjectiveTransform.create(sourceWorldToGridTransform), null, false);
              ROI cropRS = new ROIShape(cropRoiLS2);
              Rectangle2D rt = cropRoiLS2.getBounds2D();
              if (!hasIntegerBounds(rt)) {
                // Approximate Geometry
                Geometry geo = (Geometry) cropRoiLS2.getGeometry().clone();
                transformGeometry(geo);
                cropRS = new ROIShape(new LiteShape2(geo, null, null, false));
              }
              roiarr = new ROI[] {cropRS};
            } else {
              final ROIShape roi = new ROIShape(rasterSpaceROI);
              roiarr = new ROI[] {roi};
            }
          } catch (FactoryException ex) {
            throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP), ex);
          }
          pbj.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY);
          pbj.add(null);
          pbj.add(roiarr);
          pbj.add(null);
          pbj.add(CoverageUtilities.getBackgroundValues(sourceCoverage));

          // prepare the final layout
          final Rectangle bounds = rasterSpaceROI.getBounds2D().getBounds();
          Rectangle.intersect(bounds, sourceGridRange, bounds);
          if (bounds.isEmpty()) throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP));

          // we do not have to crop in this case (should not really happen at
          // this time)
          if (!doMosaic && bounds.getBounds().equals(sourceGridRange) && isSimpleTransform)
            return sourceCoverage;

          // nice trick, we use the layout to do the actual crop
          final Rectangle boundsInt = bounds.getBounds();
          layout.setMinX(boundsInt.x);
          layout.setWidth(boundsInt.width);
          layout.setMinY(boundsInt.y);
          layout.setHeight(boundsInt.height);
          operatioName = "Mosaic";
        }
      }

      // do we still have to set the operation name? If so that means we have to go for crop.
      if (operatioName == null) {
        // executing the crop
        pbj.add((float) minX);
        pbj.add((float) minY);
        pbj.add((float) width);
        pbj.add((float) height);
        operatioName = "GTCrop";
      }
      // //
      //
      // Apply operation
      //
      // //
      if (!useProvidedProcessor) {
        croppedImage = JAI.create(operatioName, pbj, targetHints);
      } else {
        croppedImage = processor.createNS(operatioName, pbj, targetHints);
      }

      // conserve the input grid to world transformation
      Map sourceProperties = sourceCoverage.getProperties();
      Map properties = null;
      if (sourceProperties != null && !sourceProperties.isEmpty()) {
        properties = new HashMap(sourceProperties);
      }
      if (rasterSpaceROI != null) {
        if (properties != null) {
          properties.put("GC_ROI", rasterSpaceROI);
        } else {
          properties = Collections.singletonMap("GC_ROI", rasterSpaceROI);
        }
      }

      return new GridCoverageFactory(hints)
          .create(
              sourceCoverage.getName(),
              croppedImage,
              new GridGeometry2D(
                  new GridEnvelope2D(croppedImage.getBounds()),
                  sourceGridGeometry.getGridToCRS2D(PixelOrientation.CENTER),
                  sourceCoverage.getCoordinateReferenceSystem()),
              (GridSampleDimension[])
                  (actionTaken == 1 ? null : sourceCoverage.getSampleDimensions().clone()),
              new GridCoverage[] {sourceCoverage},
              properties);

    } catch (TransformException e) {
      throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP), e);
    } catch (NoninvertibleTransformException e) {
      throw new CannotCropException(Errors.format(ErrorKeys.CANT_CROP), e);
    }
  }
  /**
   * Return a crop region from a specified envelope, leveraging on the grid to world transformation.
   *
   * @param refinedRequestedBBox the crop envelope
   * @return a {@code Rectangle} representing the crop region.
   * @throws TransformException in case a problem occurs when going back to raster space.
   * @throws DataSourceException
   */
  private void computeCropRasterArea() throws DataSourceException {

    // we have nothing to crop
    if (cropBBox == null) {
      destinationRasterArea = null;
      return;
    }

    //
    // We need to invert the requested gridToWorld and then adjust the requested raster area are
    // accordingly
    //

    // invert the requested grid to world keeping into account the fact that it is related to cell
    // center
    // while the raster is related to cell corner
    MathTransform2D requestedWorldToGrid;
    try {
      requestedWorldToGrid =
          (MathTransform2D)
              PixelTranslation.translate(
                      ProjectiveTransform.create(requestedGridToWorld),
                      PixelInCell.CELL_CENTER,
                      PixelInCell.CELL_CORNER)
                  .inverse();
    } catch (NoninvertibleTransformException e) {
      throw new DataSourceException(e);
    }

    if (destinationToSourceTransform == null || destinationToSourceTransform.isIdentity()) {

      // now get the requested bbox which have been already adjusted and project it back to raster
      // space
      try {
        destinationRasterArea =
            new GeneralGridEnvelope(
                    CRS.transform(requestedWorldToGrid, new GeneralEnvelope(cropBBox)),
                    PixelInCell.CELL_CORNER,
                    false)
                .toRectangle();
      } catch (IllegalStateException e) {
        throw new DataSourceException(e);
      } catch (TransformException e) {
        throw new DataSourceException(e);
      }
    } else {
      //
      // reproject the crop bbox back and then crop, notice that we are imposing
      //
      try {
        final GeneralEnvelope cropBBOXInRequestCRS =
            CRS.transform(cropBBox, requestedBBox.getCoordinateReferenceSystem());
        cropBBOXInRequestCRS.setCoordinateReferenceSystem(
            requestedBBox.getCoordinateReferenceSystem());
        // make sure it falls within the requested envelope
        cropBBOXInRequestCRS.intersect(requestedBBox);

        // now go back to raster space
        destinationRasterArea =
            new GeneralGridEnvelope(
                    CRS.transform(requestedWorldToGrid, cropBBOXInRequestCRS),
                    PixelInCell.CELL_CORNER,
                    false)
                .toRectangle();
        // intersect with the original requested raster space to be sure that we stay within the
        // requested raster area
        XRectangle2D.intersect(destinationRasterArea, requestedRasterArea, destinationRasterArea);
      } catch (NoninvertibleTransformException e) {
        throw new DataSourceException(e);
      } catch (TransformException e) {
        throw new DataSourceException(e);
      }
    }
    // is it empty??
    if (destinationRasterArea.isEmpty()) {
      if (LOGGER.isLoggable(Level.FINE))
        LOGGER.log(
            Level.FINE,
            "Requested envelope too small resulting in empty cropped raster region. cropBbox:"
                + cropBBox);
      // TODO: Future versions may define a 1x1 rectangle starting
      // from the lower coordinate
      empty = true;
      return;
    }
  }
  /**
   * Evaluates the requested envelope and builds a new adjusted version of it fitting this coverage
   * envelope.
   *
   * <p>While adjusting the requested envelope this methods also compute the source region as a
   * rectangle which is suitable for a successive read operation with {@link ImageIO} to do
   * crop-on-read.
   *
   * @param requestedBBox is the envelope we are requested to load.
   * @param destinationRasterArea represents the area to load in raster space. This parameter cannot
   *     be null since it gets filled with whatever the crop region is depending on the <code>
   *     requestedEnvelope</code>.
   * @param requestedRasterArea is the requested region where to load data of the specified
   *     envelope.
   * @param readGridToWorld the Grid to world transformation to be used
   * @return the adjusted requested envelope, empty if no requestedEnvelope has been specified,
   *     {@code null} in case the requested envelope does not intersect the coverage envelope or in
   *     case the adjusted requested envelope is covered by a too small raster region (an empty
   *     region).
   * @throws DataSourceException
   * @throws DataSourceException in case something bad occurs
   */
  private void computeRequestSpatialElements() throws DataSourceException {

    //
    // Inspect the request and precompute transformation between CRS. We
    // also check if we can simply adjust the requested GG in case the
    // request CRS is different from the coverage native CRS but the
    // transformation is simply an affine transformation.
    //
    // In such a case we can simplify our work by adjusting the
    // requested grid to world, preconcatenating the coordinate
    // operation to change CRS
    //
    inspectCoordinateReferenceSystems();

    //
    // WE DO HAVE A REQUESTED AREA!
    //

    //
    // Create the crop bbox in the coverage CRS for cropping it later on.
    //
    computeCropBBOX();
    if (empty || (cropBBox != null && cropBBox.isEmpty())) {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(Level.FINE, "RequestedBBox empty or null");
      }
      // this means that we do not have anything to load at all!
      empty = true;
      return;
    }

    //
    // CROP SOURCE REGION using the refined requested envelope
    //
    computeCropRasterArea();
    if (empty || (destinationRasterArea != null && destinationRasterArea.isEmpty())) {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(Level.FINE, "CropRasterArea empty or null");
      }
      // this means that we do not have anything to load at all!
      return;
    }

    if (LOGGER.isLoggable(Level.FINER)) {
      StringBuilder sb =
          new StringBuilder("Adjusted Requested Envelope = ")
              .append(requestedBBox.toString())
              .append("\n")
              .append("Requested raster dimension = ")
              .append(requestedRasterArea.toString())
              .append("\n")
              .append("Corresponding raster source region = ")
              .append(requestedRasterArea.toString());
      LOGGER.log(Level.FINER, sb.toString());
    }

    //
    // Compute the request resolution from the request
    //
    computeRequestedResolution();
  }
Example #15
0
  /**
   * Load a specified a raster as a portion of the granule describe by this {@link
   * GranuleDescriptor}.
   *
   * @param imageReadParameters the {@link ImageReadParam} to use for reading.
   * @param index the index to use for the {@link ImageReader}.
   * @param cropBBox the bbox to use for cropping.
   * @param mosaicWorldToGrid the cropping grid to world transform.
   * @param request the incoming request to satisfy.
   * @param hints {@link Hints} to be used for creating this raster.
   * @return a specified a raster as a portion of the granule describe by this {@link
   *     GranuleDescriptor}.
   * @throws IOException in case an error occurs.
   */
  public GranuleLoadingResult loadRaster(
      final ImageReadParam imageReadParameters,
      final int index,
      final ReferencedEnvelope cropBBox,
      final MathTransform2D mosaicWorldToGrid,
      final RasterLayerRequest request,
      final Hints hints)
      throws IOException {

    if (LOGGER.isLoggable(java.util.logging.Level.FINER)) {
      final String name = Thread.currentThread().getName();
      LOGGER.finer(
          "Thread:" + name + " Loading raster data for granuleDescriptor " + this.toString());
    }
    ImageReadParam readParameters = null;
    int imageIndex;
    final ReferencedEnvelope bbox =
        inclusionGeometry != null
            ? new ReferencedEnvelope(
                granuleBBOX.intersection(inclusionGeometry.getEnvelopeInternal()),
                granuleBBOX.getCoordinateReferenceSystem())
            : granuleBBOX;
    boolean doFiltering = false;
    if (filterMe) {
      doFiltering = Utils.areaIsDifferent(inclusionGeometry, baseGridToWorld, granuleBBOX);
    }

    // intersection of this tile bound with the current crop bbox
    final ReferencedEnvelope intersection =
        new ReferencedEnvelope(
            bbox.intersection(cropBBox), cropBBox.getCoordinateReferenceSystem());
    if (intersection.isEmpty()) {
      if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
        LOGGER.fine(
            new StringBuilder("Got empty intersection for granule ")
                .append(this.toString())
                .append(" with request ")
                .append(request.toString())
                .append(" Resulting in no granule loaded: Empty result")
                .toString());
      }
      return null;
    }

    ImageInputStream inStream = null;
    ImageReader reader = null;
    try {
      //
      // get info about the raster we have to read
      //

      // get a stream
      assert cachedStreamSPI != null : "no cachedStreamSPI available!";
      inStream =
          cachedStreamSPI.createInputStreamInstance(
              granuleUrl, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
      if (inStream == null) return null;

      // get a reader and try to cache the relevant SPI
      if (cachedReaderSPI == null) {
        reader = ImageIOExt.getImageioReader(inStream);
        if (reader != null) cachedReaderSPI = reader.getOriginatingProvider();
      } else reader = cachedReaderSPI.createReaderInstance();
      if (reader == null) {
        if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
          LOGGER.warning(
              new StringBuilder("Unable to get s reader for granuleDescriptor ")
                  .append(this.toString())
                  .append(" with request ")
                  .append(request.toString())
                  .append(" Resulting in no granule loaded: Empty result")
                  .toString());
        }
        return null;
      }
      // set input
      reader.setInput(inStream);

      // Checking for heterogeneous granules
      if (request.isHeterogeneousGranules()) {
        // create read parameters
        readParameters = new ImageReadParam();

        // override the overviews controller for the base layer
        imageIndex =
            ReadParamsController.setReadParams(
                request.getRequestedResolution(),
                request.getOverviewPolicy(),
                request.getDecimationPolicy(),
                readParameters,
                request.rasterManager,
                overviewsController);
      } else {
        imageIndex = index;
        readParameters = imageReadParameters;
      }

      // get selected level and base level dimensions
      final GranuleOverviewLevelDescriptor selectedlevel = getLevel(imageIndex, reader);

      // now create the crop grid to world which can be used to decide
      // which source area we need to crop in the selected level taking
      // into account the scale factors imposed by the selection of this
      // level together with the base level grid to world transformation
      AffineTransform2D cropWorldToGrid =
          new AffineTransform2D(selectedlevel.gridToWorldTransformCorner);
      cropWorldToGrid = (AffineTransform2D) cropWorldToGrid.inverse();
      // computing the crop source area which lives into the
      // selected level raster space, NOTICE that at the end we need to
      // take into account the fact that we might also decimate therefore
      // we cannot just use the crop grid to world but we need to correct
      // it.
      final Rectangle sourceArea =
          CRS.transform(cropWorldToGrid, intersection).toRectangle2D().getBounds();
      // gutter
      if (selectedlevel.baseToLevelTransform.isIdentity()) sourceArea.grow(2, 2);
      XRectangle2D.intersect(
          sourceArea,
          selectedlevel.rasterDimensions,
          sourceArea); // make sure roundings don't bother us
      // is it empty??
      if (sourceArea.isEmpty()) {
        if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
          LOGGER.fine(
              "Got empty area for granuleDescriptor "
                  + this.toString()
                  + " with request "
                  + request.toString()
                  + " Resulting in no granule loaded: Empty result");
        }
        return null;

      } else if (LOGGER.isLoggable(java.util.logging.Level.FINER)) {
        LOGGER.finer(
            "Loading level "
                + imageIndex
                + " with source region: "
                + sourceArea
                + " subsampling: "
                + readParameters.getSourceXSubsampling()
                + ","
                + readParameters.getSourceYSubsampling()
                + " for granule:"
                + granuleUrl);
      }

      // Setting subsampling
      int newSubSamplingFactor = 0;
      final String pluginName = cachedReaderSPI.getPluginClassName();
      if (pluginName != null && pluginName.equals(ImageUtilities.DIRECT_KAKADU_PLUGIN)) {
        final int ssx = readParameters.getSourceXSubsampling();
        final int ssy = readParameters.getSourceYSubsampling();
        newSubSamplingFactor = ImageIOUtilities.getSubSamplingFactor2(ssx, ssy);
        if (newSubSamplingFactor != 0) {
          if (newSubSamplingFactor > maxDecimationFactor && maxDecimationFactor != -1) {
            newSubSamplingFactor = maxDecimationFactor;
          }
          readParameters.setSourceSubsampling(newSubSamplingFactor, newSubSamplingFactor, 0, 0);
        }
      }

      // set the source region
      readParameters.setSourceRegion(sourceArea);
      final RenderedImage raster;
      try {
        // read
        raster =
            request
                .getReadType()
                .read(
                    readParameters,
                    imageIndex,
                    granuleUrl,
                    selectedlevel.rasterDimensions,
                    reader,
                    hints,
                    false);

      } catch (Throwable e) {
        if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
          LOGGER.log(
              java.util.logging.Level.FINE,
              "Unable to load raster for granuleDescriptor "
                  + this.toString()
                  + " with request "
                  + request.toString()
                  + " Resulting in no granule loaded: Empty result",
              e);
        }
        return null;
      }

      // use fixed source area
      sourceArea.setRect(readParameters.getSourceRegion());

      //
      // setting new coefficients to define a new affineTransformation
      // to be applied to the grid to world transformation
      // -----------------------------------------------------------------------------------
      //
      // With respect to the original envelope, the obtained planarImage
      // needs to be rescaled. The scaling factors are computed as the
      // ratio between the cropped source region sizes and the read
      // image sizes.
      //
      // place it in the mosaic using the coords created above;
      double decimationScaleX = ((1.0 * sourceArea.width) / raster.getWidth());
      double decimationScaleY = ((1.0 * sourceArea.height) / raster.getHeight());
      final AffineTransform decimationScaleTranform =
          XAffineTransform.getScaleInstance(decimationScaleX, decimationScaleY);

      // keep into account translation  to work into the selected level raster space
      final AffineTransform afterDecimationTranslateTranform =
          XAffineTransform.getTranslateInstance(sourceArea.x, sourceArea.y);

      // now we need to go back to the base level raster space
      final AffineTransform backToBaseLevelScaleTransform = selectedlevel.baseToLevelTransform;

      // now create the overall transform
      final AffineTransform finalRaster2Model = new AffineTransform(baseGridToWorld);
      finalRaster2Model.concatenate(CoverageUtilities.CENTER_TO_CORNER);
      final double x = finalRaster2Model.getTranslateX();
      final double y = finalRaster2Model.getTranslateY();

      if (!XAffineTransform.isIdentity(backToBaseLevelScaleTransform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(backToBaseLevelScaleTransform);
      if (!XAffineTransform.isIdentity(afterDecimationTranslateTranform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(afterDecimationTranslateTranform);
      if (!XAffineTransform.isIdentity(decimationScaleTranform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(decimationScaleTranform);

      // keep into account translation factors to place this tile
      finalRaster2Model.preConcatenate((AffineTransform) mosaicWorldToGrid);
      final Interpolation interpolation = request.getInterpolation();
      // paranoiac check to avoid that JAI freaks out when computing its internal layouT on images
      // that are too small
      Rectangle2D finalLayout =
          ImageUtilities.layoutHelper(
              raster,
              (float) finalRaster2Model.getScaleX(),
              (float) finalRaster2Model.getScaleY(),
              (float) finalRaster2Model.getTranslateX(),
              (float) finalRaster2Model.getTranslateY(),
              interpolation);
      if (finalLayout.isEmpty()) {
        if (LOGGER.isLoggable(java.util.logging.Level.INFO))
          LOGGER.info(
              "Unable to create a granuleDescriptor "
                  + this.toString()
                  + " due to jai scale bug creating a null source area");
        return null;
      }
      ROI granuleLoadingShape = null;
      if (granuleROIShape != null) {

        final Point2D translate =
            mosaicWorldToGrid.transform(new DirectPosition2D(x, y), (Point2D) null);
        AffineTransform tx2 = new AffineTransform();
        tx2.preConcatenate(
            AffineTransform.getScaleInstance(
                ((AffineTransform) mosaicWorldToGrid).getScaleX(),
                -((AffineTransform) mosaicWorldToGrid).getScaleY()));
        tx2.preConcatenate(
            AffineTransform.getScaleInstance(
                ((AffineTransform) baseGridToWorld).getScaleX(),
                -((AffineTransform) baseGridToWorld).getScaleY()));
        tx2.preConcatenate(
            AffineTransform.getTranslateInstance(translate.getX(), translate.getY()));
        granuleLoadingShape = (ROI) granuleROIShape.transform(tx2);
      }
      // apply the affine transform  conserving indexed color model
      final RenderingHints localHints =
          new RenderingHints(
              JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
              interpolation instanceof InterpolationNearest ? Boolean.FALSE : Boolean.TRUE);
      if (XAffineTransform.isIdentity(finalRaster2Model, Utils.AFFINE_IDENTITY_EPS)) {
        return new GranuleLoadingResult(raster, granuleLoadingShape, granuleUrl, doFiltering);
      } else {
        //
        // In case we are asked to use certain tile dimensions we tile
        // also at this stage in case the read type is Direct since
        // buffered images comes up untiled and this can affect the
        // performances of the subsequent affine operation.
        //
        final Dimension tileDimensions = request.getTileDimensions();
        if (tileDimensions != null && request.getReadType().equals(ReadType.DIRECT_READ)) {
          final ImageLayout layout = new ImageLayout();
          layout.setTileHeight(tileDimensions.width).setTileWidth(tileDimensions.height);
          localHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout));
        } else {
          if (hints != null && hints.containsKey(JAI.KEY_IMAGE_LAYOUT)) {
            final Object layout = hints.get(JAI.KEY_IMAGE_LAYOUT);
            if (layout != null && layout instanceof ImageLayout) {
              localHints.add(
                  new RenderingHints(JAI.KEY_IMAGE_LAYOUT, ((ImageLayout) layout).clone()));
            }
          }
        }
        if (hints != null && hints.containsKey(JAI.KEY_TILE_CACHE)) {
          final Object cache = hints.get(JAI.KEY_TILE_CACHE);
          if (cache != null && cache instanceof TileCache)
            localHints.add(new RenderingHints(JAI.KEY_TILE_CACHE, (TileCache) cache));
        }
        if (hints != null && hints.containsKey(JAI.KEY_TILE_SCHEDULER)) {
          final Object scheduler = hints.get(JAI.KEY_TILE_SCHEDULER);
          if (scheduler != null && scheduler instanceof TileScheduler)
            localHints.add(new RenderingHints(JAI.KEY_TILE_SCHEDULER, (TileScheduler) scheduler));
        }
        boolean addBorderExtender = true;
        if (hints != null && hints.containsKey(JAI.KEY_BORDER_EXTENDER)) {
          final Object extender = hints.get(JAI.KEY_BORDER_EXTENDER);
          if (extender != null && extender instanceof BorderExtender) {
            localHints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, (BorderExtender) extender));
            addBorderExtender = false;
          }
        }
        // border extender
        if (addBorderExtender) {
          localHints.add(ImageUtilities.BORDER_EXTENDER_HINTS);
        }
        //                boolean hasScaleX=!(Math.abs(finalRaster2Model.getScaleX()-1) <
        // 1E-2/(raster.getWidth()+1-raster.getMinX()));
        //                boolean hasScaleY=!(Math.abs(finalRaster2Model.getScaleY()-1) <
        // 1E-2/(raster.getHeight()+1-raster.getMinY()));
        //                boolean hasShearX=!(finalRaster2Model.getShearX() == 0.0);
        //                boolean hasShearY=!(finalRaster2Model.getShearY() == 0.0);
        //                boolean hasTranslateX=!(Math.abs(finalRaster2Model.getTranslateX()) <
        // 0.01F);
        //                boolean hasTranslateY=!(Math.abs(finalRaster2Model.getTranslateY()) <
        // 0.01F);
        //                boolean isTranslateXInt=!(Math.abs(finalRaster2Model.getTranslateX() -
        // (int) finalRaster2Model.getTranslateX()) <  0.01F);
        //                boolean isTranslateYInt=!(Math.abs(finalRaster2Model.getTranslateY() -
        // (int) finalRaster2Model.getTranslateY()) <  0.01F);
        //
        //                boolean isIdentity = finalRaster2Model.isIdentity() &&
        // !hasScaleX&&!hasScaleY &&!hasTranslateX&&!hasTranslateY;

        //                // TODO how can we check that the a skew is harmelss????
        //                if(isIdentity){
        //                    // TODO check if we are missing anything like tiling or such that
        // comes from hints
        //                    return new GranuleLoadingResult(raster, granuleLoadingShape,
        // granuleUrl, doFiltering);
        //                }
        //
        //                // TOLERANCE ON PIXELS SIZE
        //
        //                // Check and see if the affine transform is in fact doing
        //                // a Translate operation. That is a scale by 1 and no rotation.
        //                // In which case call translate. Note that only integer translate
        //                // is applicable. For non-integer translate we'll have to do the
        //                // affine.
        //                // If the hints contain an ImageLayout hint, we can't use
        //                // TranslateIntOpImage since it isn't capable of dealing with that.
        //                // Get ImageLayout from renderHints if any.
        //                ImageLayout layout = RIFUtil.getImageLayoutHint(localHints);
        //                if ( !hasScaleX &&
        //                     !hasScaleY  &&
        //                      !hasShearX&&
        //                      !hasShearY&&
        //                      isTranslateXInt&&
        //                      isTranslateYInt&&
        //                    layout == null) {
        //                    // It's a integer translate
        //                    return new GranuleLoadingResult(new TranslateIntOpImage(raster,
        //                                                    localHints,
        //                                                   (int) finalRaster2Model.getShearX(),
        //                                                   (int)
        // finalRaster2Model.getShearY()),granuleLoadingShape, granuleUrl, doFiltering);
        //                }

        ImageWorker iw = new ImageWorker(raster);
        iw.setRenderingHints(localHints);
        iw.affine(finalRaster2Model, interpolation, request.getBackgroundValues());
        return new GranuleLoadingResult(
            iw.getRenderedImage(), granuleLoadingShape, granuleUrl, doFiltering);
      }

    } catch (IllegalStateException e) {
      if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
        LOGGER.log(
            java.util.logging.Level.WARNING,
            new StringBuilder("Unable to load raster for granuleDescriptor ")
                .append(this.toString())
                .append(" with request ")
                .append(request.toString())
                .append(" Resulting in no granule loaded: Empty result")
                .toString(),
            e);
      }
      return null;
    } catch (org.opengis.referencing.operation.NoninvertibleTransformException e) {
      if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
        LOGGER.log(
            java.util.logging.Level.WARNING,
            new StringBuilder("Unable to load raster for granuleDescriptor ")
                .append(this.toString())
                .append(" with request ")
                .append(request.toString())
                .append(" Resulting in no granule loaded: Empty result")
                .toString(),
            e);
      }
      return null;
    } catch (TransformException e) {
      if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
        LOGGER.log(
            java.util.logging.Level.WARNING,
            new StringBuilder("Unable to load raster for granuleDescriptor ")
                .append(this.toString())
                .append(" with request ")
                .append(request.toString())
                .append(" Resulting in no granule loaded: Empty result")
                .toString(),
            e);
      }
      return null;

    } finally {
      try {
        if (request.getReadType() != ReadType.JAI_IMAGEREAD && inStream != null) {
          inStream.close();
        }
      } finally {
        if (request.getReadType() != ReadType.JAI_IMAGEREAD && reader != null) {
          reader.dispose();
        }
      }
    }
  }
    private void readTileData(
        File outputFile,
        int tileX,
        int tileY,
        int tileWidth,
        int tileHeight,
        int jp2TileX,
        int jp2TileY,
        int jp2TileWidth,
        int jp2TileHeight,
        short[] tileData,
        Rectangle destRect)
        throws IOException {

      synchronized (this) {
        if (!locks.containsKey(outputFile)) {
          locks.put(outputFile, new Object());
        }
      }
      final Object lock = locks.get(outputFile);

      synchronized (lock) {
        Jp2File jp2File = getOpenJ2pFile(outputFile);

        int jp2Width = jp2File.width;
        int jp2Height = jp2File.height;
        if (jp2Width > jp2TileWidth || jp2Height > jp2TileHeight) {
          throw new IllegalStateException(
              String.format(
                  "width (=%d) > tileWidth (=%d) || height (=%d) > tileHeight (=%d)",
                  jp2Width, jp2TileWidth, jp2Height, jp2TileHeight));
        }

        int jp2X = destRect.x - jp2TileX * jp2TileWidth;
        int jp2Y = destRect.y - jp2TileY * jp2TileHeight;
        if (jp2X < 0 || jp2Y < 0) {
          throw new IllegalStateException(
              String.format("jp2X (=%d) < 0 || jp2Y (=%d) < 0", jp2X, jp2Y));
        }

        final ImageInputStream stream = jp2File.stream;

        if (jp2X == 0
            && jp2Width == tileWidth
            && jp2Y == 0
            && jp2Height == tileHeight
            && tileWidth * tileHeight == tileData.length) {
          stream.seek(jp2File.dataPos);
          stream.readFully(tileData, 0, tileData.length);
        } else {
          final Rectangle jp2FileRect = new Rectangle(0, 0, jp2Width, jp2Height);
          final Rectangle tileRect = new Rectangle(jp2X, jp2Y, tileWidth, tileHeight);
          final Rectangle intersection = jp2FileRect.intersection(tileRect);
          System.out.printf(
              "%s: tile=(%d,%d): jp2FileRect=%s, tileRect=%s, intersection=%s\n",
              jp2File.file, tileX, tileY, jp2FileRect, tileRect, intersection);
          if (!intersection.isEmpty()) {
            long seekPos =
                jp2File.dataPos + NUM_SHORT_BYTES * (intersection.y * jp2Width + intersection.x);
            int tilePos = 0;
            for (int y = 0; y < intersection.height; y++) {
              stream.seek(seekPos);
              stream.readFully(tileData, tilePos, intersection.width);
              seekPos += NUM_SHORT_BYTES * jp2Width;
              tilePos += tileWidth;
              for (int x = intersection.width; x < tileWidth; x++) {
                tileData[y * tileWidth + x] = (short) 0;
              }
            }
            for (int y = intersection.height; y < tileWidth; y++) {
              for (int x = 0; x < tileWidth; x++) {
                tileData[y * tileWidth + x] = (short) 0;
              }
            }
          } else {
            Arrays.fill(tileData, (short) 0);
          }
        }
      }
    }
  public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param)
      throws IOException {
    if (stream == null) {
      throw new IllegalStateException(I18N.getString("WBMPImageWriter3"));
    }

    if (image == null) {
      throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4"));
    }

    clearAbortRequest();
    processImageStarted(0);
    if (param == null) param = getDefaultWriteParam();

    RenderedImage input = null;
    Raster inputRaster = null;
    boolean writeRaster = image.hasRaster();
    Rectangle sourceRegion = param.getSourceRegion();
    SampleModel sampleModel = null;

    if (writeRaster) {
      inputRaster = image.getRaster();
      sampleModel = inputRaster.getSampleModel();
    } else {
      input = image.getRenderedImage();
      sampleModel = input.getSampleModel();

      inputRaster = input.getData();
    }

    checkSampleModel(sampleModel);
    if (sourceRegion == null) sourceRegion = inputRaster.getBounds();
    else sourceRegion = sourceRegion.intersection(inputRaster.getBounds());

    if (sourceRegion.isEmpty()) throw new RuntimeException(I18N.getString("WBMPImageWriter1"));

    int scaleX = param.getSourceXSubsampling();
    int scaleY = param.getSourceYSubsampling();
    int xOffset = param.getSubsamplingXOffset();
    int yOffset = param.getSubsamplingYOffset();

    sourceRegion.translate(xOffset, yOffset);
    sourceRegion.width -= xOffset;
    sourceRegion.height -= yOffset;

    int minX = sourceRegion.x / scaleX;
    int minY = sourceRegion.y / scaleY;
    int w = (sourceRegion.width + scaleX - 1) / scaleX;
    int h = (sourceRegion.height + scaleY - 1) / scaleY;

    Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
    sampleModel = sampleModel.createCompatibleSampleModel(w, h);

    SampleModel destSM = sampleModel;

    // If the data are not formatted nominally then reformat.
    if (sampleModel.getDataType() != DataBuffer.TYPE_BYTE
        || !(sampleModel instanceof MultiPixelPackedSampleModel)
        || ((MultiPixelPackedSampleModel) sampleModel).getDataBitOffset() != 0) {
      destSM = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, w, h, 1, w + 7 >> 3, 0);
    }

    if (!destinationRegion.equals(sourceRegion)) {
      if (scaleX == 1 && scaleY == 1)
        inputRaster =
            inputRaster.createChild(
                inputRaster.getMinX(), inputRaster.getMinY(), w, h, minX, minY, null);
      else {
        WritableRaster ras = Raster.createWritableRaster(destSM, new Point(minX, minY));

        byte[] data = ((DataBufferByte) ras.getDataBuffer()).getData();

        for (int j = minY, y = sourceRegion.y, k = 0; j < minY + h; j++, y += scaleY) {

          for (int i = 0, x = sourceRegion.x; i < w; i++, x += scaleX) {
            int v = inputRaster.getSample(x, y, 0);
            data[k + (i >> 3)] |= v << (7 - (i & 7));
          }
          k += w + 7 >> 3;
        }
        inputRaster = ras;
      }
    }

    // If the data are not formatted nominally then reformat.
    if (!destSM.equals(inputRaster.getSampleModel())) {
      WritableRaster raster =
          Raster.createWritableRaster(
              destSM, new Point(inputRaster.getMinX(), inputRaster.getMinY()));
      raster.setRect(inputRaster);
      inputRaster = raster;
    }

    // Check whether the image is white-is-zero.
    boolean isWhiteZero = false;
    if (!writeRaster && input.getColorModel() instanceof IndexColorModel) {
      IndexColorModel icm = (IndexColorModel) input.getColorModel();
      isWhiteZero = icm.getRed(0) > icm.getRed(1);
    }

    // Get the line stride, bytes per row, and data array.
    int lineStride = ((MultiPixelPackedSampleModel) destSM).getScanlineStride();
    int bytesPerRow = (w + 7) / 8;
    byte[] bdata = ((DataBufferByte) inputRaster.getDataBuffer()).getData();

    // Write WBMP header.
    stream.write(0); // TypeField
    stream.write(0); // FixHeaderField
    stream.write(intToMultiByte(w)); // width
    stream.write(intToMultiByte(h)); // height

    // Write the data.
    if (!isWhiteZero && lineStride == bytesPerRow) {
      // Write the entire image.
      stream.write(bdata, 0, h * bytesPerRow);
      processImageProgress(100.0F);
    } else {
      // Write the image row-by-row.
      int offset = 0;
      if (!isWhiteZero) {
        // Black-is-zero
        for (int row = 0; row < h; row++) {
          if (abortRequested()) break;
          stream.write(bdata, offset, bytesPerRow);
          offset += lineStride;
          processImageProgress(100.0F * row / h);
        }
      } else {
        // White-is-zero: need to invert data.
        byte[] inverted = new byte[bytesPerRow];
        for (int row = 0; row < h; row++) {
          if (abortRequested()) break;
          for (int col = 0; col < bytesPerRow; col++) {
            inverted[col] = (byte) (~(bdata[col + offset]));
          }
          stream.write(inverted, 0, bytesPerRow);
          offset += lineStride;
          processImageProgress(100.0F * row / h);
        }
      }
    }

    if (abortRequested()) processWriteAborted();
    else {
      processImageComplete();
      stream.flushBefore(stream.getStreamPosition());
    }
  }
  /**
   * Subsamples and sub-bands the input <code>Raster</code> over a sub-region and stores the result
   * in a <code>WritableRaster</code>.
   *
   * @param src The source <code>Raster</code>
   * @param sourceBands The source bands to use; may be <code>null</code>
   * @param subsampleX The subsampling factor along the horizontal axis.
   * @param subsampleY The subsampling factor along the vertical axis. in which case all bands will
   *     be used.
   * @param dst The destination <code>WritableRaster</code>.
   * @throws IllegalArgumentException if <code>source</code> is <code>null</code> or empty, <code>
   *     dst</code> is <code>null</code>, <code>sourceBands.length</code> exceeds the number of
   *     bands in <code>source</code>, or <code>sourcBands</code> contains an element which is
   *     negative or greater than or equal to the number of bands in <code>source</code>.
   */
  private static void reformat(
      Raster source, int[] sourceBands, int subsampleX, int subsampleY, WritableRaster dst) {
    // Check for nulls.
    if (source == null) {
      throw new IllegalArgumentException("source == null!");
    } else if (dst == null) {
      throw new IllegalArgumentException("dst == null!");
    }

    // Validate the source bounds. XXX is this needed?
    Rectangle sourceBounds = source.getBounds();
    if (sourceBounds.isEmpty()) {
      throw new IllegalArgumentException("source.getBounds().isEmpty()!");
    }

    // Check sub-banding.
    boolean isSubBanding = false;
    int numSourceBands = source.getSampleModel().getNumBands();
    if (sourceBands != null) {
      if (sourceBands.length > numSourceBands) {
        throw new IllegalArgumentException("sourceBands.length > numSourceBands!");
      }

      boolean isRamp = sourceBands.length == numSourceBands;
      for (int i = 0; i < sourceBands.length; i++) {
        if (sourceBands[i] < 0 || sourceBands[i] >= numSourceBands) {
          throw new IllegalArgumentException(
              "sourceBands[i] < 0 || sourceBands[i] >= numSourceBands!");
        } else if (sourceBands[i] != i) {
          isRamp = false;
        }
      }

      isSubBanding = !isRamp;
    }

    // Allocate buffer for a single source row.
    int sourceWidth = sourceBounds.width;
    int[] pixels = new int[sourceWidth * numSourceBands];

    // Initialize variables used in loop.
    int sourceX = sourceBounds.x;
    int sourceY = sourceBounds.y;
    int numBands = sourceBands != null ? sourceBands.length : numSourceBands;
    int dstWidth = dst.getWidth();
    int dstYMax = dst.getHeight() - 1;
    int copyFromIncrement = numSourceBands * subsampleX;

    // Loop over source rows, subsample each, and store in destination.
    for (int dstY = 0; dstY <= dstYMax; dstY++) {
      // Read one row.
      source.getPixels(sourceX, sourceY, sourceWidth, 1, pixels);

      // Copy within the same buffer by left shifting.
      if (isSubBanding) {
        int copyFrom = 0;
        int copyTo = 0;
        for (int i = 0; i < dstWidth; i++) {
          for (int j = 0; j < numBands; j++) {
            pixels[copyTo++] = pixels[copyFrom + sourceBands[j]];
          }
          copyFrom += copyFromIncrement;
        }
      } else {
        int copyFrom = copyFromIncrement;
        int copyTo = numSourceBands;
        // Start from index 1 as no need to copy the first pixel.
        for (int i = 1; i < dstWidth; i++) {
          int k = copyFrom;
          for (int j = 0; j < numSourceBands; j++) {
            pixels[copyTo++] = pixels[k++];
          }
          copyFrom += copyFromIncrement;
        }
      }

      // Set the destionation row.
      dst.setPixels(0, dstY, dstWidth, 1, pixels);

      // Increment the source row.
      sourceY += subsampleY;
    }
  }