All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.matrices.tiff.tiles.TiffMap Maven / Gradle / Ivy

There is a newer version: 1.3.7
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2023-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.matrices.tiff.tiles;

import net.algart.arrays.Matrices;
import net.algart.arrays.Matrix;
import net.algart.arrays.TooLargeArrayException;
import net.algart.arrays.UpdatablePArray;
import net.algart.matrices.tiff.*;

import java.util.*;

public final class TiffMap {
    /**
     * Possible type of tiles in the TIFF map: 2D tile grid or horizontal strips.
     * You can know the tiling type of the map by {@link TiffMap#getTilingMode()} method.
     */
    public enum TilingMode {
        /**
         * True tiles: 2D grid of rectangular tiles with of equal size.
         *
         * 

The sizes of tiles are returned by {@link TiffMap#tileSizeX()} and * {@link TiffMap#tileSizeY()} methods. * *

IFD must contain TileWidth and TileLength tags. * The method {@link TiffMap#ifd()}.{@link TiffIFD#hasTileInformation() hasTileInformation()} * returns {@code true}.

* *

The tiles on the image boundaries, partially lying outside the image, * are stored as full-size pixel matrices; extra pixels outside the image should be ignored. * *

Used for large TIFF images.

*/ TILE_GRID, /** * Horizontal strips. In terms of this library they are also called "tiles" and represented * with help of {@link TiffTile} and {@link TiffMap} classes. * *

In this case, all strips ("tiles") has a width, * equal to the width of the entire image, and the same height H (excepting the last strip, * which height is a reminder image-height % H when the full image height is not divisible * by H). The height H is returned by {@link #tileSizeY()} method; * the {@link #tileSizeX()} method returns the total image width. * *

IFD must not contain TileWidth and TileLength tags. * The height H of every strip is specified in RowsPerStrip tag * or is the full image height if there is no such tag (in the latter case, {@link TiffMap} * will contain only 1 tile). * Though TIFF format allows to specify strips with different heights, * this library does not support this case. * *

If the full image height is not divisible by strip height H), * the last tile should be stored as a pixel matrix with reduced height * image-height % H: extra pixel outside the image are not stored. * However, if this condition is not fulfilled, for example, the last strip is stored * as a full-size JPEG with the height H, this library correctly reads such an image. */ STRIPS; public final boolean isTileGrid() { return this == TILE_GRID; } } /** * Maximal value of x/y-index of the tile. * *

This limit helps to avoid arithmetic overflow while operations with indexes. */ public static final int MAX_TILE_INDEX = 1_000_000_000; private final TiffIFD ifd; private final Map tileMap = new LinkedHashMap<>(); private final boolean resizable; private final boolean planarSeparated; private final int numberOfChannels; private final int numberOfSeparatedPlanes; private final int tileSamplesPerPixel; private final int alignedBitsPerSample; private final int bitsPerUnpackedSample; private final int tileAlignedBitsPerPixel; private final int totalAlignedBitsPerPixel; private final TiffSampleType sampleType; private final boolean wholeBytes; private final Class elementType; private final long maxNumberOfSamplesInArray; private final TilingMode tilingMode; private final int tileSizeX; private final int tileSizeY; private final int tileSizeInPixels; private final int tileSizeInBytes; // - Note: we store here information about samples and tiles structure, but // SHOULD NOT store information about image sizes (like number of tiles): // it is probable that we do not know final sizes while creating tiles of the image! private volatile int dimX = 0; private volatile int dimY = 0; private volatile int gridCountX = 0; private volatile int gridCountY = 0; private volatile int numberOfGridTiles = 0; private TiffMap(TiffIFD ifd, boolean resizable) { this.ifd = Objects.requireNonNull(ifd, "Null IFD"); this.resizable = resizable; final boolean hasImageDimensions = ifd.hasImageDimensions(); try { if (!hasImageDimensions && !resizable) { throw new IllegalArgumentException("TIFF image sizes (ImageWidth and ImageLength tags) " + "are not specified; it is not allowed for non-resizable tile map"); } this.tilingMode = ifd.hasTileInformation() ? TilingMode.TILE_GRID : TilingMode.STRIPS; if (resizable && !tilingMode.isTileGrid()) { throw new IllegalArgumentException("TIFF image is not tiled (TileWidth and TileLength tags " + "are not specified); it is not allowed for resizable tile map: any processing " + "TIFF image, such as writing its fragments, requires either knowing its final fixed sizes, " + "or splitting image into tiles with known fixed sizes"); } this.planarSeparated = ifd.isPlanarSeparated(); this.numberOfChannels = ifd.getSamplesPerPixel(); assert numberOfChannels <= TiffIFD.MAX_NUMBER_OF_CHANNELS; this.numberOfSeparatedPlanes = planarSeparated ? numberOfChannels : 1; this.tileSamplesPerPixel = planarSeparated ? 1 : numberOfChannels; this.alignedBitsPerSample = ifd.alignedBitDepth(); // - so, we allow only EQUAL number of bytes/sample (but number if bits/sample can be different) assert (long) numberOfChannels * (long) alignedBitsPerSample < TiffIFD.MAX_NUMBER_OF_CHANNELS * TiffIFD.MAX_BITS_PER_SAMPLE; // - actually must be in 8 times less this.tileAlignedBitsPerPixel = tileSamplesPerPixel * alignedBitsPerSample; this.totalAlignedBitsPerPixel = numberOfChannels * alignedBitsPerSample; this.sampleType = ifd.sampleType(); this.wholeBytes = sampleType.isWholeBytes(); if (this.wholeBytes != ((alignedBitsPerSample & 7) == 0)) { throw new ConcurrentModificationException("Corrupted IFD, probably from a parallel thread" + " (sample type " + sampleType + " is" + (wholeBytes ? "" : " NOT") + " whole-bytes, but we have " + alignedBitsPerSample + " bits/sample)"); } if ((totalAlignedBitsPerPixel == 1) != sampleType.isBinary()) { throw new ConcurrentModificationException("Corrupted IFD, probably from a parallel thread" + " (sample type is " + sampleType + ", but we have " + totalAlignedBitsPerPixel + " bits/pixel)"); } if (sampleType.isBinary() && numberOfChannels > 1) { throw new AssertionError("Binary IFD for " + numberOfChannels + " > 1 channels is not supported: invalid TiffIFD class"); } this.bitsPerUnpackedSample = sampleType.bitsPerSample(); if (bitsPerUnpackedSample < alignedBitsPerSample) { throw new AssertionError(sampleType + ".bitsPerSample() = " + bitsPerUnpackedSample + " is too little: less than ifd.alignedBitDepth() = " + alignedBitsPerSample); } this.elementType = sampleType.elementType(); this.maxNumberOfSamplesInArray = sampleType.maxNumberOfSamplesInArray(); this.tileSizeX = ifd.getTileSizeX(); this.tileSizeY = ifd.getTileSizeY(); assert tileSizeX > 0 && tileSizeY > 0 : "non-positive tile sizes are not checked in IFD methods"; if (hasImageDimensions) { setDimensions(ifd.getImageDimX(), ifd.getImageDimY(), false); } if ((long) tileSizeX * (long) tileSizeY > Integer.MAX_VALUE) { throw new IllegalArgumentException("Very large " + (tilingMode.isTileGrid() ? "TIFF tiles " : "TIFF strips ") + tileSizeX + "x" + tileSizeY + " >= 2^31 pixels are not supported"); // - note that it is also checked deeper in the next operator } this.tileSizeInPixels = tileSizeX * tileSizeY; if ((long) tileSizeInPixels * (long) tileAlignedBitsPerPixel > Integer.MAX_VALUE) { throw new IllegalArgumentException("Very large TIFF tiles " + tileSizeX + "x" + tileSizeY + ", " + tileSamplesPerPixel + " channels per " + alignedBitsPerSample + " bits >= 2^31 bits (256 MB) are not supported"); } this.tileSizeInBytes = (tileSizeInPixels * tileAlignedBitsPerPixel + 7) >>> 3; } catch (TiffException e) { throw new IllegalArgumentException("Illegal IFD: " + e.getMessage(), e); } } /** * Creates new tile map. * *

Note: you should not change the tags of the passed IFD, describing sample type, number of samples * and tile sizes, after creating this object. The constructor saves this information in this object * (it is available via access methods) and will not be renewed automatically. * * @param ifd IFD. * @param resizable whether maximal dimensions of this set will grow while adding new tiles, * or they are fixed and must be specified in IFD. */ public static TiffMap newMap(TiffIFD ifd, boolean resizable) { return new TiffMap(ifd, resizable); } public static TiffMap newFixed(TiffIFD ifd) { return newMap(ifd, false); } public static TiffMap newResizable(TiffIFD ifd) { return newMap(ifd, true); } public TiffIFD ifd() { return ifd; } public Map tileMap() { return Collections.unmodifiableMap(tileMap); } public Set indexes() { return Collections.unmodifiableSet(tileMap.keySet()); } public Collection tiles() { return Collections.unmodifiableCollection(tileMap.values()); } public boolean isResizable() { return resizable; } public boolean isPlanarSeparated() { return planarSeparated; } public int numberOfChannels() { return numberOfChannels; } public int numberOfSeparatedPlanes() { return numberOfSeparatedPlanes; } public int tileSamplesPerPixel() { return tileSamplesPerPixel; } public TiffSampleType sampleType() { return sampleType; } public boolean isBinary() { return sampleType().isBinary(); } public boolean isWholeBytes() { return wholeBytes; } /** * Minimal number of bits, necessary to store one channel of the pixel: * the value of BitsPerSample TIFF tag, aligned to the nearest non-lesser multiple of 8, * or 1 in the case of a single-channel binary matrix (BitsPerSample=1, SamplesPerPixel=1). * This class requires that this value is equal for all channels, even * if BitsPerSample tag contain different number of bits per channel (for example, 5+6+5). * *

Note that the actual number of bits, used for storing the pixel samples in memory * after reading data from TIFF file, may be little greater: see {@link #bitsPerUnpackedSample()}. * * @return number of bytes, necessary to store one channel of the pixel inside TIFF. */ public int alignedBitsPerSample() { return alignedBitsPerSample; } public OptionalInt bytesPerSample() { assert wholeBytes == ((alignedBitsPerSample & 7) == 0) : "must be checked ins the constructor"; return wholeBytes ? OptionalInt.of(alignedBitsPerSample >>> 3) : OptionalInt.empty(); } /** * Number of bits, actually used for storing one channel of the pixel in memory. * This number of bytes is correct for data, loaded from TIFF file by * {@link TiffReader}, and for source data, * that should be written by {@link TiffWriter}. * *

Usually this value is equal to results of {@link #alignedBitsPerSample()}, * excepting the following rare cases, called unusual precisions:

* *
    *
  • every channel is encoded as N-bit integer value, where 17≤N≤24, and, so, requires 3 bytes: * this method returns 32, {@link #alignedBitsPerSample()} returns 24 * (image, stored in memory, must have 2k bytes (k=1..3) per every sample, to allow to represent * it by one of Java types byte, short, int, * float, double); *
  • *
  • pixels are encoded as 16-bit or 24-bit floating point values: * this method returns 32, {@link #alignedBitsPerSample()} returns 16/24 * (in memory, such image will be unpacked into usual array of 32-bit float values). *
  • *
* *

Note that this difference is possible only while reading TIFF files, created by some other software. * While using {@link TiffWriter} class of this module, * it is not allowed to write image with precisions listed above.

* * @return number of bytes, used for storing one channel of the pixel in memory. * @see TiffReader#setAutoUnpackUnusualPrecisions(boolean) */ public int bitsPerUnpackedSample() { return bitsPerUnpackedSample; } public int tileAlignedBitsPerPixel() { return tileAlignedBitsPerPixel; } public int totalAlignedBitsPerPixel() { return totalAlignedBitsPerPixel; } public Class elementType() { return elementType; } public int sizeOfRegionWithPossibleNonStandardPrecisions(long sizeX, long sizeY) throws TiffException { return TiffIFD.sizeOfRegionInBytes(sizeX, sizeY, numberOfChannels, alignedBitsPerSample); } public long maxNumberOfSamplesInArray() { return maxNumberOfSamplesInArray; } public Optional description() { return ifd.optDescription(); } public TilingMode getTilingMode() { return tilingMode; } public int tileSizeX() { return tileSizeX; } public int tileSizeY() { return tileSizeY; } public int tileSizeInPixels() { return tileSizeInPixels; } public int tileSizeInBytes() { return tileSizeInBytes; } public int dimX() { return dimX; } public int dimY() { return dimY; } public long totalSizeInPixels() { return (long) dimX * (long) dimY; } public long totalSizeInBytes() { return (Math.multiplyExact(totalSizeInPixels(), (long) totalAlignedBitsPerPixel) + 7) >>> 3; // - but overflow here should be impossible due to the check in setDimensions } public void setDimensions(int dimX, int dimY) { setDimensions(dimX, dimY, true); } public void checkZeroDimensions() { if (dimX == 0 || dimY == 0) { throw new IllegalStateException("Zero/unset map dimensions " + dimX + "x" + dimY + " are not allowed here"); } } public void checkTooSmallDimensionsForCurrentGrid() { final int tileCountX = (int) ((long) dimX + (long) tileSizeX - 1) / tileSizeX; final int tileCountY = (int) ((long) dimY + (long) tileSizeY - 1) / tileSizeY; assert tileCountX <= this.gridCountX && tileCountY <= this.gridCountY : "Grid dimensions were not correctly grown according map dimensions"; if (tileCountX != this.gridCountX || tileCountY != this.gridCountY) { assert resizable : "Map dimensions mismatch to grid dimensions: impossible for non-resizable map"; throw new IllegalStateException("Map dimensions " + dimX + "x" + dimY + " are too small for current tile grid: " + this); } } /** * Replaces total image sizes to maximums from their current values and newMinimalDimX/Y. * *

Note: if both new x/y-sizes are not greater than existing ones, this method does nothing * and can be called even if not {@link #isResizable()}.

* *

Also note: negative arguments are allowed, but have no effect (as if they would be zero).

* * @param newMinimalDimX new minimal value for {@link #dimX() sizeX}. * @param newMinimalDimY new minimal value for {@link #dimY() sizeY}. */ public void expandDimensions(int newMinimalDimX, int newMinimalDimY) { if (newMinimalDimX > dimX || newMinimalDimY > dimY) { setDimensions(Math.max(dimX, newMinimalDimX), Math.max(dimY, newMinimalDimY)); } } public int gridCountX() { return gridCountX; } public int gridCountY() { return gridCountY; } public int numberOfGridTiles() { return numberOfGridTiles; } /** * Replaces tile x/y-count to maximums from their current values and newMinimalTileCountX/Y. * *

Note: the arguments are the desired minimal tile counts, not tile indexes. * So, you can freely specify zero arguments, and this method will do nothing in this case. * *

Note: if both new x/y-counts are not greater than existing ones, this method does nothing * and can be called even if not {@link #isResizable()}. * *

Note: this method is called automatically while changing total image sizes. * * @param newMinimalTileCountX new minimal value for {@link #gridCountX()}. * @param newMinimalTileCountY new minimal value for {@link #gridCountY()}. */ public void expandGrid(int newMinimalTileCountX, int newMinimalTileCountY) { expandGrid(newMinimalTileCountX, newMinimalTileCountY, true); } public void checkPixelCompatibility(int numberOfChannels, TiffSampleType sampleType) throws TiffException { Objects.requireNonNull(sampleType, "Null sampleType"); if (numberOfChannels <= 0) { throw new IllegalArgumentException("Zero or negative numberOfChannels = " + numberOfChannels); } if (numberOfChannels != this.numberOfChannels) { throw new TiffException("Number of channel mismatch: expected " + numberOfChannels + " channels, but TIFF image contains " + this.numberOfChannels + " channels"); } if (sampleType != this.sampleType) { throw new TiffException( "Sample type mismatch: expected elements are " + sampleType.prettyName() + ", but TIFF image contains elements " + this.sampleType.prettyName()); } } public int linearIndex(int separatedPlaneIndex, int xIndex, int yIndex) { if (separatedPlaneIndex < 0 || separatedPlaneIndex >= numberOfSeparatedPlanes) { throw new IndexOutOfBoundsException("Separated plane index " + separatedPlaneIndex + " is out of range 0.." + (numberOfSeparatedPlanes - 1)); } int tileCountX = this.gridCountX; int tileCountY = this.gridCountY; if (xIndex < 0 || xIndex >= tileCountX || yIndex < 0 || yIndex >= tileCountY) { throw new IndexOutOfBoundsException("One of X/Y-indexes (" + xIndex + ", " + yIndex + ") of the tile is out of ranges 0.." + (tileCountX - 1) + ", 0.." + (tileCountY - 1)); } // - if the tile is out of bounds, it means that we do not know actual grid dimensions // (even it is resizable): there is no way to calculate correct linear index return (separatedPlaneIndex * tileCountY + yIndex) * tileCountX + xIndex; // - overflow impossible: setDimensions checks that tileCountX * tileCountY * numberOfSeparatedPlanes < 2^31 } public TiffTileIndex index(int x, int y) { return new TiffTileIndex(this, 0, x, y); } public TiffTileIndex multiPlaneIndex(int separatedPlaneIndex, int x, int y) { return new TiffTileIndex(this, separatedPlaneIndex, x, y); } public TiffTileIndex copyIndex(TiffTileIndex other) { Objects.requireNonNull(other, "Null other index"); return new TiffTileIndex(this, other.channelPlane(), other.xIndex(), other.yIndex()); } public void checkTileIndexIFD(TiffTileIndex tileIndex) { Objects.requireNonNull(tileIndex, "Null tile index"); if (tileIndex.ifd() != this.ifd) { // - Checking references, not content! // Checking IFD, not reference to map ("this"): there is no sense to disable creating new map // and copying there the tiles from the given map. throw new IllegalArgumentException("Illegal tile index: tile map cannot process tiles from different IFD"); } } public int numberOfTiles() { return tileMap.size(); } public TiffTile getOrNew(int x, int y) { return getOrNew(index(x, y)); } public TiffTile getOrNewMultiPlane(int separatedPlaneIndex, int x, int y) { return getOrNew(multiPlaneIndex(separatedPlaneIndex, x, y)); } public TiffTile getOrNew(TiffTileIndex tileIndex) { TiffTile result = get(tileIndex); if (result == null) { result = new TiffTile(tileIndex); put(result); } return result; } public TiffTile get(TiffTileIndex tileIndex) { checkTileIndexIFD(tileIndex); return tileMap.get(tileIndex); } public void put(TiffTile tile) { Objects.requireNonNull(tile, "Null tile"); final TiffTileIndex tileIndex = tile.index(); checkTileIndexIFD(tileIndex); if (resizable) { expandGrid(tileIndex.xIndex() + 1, tileIndex.yIndex() + 1); } else { if (tileIndex.xIndex() >= gridCountX || tileIndex.yIndex() >= gridCountY) { // sizeX-1: tile MAY be partially outside the image, but it MUST have at least 1 pixel inside it throw new IndexOutOfBoundsException("New tile is completely outside the image " + "(out of maximal tilemap sizes) " + dimX + "x" + dimY + ": " + tileIndex); } } tileMap.put(tileIndex, tile); } public void putAll(Collection tiles) { Objects.requireNonNull(tiles, "Null tiles"); tiles.forEach(this::put); } public TiffMap buildTileGrid() { for (int p = 0; p < numberOfSeparatedPlanes; p++) { for (int y = 0; y < gridCountY; y++) { for (int x = 0; x < gridCountX; x++) { getOrNewMultiPlane(p, x, y).cropToMap(); } } } return this; } public void cropAll() { cropAll(true); } public void cropAll(boolean strippedOnly) { tileMap.values().forEach(tile -> tile.cropToMap(strippedOnly)); } public boolean hasUnset() { return tileMap.values().stream().anyMatch(TiffTile::hasUnset); } public void unsetAll() { tileMap.values().forEach(TiffTile::unsetAll); } public void cropAllUnset() { tileMap.values().forEach(TiffTile::cropUnsetToMap); } public void clear(boolean clearDimensions) { tileMap.clear(); if (clearDimensions) { setDimensions(0, 0); // - exception if !resizable gridCountX = 0; gridCountY = 0; numberOfGridTiles = 0; // - note: this is the only way to reduce tileCountX/Y! } } public byte[] toInterleavedSamples(byte[] samples, int numberOfChannels, long numberOfPixels) { return toInterleaveOrSeparatedSamples(samples, numberOfChannels, numberOfPixels, true); } public byte[] toSeparatedSamples(byte[] samples, int numberOfChannels, long numberOfPixels) { return toInterleaveOrSeparatedSamples(samples, numberOfChannels, numberOfPixels, false); } @Override public String toString() { return (resizable ? "resizable " : "") + "map " + (resizable ? "?x?" : dimX + "x" + dimY) + "x" + numberOfChannels + " (" + alignedBitsPerSample + " bits) " + "of " + tileMap.size() + " TIFF tiles (grid " + gridCountX + "x" + gridCountY + ") at the image " + ifd; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TiffMap that = (TiffMap) o; return ifd == that.ifd && resizable == that.resizable && dimX == that.dimX && dimY == that.dimY && Objects.equals(tileMap, that.tileMap) && planarSeparated == that.planarSeparated && numberOfChannels == that.numberOfChannels && alignedBitsPerSample == that.alignedBitsPerSample && tileSizeX == that.tileSizeX && tileSizeY == that.tileSizeY && tileSizeInBytes == that.tileSizeInBytes; // - Important! Comparing references to IFD, not content! // Moreover, it makes sense to compare fields, calculated ON THE BASE of IFD: // they may change as a result of changing the content of the same IFD. } @Override public int hashCode() { return Objects.hash(System.identityHashCode(ifd), tileMap, resizable, dimX, dimY); } public static byte[] toInterleavedBytes( byte[] bytes, int numberOfChannels, int bytesPerSample, long numberOfPixels) { Objects.requireNonNull(bytes, "Null bytes"); final int size = checkSizes(numberOfChannels, bytesPerSample, numberOfPixels); // - exception usually should not occur: this function is typically called after analyzing IFD if (bytes.length < size) { throw new IllegalArgumentException("Too short samples array: " + bytes.length + " < " + size); } if (numberOfChannels == 1) { return bytes; } final int bandSize = bytesPerSample * (int) numberOfPixels; final byte[] interleavedBytes = new byte[size]; if (bytesPerSample == 1) { Matrix mI = Matrix.as(interleavedBytes, numberOfChannels, numberOfPixels); Matrix mS = Matrix.as(bytes, numberOfPixels, numberOfChannels); Matrices.interleave(null, mI, mS.asLayers()); // if (numberOfChannels == 3) { // quickInterleave3(interleavedBytes, bytes, bandSize); // } else { // for (int i = 0, disp = 0; i < bandSize; i++) { // for (int bandDisp = i, j = 0; j < numberOfChannels; j++, bandDisp += bandSize) { // note: we must check j, not bandDisp, because "bandDisp += bandSize" can lead to overflow // interleavedBytes[disp++] = bytes[bandDisp]; // } // } // } } else { for (int i = 0, disp = 0; i < bandSize; i += bytesPerSample) { for (int bandDisp = i, j = 0; j < numberOfChannels; j++, bandDisp += bandSize) { for (int k = 0; k < bytesPerSample; k++) { interleavedBytes[disp++] = bytes[bandDisp + k]; } } } } return interleavedBytes; } static byte[] toSeparatedBytes( byte[] bytes, int numberOfChannels, int bytesPerSample, long numberOfPixels) { Objects.requireNonNull(bytes, "Null bytes"); final int size = checkSizes(numberOfChannels, bytesPerSample, numberOfPixels); // - exception usually should not occur: this function is typically called after analyzing IFD if (bytes.length < size) { throw new IllegalArgumentException("Too short samples array: " + bytes.length + " < " + size); } if (numberOfChannels == 1) { return bytes; } final int bandSize = bytesPerSample * (int) numberOfPixels; final byte[] separatedBytes = new byte[size]; if (bytesPerSample == 1) { final Matrix mI = Matrix.as(bytes, numberOfChannels, numberOfPixels); final Matrix mS = Matrix.as(separatedBytes, numberOfPixels, numberOfChannels); Matrices.separate(null, mS.asLayers(), mI); } else { for (int i = 0, disp = 0; i < bandSize; i += bytesPerSample) { for (int bandDisp = i, j = 0; j < numberOfChannels; j++, bandDisp += bandSize) { for (int k = 0; k < bytesPerSample; k++) { separatedBytes[bandDisp + k] = bytes[disp++]; } } } } return separatedBytes; } byte[] toInterleaveOrSeparatedSamples( byte[] samples, int numberOfChannels, long numberOfPixels, boolean interleave) { Objects.requireNonNull(samples, "Null samples"); if (numberOfPixels < 0) { throw new IllegalArgumentException("Negative numberOfPixels = " + numberOfPixels); } if (numberOfChannels == 1) { return samples; } if (!isWholeBytes()) { throw new AssertionError("Non-whole bytes are impossible in valid TiffMap with 1 channel"); } final int bytesPerSample = alignedBitsPerSample >>> 3; assert alignedBitsPerSample == bytesPerSample * 8 : "unaligned bitsPerSample impossible for whole bytes"; return interleave ? toInterleavedBytes(samples, numberOfChannels, bytesPerSample, numberOfPixels) : toSeparatedBytes(samples, numberOfChannels, bytesPerSample, numberOfPixels); } private static int checkSizes(int numberOfChannels, int bytesPerSample, long numberOfPixels) { TiffIFD.checkNumberOfChannels(numberOfChannels); TiffIFD.checkBitsPerSample(8L * (long) bytesPerSample); // - so, numberOfChannels * bytesPerSample is not too large value if (numberOfPixels < 0) { throw new IllegalArgumentException("Negative numberOfPixels = " + numberOfPixels); } long size; if (numberOfPixels > Integer.MAX_VALUE || (size = numberOfPixels * (long) numberOfChannels * (long) bytesPerSample) > Integer.MAX_VALUE) { throw new TooLargeArrayException("Too large number of pixels " + numberOfPixels + " (" + numberOfChannels + " samples/pixel, " + bytesPerSample + " bytes/sample): it requires > 2 GB to store"); } return (int) size; } private void setDimensions(int dimX, int dimY, boolean checkResizable) { if (checkResizable && !resizable) { throw new IllegalArgumentException("Cannot change dimensions of a non-resizable tile map"); } if (dimX < 0) { throw new IllegalArgumentException("Negative x-dimension: " + dimX); } if (dimY < 0) { throw new IllegalArgumentException("Negative y-dimension: " + dimY); } if ((long) dimX * (long) dimY > Long.MAX_VALUE / totalAlignedBitsPerPixel) { // - Very improbable! But we would like to be sure that 63-bit arithmetic // is enough to calculate total size of the map in BYTES. throw new TooLargeArrayException("Extremely large image sizes " + dimX + "x" + dimY + ", " + totalAlignedBitsPerPixel + " bits/pixel: total number of bits is greater than 2^63-1 (!)"); } final int tileCountX = (int) ((long) dimX + (long) tileSizeX - 1) / tileSizeX; final int tileCountY = (int) ((long) dimY + (long) tileSizeY - 1) / tileSizeY; expandGrid(tileCountX, tileCountY, checkResizable); this.dimX = dimX; this.dimY = dimY; } private void expandGrid(int newMinimalTileCountX, int newMinimalTileCountY, boolean checkResizable) { if (newMinimalTileCountX < 0) { throw new IllegalArgumentException("Negative new minimal tiles x-count: " + newMinimalTileCountX); } if (newMinimalTileCountY < 0) { throw new IllegalArgumentException("Negative new minimal tiles y-count: " + newMinimalTileCountY); } if (newMinimalTileCountX <= gridCountX && newMinimalTileCountY <= gridCountY) { return; // - even in a case !resizable } if (checkResizable && !resizable) { throw new IllegalArgumentException("Cannot expand tile counts in a non-resizable tile map"); } final int tileCountX = Math.max(this.gridCountX, newMinimalTileCountX); final int tileCountY = Math.max(this.gridCountY, newMinimalTileCountY); if ((long) tileCountX * (long) tileCountY > Integer.MAX_VALUE / numberOfSeparatedPlanes) { throw new IllegalArgumentException("Too large number of tiles/strips: " + (numberOfSeparatedPlanes > 1 ? numberOfSeparatedPlanes + " separated planes * " : "") + tileCountX + " * " + tileCountY + " > 2^31-1"); } this.gridCountX = tileCountX; this.gridCountY = tileCountY; this.numberOfGridTiles = tileCountX * tileCountY * numberOfSeparatedPlanes; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy