net.algart.matrices.tiff.tiles.TiffTile Maven / Gradle / Ivy
Show all versions of algart-tiff Show documentation
/*
* 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.PackedBitArraysPer8;
import net.algart.arrays.TooLargeArrayException;
import net.algart.math.IRectangularArea;
import net.algart.matrices.tiff.*;
import net.algart.matrices.tiff.data.TiffUnusualPrecisions;
import java.nio.ByteOrder;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* TIFF tile: container for samples (encoded or decoded) with given {@link TiffTileIndex index}.
*
* @author Denial Alievsky
*/
public final class TiffTile {
private static final boolean DISABLE_CROPPING = false;
// - You may set this to true for creating little invalid stripped TIFF, where the last strip is not cropped.
// Normal value is false.
private final TiffMap map;
private final int samplesPerPixel;
private final int bitsPerSample;
private final int bitsPerPixel;
private final ByteOrder byteOrder;
private final TiffTileIndex index;
private int sizeX;
private int sizeY;
private int sizeInPixels;
private int sizeInBytes;
private int sizeInBits;
private int rowSizeInBytes;
private boolean interleaved = false;
private boolean encoded = false;
private byte[] data = null;
private long storedDataFileOffset = -1;
private int storedDataLength = 0;
private int estimatedNumberOfPixels = 0;
private Queue unsetArea = null;
// - null value marks that all is empty;
// it helps to defer actual subtracting until the moment when we know correct tile sizes
/**
* Creates new tile with given index.
*
* Note: created tile may lie outside its map, it is not prohibited.
* This condition is checked not here, but in {@link TiffMap#put(TiffTile)} and other {@link TiffMap} methods.
*
* @param index tile index.
*/
public TiffTile(TiffTileIndex index) {
this.index = Objects.requireNonNull(index, "Null tile index");
this.map = index.map();
this.samplesPerPixel = map.tileSamplesPerPixel();
this.bitsPerSample = map.alignedBitsPerSample();
this.bitsPerPixel = map.tileAlignedBitsPerPixel();
assert this.bitsPerPixel == samplesPerPixel * bitsPerSample;
this.byteOrder = map.ifd().getByteOrder();
assert index.ifd() == map.ifd() : "index retrieved ifd from its tile map!";
setSizes(map.tileSizeX(), map.tileSizeY());
}
public TiffMap map() {
return map;
}
public TiffIFD ifd() {
return map.ifd();
}
public TiffTileIndex index() {
return index;
}
public boolean isPlanarSeparated() {
return map.isPlanarSeparated();
}
public int samplesPerPixel() {
return samplesPerPixel;
}
public boolean isBinary() {
return map.isBinary();
}
public boolean isWholeBytes() {
return map.isWholeBytes();
}
/**
* Returns number of bits per each sample of this tile.
* Always equal to {@link #map()}.{@link TiffMap#alignedBitsPerSample() bitsPerSample()}.
* Note that this number is always the same for all channels and is always divided by 8,
* excepting the only case 1-channel 1-bit pixels.
*
*
Warning: this number can be smaller than the result of the same method of {@link #sampleType()}
* object! This is possible for unusual precisions, like 24-bit integer or 16/24-bit float samples.
* See {@link TiffReader#setAutoUnpackUnusualPrecisions(boolean)} and
* {@link TiffReader#completeDecoding(TiffTile)} methods.
*
* Note that you can see unpacked data only in two variants:
*
* - fully unpacked, when the number of bits per sample corresponds to one of variants of
* {@link TiffSampleType};
* - unpacked to unusual precision: 3-byte samples (17..24 bits/sample),
* 16- or 24-bit floating-point formats.
*
* Inside this class, you are always dealing with the variant #2 (excepting call of
* {@link #unpackUnusualDecodedData()} method). The {@link TiffReader} class
* usually returns data in the option #1, unless you disable this by
* {@link TiffReader#setAutoUnpackUnusualPrecisions(boolean)} method.
* The {@link TiffWriter} class always takes the data in the variant #1.
*
* @return number of bits per each sample (1 channel for 1 pixel).
* @see TiffMap#bitsPerUnpackedSample()
* @see TiffTile#bitsPerSample()
*/
public int bitsPerSample() {
return bitsPerSample;
}
public OptionalInt bytesPerSample() {
return map.bytesPerSample();
}
public int bitsPerPixel() {
return bitsPerPixel;
}
public OptionalInt bytesPerPixel() {
OptionalInt opt = bytesPerSample();
return opt.isPresent() ? OptionalInt.of(opt.getAsInt() * samplesPerPixel) : OptionalInt.empty();
}
public ByteOrder byteOrder() {
return byteOrder;
}
public TiffSampleType sampleType() {
return map.sampleType();
}
public Class> elementType() {
return map.elementType();
}
public int getSizeX() {
return sizeX;
}
public TiffTile setSizeX(int sizeX) {
return setSizes(sizeX, this.sizeY);
}
public int getSizeY() {
return sizeY;
}
public TiffTile setSizeY(int sizeY) {
return setSizes(this.sizeX, sizeY);
}
/**
* Sets the sizes of this tile.
*
* These are purely informational properties, not affecting processing the stored data
* and supported for additional convenience of usage this object.
*
*
There is a guarantee that the total {@link #getSizeInBits() number of bits},
* required to store sizeX*sizeY
pixels, will be ≤Integer.MAX_VALUE
.
* Moreover, there is a guarantee that the same is true for the nearest integer ≥sizeX
,
* whisi is divisible by 8, i.e. (sizeX + 7) / 8 * 8
):
*
* ((sizeX + 7) / 8 * 8) * sizeY * {@link #getSizeInBits()} ≤ Integer.MAX_VALUE
*
* If the specified sizes are too large to fit this limitation, * this method throws {@link TooLargeArrayException}.
* * @param sizeX the tile width; must be positive. * @param sizeY the tile height; must be positive. * @return a reference to this object. * @throws IllegalArgumentException if the specified sizes are negative, zero, or too large. */ public TiffTile setSizes(int sizeX, int sizeY) { if (sizeX <= 0) { throw new IllegalArgumentException("Zero or negative tile x-size: " + sizeX); } if (sizeY <= 0) { throw new IllegalArgumentException("Zero or negative tile y-size: " + sizeY); } // - zero sizes are disabled, in particular, to provide correct IRectangularArea processing final long alignedSizeX = ((long) sizeX + 7L) & ~7L; // - for checking sizes only! This value has no sense for whole-byte format! assert alignedSizeX >= sizeX && alignedSizeX >> 3 == (sizeX + 7) >>> 3; Supplierbyte[]
data array, enough to store all tile pixels.
*/
public int getSizeInBytes() {
return sizeInBytes;
}
/**
* Returns ({@link #getSizeX()} * {@link #bitsPerPixel()} + 7) / 8:
* size of each line in bytes.
* (According the TIFF format, lines should be aligned to an integer number of bytes.)
*
* @return the number of bytes in each horizontal row of pixels.
*/
public int getRowSizeInBytes() {
return rowSizeInBytes;
}
/**
* Returns {@link #getSizeInPixels()} * {@link #bitsPerPixel()}.
* There is a guarantee that this value is ≤231 and, so,
* can be represented by int
value.
*
* @return number of bits, necessary to store all tile pixels.
*/
public int getSizeInBits() {
return sizeInBits;
}
public IRectangularArea rectangle() {
return rectangleInTile(0, 0, sizeX, sizeY);
}
public IRectangularArea rectangleInTile(int fromXInTile, int fromYInTile, int sizeXInTile, int sizeYInTile) {
if (sizeXInTile <= 0) {
throw new IllegalArgumentException("Zero or negative sizeXInTile = " + sizeXInTile);
}
if (sizeYInTile <= 0) {
throw new IllegalArgumentException("Zero or negative sizeYInTile = " + sizeYInTile);
}
final long minX = (long) index.fromX() + (long) fromXInTile;
final long minY = (long) index.fromY() + (long) fromYInTile;
final long maxX = minX + (long) sizeXInTile - 1;
final long maxY = minY + (long) sizeYInTile - 1;
return IRectangularArea.valueOf(minX, minY, maxX, maxY);
}
public boolean isFullyInsideMap() {
return index.fromX() + sizeX <= map.dimX() && index.fromY() + sizeY <= map.dimY();
}
/**
* Equivalent to {@link #cropToMap(boolean) cropToMap(true)}.
*
* @return a reference to this object.
* @throws IllegalStateException if this tile is completely outside map dimensions.
*/
public TiffTile cropToMap() {
return cropToMap(true);
}
/**
* Reduces sizes of this tile so that it will completely lie inside map dimensions.
*
* This operation can be useful for
* {@link net.algart.matrices.tiff.tiles.TiffMap.TilingMode#STRIPS stripped TIFF image},
* especially while writing.
* But there are no reasons to call this for
* {@link net.algart.matrices.tiff.tiles.TiffMap.TilingMode#TILE_GRID tiled image}.
* For tiled image, TIFF file usually contains full-size encoded tiles even on image boundary;
* they should be cropped after decoding by external means. You can disable attempt to reduce
* tile in tiled image by passing By default, the data are considered to be not interleaved, in other words, {@link #isSeparated()
* separated}. Methods, reading and decoding the tile from TIFF, always return separated tile.
* Methods, encoding the file for writing to TIFF, may work both with interleaved tiles,
* but it should be explicitly declared, like in
* {@link TiffWriter#setAutoInterleaveSource(boolean)} method (with This is purely informational property, not affecting processing the stored data
* by methods of this object and supported for additional convenience of usage this class. In addition to the standard precisions provided by {@link TiffSampleType}, samples can be represented
* in the following unusual precisions: This method is necessary rarely: {@link #getDecodedData()} is enough for most needs.
*
* @return unpacked data.
* @see TiffUnusualPrecisions#unpackUnusualPrecisions(byte[], TiffIFD, int, long, boolean)
* @see #bitsPerSample()
* @see TiffMap#bitsPerUnpackedSample()
*/
public byte[] unpackUnusualDecodedData() {
byte[] samples = getDecodedData();
try {
samples = TiffUnusualPrecisions.unpackUnusualPrecisions(
samples, ifd(), samplesPerPixel, sizeInPixels, true);
} catch (TiffException e) {
throw new IllegalStateException("Illegal IFD inside the tile map", e);
}
return samples;
}
public TiffTile setDecodedData(byte[] data) {
return setData(data, false, true);
}
public TiffTile setPartiallyDecodedData(byte[] data) {
return setData(data, false, false);
}
public TiffTile free() {
this.data = null;
this.interleaved = false;
// - before possible setting new decoded data, we should restore default status interleaved = false
this.encoded = false;
// - method checkReadyForNewDecodedData() require that the tile should not be declared as encoded
// Note: we should not clear information about stored data file range, because
// it will be used even after flushing data to disk (with freeing this tile)
return this;
}
/**
* Return the length of the last non-null {@link #getData() data array}, stored in this tile,
* or 0 after creating this object.
*
* Immediately after reading tile from file, as well as
* immediately before/after writing it into file, this method returns the number of encoded bytes,
* which are actually stored in the file for this tile.
*
* Note: {@link #free()} method does not change this value! So, you can know the stored data size
* even after freeing data inside this object.
*
* @return the length of the last non-null data array, which was stored in this object.
*/
public int getStoredDataLength() {
return storedDataLength;
}
public boolean isStoredInFile() {
return storedDataFileOffset >= 0;
}
public long getStoredDataFileOffset() {
checkStoredFilePosition();
return storedDataFileOffset;
}
public TiffTile setStoredDataFileOffset(long storedDataFileOffset) {
if (storedDataFileOffset < 0) {
throw new IllegalArgumentException("Negative storedDataFileOffset = " + storedDataFileOffset);
}
this.storedDataFileOffset = storedDataFileOffset;
return this;
}
public TiffTile removeStoredDataFileOffset() {
storedDataFileOffset = -1;
return this;
}
public TiffTile setStoredDataFileRange(long storedDataFileOffset, int storedDataLength) {
if (storedDataFileOffset < 0) {
throw new IllegalArgumentException("Negative storedDataFileOffset = " + storedDataFileOffset);
}
if (storedDataLength < 0) {
throw new IllegalArgumentException("Negative storedDataLength = " + storedDataLength);
}
this.storedDataLength = storedDataLength;
this.storedDataFileOffset = storedDataFileOffset;
return this;
}
public TiffTile copyStoredDataFileRange(TiffTile other) {
Objects.requireNonNull(other, "Null other tile");
this.storedDataLength = other.storedDataLength;
this.storedDataFileOffset = other.storedDataFileOffset;
return this;
}
/**
* Returns the estimated number of pixels, that can be stored in the {@link #getData() data array} in this tile
* in the decoded form, or 0 after creating this object.
*
* Note: that this method throws If the data are not {@link #isEncoded() encoded}, the following equality is usually true: The only possible exception is when you sets the data with help of
* {@link #setPartiallyDecodedData(byte[])} (when data are almost decoded, but, maybe, some additional
* unpacking is necessary). This condition is always checked inside {@link #setDecodedData(byte[])} method.
* You may also check this directly by {@link #checkDataLengthAlignment()} method. Warning: the estimated number of pixels, returned by this method, may differ from the tile
* size {@link #getSizeX()} * {@link #getSizeY()}! Usually it occurs after decoding encoded tile, when the
* decoding method returns only sequence of pixels and does not return information about the size.
* In this situation, the external code sets the tile sizes from a priory information, but the decoded tile
* may be actually less; for example, it takes place for the last strip in non-tiled TIFF format.
* You can check, does the actual number of stored pixels equal to tile size, via
* {@link #checkStoredNumberOfPixels()} method.
*
* @return the number of pixels in the last non-null data array, which was stored in this object.
*/
@SuppressWarnings("JavadocDeclaration")
public int getEstimatedNumberOfPixels() {
// - This method is private, because it does not return exact number of pixels for 1-bit channels
// and should be used carefully.
// Maybe, in future we will support better field "numberOfPixels", always correct also for 1-bit channels,
// then this method will become public.
if (isEncoded()) {
throw new IllegalStateException("TIFF tile data are not decoded, number of pixels is unknown: " + this);
}
return estimatedNumberOfPixels;
}
/**
* Checks whether the length of the data array in bytes is correctly aligned: the data contains an integer number
* of whole pixels. If it is not so, throws Note that unaligned length is impossible for 1 bit/sample, because we support only 1-channel images
* with 1 bit/sample. This method must not be called for {@link #isEncoded() encoded} tile.
*
* This method is called after reading and complete decoding any tile of the TIFF file.
*/
public void checkDataLengthAlignment() {
checkEmpty();
final int estimatedNumberOfPixels = getEstimatedNumberOfPixels();
// - IllegalStateException if encoded
assert !encoded;
final int expectedNumberOfBytes = (estimatedNumberOfPixels * bitsPerPixel + 7) >>> 3;
if (expectedNumberOfBytes != data.length) {
assert bitsPerPixel != 1 : "unaligned estimatedNumberOfPixels cannot appear for 1 bit/pixel";
// - in current version it means that we have whole bytes: bitsPerPixel = 8*K;
// see assertions in setData for a case of bitsPerPixel == 1
throw new IllegalStateException("Unaligned length of decoded data " + data.length +
": it is not equal to ceil(number of pixels * bits per pixel / 8) = ceil(" +
estimatedNumberOfPixels + " * " + bitsPerPixel + " / 8) = " + expectedNumberOfBytes +
", as if the last pixel is stored \"partially\"");
}
}
/**
* Checks whether the length of the data array length matches the declared tile sizes {@link #getSizeInBytes()}.
* If it is not so, throws This method must not be called for {@link #isEncoded() encoded} tile.
*
* This method is called before encoding and writing any tile to the TIFF file.
*/
public TiffTile checkStoredNumberOfPixels() {
checkEmpty();
final int estimatedNumberOfPixels = getEstimatedNumberOfPixels();
// - necessary to throw IllegalStateException if encoded
assert !encoded;
if (data.length != storedDataLength) {
throw new IllegalStateException("Stored data length field " + storedDataLength +
" is set to value, different than the actual data length " + data.length);
}
if (data.length != sizeInBytes) {
throw new IllegalStateException("Number of stored pixels " + estimatedNumberOfPixels +
" does not match tile sizes " + sizeX + "x" + sizeY + " = " + sizeInPixels);
}
final int dataLength = (estimatedNumberOfPixels * bitsPerPixel + 7) >>> 3;
if (dataLength != data.length) {
// - this check must be AFTER possible throwing IllegalStateException:
// in another case, we will throw AssertionError instead of correct IllegalStateException
throw new AssertionError("Invalid estimatedNumberOfPixels " + estimatedNumberOfPixels +
": does not match data.length = " + data.length);
}
// - in other words, checkDataLengthAlignment() must be unnecessary:
// if bitsPerPixel = 1, unaligned data is impossible;
// if bitsPerPixel = 8*i, storedDataLength = sizeInBytes is divided by bytesPerPixel
return this;
}
public TiffTile adjustNumberOfPixels(boolean allowDecreasing) {
return changeNumberOfPixels(sizeInPixels, allowDecreasing);
}
public TiffTile changeNumberOfPixels(long newNumberOfPixels, boolean allowDecreasing) {
if (newNumberOfPixels < 0) {
throw new IllegalArgumentException("Negative new number of pixels = " + newNumberOfPixels);
}
final long newNumberOfBits = newNumberOfPixels * (long) bitsPerPixel;
if (newNumberOfPixels > Integer.MAX_VALUE || newNumberOfBits > Integer.MAX_VALUE) {
// - first check is necessary for a case of overflow in newNumberOfBits
throw new IllegalArgumentException("Too large requested number of pixels in tile: " + newNumberOfPixels +
" pixels * " + samplesPerPixel + " samples/pixel * " + bitsPerSample + " bits/sample >= " +
"2^31 bits (256 MB), such large tiles are not supported");
}
final int newLength = (int) ((newNumberOfBits + 7) >>> 3);
final byte[] data = getDecodedData();
// - performs all necessary state checks
if (newLength == data.length) {
// - nothing to change: data has a correct length
return this;
}
// The following code is executed rarely (excepting 1-bit case), for example, while reading a stripped TIFF,
// where the last strip is not cropped correctly
// (see resources\demo\images\tiff\algart\jpeg_rgb_stripped_with_uncropped_last_strip.tiff)
if (newLength < data.length && !allowDecreasing) {
throw new IllegalArgumentException("The new number of pixels " + newNumberOfPixels +
" is less than actually stored; this is not allowed: data may be lost");
}
byte[] newData;
if (interleaved || samplesPerPixel == 1) {
// Note: for interleaved tile we ALSO do not need estimatedNumberOfPixels.
// In future versions this can allow us to implement multichannel 1-bit images,
// but ONLY IF they are always stored interleaved (as for Deflate/LZW and similar "old" formats).
newData = Arrays.copyOf(data, newLength);
} else {
if ((bitsPerPixel & 7) != 0) {
throw new AssertionError("Unsupported bits per pixel " + bitsPerPixel + " for " +
samplesPerPixel + " channel (more than one)");
// - for example, 1-bit RGB is not supported:
// we cannot calculate number of pixels to separate or interleave them
}
newData = new byte[newLength];
// - zero-filled by Java
final long size = (long) getEstimatedNumberOfPixels() * bitsPerSample;
// bitsPerPixels is multiply of 8, so, estimatedNumberOfPixels is the actual number of stored pixels
final long newSize = newNumberOfPixels * bitsPerSample;
final long sizeToCopy = Math.min(size, newSize);
for (long s = 0, disp = 0, newDisp = 0; s < samplesPerPixel; s++, disp += size, newDisp += newSize) {
PackedBitArraysPer8.copyBitsNoSync(newData, newDisp, data, disp, sizeToCopy);
// - actually this is equivalent to System.arraycopy,
// but we use copyBitsNoSync for possible future version, if they will allow multichannel 1-bit images
}
}
return setDecodedData(newData);
}
public TiffTile interleaveSamplesIfNecessary() {
checkEmpty();
if (!isInterleaved()) {
interleaveSamples();
}
return this;
}
public TiffTile separateSamplesIfNecessary() {
checkEmpty();
if (isInterleaved()) {
separateSamples();
}
return this;
}
public TiffTile interleaveSamples() {
byte[] data = getDecodedData();
if (isInterleaved()) {
throw new IllegalStateException("TIFF tile is already interleaved: " + this);
}
data = map.toInterleavedSamples(data, samplesPerPixel, getEstimatedNumberOfPixels());
// - getEstimatedNumberOfPixels can return invalid value only for 1 channel, when this argument is not used
setInterleaved(true);
setDecodedData(data);
return this;
}
public TiffTile separateSamples() {
byte[] data = getDecodedData();
if (!isInterleaved()) {
throw new IllegalStateException("TIFF tile is already separated: " + this);
}
data = map.toSeparatedSamples(data, samplesPerPixel, getEstimatedNumberOfPixels());
// - getEstimatedNumberOfPixels can return invalid value only for 1 channel, when this argument is not used
setInterleaved(false);
setDecodedData(data);
return this;
}
@Override
public String toString() {
return "TIFF " +
(isEmpty() ? "(empty) " : "") +
(encoded ? "encoded" : "non-encoded") +
(interleaved ? " interleaved" : "") +
" tile" +
(isEmpty() ?
", " + sizeX + "x" + sizeY + "x" + samplesPerPixel :
", actual sizes " + sizeX + "x" + sizeY + "x" + samplesPerPixel + " (" +
storedDataLength + " bytes)" +
(isCompleted() ? ", completed" : ", partial")) +
", " + bitsPerSample + " bits/sample" +
", index " + index +
(isStoredInFile() ? " at file offset " + storedDataFileOffset : "");
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TiffTile tiffTile = (TiffTile) o;
return sizeX == tiffTile.sizeX && sizeY == tiffTile.sizeY &&
interleaved == tiffTile.interleaved && encoded == tiffTile.encoded &&
samplesPerPixel == tiffTile.samplesPerPixel && bitsPerSample == tiffTile.bitsPerSample &&
storedDataFileOffset == tiffTile.storedDataFileOffset &&
storedDataLength == tiffTile.storedDataLength &&
Objects.equals(index, tiffTile.index) &&
Arrays.equals(data, tiffTile.data);
// Note: doesn't check "map" to avoid infinite recursion!
}
@Override
public int hashCode() {
int result = Objects.hash(index, sizeX, sizeY,
interleaved, encoded, storedDataFileOffset, storedDataLength);
result = 31 * result + Arrays.hashCode(data);
return result;
// Note: doesn't check this.map to avoid infinite recursion!
}
private TiffTile setData(byte[] data, boolean encoded, boolean checkAligned) {
Objects.requireNonNull(data, "Null " + (encoded ? "encoded" : "decoded") + " data");
final long numberOfBits = 8L * (long) data.length;
final long numberOfPixels = numberOfBits / bitsPerPixel;
if (bitsPerPixel > 1) {
// - if it is 1, data cannot be unaligned (X % 1 == 0 always)
if ((bitsPerPixel & 7) != 0) {
throw new AssertionError("Unsupported bits per pixel " + bitsPerPixel);
// - for example, 1-bit RGB is not supported:
// we cannot calculate number of pixels to separate or interleave them
}
final int bytesPerPixel = bitsPerPixel >>> 3;
assert numberOfPixels == data.length / bytesPerPixel;
if (checkAligned && !encoded && numberOfPixels * bytesPerPixel != data.length) {
throw new IllegalArgumentException("Invalid length of decoded data " + data.length +
" bytes, or " + numberOfBits + " bits: not a multiple of the bits-per-pixel " +
bitsPerPixel + " = " + samplesPerPixel + " * " + bitsPerSample +
" (channels per pixel * bits per channel sample), " +
"as if the last pixel is stored \"partially\"");
}
} else {
assert bitsPerPixel == 1 : "zero or negative bitsPerPixel = " + bitsPerPixel;
final long expectedNumberOfBytes = (numberOfPixels + 7) >>> 3;
assert expectedNumberOfBytes == data.length;
// - in other words, the condition, required by checkAligned argument, is always fulfilled
}
if (numberOfPixels > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Cannot store " + numberOfPixels +
" pixels: very large TIFF tiles >= 2^31 pixels are not supported");
}
this.data = data;
this.storedDataLength = data.length;
this.estimatedNumberOfPixels = (int) numberOfPixels;
this.encoded = encoded;
if (!encoded) {
removeStoredDataFileOffset();
// - data file offset has no sense for decoded data
}
return this;
}
private void initializeEmptyArea() {
if (unsetArea == null) {
unsetArea = new LinkedList<>();
unsetArea.add(rectangle());
}
}
private void checkEmpty() {
if (data == null) {
throw new IllegalStateException("TIFF tile is still not filled by any data: " + this);
}
}
private void checkOutsideMap() {
if (index.fromX() >= map.dimX() || index.fromY() >= map.dimY()) {
throw new IllegalStateException("Tile is fully outside the map dimensions " +
map.dimX() + "x" + map.dimY() + ": " + this);
}
}
private void checkStoredFilePosition() {
if (storedDataFileOffset < 0) {
throw new IllegalStateException("File offset of the TIFF tile is not set yet: " + this);
}
}
}
strippedOnly=true
.
*
* @param strippedOnly if true
, this function will not do anything when the map
* is a {@link net.algart.matrices.tiff.tiles.TiffMap.TilingMode#TILE_GRID tiled image}.
* While using for reading/writing TIFF files,
* this argument usually should be true
.
* @return a reference to this object.
* @throws IllegalStateException if this tile is completely outside map dimensions.
*/
public TiffTile cropToMap(boolean strippedOnly) {
checkOutsideMap();
if (DISABLE_CROPPING || (strippedOnly && map.getTilingMode().isTileGrid())) {
return this;
} else {
return setSizes(Math.min(sizeX, map.dimX() - index.fromX()), Math.min(sizeY, map.dimY() - index.fromY()));
}
}
public Collectiontrue
>, if the stored pixel samples (as supposed) are interleaved, like RGBRGB...,
* or false
if not (RRR...GGG...BBB...).
* It doesn't matter in a case of monochrome images and in a case of {@link #isEncoded() encoded} data.
* Default value is false
.
*
* false
argument).K%8 ≠ 0
, they are automatically
* unpacked into ⌈K/8⌉*8
bit integers while decoding.
*
*
*
*
* @return unpacked data.
* @throws IllegalStateException if the tile is {@link #isEncoded() encoded}.
* @see #unpackUnusualDecodedData()
*/
public byte[] getDecodedData() {
checkEmpty();
if (isEncoded()) {
throw new IllegalStateException("TIFF tile data are not decoded and cannot be retrieved: " + this);
}
return data;
}
/**
* Gets the decoded data with unpacking non-usual precisions: 16/24-bit floating points data
* and any 3-byte/sample integer data. The same operations are performed by
* {@link TiffReader} automatically
* if the {@link TiffReader#setAutoUnpackUnusualPrecisions(boolean)} mode is set.
*
* IllegalStateException
if the data are
* {@link #isEncoded() encoded}, for example, immediately after reading tile from file.
* If the tile is {@link #isEmpty() empty} (no data),
* the exception is not thrown, though usually there is no sense to call this method in this situation.{@link #getStoredDataLength()} == ({@link #getEstimatedNumberOfPixels()} * {@link
* #bitsPerPixel()} + 7) / 8
*
* IllegalStateException
.
*
* IllegalStateException
.
*
*