Java tutorial
/* * Copyright (C) 2014-2015 CS-SI (foss-contact@thor.si.c-s.fr) * Copyright (C) 2014-2015 CS-Romania (office@c-s.ro) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.s2tbx.dataio.jp2.internal; import com.bc.ceres.core.Assert; import com.bc.ceres.glevel.MultiLevelModel; import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader; import org.esa.s2tbx.dataio.Utils; import org.esa.s2tbx.dataio.jp2.TileLayout; import org.esa.s2tbx.dataio.openjp2.OpenJP2Decoder; import org.esa.s2tbx.dataio.openjpeg.OpenJpegExecRetriever; import org.esa.s2tbx.dataio.readers.PathUtils; import org.esa.snap.core.image.ResolutionLevel; import org.esa.snap.core.image.SingleBandedOpImage; import org.esa.snap.core.util.ImageUtils; import org.esa.snap.core.util.SystemUtils; import org.esa.snap.runtime.Config; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.stream.ImageInputStream; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.operator.ConstantDescriptor; import java.awt.*; import java.awt.image.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.logging.Logger; import static org.esa.s2tbx.dataio.Utils.GetIterativeShortPathNameW; import static org.esa.s2tbx.dataio.Utils.diffLastModifiedTimes; /** * A JAI operator for handling JP2 tiles. * * @author Cosmin Cara */ public class JP2TileOpImage extends SingleBandedOpImage { // We need this sort of cache to hold the tile rectangles because // it seems that the TIFFImageReader.getImageWidth() goes into the stream // each time and, therefore, takes unnecessary time private static final Map<Path, Rectangle> tileDims = new HashMap<>(); private static final int[] bands = new int[] { 0 }; private Boolean useOpenJp2Jna; private final TileLayout tileLayout; private final Path imageFile; private final Path cacheDir; private final int tileIndex; private final int bandIndex; private final int dataType; private final Logger logger; private JP2TileOpImage(Path imageFile, int bandIdx, Path cacheDir, int row, int col, TileLayout tileLayout, MultiLevelModel imageModel, int dataType, int level) throws IOException { super(dataType, null, tileLayout.tileWidth, tileLayout.tileHeight, getTileDimAtResolutionLevel(tileLayout.tileWidth, tileLayout.tileHeight, level), null, ResolutionLevel.create(imageModel, level)); Assert.notNull(imageFile, "imageFile"); Assert.notNull(cacheDir, "cacheDir"); Assert.notNull(tileLayout, "tileLayout"); Assert.notNull(imageModel, "imageModel"); this.logger = SystemUtils.LOG; this.imageFile = imageFile; this.cacheDir = cacheDir; this.tileLayout = tileLayout; this.tileIndex = col + row * tileLayout.numXTiles; this.bandIndex = bandIdx; this.dataType = dataType; //if (useOpenJp2Jna == null) { /* Uncomment to use the direct openJp2 decompression */ String openJp2 = OpenJpegExecRetriever.getOpenJp2(); useOpenJp2Jna = Boolean.parseBoolean(Config.instance("s2tbx").preferences().get("use.openjp2.jna", "false")) && openJp2 != null && tileLayout.numBands == 1; /*useOpenJp2Jna = false;*/ //} } /** * Factory method for creating a TileOpImage instance. * * @param imageFile The JP2 file * @param cacheDir The directory where decompressed tiles will be extracted * @param bandIdx The index of the band for which the operator is created * @param row The row of the tile in the scene layout * @param col The column of the tile in the scene layout * @param tileLayout The scene layout * @param imageModel The multi-level image model * @param dataType The data type of the tile raster * @param level The resolution at which the tile is created */ public static PlanarImage create(Path imageFile, Path cacheDir, int bandIdx, int row, int col, TileLayout tileLayout, MultiLevelModel imageModel, int dataType, int level) throws IOException { Assert.notNull(cacheDir, "cacheDir"); Assert.notNull(tileLayout, "imageLayout"); Assert.notNull(imageModel, "imageModel"); if (imageFile != null) { //jp2TileOpImage.setTileCache(null); // the MosaicOpImage will be in the cache return new JP2TileOpImage(imageFile, bandIdx, cacheDir, row, col, tileLayout, imageModel, dataType, level); } else { int targetWidth = tileLayout.tileWidth; int targetHeight = tileLayout.tileHeight; Dimension targetTileDim = getTileDimAtResolutionLevel(tileLayout.tileWidth, tileLayout.tileHeight, level); SampleModel sampleModel = ImageUtils.createSingleBandedSampleModel(dataType, targetWidth, targetHeight); ImageLayout imageLayout = new ImageLayout(0, 0, targetWidth, targetHeight, 0, 0, targetTileDim.width, targetTileDim.height, sampleModel, null); return ConstantDescriptor.create((float) imageLayout.getWidth(null), (float) imageLayout.getHeight(null), new Short[] { 0 }, new RenderingHints(JAI.KEY_IMAGE_LAYOUT, imageLayout)); } } @Override protected synchronized void computeRect(PlanarImage[] sources, WritableRaster dest, Rectangle destRect) { if (useOpenJp2Jna) { computeRectDirect(dest, destRect); } else { computeRectIndirect(dest, destRect); } } private void computeRectIndirect(WritableRaster dest, Rectangle destRect) { try { Path tile = decompressTile(tileIndex, getLevel()); RenderedImage readTileImage = null; if (tile != null) { try (ImageReader imageReader = new ImageReader(tile)) { final DataBuffer dataBuffer = dest.getDataBuffer(); int tileWidth = this.getTileWidth(); int tileHeight = this.getTileHeight(); final int fileTileX = destRect.x / tileLayout.tileWidth; final int fileTileY = destRect.y / tileLayout.tileHeight; int fileTileOriginX = destRect.x - fileTileX * tileLayout.tileWidth; int fileTileOriginY = destRect.y - fileTileY * tileLayout.tileHeight; Rectangle fileTileRect = tileDims.get(tile); if (fileTileRect == null) { fileTileRect = new Rectangle(0, 0, imageReader.getImageWidth(), imageReader.getImageHeight()); tileDims.put(tile, fileTileRect); } if (fileTileOriginX == 0 && tileLayout.tileWidth == tileWidth && fileTileOriginY == 0 && tileLayout.tileHeight == tileHeight && tileWidth * tileHeight == dataBuffer.getSize()) { readTileImage = imageReader.read(); } else { final Rectangle tileRect = new Rectangle(fileTileOriginX, fileTileOriginY, tileWidth, tileHeight); final Rectangle intersection = fileTileRect.intersection(tileRect); if (!intersection.isEmpty()) { readTileImage = imageReader.read(intersection); } } if (readTileImage != null) { Raster readBandRaster = readTileImage.getData().createChild(0, 0, readTileImage.getWidth(), readTileImage.getHeight(), 0, 0, new int[] { bandIndex }); dest.setDataElements(dest.getMinX(), dest.getMinY(), readBandRaster); } } catch (IOException e) { logger.severe(e.getMessage()); } } } catch (IOException e) { logger.severe(e.getMessage()); } } private void computeRectDirect(WritableRaster dest, Rectangle destRect) { try (OpenJP2Decoder decoder = new OpenJP2Decoder(this.cacheDir, this.imageFile, this.bandIndex, this.dataType, getLevel(), 20, tileIndex)) { Raster readTileImage = null; final DataBuffer dataBuffer = dest.getDataBuffer(); int tileWidth = this.getTileWidth(); int tileHeight = this.getTileHeight(); final int fileTileX = destRect.x / tileLayout.tileWidth; final int fileTileY = destRect.y / tileLayout.tileHeight; int fileTileOriginX = destRect.x - fileTileX * tileLayout.tileWidth; int fileTileOriginY = destRect.y - fileTileY * tileLayout.tileHeight; Dimension dimensions = decoder.getImageDimensions(); Rectangle fileTileRect = new Rectangle(0, 0, dimensions.width, dimensions.height); if (fileTileOriginX == 0 && tileLayout.tileWidth == tileWidth && fileTileOriginY == 0 && tileLayout.tileHeight == tileHeight && tileWidth * tileHeight == dataBuffer.getSize()) { readTileImage = decoder.read(null); } else { final Rectangle tileRect = new Rectangle(fileTileOriginX, fileTileOriginY, tileWidth, tileHeight); final Rectangle intersection = fileTileRect.intersection(tileRect); if (!intersection.isEmpty()) { readTileImage = decoder.read(intersection); } } if (readTileImage != null) { Raster readBandRaster = readTileImage.createChild(0, 0, readTileImage.getWidth(), readTileImage.getHeight(), 0, 0, bands); dest.setDataElements(dest.getMinX(), dest.getMinY(), readBandRaster); } } catch (IOException e) { logger.severe(e.getMessage()); } } private static int scaleValue(int source, int level) { int size = source >> level; int sizeTest = size << level; if (sizeTest < source) { size++; } return size; } private static Dimension getTileDimAtResolutionLevel(int fullTileWidth, int fullTileHeight, int level) { int width = scaleValue(fullTileWidth, level); int height = scaleValue(fullTileHeight, level); return getTileDim(width, height); } private static Dimension getTileDim(int width, int height) { return new Dimension(width < JAI.getDefaultTileSize().width ? width : JAI.getDefaultTileSize().width, height < JAI.getDefaultTileSize().height ? height : JAI.getDefaultTileSize().height); } private Path decompressTile(int tileIndex, int level) throws IOException { Path tileFile = PathUtils.get(cacheDir, PathUtils.getFileNameWithoutExtension(imageFile).toLowerCase() + "_tile_" + String.valueOf(tileIndex) + "_" + String.valueOf(level) + ".tif"); if ((!Files.exists(tileFile)) || (diffLastModifiedTimes(tileFile.toFile(), imageFile.toFile()) < 0L)) { final OpjExecutor decompress = new OpjExecutor(OpenJpegExecRetriever.getOpjDecompress()); final Map<String, String> params = new HashMap<String, String>() { { put("-i", GetIterativeShortPathNameW(imageFile.toString())); put("-r", String.valueOf(level)); put("-l", "20"); } }; String tileFileName; if (org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS && (tileFile.getParent() != null)) { tileFileName = Utils.GetIterativeShortPathNameW(tileFile.getParent().toString()) + File.separator + tileFile.getName(tileFile.getNameCount() - 1); } else { tileFileName = tileFile.toString(); } params.put("-o", tileFileName); params.put("-t", String.valueOf(tileIndex)); params.put("-p", String.valueOf(DataBuffer.getDataTypeSize(this.getSampleModel().getDataType()))); params.put("-threads", "ALL_CPUS"); if (decompress.execute(params) != 0) { logger.severe(decompress.getLastError()); tileFile = null; } else { logger.fine("Decompressed tile #" + String.valueOf(tileIndex) + " @ resolution " + String.valueOf(level)); } } return tileFile; } @Override protected void finalize() throws Throwable { super.finalize(); dispose(); } private static class ImageReader implements AutoCloseable { private TIFFImageReader imageReader; ImageInputStream inputStream; ImageReader(Path input) throws IOException { Iterator<javax.imageio.ImageReader> imageReaders = ImageIO.getImageReadersByFormatName("tiff"); while (imageReaders.hasNext()) { final javax.imageio.ImageReader reader = imageReaders.next(); if (reader instanceof TIFFImageReader) { imageReader = (TIFFImageReader) reader; break; } } if (imageReader == null) { throw new IOException("Tiff imageReader not found"); } inputStream = ImageIO.createImageInputStream(input.toFile()); imageReader.setInput(inputStream); } int getImageWidth() throws IOException { return inputStream != null ? imageReader.getWidth(0) : 0; } int getImageHeight() throws IOException { return inputStream != null ? imageReader.getHeight(0) : 0; } public RenderedImage read() throws IOException { if (inputStream == null) { throw new IOException("No input stream"); } return imageReader.readAsRenderedImage(0, null); } public RenderedImage read(Rectangle rectangle) throws IOException { if (inputStream == null) { throw new IOException("No input stream"); } ImageReadParam params = imageReader.getDefaultReadParam(); params.setSourceRegion(rectangle); return imageReader.read(0, params); } public void close() { try { if (inputStream != null) inputStream.close(); //imageReader.dispose(); } catch (IOException ignored) { } } } }