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

net.algart.matrices.tiff.compatibility.TiffParser Maven / Gradle / Ivy

There is a newer version: 1.3.6
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.compatibility;

import io.scif.FormatException;
import io.scif.SCIFIO;
import io.scif.codec.CodecOptions;
import io.scif.common.Constants;
import io.scif.enumeration.EnumException;
import io.scif.formats.tiff.*;
import net.algart.arrays.PackedBitArraysPer8;
import net.algart.matrices.tiff.TiffException;
import net.algart.matrices.tiff.TiffIFD;
import net.algart.matrices.tiff.TiffReader;
import net.algart.matrices.tiff.codecs.TiffCodec;
import net.algart.matrices.tiff.tags.TagPhotometricInterpretation;
import net.algart.matrices.tiff.tags.TagRational;
import net.algart.matrices.tiff.tiles.TiffMap;
import net.algart.matrices.tiff.tiles.TiffTile;
import net.algart.matrices.tiff.tiles.TiffTileIndex;
import org.scijava.Context;
import org.scijava.io.handle.DataHandle;
import org.scijava.io.handle.DataHandleService;
import org.scijava.io.location.FileLocation;
import org.scijava.io.location.Location;
import org.scijava.log.LogService;
import org.scijava.util.IntRect;

import java.io.IOException;
import java.nio.file.Path;
import java.util.*;

/**
 * Legacy version of {@link TiffReader} with some deprecated method.
 * Should be replaced with {@link TiffReader}.
 */

public class TiffParser extends TiffReader {
    /**
     * Whether 64-bit offsets are used for non-BigTIFF files.
     */
    private boolean fakeBigTiff = false;
    // - Probably support of this feature was implemented incorrectly.
    // See https://github.com/scifio/scifio/issues/514

    private boolean ycbcrCorrection = true;

    private boolean equalStrips = false;
    /** Codec options to be used when decoding compressed pixel data. */
    private CodecOptions codecOptions = CodecOptions.getDefaultOptions();

    private IFDList ifdList;
    private IFD firstIFD;

    private final SCIFIO scifio;
    private final LogService log;


    // This constructor is new for TiffParser: it is added for more convenience and
    // compatibility with TiffReader constructor
    public TiffParser(Context context, Path file) throws IOException {
        this(context, new FileLocation(file.toFile()));
    }


    public TiffParser(final Context context, final Location loc) {
        this(Objects.requireNonNull(context, "Null context"),
                context.getService(DataHandleService.class).create(loc));
    }

    /**
     * Constructs a new TIFF parser from the given input source.
     */
    @Deprecated
    public TiffParser(final Context context, final DataHandle in) {
        super(in, null);
        Objects.requireNonNull(context, "Null context");
        setContext(context);
        // Disable new features of TiffReader for compatibility:
        this.setAutoUnpackBitsToBytes(true);
        this.setAutoUnpackUnusualPrecisions(false);
        this.setCropTilesToImageBoundaries(false);
        this.setEnforceUseExternalCodec(true);
        this.setMissingTilesAllowed(true);
        // - This is an interesting undocumented feature: old TiffParser really supported missing tiles!
        this.scifio = new SCIFIO(context);
        this.log = scifio.log();
    }

    public static TiffIFD toTiffIFD(IFD ifd) {
        Objects.requireNonNull(ifd, "Null IFD");
        boolean bigTiff = false;
        try {
            bigTiff = ifd.isBigTiff();
        } catch (FormatException ignored) {
        }
        boolean littleEndian = false;
        try {
            littleEndian = ifd.isLittleEndian();
        } catch (FormatException ignored) {
        }
        final Map ifdEntries = new LinkedHashMap<>();
        for (Map.Entry entry : ifd.entrySet()) {
            final Integer key = entry.getKey();
            if (key.equals(IFD.LITTLE_ENDIAN) || key.equals(IFD.BIG_TIFF) || key.equals(IFD.REUSE)) {
                continue;
            }
            ifdEntries.put(key, entry.getValue());
        }
        return TiffIFD.valueOf(ifdEntries).setBigTiff(bigTiff).setLittleEndian(littleEndian);
    }

    public IFD toScifioIFD(TiffIFD ifd) {
        return toScifioIFD(ifd, log);
    }

