/** * Returns {@code true} if at least one tile in the given collection has at "<cite>grid to real * world</cite>" transform waiting to be processed by {@link RegionCalculator}. It is okay to * conservatively returns {@code true} in situations where we would have got {@code false} if * synchronization was performed on every tiles. */ private static boolean hasPendingGridToCRS(final Collection<Tile> tiles) { for (final Tile tile : tiles) { if (tile.getPendingGridToCRS(false) != null) { return true; } } return false; }
private List<Tile> toTiles( ImageReaderSpi provider, TileReaderPool readers, Set<ImageReaderSpi> providers, Path input) throws IOException { // Creates the tile for the first image, which usually have the maximal resolution. // The Tile constructor will read the TFW file and infer a provider if the given // 'provider' argument is null. If this is a new provider, then we need to declare // it to the pool of image readers before to use it. final List<Tile> tiles = new ArrayList<>(); final Tile root = new Tile(provider, input, 0); if (providers.add(root.getImageReaderSpi())) { readers.setProviders(providers); } final AffineTransform scaledGridToCRS = new AffineTransform(); final AffineTransform gridToCRS = root.getPendingGridToCRS(false); final ImageReader reader = root.getImageReader(readers, true, true); final int numImages = reader.getNumImages(false); // Result may be -1. for (int index = 0; index != numImages; index++) { // Intentional use of !=, not <. final int width, height; try { width = reader.getWidth(index); height = reader.getHeight(index); } catch (IndexOutOfBoundsException e) { // As explained in ImageReader javadoc, this approach is sometime // more efficient than invoking reader.getNumImages(true) first. break; } final Tile tile; if (index == 0) { tile = root; } else { final Rectangle region = root.getRegion(); scaledGridToCRS.setTransform(new AffineTransform(gridToCRS)); scaledGridToCRS.scale(region.width / (double) width, region.height / (double) height); tile = new Tile(root.getImageReaderSpi(), input, index, region, scaledGridToCRS); } tile.setSize(width, height); tiles.add(tile); } reader.dispose(); return tiles; }
/** * Creates tile managers from the specified collection of tiles. This method usually returns a * single tile manager, but more could be returned if this factory has been unable to put every * tiles in a single mosaic (for example if the ratio between {@linkplain AffineTransform affine * transform} given to {@linkplain Tile#Tile(ImageReaderSpi,Object,int,Rectangle,AffineTransform) * tile constructor} would lead to fractional {@linkplain Tile#getSubsampling subsampling}). * * @param tiles The tiles to give to a tile manager. * @return A tile manager created from the given tiles. * @throws IOException If an I/O operation was required and failed. */ public TileManager[] create(Collection<Tile> tiles) throws IOException { int count = 0; final TileManager[] managers; if (!hasPendingGridToCRS(tiles)) { /* * There is no tile having a "gridToCRS" transform pending RegionCalculator work. So we * can create (at the end of this method) a single TileManager using all those tiles. */ if (!tiles.isEmpty()) { count = 1; } managers = new TileManager[count]; } else { /* * At least one tile have a pending "gridToCRS" transform (actually we should have * more than one - typically all of them - otherwise the RegionCalculator work will * be useless). Computes their region now. Note that we could execute this block * unconditionally. The 'hasPendingGridToCRS' check we just for avoiding the cost * of creating RegionCalculator in the common case where it is not needed. So it is * not a big deal if 'hasPendingGridToCRS' conservatively returned 'true'. */ final Collection<Tile> remainings = new ArrayList<>(Math.min(16, tiles.size())); final RegionCalculator calculator = new RegionCalculator(); for (final Tile tile : tiles) { if (!calculator.add(tile)) { remainings.add(tile); } } if (!remainings.isEmpty()) { count = 1; } final Map<ImageGeometry, Tile[]> split = calculator.tiles(); managers = new TileManager[split.size() + count]; for (final Map.Entry<ImageGeometry, Tile[]> entry : split.entrySet()) { final TileManager manager = createGeneric(entry.getValue()); manager.setGridGeometry(entry.getKey()); managers[count++] = manager; } tiles = remainings; } /* * The collection now contains tiles that has not been processed by RegionCalculator, * because their 'gridToCRS' transform is flagged as already computed. Create a mosaic * for them, and use the affine transform having the finest resolution as the "global" * one. */ if (!tiles.isEmpty()) { final TileManager manager = createGeneric(tiles.toArray(new Tile[tiles.size()])); final Rectangle imageBounds = new Rectangle(-1, -1); AffineTransform gridToCRS = null; Dimension subsampling = null; double scale = Double.POSITIVE_INFINITY; for (final Tile tile : tiles) { imageBounds.add(tile.getAbsoluteRegion()); final AffineTransform candidate = tile.getGridToCRS(); if (candidate != null && !candidate.equals(gridToCRS)) { final double cs = XAffineTransform.getScale(candidate); if (cs < scale) { // Found a new tile at a finer resolution. scale = cs; gridToCRS = candidate; subsampling = tile.getSubsampling(); } else if (cs == scale) { // Inconsistent transform at the finest level. // Abandon the attempt to create a grid geometry. gridToCRS = null; break; } } } if (gridToCRS != null) { if (subsampling.width != 1 || subsampling.height != 1) { gridToCRS = new AffineTransform(gridToCRS); gridToCRS.scale(subsampling.width, subsampling.height); } manager.setGridGeometry(new ImageGeometry(imageBounds, gridToCRS)); } managers[0] = manager; } return managers; }