/** * 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); }
@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); } }
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); }
/** * 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(); }
/** * 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; } }