    public static IFD toScifioIFD(TiffIFD ifd, LogService log) {
        IFD result = new IFD(log);
        result.putAll(ifd.map());
        result.put(IFD.LITTLE_ENDIAN, ifd.isLittleEndian());
        result.put(IFD.BIG_TIFF, ifd.isBigTiff());
        return result;
    }

    @Deprecated
    public DataHandle getStream() {
        return super.stream();
    }

    /**
     * Sets whether to assume that strips are of equal size.
     *
     * @param equalStrips Whether or not the strips are of equal size.
     */
    @Deprecated
    public TiffParser setAssumeEqualStrips(final boolean equalStrips) {
        this.equalStrips = equalStrips;
        return this;
    }


    /**
     * Sets whether 64-bit offsets are used for non-BigTIFF files.
     */
    @Deprecated
    public TiffParser setUse64BitOffsets(final boolean use64Bit) {
        fakeBigTiff = use64Bit;
        return this;
    }

    /**
     * Sets whether YCbCr color correction is allowed.
     * Note: this mode was not implemented well in SCIFIO 0.46.0:
     * please use TiffReader for correct processing this case.
     */
    public TiffParser setYCbCrCorrection(final boolean correctionAllowed) {
        ycbcrCorrection = correctionAllowed;
        return this;
    }

    /**
     * Sets whether IFD entries should be cached.
     * Use {@link #setCachingIFDs(boolean)} instead.
     */
    @Deprecated
    public void setDoCaching(final boolean cachingIFDs) {
        super.setCachingIFDs(cachingIFDs);
    }

    public void setCodecOptions(final CodecOptions codecOptions) {
        this.codecOptions = codecOptions;
        TiffCodec.Options options = getCodecOptions();
        options.setToScifioStyleOptions(codecOptions);
        // - not too important, but also does not create problems
        //noinspection resource
        setCodecOptions(options);
    }


    @Deprecated
    /** Use {@link #allIFDs()} instead. */
    public IFDList getIFDs() throws IOException {
        if (ifdList != null) return ifdList;
        final boolean doCaching = isCachingIFDs();

        final long[] offsets = getIFDOffsets();
        final IFDList ifds = new IFDList();

        for (final long offset : offsets) {
            final IFD ifd = getIFD(offset);
            if (ifd == null) continue;
            if (ifd.containsKey(IFD.IMAGE_WIDTH)) ifds.add(ifd);
            long[] subOffsets = null;
            try {
                if (!doCaching && ifd.containsKey(IFD.SUB_IFD)) {
                    fillInIFD(ifd);
                }
                subOffsets = ifd.getIFDLongArray(IFD.SUB_IFD);
            } catch (final FormatException e) {
            }
            if (subOffsets != null) {
                for (final long subOffset : subOffsets) {
                    final IFD sub = getIFD(subOffset);
                    if (sub != null) {
                        ifds.add(sub);
                    }
                }
            }
        }
        if (doCaching) ifdList = ifds;

        return ifds;
    }

    /**
     * Returns thumbnail IFDs.
     */
    public List allThumbnailIFDs() throws IOException, FormatException {
        return allIFDs().stream().filter(TiffIFD::isThumbnail).toList();
    }

    /**
     * Returns non-thumbnail IFDs.
     */
    public List allNonThumbnailIFDs() throws IOException, FormatException {
        return allIFDs().stream().filter(ifd -> !ifd.isThumbnail()).toList();
    }

    /**
     * Use {@link #allThumbnailIFDs()} instead.
     */
    @Deprecated
    public IFDList getThumbnailIFDs() throws IOException {
        final IFDList ifds = getIFDs();
        final IFDList thumbnails = new IFDList();
        for (final IFD ifd : ifds) {
            final Number subfile = (Number) ifd.getIFDValue(IFD.NEW_SUBFILE_TYPE);
            final int subfileType = subfile == null ? 0 : subfile.intValue();
            if (subfileType == 1) {
                thumbnails.add(ifd);
            }
        }
        return thumbnails;
    }

    /**
     * Use {@link #allNonThumbnailIFDs()} instead.
     */
    @Deprecated
    public IFDList getNonThumbnailIFDs() throws IOException {
        final IFDList ifds = getIFDs();
        final IFDList nonThumbs = new IFDList();
        for (final IFD ifd : ifds) {
            final Number subfile = (Number) ifd.getIFDValue(IFD.NEW_SUBFILE_TYPE);
            final int subfileType = subfile == null ? 0 : subfile.intValue();
            if (subfileType != 1 || ifds.size() <= 1) {
                nonThumbs.add(ifd);
            }
        }
        return nonThumbs;
    }

    /**
     * Use {@link #exifIFDs()} instead.
     */
    @Deprecated
    public IFDList getExifIFDs() throws IOException, FormatException {
        final IFDList ifds = getIFDs();
        final IFDList exif = new IFDList();
        for (final IFD ifd : ifds) {
            final long offset = ifd.getIFDLongValue(IFD.EXIF, 0);
            if (offset != 0) {
                final IFD exifIFD = getIFD(offset);
                if (exifIFD != null) {
                    exif.add(exifIFD);
                }
            }
        }
        return exif;
    }


    /**
     * Tests this stream to see if it represents a TIFF file.
     *
     * 

Deprecated. Use {@link #isValid()} instead. */ @Deprecated public boolean isValidHeader() { try { return checkHeader() != null; } catch (final IOException e) { return false; } } /** * Deprecated. Use {@link #isValid()} and {@link #isBigTiff()} instead. */ @Deprecated public Boolean checkHeader() throws IOException { final DataHandle in = stream(); if (in.length() < 4) return null; // byte order must be II or MM in.seek(0); final int endianOne = in.read(); final int endianTwo = in.read(); final boolean littleEndian = endianOne == TiffReader.FILE_PREFIX_LITTLE_ENDIAN && endianTwo == TiffReader.FILE_PREFIX_LITTLE_ENDIAN; // II final boolean bigEndian = endianOne == TiffReader.FILE_PREFIX_BIG_ENDIAN && endianTwo == TiffReader.FILE_PREFIX_BIG_ENDIAN; // MM if (!littleEndian && !bigEndian) return null; // check magic number (42) in.setLittleEndian(littleEndian); final short magic = in.readShort(); // bigTiff = magic == TiffConstants.BIG_TIFF_MAGIC_NUMBER; // - already set by the constructor if (magic != TiffReader.FILE_USUAL_MAGIC_NUMBER && magic != TiffReader.FILE_BIG_TIFF_MAGIC_NUMBER) { return null; } return littleEndian; } /** * Use {@link #readIFDOffsets()} instead. */ @Deprecated public long[] getIFDOffsets() throws IOException { final DataHandle in = stream(); final boolean bigTiff = isBigTiff(); final int bytesPerEntry = bigTiff ? TiffReader.BIG_TIFF_BYTES_PER_ENTRY : TiffReader.BYTES_PER_ENTRY; final Vector offsets = new Vector<>(); long offset = getFirstOffset(); while (offset > 0 && offset < in.length()) { in.seek(offset); offsets.add(offset); final int nEntries = bigTiff ? (int) in.readLong() : in .readUnsignedShort(); in.skipBytes(nEntries * bytesPerEntry); offset = getNextOffset(offset); } final long[] f = new long[offsets.size()]; for (int i = 0; i < f.length; i++) { f[i] = offsets.get(i).longValue(); } return f; } /** * Use {@link #readFirstIFDOffset()} instead, together with {@link #isValid()} check. */ @Deprecated public long getFirstOffset() throws IOException { final DataHandle in = stream(); final boolean bigTiff = isBigTiff(); final Boolean header = checkHeader(); if (header == null) return -1; if (bigTiff) in.skipBytes(4); return getNextOffset(0); } /** * Use {@link #firstIFD()} instead, together with {@link #isValid()} check. */ @Deprecated public IFD getFirstIFD() throws IOException { final boolean doCaching = isCachingIFDs(); if (firstIFD != null) return firstIFD; final long offset = getFirstOffset(); final IFD ifd = getIFD(offset); if (doCaching) firstIFD = ifd; return ifd; } /** * Use {@link #readIFDAt(long)} instead. */ @SuppressWarnings("removal") @Deprecated public IFD getIFD(long offset) throws IOException { final DataHandle in = stream(); final boolean bigTiff = isBigTiff(); final boolean doCaching = isCachingIFDs(); if (offset < 0 || offset >= in.length()) return null; //?? offset == -1 may be a signal about invalid file, //?? but why we ignore too large offset?? final IFD ifd = new IFD(log); // save little-endian flag to internal LITTLE_ENDIAN tag ifd.put(new Integer(IFD.LITTLE_ENDIAN), Boolean.valueOf(in .isLittleEndian())); ifd.put(new Integer(IFD.BIG_TIFF), Boolean.valueOf(bigTiff)); // read in directory entries for this IFD log.trace("getIFDs: seeking IFD at " + offset); in.seek(offset); final long numEntries = bigTiff ? in.readLong() : in.readUnsignedShort(); log.trace("getIFDs: " + numEntries + " directory entries to read"); if (numEntries == 0 || numEntries == 1) return ifd; //?? Why numEntries == 1 should lead to EMPTY IFD? final int bytesPerEntry = bigTiff ? TiffReader.BIG_TIFF_BYTES_PER_ENTRY : TiffReader.BYTES_PER_ENTRY; final int baseOffset = bigTiff ? 8 : 2; for (int i = 0; i < numEntries; i++) { in.seek(offset + baseOffset + bytesPerEntry * i); TiffIFDEntry entry = null; try { entry = readTiffIFDEntry(); } catch (final EnumException e) { log.debug("", e); } if (entry == null) break; //?? What does mean this solution? If we found ONE unknown tag, //?? we should ignore this and ALL FOLLOWING entries?? int count = entry.getValueCount(); final int tag = entry.getTag(); final long pointer = entry.getValueOffset(); final int bpe = entry.getType().getBytesPerElement(); if (count < 0 || bpe <= 0) { //?? How bpe may become <= 0? //?? valueCount is also checked inside readTiffIFDEntry and also cannot be < 0! // invalid data in.skipBytes(bytesPerEntry - 4 - (bigTiff ? 8 : 4)); continue; } Object value = null; final long inputLen = in.length(); if (count * bpe + pointer > inputLen) { final int oldCount = count; count = (int) ((inputLen - pointer) / bpe); log.trace("getIFDs: truncated " + (oldCount - count) + " array elements for tag " + tag); if (count < 0) count = oldCount; entry = new TiffIFDEntry(entry.getTag(), entry.getType(), count, entry .getValueOffset()); //?????? What does it mean? Some IFD array (maybe offsets or byte counts) is "truncated" //?? by the file end, and what do we do - just ignore its last part?? //?? It is especially strange that if the file length < value offset, we just... //?? restore previous count. } if (count < 0 || count > in.length()) break; //?? count < 0 is impossible here! //?? And comparing count with file length has no sense: count is a number of ENTRIES, not BYTES if (pointer != in.offset() && !doCaching) { value = entry; } else value = getIFDValue(entry); if (value != null && !ifd.containsKey(new Integer(tag))) { ifd.put(new Integer(tag), value); } } in.seek(offset + baseOffset + bytesPerEntry * numEntries); return ifd; } /** * Fill in IFD entries that are stored at an arbitrary offset. */ @Deprecated public void fillInIFD(final IFD ifd) throws IOException { final HashSet entries = new HashSet<>(); for (final Object key : ifd.keySet()) { if (ifd.get(key) instanceof TiffIFDEntry) { entries.add((TiffIFDEntry) ifd.get(key)); } } for (final TiffIFDEntry entry : entries) { if (entry.getValueCount() < 10 * 1024 * 1024 || entry.getTag() < 32768) { ifd.put(entry.getTag(), getIFDValue(entry)); } } } @Deprecated @SuppressWarnings("removal") public Object getIFDValue(final TiffIFDEntry entry) throws IOException { DataHandle in = stream(); final IFDType type = entry.getType(); final int count = entry.getValueCount(); final long offset = entry.getValueOffset(); // log.trace("Reading entry " + entry.getTag() + " from " + offset + // "; type=" + type + ", count=" + count); if (offset >= in.length()) { return null; } if (offset != in.offset()) { in.seek(offset); } if (type == IFDType.BYTE) { // 8-bit unsigned integer if (count == 1) return new Short(in.readByte()); final byte[] bytes = new byte[count]; in.readFully(bytes); // bytes are unsigned, so use shorts final short[] shorts = new short[count]; for (int j = 0; j < count; j++) shorts[j] = (short) (bytes[j] & 0xff); return shorts; } else if (type == IFDType.ASCII) { // 8-bit byte that contain a 7-bit ASCII code; // the last byte must be NUL (binary zero) final byte[] ascii = new byte[count]; in.read(ascii); // count number of null terminators int nullCount = 0; for (int j = 0; j < count; j++) { if (ascii[j] == 0 || j == count - 1) nullCount++; } // convert character array to array of strings final String[] strings = nullCount == 1 ? null : new String[nullCount]; String s = null; int c = 0, ndx = -1; for (int j = 0; j < count; j++) { if (ascii[j] == 0) { s = new String(ascii, ndx + 1, j - ndx - 1, Constants.ENCODING); ndx = j; } else if (j == count - 1) { // handle non-null-terminated strings s = new String(ascii, ndx + 1, j - ndx, Constants.ENCODING); } else s = null; if (strings != null && s != null) strings[c++] = s; } return strings == null ? (Object) s : strings; } else if (type == IFDType.SHORT) { // 16-bit (2-byte) unsigned integer if (count == 1) return new Integer(in.readUnsignedShort()); final int[] shorts = new int[count]; for (int j = 0; j < count; j++) { shorts[j] = in.readUnsignedShort(); } return shorts; } else if (type == IFDType.LONG || type == IFDType.IFD) { // 32-bit (4-byte) unsigned integer if (count == 1) return new Long(in.readInt()); final long[] longs = new long[count]; for (int j = 0; j < count; j++) { if (in.offset() + 4 <= in.length()) { longs[j] = in.readInt(); } } return longs; } else if (type == IFDType.LONG8 || type == IFDType.SLONG8 || type == IFDType.IFD8) { if (count == 1) return new Long(in.readLong()); long[] longs = null; if (equalStrips && (entry.getTag() == IFD.STRIP_BYTE_COUNTS || entry .getTag() == IFD.TILE_BYTE_COUNTS)) { longs = new long[1]; longs[0] = in.readLong(); } else if (equalStrips && (entry.getTag() == IFD.STRIP_OFFSETS || entry .getTag() == IFD.TILE_OFFSETS)) { final OnDemandLongArray offsets = new OnDemandLongArray(in); offsets.setSize(count); return offsets; } else { longs = new long[count]; for (int j = 0; j < count; j++) longs[j] = in.readLong(); } return longs; } else if (type == IFDType.RATIONAL || type == IFDType.SRATIONAL) { // Two LONGs or SLONGs: the first represents the numerator // of a fraction; the second, the denominator if (count == 1) return new TagRational(in.readInt(), in.readInt()); final TagRational[] rationals = new TagRational[count]; for (int j = 0; j < count; j++) { rationals[j] = new TagRational(in.readInt(), in.readInt()); } return rationals; } else if (type == IFDType.SBYTE || type == IFDType.UNDEFINED) { // SBYTE: An 8-bit signed (twos-complement) integer // UNDEFINED: An 8-bit byte that may contain anything, // depending on the definition of the field if (count == 1) return new Byte(in.readByte()); final byte[] sbytes = new byte[count]; in.read(sbytes); return sbytes; } else if (type == IFDType.SSHORT) { // A 16-bit (2-byte) signed (twos-complement) integer if (count == 1) return new Short(in.readShort()); final short[] sshorts = new short[count]; for (int j = 0; j < count; j++) sshorts[j] = in.readShort(); return sshorts; } else if (type == IFDType.SLONG) { // A 32-bit (4-byte) signed (twos-complement) integer if (count == 1) return new Integer(in.readInt()); final int[] slongs = new int[count]; for (int j = 0; j < count; j++) slongs[j] = in.readInt(); return slongs; } else if (type == IFDType.FLOAT) { // Single precision (4-byte) IEEE format if (count == 1) return new Float(in.readFloat()); final float[] floats = new float[count]; for (int j = 0; j < count; j++) floats[j] = in.readFloat(); return floats; } else if (type == IFDType.DOUBLE) { // Double precision (8-byte) IEEE format if (count == 1) return new Double(in.readDouble()); final double[] doubles = new double[count]; for (int j = 0; j < count; j++) { doubles[j] = in.readDouble(); } return doubles; } return null; } /** * Retrieve a given entry from the first IFD in the stream. * * @param tag the tag of the entry to be retrieved. * @return an object representing the entry's fields. * @throws IOException when there is an error accessing the stream. * @throws IllegalArgumentException when the tag number is unknown. */ // loci.formats.in.MetamorphReader. @Deprecated public TiffIFDEntry getFirstIFDEntry(final int tag) throws IOException { // Get the offset of the first IFD final long offset = getFirstOffset(); if (offset < 0) { return null; } final DataHandle in = stream(); final boolean bigTiff = isBigTiff(); // The following loosely resembles the logic of readIFD()... in.seek(offset); final long numEntries = bigTiff ? in.readLong() : in.readUnsignedShort(); for (int i = 0; i < numEntries; i++) { in.seek(offset + // The beginning of the IFD (bigTiff ? 8 : 2) + // The width of the initial numEntries field (bigTiff ? TiffReader.BIG_TIFF_BYTES_PER_ENTRY : TiffReader.BYTES_PER_ENTRY) * (long) i); final TiffIFDEntry entry = readTiffIFDEntry(); if (entry.getTag() == tag) { return entry; } } throw new IllegalArgumentException("Unknown tag: " + tag); } @Deprecated public byte[] getTile(final IFD ifd, byte[] buf, int row, final int col) throws IOException, FormatException { TiffMap map = TiffMap.newFixed(toTiffIFD(ifd)); int planeIndex = 0; if (map.isPlanarSeparated()) { planeIndex = row / map.gridCountY(); row = row % map.gridCountY(); // - in terms of the old TiffParser, "row" index already contains index of the plane } TiffTileIndex tileIndex = map.multiPlaneIndex(planeIndex, col, row); if (buf == null) { buf = new byte[map.tileSizeInBytes()]; } TiffTile tile = readTile(tileIndex); if (!tile.isEmpty()) { byte[] data = tile.getDecodedData(); System.arraycopy(data, 0, buf, 0, data.length); } return buf; } @Deprecated public byte[] getSamples(final IFD ifd, final byte[] buf) throws IOException, FormatException { final long width = ifd.getImageWidth(); final long length = ifd.getImageLength(); return getSamples(ifd, buf, 0, 0, width, length); } /** * This function is deprecated, because almost identical behaviour is implemented by * {@link #readSamples(TiffMap, int, int, int, int)}. */ @Deprecated public byte[] getSamples(final IFD ifd, final byte[] buf, final int x, final int y, final long width, final long height) throws FormatException, IOException { TiffReader.checkRequestedArea(x, y, width, height); byte[] result = readSamples(newMap(toTiffIFD(ifd)), x, y, (int) width, (int) height); if (result.length > buf.length) { throw new IllegalArgumentException( "Insufficient length of the result buf array: " + buf.length + " < necessary " + result.length + " bytes"); } System.arraycopy(result, 0, buf, 0, result.length); return buf; } /** * This function is deprecated, because it is almost not used - the only exception is * TrestleReader from OME BioFormats. */ @Deprecated public byte[] getSamples(final IFD ifd, final byte[] buf, final int x, final int y, final long width, final long height, final int overlapX, final int overlapY) throws IOException, FormatException { final DataHandle in = stream(); // get internal non-IFD entries in.setLittleEndian(ifd.isLittleEndian()); // get relevant IFD entries final int samplesPerPixel = ifd.getSamplesPerPixel(); final long tileWidth = ifd.getTileWidth(); long tileLength = ifd.getTileLength(); if (tileLength <= 0) { // log.trace("Tile length is " + tileLength + "; setting it to " + height); tileLength = height; } long numTileRows = ifd.getTilesPerColumn(); final long numTileCols = ifd.getTilesPerRow(); final PhotoInterp photoInterp = ifd.getPhotometricInterpretation(); final int planarConfig = ifd.getPlanarConfiguration(); final int pixel = ifd.getBytesPerSample()[0]; final int effectiveChannels = planarConfig == 2 ? 1 : samplesPerPixel; // if (log.isTrace()) { // ifd.printIFD(); // } if (width * height > Integer.MAX_VALUE) { throw new FormatException( "Sorry, ImageWidth x ImageLength > " + Integer.MAX_VALUE + " is not supported (" + width + " x " + height + ")"); } if (width * height * effectiveChannels * pixel > Integer.MAX_VALUE) { throw new FormatException( "Sorry, ImageWidth x ImageLength x " + "SamplesPerPixel x BitsPerSample > " + Integer.MAX_VALUE + " is not supported (" + width + " x " + height + " x " + samplesPerPixel + " x " + (pixel * 8) + ")"); } // casting to int is safe because we have already determined that // width * height is less than Integer.MAX_VALUE final int numSamples = (int) (width * height); // read in image strips final TiffCompression compression = ifd.getCompression(); CodecOptions codecOptions = this.codecOptions; if (compression == TiffCompression.JPEG_2000 || compression == TiffCompression.JPEG_2000_LOSSY) { codecOptions = compression.getCompressionCodecOptions(ifd, codecOptions); } else codecOptions = compression.getCompressionCodecOptions(ifd); codecOptions.interleaved = true; codecOptions.littleEndian = ifd.isLittleEndian(); this.setCodecOptions(codecOptions); final long imageLength = ifd.getImageLength(); // special case: if we only need one tile, and that tile doesn't need // any special handling, then we can just read it directly and return if ((x % tileWidth) == 0 && (y % tileLength) == 0 && width == tileWidth && height == imageLength && samplesPerPixel == 1 && (ifd.getBitsPerSample()[0] % 8) == 0 && photoInterp != PhotoInterp.WHITE_IS_ZERO && photoInterp != PhotoInterp.CMYK && photoInterp != PhotoInterp.Y_CB_CR && compression == TiffCompression.UNCOMPRESSED) { final long[] stripOffsets = ifd.getStripOffsets(); final long[] stripByteCounts = ifd.getStripByteCounts(); if (stripOffsets != null && stripByteCounts != null) { final long column = x / tileWidth; final int firstTile = (int) ((y / tileLength) * numTileCols + column); int lastTile = (int) (((y + height) / tileLength) * numTileCols + column); lastTile = Math.min(lastTile, stripOffsets.length - 1); int offset = 0; for (int tile = firstTile; tile <= lastTile; tile++) { long byteCount = equalStrips ? stripByteCounts[0] : stripByteCounts[tile]; if (byteCount == numSamples && pixel > 1) { byteCount *= pixel; } in.seek(stripOffsets[tile]); final int len = (int) Math.min(buf.length - offset, byteCount); in.read(buf, offset, len); offset += len; } } return adjustFillOrder(ifd, buf); } final long nrows = numTileRows; if (planarConfig == 2) numTileRows *= samplesPerPixel; final IntRect imageBounds = new IntRect(x, y, (int) width, (int) height); final int endX = (int) width + x; final int endY = (int) height + y; final long w = tileWidth; final long h = tileLength; final int rowLen = pixel * (int) w;// tileWidth; final int tileSize = (int) (rowLen * h);// tileLength); final int planeSize = (int) (width * height * pixel); final int outputRowLen = (int) (pixel * width); int bufferSizeSamplesPerPixel = samplesPerPixel; if (ifd.getPlanarConfiguration() == 2) bufferSizeSamplesPerPixel = 1; final int bpp = ifd.getBytesPerSample()[0]; final int bufferSize = (int) tileWidth * (int) tileLength * bufferSizeSamplesPerPixel * bpp; byte[] cachedTileBuffer = new byte[bufferSize]; final IntRect tileBounds = new IntRect(0, 0, (int) tileWidth, (int) tileLength); for (int row = 0; row < numTileRows; row++) { // make the first row shorter to account for row overlap if (row == 0) { tileBounds.height = (int) (tileLength - overlapY); } for (int col = 0; col < numTileCols; col++) { // make the first column narrower to account for column overlap if (col == 0) { tileBounds.width = (int) (tileWidth - overlapX); } tileBounds.x = col * (int) (tileWidth - overlapX); tileBounds.y = row * (int) (tileLength - overlapY); if (planarConfig == 2) { tileBounds.y = (int) ((row % nrows) * (tileLength - overlapY)); } if (!imageBounds.intersects(tileBounds)) continue; getTile(ifd, cachedTileBuffer, row, col); // adjust tile bounds, if necessary final int tileX = Math.max(tileBounds.x, x); final int tileY = Math.max(tileBounds.y, y); int realX = tileX % (int) (tileWidth - overlapX); int realY = tileY % (int) (tileLength - overlapY); int twidth = (int) Math.min(endX - tileX, tileWidth - realX); if (twidth <= 0) { twidth = (int) Math.max(endX - tileX, tileWidth - realX); } int theight = (int) Math.min(endY - tileY, tileLength - realY); if (theight <= 0) { theight = (int) Math.max(endY - tileY, tileLength - realY); } // copy appropriate portion of the tile to the output buffer final int copy = pixel * twidth; realX *= pixel; realY *= rowLen; for (int q = 0; q < effectiveChannels; q++) { int src = q * tileSize + realX + realY; int dest = q * planeSize + pixel * (tileX - x) + outputRowLen * (tileY - y); if (planarConfig == 2) dest += (planeSize * (row / nrows)); // copying the tile directly will only work if there is no // overlap; // otherwise, we may be overwriting a previous tile // (or the current tile may be overwritten by a subsequent // tile) if (rowLen == outputRowLen && overlapX == 0 && overlapY == 0) { //!! Note: here is a bug! It is possible that x != 0! System.arraycopy(cachedTileBuffer, src, buf, dest, copy * theight); } else { for (int tileRow = 0; tileRow < theight; tileRow++) { System.arraycopy(cachedTileBuffer, src, buf, dest, copy); src += rowLen; dest += outputRowLen; } } } } } return adjustFillOrder(ifd, buf); } // Equivalent method in TiffReader became private: no reasons to declare it public @Deprecated public TiffIFDEntry readTiffIFDEntry() throws IOException { DataHandle in = stream(); final int entryTag = in.readUnsignedShort(); // Parse the entry's "Type" IFDType entryType; try { entryType = IFDType.get(in.readUnsignedShort()); } catch (final EnumException e) { // log.error("Error reading IFD type at: " + in.offset()); throw e; } // Parse the entry's "ValueCount" final int valueCount = isBigTiff() ? (int) in.readLong() : in.readInt(); if (valueCount < 0) { throw new RuntimeException("Count of '" + valueCount + "' unexpected."); } final int nValueBytes = valueCount * entryType.getBytesPerElement(); final int threshhold = isBigTiff() ? 8 : 4; final long offset = nValueBytes > threshhold ? getNextOffset(0) : in .offset(); return new TiffIFDEntry(entryTag, entryType, valueCount, offset); } // Note: this logic cannot be implemented in the superclass, because it does not contain "ycbcrCorrection" flag. // Note: the algorithm, implemented in SCIFIO JPEGCodec until 0.46.0 version, works incorrectly. // Note: this method may be tested with the image jpeg_ycbcr_encoded_as_rgb.tiff @Override protected Optional decodeByExternalCodec(TiffTile tile, byte[] encodedData, TiffCodec.Options options) throws TiffException { Objects.requireNonNull(tile, "Null tile"); Objects.requireNonNull(encodedData, "Null encoded data"); Objects.requireNonNull(options, "Null options"); final CodecOptions codecOptions = options.toScifioStyleOptions(CodecOptions.class); final TiffIFD ifd = tile.ifd(); final int[] declaredSubsampling = ifd.getYCbCrSubsampling(); if (ifd.getPhotometricInterpretation() == TagPhotometricInterpretation.Y_CB_CR && declaredSubsampling.length >= 2 && declaredSubsampling[0] == 1 && declaredSubsampling[1] == 1 && this.ycbcrCorrection) { codecOptions.ycbcr = true; } return Optional.of(decompressByScifioCodec(tile.ifd(), encodedData, codecOptions)); } @Deprecated private byte[] adjustFillOrder(final IFD ifd, final byte[] buf) throws FormatException { if (ifd.getFillOrder() == FillOrder.REVERSED) { PackedBitArraysPer8.reverseBitOrderInPlace(buf); } return buf; } // This trick is deprecated @Deprecated private long getNextOffset(final long previous) throws IOException { DataHandle in = stream(); if (isBigTiff() || fakeBigTiff) { // ?? getFirstOffset did not check fakeBigTiff, so it cannot work! - Daniel Alievsky return in.readLong(); } long offset = (previous & ~0xffffffffL) | (in.readInt() & 0xffffffffL); // Only adjust the offset if we know that the file is too large for // 32-bit // offsets to be accurate; otherwise, we're making the incorrect // assumption // that IFDs are stored sequentially. if (offset < previous && offset != 0 && in.length() > Integer.MAX_VALUE) { offset += 0x100000000L; } return offset; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy