/**************************************************************************** * * LibTiff.Net * Copyright (c) 2008-2011, Bit Miracle * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Bit Miracle nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS Software IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND Any EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BIT MIRACLE BE * LIABLE FOR Any DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON Any THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN Any WAY OUT OF THE USE OF THIS Software, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ using System; using System.Collections.Generic; using System.Text; using System.IO; using ComponentAce.Compression.Libs.zlib; using BitMiracle.LibJpeg; using System.Collections; using System.Diagnostics; using System.Globalization; namespace BitMiracle.LibTiff { #region Tiff /// /// Tag Image File Format (TIFF) /// /// /// Based on Rev 6.0 from /// /// public class Tiff : IDisposable { private bool writeCheckStrips(string module) { return ((m_flags & TiffFlags.BeenWriting) == TiffFlags.BeenWriting || WriteCheck(false, module)); } private bool writeCheckTiles(string module) { return ((m_flags & TiffFlags.BeenWriting) == TiffFlags.BeenWriting || WriteCheck(true, module)); } private void bufferCheck() { if (!((m_flags & TiffFlags.BufferSetup) == TiffFlags.BufferSetup && m_rawdata != null)) WriteBufferSetup(null, -1); } private bool writeOK(byte[] buffer, int offset, int count) { try { m_stream.Write(m_clientdata, buffer, offset, count); } catch (Exception) { Tiff.Warning(this, "writeOK", "Failed to write {0} bytes", count); return false; } return true; } private bool writeHeaderOK(TiffHeader header) { bool res = writeShortOK(header.tiff_magic); if (res) res = writeShortOK(header.tiff_version); if (res) res = writeIntOK((int)header.tiff_diroff); return res; } private bool writeDirEntryOK(TiffDirEntry[] entries, int count) { bool res = true; for (int i = 0; i < count; i++) { res = writeShortOK((short)entries[i].tdir_tag); if (res) res = writeShortOK((short)entries[i].tdir_type); if (res) res = writeIntOK(entries[i].tdir_count); if (res) res = writeIntOK((int)entries[i].tdir_offset); if (!res) break; } return res; } private bool writeShortOK(short value) { byte[] cp = new byte[2]; cp[0] = (byte)value; cp[1] = (byte)(value >> 8); return writeOK(cp, 0, 2); } private bool writeIntOK(int value) { byte[] cp = new byte[4]; cp[0] = (byte)value; cp[1] = (byte)(value >> 8); cp[2] = (byte)(value >> 16); cp[3] = (byte)(value >> 24); return writeOK(cp, 0, 4); } private bool isUnspecified(int f) { return (fieldSet(f) && m_dir.td_imagelength == 0); } /* * Grow the strip data structures by delta strips. */ private bool growStrips(int delta) { Debug.Assert(m_dir.td_planarconfig == PlanarConfig.Contig); uint[] new_stripoffset = Realloc(m_dir.td_stripoffset, m_dir.td_nstrips, m_dir.td_nstrips + delta); uint[] new_stripbytecount = Realloc(m_dir.td_stripbytecount, m_dir.td_nstrips, m_dir.td_nstrips + delta); m_dir.td_stripoffset = new_stripoffset; m_dir.td_stripbytecount = new_stripbytecount; Array.Clear(m_dir.td_stripoffset, m_dir.td_nstrips, delta); Array.Clear(m_dir.td_stripbytecount, m_dir.td_nstrips, delta); m_dir.td_nstrips += delta; return true; } /// /// Appends the data to the specified strip. /// private bool appendToStrip(int strip, byte[] buffer, int offset, int count) { const string module = "appendToStrip"; if (m_dir.td_stripoffset[strip] == 0 || m_curoff == 0) { Debug.Assert(m_dir.td_nstrips > 0); if (m_dir.td_stripbytecount[strip] != 0 && m_dir.td_stripoffset[strip] != 0 && m_dir.td_stripbytecount[strip] >= count) { // There is already tile data on disk, and the new tile // data we have to will fit in the same space. The only // aspect of this that is risky is that there could be // more data to append to this strip before we are done // depending on how we are getting called. if (!seekOK(m_dir.td_stripoffset[strip])) { ErrorExt(this, m_clientdata, module, "Seek error at scanline {0}", m_row); return false; } } else { // Seek to end of file, and set that as our location // to write this strip. m_dir.td_stripoffset[strip] = (uint)seekFile(0, SeekOrigin.End); } m_curoff = m_dir.td_stripoffset[strip]; // We are starting a fresh strip/tile, so set the size to zero. m_dir.td_stripbytecount[strip] = 0; } if (!writeOK(buffer, offset, count)) { ErrorExt(this, m_clientdata, module, "Write error at scanline {0}", m_row); return false; } m_curoff += (uint)count; m_dir.td_stripbytecount[strip] += (uint)count; return true; } /* * Internal version of FlushData that can be * called by ``encodestrip routines'' w/o concern * for infinite recursion. */ internal bool flushData1() { if (m_rawcc > 0) { if (!isFillOrder(m_dir.td_fillorder) && (m_flags & TiffFlags.NoBitRev) != TiffFlags.NoBitRev) ReverseBits(m_rawdata, m_rawcc); if (!appendToStrip(IsTiled() ? m_curtile : m_curstrip, m_rawdata, 0, m_rawcc)) return false; m_rawcc = 0; m_rawcp = 0; } return true; } /* * Bit reversal tables. TIFFBitRevTable[] gives * the bit reversed value of . Used in various * places in the library when the BitOrder requires * bit reversal of byte values (e.g. CCITT Fax 3 * encoding/decoding). TIFFNoBitRevTable is provided * for algorithms that want an equivalent table that * do not reverse bit values. */ private static readonly byte[] TIFFBitRevTable = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff }; private static readonly byte[] TIFFNoBitRevTable = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, }; private int summarize(int summand1, int summand2, string where) { int bytes = summand1 + summand2; if (bytes - summand1 != summand2) { ErrorExt(this, m_clientdata, m_name, "Integer overflow in {0}", where); bytes = 0; } return bytes; } private int multiply(int nmemb, int elem_size, string where) { int bytes = nmemb * elem_size; if (elem_size != 0 && bytes / elem_size != nmemb) { ErrorExt(this, m_clientdata, m_name, "Integer overflow in {0}", where); bytes = 0; } return bytes; } /* * Return the number of bytes to read/write in a call to * one of the scanline-oriented i/o routines. Note that * this number may be 1/samples-per-pixel if data is * stored as separate planes. * The ScanlineSize in case of YCbCrSubsampling is defined as the * strip size divided by the strip height, i.e. the size of a pack of vertical * subsampling lines divided by vertical subsampling. It should thus make * sense when multiplied by a multiple of vertical subsampling. * Some stuff depends on this newer version of TIFFScanlineSize * TODO: resolve this */ internal int newScanlineSize() { int scanline; if (m_dir.td_planarconfig == PlanarConfig.Contig) { if (m_dir.td_photometric == Photometric.YCBCR && !IsUpSampled()) { FieldValue[] result = GetField(TiffTag.YCBCRSUBSAMPLING); ushort ycbcrsubsampling0 = result[0].ToUShort(); ushort ycbcrsubsampling1 = result[1].ToUShort(); if (ycbcrsubsampling0 * ycbcrsubsampling1 == 0) { ErrorExt(this, m_clientdata, m_name, "Invalid YCbCr subsampling"); return 0; } return ((((m_dir.td_imagewidth + ycbcrsubsampling0 - 1) / ycbcrsubsampling0) * (ycbcrsubsampling0 * ycbcrsubsampling1 + 2) * m_dir.td_bitspersample + 7) / 8) / ycbcrsubsampling1; } else { scanline = multiply(m_dir.td_imagewidth, m_dir.td_samplesperpixel, "TIFFScanlineSize"); } } else { scanline = m_dir.td_imagewidth; } return howMany8(multiply(scanline, m_dir.td_bitspersample, "TIFFScanlineSize")); } /* * Some stuff depends on this older version of TIFFScanlineSize * TODO: resolve this */ internal int oldScanlineSize() { int scanline = multiply(m_dir.td_bitspersample, m_dir.td_imagewidth, "TIFFScanlineSize"); if (m_dir.td_planarconfig == PlanarConfig.Contig) scanline = multiply(scanline, m_dir.td_samplesperpixel, "TIFFScanlineSize"); return howMany8(scanline); } /// /// undefined state /// private const int NOSTRIP = -1; /// /// undefined state /// private const int NOTILE = -1; internal const int O_RDONLY = 0; internal const int O_WRONLY = 0x0001; internal const int O_CREAT = 0x0100; internal const int O_TRUNC = 0x0200; internal const int O_RDWR = 0x0002; // // Default Read/Seek/Write definitions. // private int readFile(byte[] buf, int offset, int size) { return m_stream.Read(m_clientdata, buf, offset, size); } private long seekFile(long off, SeekOrigin whence) { return m_stream.Seek(m_clientdata, off, whence); } private long getFileSize() { return m_stream.Size(m_clientdata); } private bool readOK(byte[] buf, int size) { return (readFile(buf, 0, size) == size); } private bool readShortOK(out short value) { byte[] bytes = new byte[2]; bool res = readOK(bytes, 2); value = 0; if (res) { value = (short)(bytes[0] & 0xFF); value += (short)((bytes[1] & 0xFF) << 8); } return res; } private bool readUIntOK(out uint value) { int temp; bool res = readIntOK(out temp); if (res) value = (uint)temp; else value = 0; return res; } private bool readIntOK(out int value) { byte[] cp = new byte[4]; bool res = readOK(cp, 4); value = 0; if (res) { value = cp[0] & 0xFF; value += (cp[1] & 0xFF) << 8; value += (cp[2] & 0xFF) << 16; value += cp[3] << 24; } return res; } private bool readDirEntryOk(TiffDirEntry[] dir, short dircount) { int entrySize = sizeof(short) * 2 + sizeof(int) * 2; int totalSize = entrySize * dircount; byte[] bytes = new byte[totalSize]; bool res = readOK(bytes, totalSize); if (res) readDirEntry(dir, dircount, bytes, 0); return res; } private static void readDirEntry(TiffDirEntry[] dir, short dircount, byte[] bytes, int offset) { int pos = offset; for (int i = 0; i < dircount; i++) { TiffDirEntry entry = new TiffDirEntry(); entry.tdir_tag = (TiffTag)(ushort)readShort(bytes, pos); pos += sizeof(short); entry.tdir_type = (TiffType)readShort(bytes, pos); pos += sizeof(short); entry.tdir_count = readInt(bytes, pos); pos += sizeof(int); entry.tdir_offset = (uint)readInt(bytes, pos); pos += sizeof(int); dir[i] = entry; } } private bool readHeaderOk(ref TiffHeader header) { bool res = readShortOK(out header.tiff_magic); if (res) res = readShortOK(out header.tiff_version); if (res) res = readUIntOK(out header.tiff_diroff); return res; } private bool seekOK(long off) { return (seekFile(off, SeekOrigin.Begin) == off); } /* * Seek to a random row+sample in a file. */ private bool seek(int row, short sample) { if (row >= m_dir.td_imagelength) { /* out of range */ ErrorExt(this, m_clientdata, m_name, "{0}: Row out of range, max {1}", row, m_dir.td_imagelength); return false; } int strip; if (m_dir.td_planarconfig == PlanarConfig.Separate) { if (sample >= m_dir.td_samplesperpixel) { ErrorExt(this, m_clientdata, m_name, "{0}: Sample out of range, max {1}", sample, m_dir.td_samplesperpixel); return false; } if (m_dir.td_rowsperstrip != -1) strip = sample * m_dir.td_stripsperimage + row / m_dir.td_rowsperstrip; else strip = 0; } else { if (m_dir.td_rowsperstrip != -1) strip = row / m_dir.td_rowsperstrip; else strip = 0; } if (strip != m_curstrip) { /* different strip, refill */ if (!fillStrip(strip)) return false; } else if (row < m_row) { /* * Moving backwards within the same strip: backup * to the start and then decode forward (below). * * NB: If you're planning on lots of random access within a * strip, it's better to just read and decode the entire * strip, and then access the decoded data in a random fashion. */ if (!startStrip(strip)) return false; } if (row != m_row) { /* * Seek forward to the desired row. */ if (!m_currentCodec.Seek(row - m_row)) return false; m_row = row; } return true; } private int readRawStrip1(int strip, byte[] buf, int offset, int size, string module) { Debug.Assert((m_flags & TiffFlags.NoReadRaw) != TiffFlags.NoReadRaw); if (!seekOK(m_dir.td_stripoffset[strip])) { ErrorExt(this, m_clientdata, module, "{0}: Seek error at scanline {1}, strip {2}", m_name, m_row, strip); return -1; } int cc = readFile(buf, offset, size); if (cc != size) { ErrorExt(this, m_clientdata, module, "{0}: Read error at scanline {1}; got {2} bytes, expected {3}", m_name, m_row, cc, size); return -1; } return size; } private int readRawTile1(int tile, byte[] buf, int offset, int size, string module) { Debug.Assert((m_flags & TiffFlags.NoReadRaw) != TiffFlags.NoReadRaw); if (!seekOK(m_dir.td_stripoffset[tile])) { ErrorExt(this, m_clientdata, module, "{0}: Seek error at row {1}, col {2}, tile {3}", m_name, m_row, m_col, tile); return -1; } int cc = readFile(buf, offset, size); if (cc != size) { ErrorExt(this, m_clientdata, module, "{0}: Read error at row {1}, col {2}; got {3} bytes, expected {4}", m_name, m_row, m_col, cc, size); return -1; } return size; } /// /// Set state to appear as if a strip has just been read in. /// private bool startStrip(int strip) { if ((m_flags & TiffFlags.CoderSetup) != TiffFlags.CoderSetup) { if (!m_currentCodec.SetupDecode()) return false; m_flags |= TiffFlags.CoderSetup; } m_curstrip = strip; m_row = (strip % m_dir.td_stripsperimage) * m_dir.td_rowsperstrip; m_rawcp = 0; if ((m_flags & TiffFlags.NoReadRaw) == TiffFlags.NoReadRaw) m_rawcc = 0; else m_rawcc = (int)m_dir.td_stripbytecount[strip]; return m_currentCodec.PreDecode((short)(strip / m_dir.td_stripsperimage)); } /* * Set state to appear as if a * tile has just been read in. */ private bool startTile(int tile) { if ((m_flags & TiffFlags.CoderSetup) != TiffFlags.CoderSetup) { if (!m_currentCodec.SetupDecode()) return false; m_flags |= TiffFlags.CoderSetup; } m_curtile = tile; m_row = (tile % howMany(m_dir.td_imagewidth, m_dir.td_tilewidth)) * m_dir.td_tilelength; m_col = (tile % howMany(m_dir.td_imagelength, m_dir.td_tilelength)) * m_dir.td_tilewidth; m_rawcp = 0; if ((m_flags & TiffFlags.NoReadRaw) == TiffFlags.NoReadRaw) m_rawcc = 0; else m_rawcc = (int)m_dir.td_stripbytecount[tile]; return m_currentCodec.PreDecode((short)(tile / m_dir.td_stripsperimage)); } private bool checkRead(bool tiles) { if (m_mode == O_WRONLY) { ErrorExt(this, m_clientdata, m_name, "File not open for reading"); return false; } if (tiles ^ IsTiled()) { ErrorExt(this, m_clientdata, m_name, tiles ? "Can not read tiles from a stripped image" : "Can not read scanlines from a tiled image"); return false; } return true; } private static void swab16BitData(byte[] buffer, int offset, int count) { Debug.Assert((count & 1) == 0); short[] swabee = ByteArrayToShorts(buffer, offset, count); SwabArrayOfShort(swabee, count / 2); ShortsToByteArray(swabee, 0, count / 2, buffer, offset); } private static void swab24BitData(byte[] buffer, int offset, int count) { Debug.Assert((count % 3) == 0); SwabArrayOfTriples(buffer, offset, count / 3); } private static void swab32BitData(byte[] buffer, int offset, int count) { Debug.Assert((count & 3) == 0); int[] swabee = ByteArrayToInts(buffer, offset, count); SwabArrayOfLong(swabee, count / 4); IntsToByteArray(swabee, 0, count / 4, buffer, offset); } private static void swab64BitData(byte[] buffer, int offset, int count) { Debug.Assert((count & 7) == 0); int doubleCount = count / 8; double[] doubles = new double[doubleCount]; int byteOffset = offset; for (int i = 0; i < doubleCount; i++) { doubles[i] = BitConverter.ToDouble(buffer, byteOffset); byteOffset += 8; } SwabArrayOfDouble(doubles, doubleCount); byteOffset = offset; for (int i = 0; i < doubleCount; i++) { byte[] bytes = BitConverter.GetBytes(doubles[i]); Buffer.BlockCopy(bytes, 0, buffer, byteOffset, bytes.Length); byteOffset += bytes.Length; } } /// /// Read the specified strip and setup for decoding. /// The data buffer is expanded, as necessary, to hold the strip's data. /// internal bool fillStrip(int strip) { const string module = "fillStrip"; if ((m_flags & TiffFlags.NoReadRaw) != TiffFlags.NoReadRaw) { int bytecount = (int)m_dir.td_stripbytecount[strip]; if (bytecount <= 0) { ErrorExt(this, m_clientdata, m_name, "{0}: Invalid strip byte count, strip {1}", bytecount, strip); return false; } /* * Expand raw data buffer, if needed, to * hold data strip coming from file * (perhaps should set upper bound on * the size of a buffer we'll use?). */ if (bytecount > m_rawdatasize) { m_curstrip = NOSTRIP; if ((m_flags & TiffFlags.MyBuffer) != TiffFlags.MyBuffer) { ErrorExt(this, m_clientdata, module, "{0}: Data buffer too small to hold strip {1}", m_name, strip); return false; } ReadBufferSetup(null, roundUp(bytecount, 1024)); } if (readRawStrip1(strip, m_rawdata, 0, bytecount, module) != bytecount) return false; if (!isFillOrder(m_dir.td_fillorder) && (m_flags & TiffFlags.NoBitRev) != TiffFlags.NoBitRev) ReverseBits(m_rawdata, bytecount); } return startStrip(strip); } /// /// Read the specified tile and setup for decoding. /// The data buffer is expanded, as necessary, to hold the tile's data. /// internal bool fillTile(int tile) { const string module = "fillTile"; if ((m_flags & TiffFlags.NoReadRaw) != TiffFlags.NoReadRaw) { int bytecount = (int)m_dir.td_stripbytecount[tile]; if (bytecount <= 0) { ErrorExt(this, m_clientdata, m_name, "{0}: Invalid tile byte count, tile {1}", bytecount, tile); return false; } /* * Expand raw data buffer, if needed, to * hold data tile coming from file * (perhaps should set upper bound on * the size of a buffer we'll use?). */ if (bytecount > m_rawdatasize) { m_curtile = NOTILE; if ((m_flags & TiffFlags.MyBuffer) != TiffFlags.MyBuffer) { ErrorExt(this, m_clientdata, module, "{0}: Data buffer too small to hold tile {1}", m_name, tile); return false; } ReadBufferSetup(null, roundUp(bytecount, 1024)); } if (readRawTile1(tile, m_rawdata, 0, bytecount, module) != bytecount) return false; if (!isFillOrder(m_dir.td_fillorder) && (m_flags & TiffFlags.NoBitRev) != TiffFlags.NoBitRev) ReverseBits(m_rawdata, bytecount); } return startTile(tile); } private static readonly string[] photoNames = { "min-is-white", // Photometric.MinIsWhite "min-is-black", // Photometric.MinIsBlack "RGB color", // Photometric.RGB "palette color (RGB from colormap)", // Photometric.Palette "transparency mask", // Photometric.Mask "separated", // Photometric.Separated "YCbCr", // Photometric.YCBCR "7 (0x7)", "CIE L*a*b*", // Photometric.CIELAB }; private static readonly string[] orientNames = { "0 (0x0)", "row 0 top, col 0 lhs", // Orientation.TopLeft "row 0 top, col 0 rhs", // Orientation.TopRight "row 0 bottom, col 0 rhs", // Orientation.BottomRight "row 0 bottom, col 0 lhs", // Orientation.BottomLeft "row 0 lhs, col 0 top", // Orientation.LeftTop "row 0 rhs, col 0 top", // Orientation.RightTop "row 0 rhs, col 0 bottom", // Orientation.RightBottom "row 0 lhs, col 0 bottom", // Orientation.LeftBottom }; private static void printField(Stream fd, TiffFieldInfo fip, int value_count, object raw_data) { fprintf(fd, " {0}: ", fip.Name); byte[] bytes = raw_data as byte[]; sbyte[] sbytes = raw_data as sbyte[]; short[] shorts = raw_data as short[]; ushort[] ushorts = raw_data as ushort[]; int[] ints = raw_data as int[]; uint[] uints = raw_data as uint[]; float[] floats = raw_data as float[]; double[] doubles = raw_data as double[]; string s = raw_data as string; for (int j = 0; j < value_count; j++) { if (fip.Type == TiffType.Byte || fip.Type == TiffType.SByte) { if (bytes != null) fprintf(fd, "{0}", bytes[j]); else if (sbytes != null) fprintf(fd, "{0}", sbytes[j]); } else if (fip.Type == TiffType.Undefined) { if (bytes != null) fprintf(fd, "0x{0:x}", bytes[j]); } else if (fip.Type == TiffType.Short || fip.Type == TiffType.SShort) { if (shorts != null) fprintf(fd, "{0}", shorts[j]); else if (ushorts != null) fprintf(fd, "{0}", ushorts[j]); } else if (fip.Type == TiffType.Long || fip.Type == TiffType.SLong) { if (ints != null) fprintf(fd, "{0}", ints[j]); else if (uints != null) fprintf(fd, "{0}", uints[j]); } else if (fip.Type == TiffType.Rational || fip.Type == TiffType.SRational || fip.Type == TiffType.Float) { if (floats != null) fprintf(fd, "{0}", floats[j]); } else if (fip.Type == TiffType.IFD) { if (ints != null) fprintf(fd, "0x{0:x}", ints[j]); else if (uints != null) fprintf(fd, "0x{0:x}", uints[j]); } else if (fip.Type == TiffType.ASCII) { if (s != null) fprintf(fd, "{0}", s); break; } else if (fip.Type == TiffType.Double || fip.Type == TiffType.Float) { if (floats != null) fprintf(fd, "{0}", floats[j]); else if (doubles != null) fprintf(fd, "{0}", doubles[j]); } else { fprintf(fd, ""); break; } if (j < value_count - 1) fprintf(fd, ","); } fprintf(fd, "\r\n"); } private bool prettyPrintField(Stream fd, TiffTag tag, int value_count, object raw_data) { FieldValue value = new FieldValue(raw_data); short[] sdata = value.ToShortArray(); float[] fdata = value.ToFloatArray(); double[] ddata = value.ToDoubleArray(); switch (tag) { case TiffTag.InkSet: if (sdata != null) { fprintf(fd, " Ink Set: "); switch ((InkSet)sdata[0]) { case InkSet.CMYK: fprintf(fd, "CMYK\n"); break; default: fprintf(fd, "{0} (0x{1:x})\n", sdata[0], sdata[0]); break; } return true; } return false; case TiffTag.DotRange: if (sdata != null) { fprintf(fd, " Dot Range: {0}-{1}\n", sdata[0], sdata[1]); return true; } return false; case TiffTag.WhitePoint: if (fdata != null) { fprintf(fd, " White Point: {0:G}-{1:G}\n", fdata[0], fdata[1]); return true; } return false; case TiffTag.REFERENCEBLACKWHITE: if (fdata != null) { fprintf(fd, " Reference Black/White:\n"); for (short i = 0; i < 3; i++) fprintf(fd, " {0,2:D}: {1,5:G} {2,5:G}\n", i, fdata[2 * i + 0], fdata[2 * i + 1]); return true; } return false; case TiffTag.XMLPACKET: string s = raw_data as string; if (s != null) { fprintf(fd, " XMLPacket (XMP Metadata):\n"); fprintf(fd, s.Substring(0, value_count)); fprintf(fd, "\n"); return true; } return false; case TiffTag.RICHTIFFIPTC: // XXX: for some weird reason RichTIFFIPTC tag defined // as array of Long values. fprintf(fd, " RichTIFFIPTC Data: , {0} bytes\n", value_count * 4); return true; case TiffTag.PHOTOSHOP: fprintf(fd, " Photoshop Data: , {0} bytes\n", value_count); return true; case TiffTag.ICCPROFILE: fprintf(fd, " ICC Profile: , {0} bytes\n", value_count); return true; case TiffTag.STONITS: if (ddata != null) { fprintf(fd, " Sample to Nits conversion factor: {0:e4}\n", ddata[0]); return true; } return false; } return false; } private static void printAscii(Stream fd, string cp) { for (int cpPos = 0; cp[cpPos] != '\0'; cpPos++) { if (!char.IsControl(cp[cpPos])) { fprintf(fd, "{0}", cp[cpPos]); continue; } string tp = "\tt\bb\rr\nn\vv"; int tpPos = 0; for (; tp[tpPos] != 0; tpPos++) { if (tp[tpPos++] == cp[cpPos]) break; } if (tp[tpPos] != 0) fprintf(fd, "\\{0}", tp[tpPos]); else fprintf(fd, "\\{0}", encodeOctalString((byte)(cp[cpPos] & 0xff))); } } private static readonly uint[] typemask = { 0, // TIFF_NOTYPE 0x000000ff, // TIFF_BYTE 0xffffffff, // TIFF_ASCII 0x0000ffff, // TIFF_SHORT 0xffffffff, // TIFF_LONG 0xffffffff, // TIFF_RATIONAL 0x000000ff, // TIFF_SBYTE 0x000000ff, // TIFF_UNDEFINED 0x0000ffff, // TIFF_SSHORT 0xffffffff, // TIFF_SLONG 0xffffffff, // TIFF_SRATIONAL 0xffffffff, // TIFF_FLOAT 0xffffffff, // TIFF_DOUBLE }; private static readonly int[] bigTypeshift = { 0, // TIFF_NOTYPE 24, // TIFF_BYTE 0, // TIFF_ASCII 16, // TIFF_SHORT 0, // TIFF_LONG 0, // TIFF_RATIONAL 24, // TIFF_SBYTE 24, // TIFF_UNDEFINED 16, // TIFF_SSHORT 0, // TIFF_SLONG 0, // TIFF_SRATIONAL 0, // TIFF_FLOAT 0, // TIFF_DOUBLE }; private static readonly int[] litTypeshift = { 0, // TIFF_NOTYPE 0, // TIFF_BYTE 0, // TIFF_ASCII 0, // TIFF_SHORT 0, // TIFF_LONG 0, // TIFF_RATIONAL 0, // TIFF_SBYTE 0, // TIFF_UNDEFINED 0, // TIFF_SSHORT 0, // TIFF_SLONG 0, // TIFF_SRATIONAL 0, // TIFF_FLOAT 0, // TIFF_DOUBLE }; /* * Initialize the shift & mask tables, and the * byte swapping state according to the file * contents and the machine architecture. */ private void initOrder(int magic) { m_typemask = typemask; if (magic == TIFF_BIGENDIAN) { m_typeshift = bigTypeshift; m_flags |= TiffFlags.Swab; } else { m_typeshift = litTypeshift; } } private static int getMode(string mode, string module, out FileMode m, out FileAccess a) { m = 0; a = 0; int tiffMode = -1; if (mode.Length == 0) return tiffMode; switch (mode[0]) { case 'r': m = FileMode.Open; a = FileAccess.Read; tiffMode = O_RDONLY; if (mode.Length > 1 && mode[1] == '+') { a = FileAccess.ReadWrite; tiffMode = O_RDWR; } break; case 'w': m = FileMode.Create; a = FileAccess.ReadWrite; tiffMode = O_RDWR | O_CREAT | O_TRUNC; break; case 'a': m = FileMode.Open; a = FileAccess.ReadWrite; tiffMode = O_RDWR | O_CREAT; break; default: ErrorExt(null, 0, module, "\"{0}\": Bad mode", mode); break; } return tiffMode; } private const int TIFF_VERSION = 42; private const int TIFF_BIGTIFF_VERSION = 43; private const short TIFF_BIGENDIAN = 0x4d4d; private const short TIFF_LITTLEENDIAN = 0x4949; private const short MDI_LITTLEENDIAN = 0x5045; // reference white private const float D50_X0 = 96.4250F; private const float D50_Y0 = 100.0F; private const float D50_Z0 = 82.4680F; internal const int STRIP_SIZE_DEFAULT = 8192; /// /// Support strip chopping (whether or not to convert single-strip /// uncompressed images to mutiple strips of ~8Kb to reduce memory usage) /// internal const TiffFlags STRIPCHOP_DEFAULT = TiffFlags.StripChop; /// /// Treat extra sample as alpha (default enabled). The RGBA interface /// will treat a fourth sample with no EXTRASAMPLE_ value as being /// AssociatedAlpha. Many packages produce RGBA files but don't mark the /// alpha properly. /// internal const bool DEFAULT_EXTRASAMPLE_AS_ALPHA = true; /// /// Pick up YCbCr subsampling info from the JPEG data stream to support /// files lacking the tag (default enabled). /// internal const bool CHECK_JPEG_YCBCR_SUBSAMPLING = true; internal static Encoding Latin1Encoding = Encoding.GetEncoding("Latin1"); internal enum PostDecodeMethodType { pdmNone, pdmSwab16Bit, pdmSwab24Bit, pdmSwab32Bit, pdmSwab64Bit }; /// /// name of open file /// internal string m_name; /// /// open mode (O_*) /// internal int m_mode; internal TiffFlags m_flags; // // the first directory // /// /// file offset of current directory /// internal uint m_diroff; // directories to prevent IFD looping /// /// internal rep of current directory /// internal TiffDirectory m_dir; /// /// current scanline /// internal int m_row; /// /// current strip for read/write /// internal int m_curstrip; // tiling support /// /// current tile for read/write /// internal int m_curtile; /// /// # of bytes in a tile /// internal int m_tilesize; // compression scheme hooks internal TiffCodec m_currentCodec; // input/output buffering /// /// # of bytes in a scanline /// internal int m_scanlinesize; /// /// raw data buffer /// internal byte[] m_rawdata; /// /// # of bytes in raw data buffer /// internal int m_rawdatasize; /// /// current spot in raw buffer /// internal int m_rawcp; /// /// bytes unread from raw buffer /// internal int m_rawcc; /// /// callback parameter /// internal object m_clientdata; // post-decoding support /// /// post decoding method type /// internal PostDecodeMethodType m_postDecodeMethod; // tag support /// /// tag get/set/print routines /// internal TiffTagMethods m_tagmethods; private class codecList { public codecList next; public TiffCodec codec; }; private class clientInfoLink { public clientInfoLink next; public object data; public string name; }; // the first directory /// /// file offset of following directory /// private uint m_nextdiroff; /// /// list of offsets to already seen directories to prevent IFD looping /// private uint[] m_dirlist; /// /// number of entires in offset list /// private int m_dirlistsize; /// /// number of already seen directories /// private short m_dirnumber; /// /// file's header block /// private TiffHeader m_header; /// /// data type shift counts /// private int[] m_typeshift; /// /// data type masks /// private uint[] m_typemask; /// /// current directory (index) /// private short m_curdir; /// /// current offset for read/write /// private uint m_curoff; /// /// current offset for writing dir /// private uint m_dataoff; // // SubIFD support // /// /// remaining subifds to write /// private short m_nsubifd; /// /// offset for patching SubIFD link /// private uint m_subifdoff; // tiling support /// /// current column (offset by row too) /// private int m_col; // compression scheme hooks private bool m_decodestatus; // tag support /// /// sorted table of registered tags /// private TiffFieldInfo[] m_fieldinfo; /// /// # entries in registered tag table /// private int m_nfields; /// /// cached pointer to already found tag /// private TiffFieldInfo m_foundfield; /// /// extra client information. /// private clientInfoLink m_clientinfo; private TiffCodec[] m_builtInCodecs; private codecList m_registeredCodecs; private TiffTagMethods m_defaultTagMethods; private static TiffErrorHandler m_errorHandler; private TiffErrorHandler m_defaultErrorHandler; private bool m_disposed; private Stream m_fileStream; /// /// Client Tag extension support (from Niles Ritter). /// private static TiffExtendProc m_extender; /// /// stream used for read|write|etc. /// private TiffStream m_stream; private Tiff() { m_clientdata = 0; m_postDecodeMethod = PostDecodeMethodType.pdmNone; setupBuiltInCodecs(); m_defaultTagMethods = new TiffTagMethods(); m_defaultErrorHandler = null; if (m_errorHandler == null) { // user did not setup custom handler. // install default m_defaultErrorHandler = new TiffErrorHandler(); m_errorHandler = m_defaultErrorHandler; } } private void Dispose(bool disposing) { if (!this.m_disposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. Close(); if (m_fileStream != null) m_fileStream.Dispose(); } // Call the appropriate methods to clean up // unmanaged resources here. // Note disposing has been done. m_disposed = true; } } /// /// Writes custom directory. See ticket #51. /// /// Output directory offset. /// true if succeeded; otherwise, false private bool WriteCustomDirectory(out long pdiroff) { pdiroff = -1; if (m_mode == O_RDONLY) return true; // Size the directory so that we can calculate offsets for the data // items that aren't kept in-place in each field. int nfields = 0; for (int b = 0; b <= FieldBit.Last; b++) { if (fieldSet(b) && b != FieldBit.Custom) nfields += (b < FieldBit.SubFileType ? 2 : 1); } nfields += m_dir.td_customValueCount; int dirsize = nfields * TiffDirEntry.SizeInBytes; TiffDirEntry[] data = new TiffDirEntry[nfields]; // Put the directory at the end of the file. m_diroff = (uint)((seekFile(0, SeekOrigin.End) + 1) & ~1); m_dataoff = m_diroff + sizeof(short) + (uint)dirsize + sizeof(int); if ((m_dataoff & 1) != 0) m_dataoff++; seekFile(m_dataoff, SeekOrigin.Begin); // Setup external form of directory entries and write data items. int[] fields = new int[FieldBit.SetLongs]; Buffer.BlockCopy(m_dir.td_fieldsset, 0, fields, 0, FieldBit.SetLongs * sizeof(int)); for (int fi = 0, nfi = m_nfields; nfi > 0; nfi--, fi++) { TiffFieldInfo fip = m_fieldinfo[fi]; // For custom fields, we test to see if the custom field // is set or not. For normal fields, we just use the FieldSet test. if (fip.Bit == FieldBit.Custom) { bool is_set = false; for (int ci = 0; ci < m_dir.td_customValueCount; ci++) is_set |= (m_dir.td_customValues[ci].info == fip); if (!is_set) continue; } else if (!fieldSet(fields, fip.Bit)) continue; if (fip.Bit != FieldBit.Custom) resetFieldBit(fields, fip.Bit); } // Write directory. short dircount = (short)nfields; pdiroff = m_nextdiroff; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { // The file's byte order is opposite to the native machine // architecture. We overwrite the directory information with // impunity because it'll be released below after we write it // to the file. Note that all the other tag construction // routines assume that we do this byte-swapping; i.e. they only // byte-swap indirect data. for (int i = 0; i < dircount; i++) { TiffDirEntry dirEntry = data[i]; short temp = (short)dirEntry.tdir_tag; SwabShort(ref temp); dirEntry.tdir_tag = (TiffTag)(ushort)temp; temp = (short)dirEntry.tdir_type; SwabShort(ref temp); dirEntry.tdir_type = (TiffType)temp; SwabLong(ref dirEntry.tdir_count); SwabUInt(ref dirEntry.tdir_offset); } dircount = (short)nfields; SwabShort(ref dircount); int tempOff = (int)pdiroff; SwabLong(ref tempOff); pdiroff = tempOff; } seekFile(m_diroff, SeekOrigin.Begin); if (!writeShortOK(dircount)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory count"); return false; } if (!writeDirEntryOK(data, dirsize / TiffDirEntry.SizeInBytes)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory contents"); return false; } if (!writeIntOK((int)pdiroff)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory link"); return false; } return true; } internal static void SwabUInt(ref uint lp) { byte[] cp = new byte[4]; cp[0] = (byte)lp; cp[1] = (byte)(lp >> 8); cp[2] = (byte)(lp >> 16); cp[3] = (byte)(lp >> 24); byte t = cp[3]; cp[3] = cp[0]; cp[0] = t; t = cp[2]; cp[2] = cp[1]; cp[1] = t; lp = (uint)(cp[0] & 0xFF); lp += (uint)((cp[1] & 0xFF) << 8); lp += (uint)((cp[2] & 0xFF) << 16); lp += (uint)(cp[3] << 24); } internal static uint[] Realloc(uint[] buffer, int elementCount, int newElementCount) { uint[] newBuffer = new uint[newElementCount]; if (buffer != null) { int copyLength = Math.Min(elementCount, newElementCount); Buffer.BlockCopy(buffer, 0, newBuffer, 0, copyLength * sizeof(uint)); } return newBuffer; } internal static TiffFieldInfo[] Realloc(TiffFieldInfo[] buffer, int elementCount, int newElementCount) { TiffFieldInfo[] newBuffer = new TiffFieldInfo[newElementCount]; if (buffer != null) { int copyLength = Math.Min(elementCount, newElementCount); Array.Copy(buffer, newBuffer, copyLength); } return newBuffer; } internal static TiffTagValue[] Realloc(TiffTagValue[] buffer, int elementCount, int newElementCount) { TiffTagValue[] newBuffer = new TiffTagValue[newElementCount]; if (buffer != null) { int copyLength = Math.Min(elementCount, newElementCount); Array.Copy(buffer, newBuffer, copyLength); } return newBuffer; } internal bool setCompressionScheme(Compression scheme) { TiffCodec c = FindCodec(scheme); if (c == null) { /* * Don't treat an unknown compression scheme as an error. * This permits applications to open files with data that * the library does not have builtin support for, but which * may still be meaningful. */ c = m_builtInCodecs[0]; } m_decodestatus = c.CanDecode; m_flags &= ~(TiffFlags.NoBitRev | TiffFlags.NoReadRaw); m_currentCodec = c; return c.Init(); } /// /// post decoding routine /// private void postDecode(byte[] buffer, int offset, int count) { switch (m_postDecodeMethod) { case PostDecodeMethodType.pdmSwab16Bit: swab16BitData(buffer, offset, count); break; case PostDecodeMethodType.pdmSwab24Bit: swab24BitData(buffer, offset, count); break; case PostDecodeMethodType.pdmSwab32Bit: swab32BitData(buffer, offset, count); break; case PostDecodeMethodType.pdmSwab64Bit: swab64BitData(buffer, offset, count); break; } } private uint insertData(TiffType type, int v) { int t = (int)type; if (m_header.tiff_magic == TIFF_BIGENDIAN) return (((uint)v & m_typemask[t]) << m_typeshift[t]); return ((uint)v & m_typemask[t]); } private static void resetFieldBit(int[] fields, short f) { fields[f / 32] &= ~BITn(f); } private static bool fieldSet(int[] fields, short f) { return ((fields[f / 32] & BITn(f)) != 0); } private bool writeRational(TiffType type, TiffTag tag, ref TiffDirEntry dir, float v) { dir.tdir_tag = tag; dir.tdir_type = type; dir.tdir_count = 1; float[] a = new float[1]; a[0] = v; if (!writeRationalArray(ref dir, a)) return false; return true; } private bool writeRationalPair(TiffDirEntry[] entries, int dirOffset, TiffType type, TiffTag tag1, float v1, TiffTag tag2, float v2) { if (!writeRational(type, tag1, ref entries[dirOffset], v1)) return false; if (!writeRational(type, tag2, ref entries[dirOffset + 1], v2)) return false; return true; } /// /// Writes the contents of the current directory to the specified file. /// /// This routine doesn't handle overwriting a directory with /// auxiliary storage that's been changed. private bool writeDirectory(bool done) { if (m_mode == O_RDONLY) return true; // Clear write state so that subsequent images with different // characteristics get the right buffers setup for them. if (done) { if ((m_flags & TiffFlags.PostEncode) == TiffFlags.PostEncode) { m_flags &= ~TiffFlags.PostEncode; if (!m_currentCodec.PostEncode()) { ErrorExt(this, m_clientdata, m_name, "Error post-encoding before directory write"); return false; } } // shutdown encoder m_currentCodec.Close(); // Flush any data that might have been written by the // compression close+cleanup routines. if (m_rawcc > 0 && (m_flags & TiffFlags.BeenWriting) == TiffFlags.BeenWriting && !flushData1()) { ErrorExt(this, m_clientdata, m_name, "Error flushing data before directory write"); return false; } if ((m_flags & TiffFlags.MyBuffer) == TiffFlags.MyBuffer && m_rawdata != null) { m_rawdata = null; m_rawcc = 0; m_rawdatasize = 0; } m_flags &= ~(TiffFlags.BeenWriting | TiffFlags.BufferSetup); } // Size the directory so that we can calculate offsets for the data // items that aren't kept in-place in each field. int nfields = 0; for (int b = 0; b <= FieldBit.Last; b++) { if (fieldSet(b) && b != FieldBit.Custom) nfields += (b < FieldBit.SubFileType ? 2 : 1); } nfields += m_dir.td_customValueCount; int dirsize = nfields * TiffDirEntry.SizeInBytes; TiffDirEntry[] data = new TiffDirEntry[nfields]; for (int i = 0; i < nfields; i++) data[i] = new TiffDirEntry(); // Directory hasn't been placed yet, put it at the end of the file // and link it into the existing directory structure. if (m_diroff == 0 && !linkDirectory()) return false; m_dataoff = m_diroff + sizeof(short) + (uint)dirsize + sizeof(int); if ((m_dataoff & 1) != 0) m_dataoff++; seekFile(m_dataoff, SeekOrigin.Begin); m_curdir++; int dir = 0; // Setup external form of directory entries and write data items. int[] fields = new int[FieldBit.SetLongs]; Buffer.BlockCopy(m_dir.td_fieldsset, 0, fields, 0, FieldBit.SetLongs * sizeof(int)); // Write out ExtraSamples tag only if extra samples are present in the data. if (fieldSet(fields, FieldBit.ExtraSamples) && m_dir.td_extrasamples == 0) { resetFieldBit(fields, FieldBit.ExtraSamples); nfields--; dirsize -= TiffDirEntry.SizeInBytes; } // XXX for (int fi = 0, nfi = m_nfields; nfi > 0; nfi--, fi++) { TiffFieldInfo fip = m_fieldinfo[fi]; // For custom fields, we test to see if the custom field is set // or not. For normal fields, we just use the fieldSet test. if (fip.Bit == FieldBit.Custom) { bool is_set = false; for (int ci = 0; ci < m_dir.td_customValueCount; ci++) is_set |= (m_dir.td_customValues[ci].info == fip); if (!is_set) continue; } else if (!fieldSet(fields, fip.Bit)) { continue; } // Handle other fields. TiffTag tag = (TiffTag)FieldBit.Ignore; switch (fip.Bit) { case FieldBit.StripOffsets: // We use one field bit for both strip and tile // offsets, and so must be careful in selecting // the appropriate field descriptor (so that tags // are written in sorted order). tag = IsTiled() ? TiffTag.TileOffsets : TiffTag.StripOffsets; if (tag != fip.Tag) continue; data[dir].tdir_tag = tag; data[dir].tdir_type = TiffType.Long; data[dir].tdir_count = m_dir.td_nstrips; if (!writeLongArray(ref data[dir], m_dir.td_stripoffset)) return false; break; case FieldBit.StripByteCounts: // We use one field bit for both strip and tile byte // counts, and so must be careful in selecting the // appropriate field descriptor (so that tags are // written in sorted order). tag = IsTiled() ? TiffTag.TileByteCounts : TiffTag.StripByteCounts; if (tag != fip.Tag) continue; data[dir].tdir_tag = tag; data[dir].tdir_type = TiffType.Long; data[dir].tdir_count = m_dir.td_nstrips; if (!writeLongArray(ref data[dir], m_dir.td_stripbytecount)) return false; break; case FieldBit.RowsPerStrip: setupShortLong(TiffTag.RowsPerStrip, ref data[dir], m_dir.td_rowsperstrip); break; case FieldBit.ColorMap: if (!writeShortTable(TiffTag.Colormap, ref data[dir], 3, m_dir.td_colormap)) return false; break; case FieldBit.ImageDimensions: setupShortLong(TiffTag.ImageWidth, ref data[dir++], m_dir.td_imagewidth); setupShortLong(TiffTag.ImageLength, ref data[dir], m_dir.td_imagelength); break; case FieldBit.TileDimensions: setupShortLong(TiffTag.TileWidth, ref data[dir++], m_dir.td_tilewidth); setupShortLong(TiffTag.TileLength, ref data[dir], m_dir.td_tilelength); break; case FieldBit.Compression: setupShort(TiffTag.Compression, ref data[dir], (short)m_dir.td_compression); break; case FieldBit.Photometric: setupShort(TiffTag.Photometric, ref data[dir], (short)m_dir.td_photometric); break; case FieldBit.Position: if (!writeRationalPair(data, dir, TiffType.Rational, TiffTag.XPosition, m_dir.td_xposition, TiffTag.YPosition, m_dir.td_yposition)) return false; dir++; break; case FieldBit.Resolution: if (!writeRationalPair(data, dir, TiffType.Rational, TiffTag.XResolution, m_dir.td_xresolution, TiffTag.YResolution, m_dir.td_yresolution)) return false; dir++; break; case FieldBit.BitsPerSample: case FieldBit.MinSampleValue: case FieldBit.MaxSampleValue: case FieldBit.SampleFormat: if (!writePerSampleShorts(fip.Tag, ref data[dir])) return false; break; case FieldBit.SMinSampleValue: case FieldBit.SMaxSampleValue: if (!writePerSampleAnys(sampleToTagType(), fip.Tag, ref data[dir])) return false; break; case FieldBit.PageNumber: case FieldBit.HalftoneHints: case FieldBit.YCbCrSubsampling: if (!setupShortPair(fip.Tag, ref data[dir])) return false; break; case FieldBit.InkNames: if (!writeInkNames(ref data[dir])) return false; break; case FieldBit.TransferFunction: if (!writeTransferFunction(ref data[dir])) return false; break; case FieldBit.SubIFD: // XXX: Always write this field using Long type // for backward compatibility. data[dir].tdir_tag = fip.Tag; data[dir].tdir_type = TiffType.Long; data[dir].tdir_count = m_dir.td_nsubifd; if (!writeLongArray(ref data[dir], m_dir.td_subifd)) return false; // Total hack: if this directory includes a SubIFD // tag then force the next directories to be // written as "sub directories" of this one. This // is used to write things like thumbnails and // image masks that one wants to keep out of the // normal directory linkage access mechanism. if (data[dir].tdir_count > 0) { m_flags |= TiffFlags.InSubIFD; m_nsubifd = (short)data[dir].tdir_count; if (data[dir].tdir_count > 1) { m_subifdoff = data[dir].tdir_offset; } else { m_subifdoff = m_diroff + sizeof(short) + (uint)dir * TiffDirEntry.SizeInBytes + sizeof(short) * 2 + sizeof(int); } } break; default: // XXX: Should be fixed and removed. if (fip.Tag == TiffTag.DotRange) { if (!setupShortPair(fip.Tag, ref data[dir])) return false; } else if (!writeNormalTag(ref data[dir], fip)) return false; break; } dir++; if (fip.Bit != FieldBit.Custom) resetFieldBit(fields, fip.Bit); } // Write directory. short dircount = (short)nfields; uint diroff = m_nextdiroff; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { // The file's byte order is opposite to the native machine // architecture. We overwrite the directory information with // impunity because it'll be released below after we write it to // the file. Note that all the other tag construction routines // assume that we do this byte-swapping; i.e. they only // byte-swap indirect data. for (dir = 0; dircount != 0; dir++, dircount--) { short temp = (short)data[dir].tdir_tag; SwabShort(ref temp); data[dir].tdir_tag = (TiffTag)(ushort)temp; temp = (short)data[dir].tdir_type; SwabShort(ref temp); data[dir].tdir_type = (TiffType)temp; SwabLong(ref data[dir].tdir_count); SwabUInt(ref data[dir].tdir_offset); } dircount = (short)nfields; SwabShort(ref dircount); SwabUInt(ref diroff); } seekFile(m_diroff, SeekOrigin.Begin); if (!writeShortOK(dircount)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory count"); return false; } if (!writeDirEntryOK(data, dirsize / TiffDirEntry.SizeInBytes)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory contents"); return false; } if (!writeIntOK((int)diroff)) { ErrorExt(this, m_clientdata, m_name, "Error writing directory link"); return false; } if (done) { FreeDirectory(); m_flags &= ~TiffFlags.DirtyDirect; m_currentCodec.Cleanup(); // Reset directory-related state for subsequent directories. CreateDirectory(); } return true; } /// /// Writes tags that are not special cased. /// private bool writeNormalTag(ref TiffDirEntry dir, TiffFieldInfo fip) { short wc = fip.WriteCount; dir.tdir_tag = fip.Tag; dir.tdir_type = fip.Type; dir.tdir_count = wc; switch (fip.Type) { case TiffType.Short: case TiffType.SShort: if (fip.PassCount) { short[] wp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); wp = result[1].ToShortArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); wp = result[1].ToShortArray(); dir.tdir_count = wc; } if (!writeShortArray(ref dir, wp)) return false; } else { if (wc == 1) { FieldValue[] result = GetField(fip.Tag); short sv = result[0].ToShort(); dir.tdir_offset = insertData(dir.tdir_type, sv); } else { FieldValue[] result = GetField(fip.Tag); short[] wp = result[0].ToShortArray(); if (!writeShortArray(ref dir, wp)) return false; } } break; case TiffType.Long: case TiffType.SLong: case TiffType.IFD: if (fip.PassCount) { int[] lp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); lp = result[1].ToIntArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); lp = result[1].ToIntArray(); dir.tdir_count = wc; } if (!writeLongArray(ref dir, lp)) return false; } else { if (wc == 1) { // XXX handle Long->Short conversion FieldValue[] result = GetField(fip.Tag); dir.tdir_offset = result[0].ToUInt(); } else { int[] lp; FieldValue[] result = GetField(fip.Tag); lp = result[0].ToIntArray(); if (!writeLongArray(ref dir, lp)) return false; } } break; case TiffType.Rational: case TiffType.SRational: if (fip.PassCount) { float[] fp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); fp = result[1].ToFloatArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); fp = result[1].ToFloatArray(); dir.tdir_count = wc; } if (!writeRationalArray(ref dir, fp)) return false; } else { if (wc == 1) { float[] fv = new float[1]; FieldValue[] result = GetField(fip.Tag); fv[0] = result[0].ToFloat(); if (!writeRationalArray(ref dir, fv)) return false; } else { FieldValue[] result = GetField(fip.Tag); float[] fp = result[0].ToFloatArray(); if (!writeRationalArray(ref dir, fp)) return false; } } break; case TiffType.Float: if (fip.PassCount) { float[] fp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); fp = result[1].ToFloatArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); fp = result[1].ToFloatArray(); dir.tdir_count = wc; } if (!writeFloatArray(ref dir, fp)) return false; } else { if (wc == 1) { float[] fv = new float[1]; FieldValue[] result = GetField(fip.Tag); fv[0] = result[0].ToFloat(); if (!writeFloatArray(ref dir, fv)) return false; } else { FieldValue[] result = GetField(fip.Tag); float[] fp = result[0].ToFloatArray(); if (!writeFloatArray(ref dir, fp)) return false; } } break; case TiffType.Double: if (fip.PassCount) { double[] dp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); dp = result[1].ToDoubleArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); dp = result[1].ToDoubleArray(); dir.tdir_count = wc; } if (!writeDoubleArray(ref dir, dp)) return false; } else { if (wc == 1) { double[] dv = new double[1]; FieldValue[] result = GetField(fip.Tag); dv[0] = result[0].ToDouble(); if (!writeDoubleArray(ref dir, dv)) return false; } else { FieldValue[] result = GetField(fip.Tag); double[] dp = result[0].ToDoubleArray(); if (!writeDoubleArray(ref dir, dp)) return false; } } break; case TiffType.ASCII: { FieldValue[] result = GetField(fip.Tag); string cp; if (fip.PassCount) cp = result[1].ToString(); else cp = result[0].ToString(); // add zero ('\0') at the end of the byte array byte[] stringBytes = Latin1Encoding.GetBytes(cp); byte[] totalBytes = new byte[stringBytes.Length + 1]; Buffer.BlockCopy(stringBytes, 0, totalBytes, 0, stringBytes.Length); dir.tdir_count = totalBytes.Length; if (!writeByteArray(ref dir, totalBytes)) return false; } break; case TiffType.Byte: case TiffType.SByte: if (fip.PassCount) { byte[] cp; int wc2; if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); cp = result[1].ToByteArray(); dir.tdir_count = wc2; } else { // Assume TiffFieldInfo.Variable FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); cp = result[1].ToByteArray(); dir.tdir_count = wc; } if (!writeByteArray(ref dir, cp)) return false; } else { if (wc == 1) { byte[] cv = new byte[1]; FieldValue[] result = GetField(fip.Tag); cv[0] = result[0].ToByte(); if (!writeByteArray(ref dir, cv)) return false; } else { FieldValue[] result = GetField(fip.Tag); byte[] cp = result[0].ToByteArray(); if (!writeByteArray(ref dir, cp)) return false; } } break; case TiffType.Undefined: { byte[] cp; int wc2; if (wc == TiffFieldInfo.Variable) { FieldValue[] result = GetField(fip.Tag); wc = result[0].ToShort(); cp = result[1].ToByteArray(); dir.tdir_count = wc; } else if (wc == TiffFieldInfo.Variable2) { FieldValue[] result = GetField(fip.Tag); wc2 = result[0].ToInt(); cp = result[1].ToByteArray(); dir.tdir_count = wc2; } else { FieldValue[] result = GetField(fip.Tag); cp = result[0].ToByteArray(); } if (!writeByteArray(ref dir, cp)) return false; } break; case TiffType.NoType: break; } return true; } /// /// Setups a directory entry with either a Short or Long type /// according to the value. /// private void setupShortLong(TiffTag tag, ref TiffDirEntry dir, int v) { dir.tdir_tag = tag; dir.tdir_count = 1; if (v > 0xffffL) { dir.tdir_type = TiffType.Long; dir.tdir_offset = (uint)v; } else { dir.tdir_type = TiffType.Short; dir.tdir_offset = insertData(TiffType.Short, v); } } /// /// Setups a Short directory entry /// private void setupShort(TiffTag tag, ref TiffDirEntry dir, short v) { dir.tdir_tag = tag; dir.tdir_count = 1; dir.tdir_type = TiffType.Short; dir.tdir_offset = insertData(TiffType.Short, v); } /* * Setup a directory entry that references a * samples/pixel array of Short values and * (potentially) write the associated indirect * values. */ private bool writePerSampleShorts(TiffTag tag, ref TiffDirEntry dir) { short[] w = new short[m_dir.td_samplesperpixel]; FieldValue[] result = GetField(tag); short v = result[0].ToShort(); for (short i = 0; i < m_dir.td_samplesperpixel; i++) w[i] = v; dir.tdir_tag = tag; dir.tdir_type = TiffType.Short; dir.tdir_count = m_dir.td_samplesperpixel; bool status = writeShortArray(ref dir, w); return status; } /* * Setup a directory entry that references a samples/pixel array of ``type'' * values and (potentially) write the associated indirect values. The source * data from GetField() for the specified tag must be returned as double. */ private bool writePerSampleAnys(TiffType type, TiffTag tag, ref TiffDirEntry dir) { double[] w = new double[m_dir.td_samplesperpixel]; FieldValue[] result = GetField(tag); double v = result[0].ToDouble(); for (short i = 0; i < m_dir.td_samplesperpixel; i++) w[i] = v; bool status = writeAnyArray(type, tag, ref dir, m_dir.td_samplesperpixel, w); return status; } /* * Setup a pair of shorts that are returned by * value, rather than as a reference to an array. */ private bool setupShortPair(TiffTag tag, ref TiffDirEntry dir) { short[] v = new short[2]; FieldValue[] result = GetField(tag); v[0] = result[0].ToShort(); v[1] = result[1].ToShort(); dir.tdir_tag = tag; dir.tdir_type = TiffType.Short; dir.tdir_count = 2; return writeShortArray(ref dir, v); } /// /// Setup a directory entry for an NxM table of shorts, where M is /// known to be 2**bitspersample, and write the associated indirect data. /// private bool writeShortTable(TiffTag tag, ref TiffDirEntry dir, int n, short[][] table) { dir.tdir_tag = tag; dir.tdir_type = TiffType.Short; // XXX -- yech, fool writeData dir.tdir_count = 1 << m_dir.td_bitspersample; uint off = m_dataoff; for (int i = 0; i < n; i++) { if (!writeData(ref dir, table[i], dir.tdir_count)) return false; } dir.tdir_count *= n; dir.tdir_offset = off; return true; } /// /// Write/copy data associated with an ASCII or opaque tag value. /// private bool writeByteArray(ref TiffDirEntry dir, byte[] cp) { if (dir.tdir_count <= 4) { if (m_header.tiff_magic == TIFF_BIGENDIAN) { dir.tdir_offset = (uint)(cp[0] << 24); if (dir.tdir_count >= 2) dir.tdir_offset |= (uint)(cp[1] << 16); if (dir.tdir_count >= 3) dir.tdir_offset |= (uint)(cp[2] << 8); if (dir.tdir_count == 4) dir.tdir_offset |= cp[3]; } else { dir.tdir_offset = cp[0]; if (dir.tdir_count >= 2) dir.tdir_offset |= (uint)(cp[1] << 8); if (dir.tdir_count >= 3) dir.tdir_offset |= (uint)(cp[2] << 16); if (dir.tdir_count == 4) dir.tdir_offset |= (uint)(cp[3] << 24); } return true; } return writeData(ref dir, cp, dir.tdir_count); } /// /// Setup a directory entry of an array of Short or SShort and write /// the associated indirect values. /// private bool writeShortArray(ref TiffDirEntry dir, short[] v) { if (dir.tdir_count <= 2) { if (m_header.tiff_magic == TIFF_BIGENDIAN) { dir.tdir_offset = (uint)(v[0] << 16); if (dir.tdir_count == 2) dir.tdir_offset |= (uint)(v[1] & 0xffff); } else { dir.tdir_offset = (uint)(v[0] & 0xffff); if (dir.tdir_count == 2) dir.tdir_offset |= (uint)(v[1] << 16); } return true; } return writeData(ref dir, v, dir.tdir_count); } /// /// Setup a directory entry of an array of Long or SLong and write the /// associated indirect values. /// private bool writeLongArray(ref TiffDirEntry dir, int[] v) { if (dir.tdir_count == 1) { dir.tdir_offset = (uint)v[0]; return true; } return writeData(ref dir, v, dir.tdir_count); } private bool writeLongArray(ref TiffDirEntry dir, uint[] v) { int[] temp = new int[v.Length]; Buffer.BlockCopy(v, 0, temp, 0, v.Length * sizeof(uint)); return writeLongArray(ref dir, temp); } /// /// Setup a directory entry of an array of Rational or SRational and /// write the associated indirect values. /// private bool writeRationalArray(ref TiffDirEntry dir, float[] v) { int[] t = new int[2 * dir.tdir_count]; for (int i = 0; i < dir.tdir_count; i++) { int sign = 1; float fv = v[i]; if (fv < 0) { if (dir.tdir_type == TiffType.Rational) { WarningExt(this, m_clientdata, m_name, "\"{0}\": Information lost writing value ({1:G}) as (unsigned) Rational", FieldWithTag(dir.tdir_tag).Name, fv); fv = 0; } else { fv = -fv; sign = -1; } } int den = 1; if (fv > 0) { while (fv < (1L << (31 - 3)) && den < (1L << (31 - 3))) { fv *= 1 << 3; den *= 1 << 3; } } t[2 * i + 0] = (int)(sign * (fv + 0.5)); t[2 * i + 1] = den; } return writeData(ref dir, t, 2 * dir.tdir_count); } private bool writeFloatArray(ref TiffDirEntry dir, float[] v) { if (dir.tdir_count == 1) { dir.tdir_offset = BitConverter.ToUInt32(BitConverter.GetBytes(v[0]), 0); return true; } return writeData(ref dir, v, dir.tdir_count); } private bool writeDoubleArray(ref TiffDirEntry dir, double[] v) { return writeData(ref dir, v, dir.tdir_count); } /// /// Writes an array of "type" values for a specified tag (i.e. this is /// a tag which is allowed to have different types, e.g. SMaxSampleType). /// Internally the data values are represented as double since a double /// can hold any of the TIFF tag types (yes, this should really be an abstract /// type tany_t for portability). The data is converted into the specified /// type in a temporary buffer and then handed off to the appropriate array /// writer. /// private bool writeAnyArray(TiffType type, TiffTag tag, ref TiffDirEntry dir, int n, double[] v) { dir.tdir_tag = tag; dir.tdir_type = type; dir.tdir_count = n; bool failed = false; switch (type) { case TiffType.Byte: case TiffType.SByte: { byte[] bp = new byte[n]; for (int i = 0; i < n; i++) bp[i] = (byte)v[i]; if (!writeByteArray(ref dir, bp)) failed = true; } break; case TiffType.Short: case TiffType.SShort: { short[] bp = new short[n]; for (int i = 0; i < n; i++) bp[i] = (short)v[i]; if (!writeShortArray(ref dir, bp)) failed = true; } break; case TiffType.Long: case TiffType.SLong: { int[] bp = new int[n]; for (int i = 0; i < n; i++) bp[i] = (int)v[i]; if (!writeLongArray(ref dir, bp)) failed = true; } break; case TiffType.Float: { float[] bp = new float[n]; for (int i = 0; i < n; i++) bp[i] = (float)v[i]; if (!writeFloatArray(ref dir, bp)) failed = true; } break; case TiffType.Double: if (!writeDoubleArray(ref dir, v)) failed = true; break; default: // NoType // ASCII // Undefined // Rational // SRational failed = true; break; } return !failed; } private bool writeTransferFunction(ref TiffDirEntry dir) { // Check if the table can be written as a single column, or if it // must be written as 3 columns. Note that we write a 3-column tag // if there are 2 samples/pixel and a single column of data // won't suffice--hmm. int u = m_dir.td_samplesperpixel - m_dir.td_extrasamples; int ncols = 1; bool reCheck = false; int n = 1 << m_dir.td_bitspersample; if (u < 0 || u > 2) { if (Compare(m_dir.td_transferfunction[0], m_dir.td_transferfunction[2], n) != 0) ncols = 3; else reCheck = true; } if (u == 2 || reCheck) { if (Compare(m_dir.td_transferfunction[0], m_dir.td_transferfunction[1], n) != 0) ncols = 3; } return writeShortTable(TiffTag.TransferFunction, ref dir, ncols, m_dir.td_transferfunction); } private bool writeInkNames(ref TiffDirEntry dir) { dir.tdir_tag = TiffTag.InkNames; dir.tdir_type = TiffType.ASCII; byte[] bytes = Latin1Encoding.GetBytes(m_dir.td_inknames); dir.tdir_count = bytes.Length; return writeByteArray(ref dir, bytes); } /// /// Writes a contiguous directory item. /// private bool writeData(ref TiffDirEntry dir, byte[] buffer, int count) { dir.tdir_offset = m_dataoff; count = (int)dir.tdir_count * DataWidth(dir.tdir_type); if (seekOK(dir.tdir_offset) && writeOK(buffer, 0, count)) { m_dataoff += (uint)((count + 1) & ~1); return true; } ErrorExt(this, m_clientdata, m_name, "Error writing data for field \"{0}\"", FieldWithTag(dir.tdir_tag).Name); return false; } private bool writeData(ref TiffDirEntry dir, short[] buffer, int count) { if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabArrayOfShort(buffer, count); int byteCount = count * sizeof(short); byte[] bytes = new byte[byteCount]; ShortsToByteArray(buffer, 0, count, bytes, 0); return writeData(ref dir, bytes, byteCount); } private bool writeData(ref TiffDirEntry dir, int[] cp, int cc) { if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabArrayOfLong(cp, cc); int byteCount = cc * sizeof(int); byte[] bytes = new byte[byteCount]; IntsToByteArray(cp, 0, cc, bytes, 0); bool res = writeData(ref dir, bytes, byteCount); return res; } private bool writeData(ref TiffDirEntry dir, float[] cp, int cc) { int[] ints = new int[cc]; for (int i = 0; i < cc; i++) { byte[] result = BitConverter.GetBytes(cp[i]); ints[i] = BitConverter.ToInt32(result, 0); } return writeData(ref dir, ints, cc); } private bool writeData(ref TiffDirEntry dir, double[] buffer, int count) { if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabArrayOfDouble(buffer, count); byte[] bytes = new byte[count * sizeof(double)]; Buffer.BlockCopy(buffer, 0, bytes, 0, bytes.Length); return writeData(ref dir, bytes, count * sizeof(double)); } /// /// Link the current directory into the directory chain for the file. /// private bool linkDirectory() { const string module = "linkDirectory"; m_diroff = (uint)((seekFile(0, SeekOrigin.End) + 1) & ~1); uint diroff = m_diroff; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabUInt(ref diroff); // Handle SubIFDs if ((m_flags & TiffFlags.InSubIFD) == TiffFlags.InSubIFD) { seekFile(m_subifdoff, SeekOrigin.Begin); if (!writeIntOK((int)diroff)) { ErrorExt(this, m_clientdata, module, "{0}: Error writing SubIFD directory link", m_name); return false; } // Advance to the next SubIFD or, if this is the last one // configured, revert back to the normal directory linkage. --m_nsubifd; if (m_nsubifd != 0) m_subifdoff += sizeof(int); else m_flags &= ~TiffFlags.InSubIFD; return true; } if (m_header.tiff_diroff == 0) { // First directory, overwrite offset in header. m_header.tiff_diroff = m_diroff; seekFile(TiffHeader.TIFF_MAGIC_SIZE + TiffHeader.TIFF_VERSION_SIZE, SeekOrigin.Begin); if (!writeIntOK((int)diroff)) { ErrorExt(this, m_clientdata, m_name, "Error writing TIFF header"); return false; } return true; } // Not the first directory, search to the last and append. uint nextdir = m_header.tiff_diroff; do { short dircount; if (!seekOK(nextdir) || !readShortOK(out dircount)) { ErrorExt(this, m_clientdata, module, "Error fetching directory count"); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabShort(ref dircount); seekFile(dircount * TiffDirEntry.SizeInBytes, SeekOrigin.Current); if (!readUIntOK(out nextdir)) { ErrorExt(this, m_clientdata, module, "Error fetching directory link"); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabUInt(ref nextdir); } while (nextdir != 0); // get current offset long off = seekFile(0, SeekOrigin.Current); seekFile(off - sizeof(int), SeekOrigin.Begin); if (!writeIntOK((int)diroff)) { ErrorExt(this, m_clientdata, module, "Error writing directory link"); return false; } return true; } private int extractData(TiffDirEntry dir) { int type = (int)dir.tdir_type; if (m_header.tiff_magic == TIFF_BIGENDIAN) return (int)((dir.tdir_offset >> m_typeshift[type]) & m_typemask[type]); return (int)(dir.tdir_offset & m_typemask[type]); } private bool byteCountLooksBad(TiffDirectory td) { /* * Assume we have wrong StripByteCount value (in case of single strip) in * following cases: * - it is equal to zero along with StripOffset; * - it is larger than file itself (in case of uncompressed image); * - it is smaller than the size of the bytes per row multiplied on the * number of rows. The last case should not be checked in the case of * writing new image, because we may do not know the exact strip size * until the whole image will be written and directory dumped out. */ return ( (td.td_stripbytecount[0] == 0 && td.td_stripoffset[0] != 0) || (td.td_compression == Compression.None && td.td_stripbytecount[0] > getFileSize() - td.td_stripoffset[0]) || (m_mode == O_RDONLY && td.td_compression == Compression.None && td.td_stripbytecount[0] < ScanlineSize() * td.td_imagelength) ); } private static int howMany8(int x) { return ((x & 0x07) != 0 ? (x >> 3) + 1 : x >> 3); } private bool estimateStripByteCounts(TiffDirEntry[] dir, short dircount) { const string module = "estimateStripByteCounts"; m_dir.td_stripbytecount = new uint[m_dir.td_nstrips]; if (m_dir.td_compression != Compression.None) { long space = TiffHeader.SizeInBytes + sizeof(short) + (dircount * TiffDirEntry.SizeInBytes) + sizeof(int); long filesize = getFileSize(); // calculate amount of space used by indirect values for (short n = 0; n < dircount; n++) { int cc = DataWidth((TiffType)dir[n].tdir_type); if (cc == 0) { ErrorExt(this, m_clientdata, module, "{0}: Cannot determine size of unknown tag type {1}", m_name, dir[n].tdir_type); return false; } cc = cc * dir[n].tdir_count; if (cc > sizeof(int)) space += cc; } space = filesize - space; if (m_dir.td_planarconfig == PlanarConfig.Separate) space /= m_dir.td_samplesperpixel; int strip = 0; for (; strip < m_dir.td_nstrips; strip++) m_dir.td_stripbytecount[strip] = (uint)space; // This gross hack handles the case were the offset to the last // strip is past the place where we think the strip should begin. // Since a strip of data must be contiguous, it's safe to assume // that we've overestimated the amount of data in the strip and // trim this number back accordingly. strip--; if ((m_dir.td_stripoffset[strip] + m_dir.td_stripbytecount[strip]) > filesize) m_dir.td_stripbytecount[strip] = (uint)(filesize - m_dir.td_stripoffset[strip]); } else if (IsTiled()) { int bytespertile = TileSize(); for (int strip = 0; strip < m_dir.td_nstrips; strip++) m_dir.td_stripbytecount[strip] = (uint)bytespertile; } else { int rowbytes = ScanlineSize(); int rowsperstrip = m_dir.td_imagelength / m_dir.td_stripsperimage; for (int strip = 0; strip < m_dir.td_nstrips; strip++) m_dir.td_stripbytecount[strip] = (uint)(rowbytes * rowsperstrip); } setFieldBit(FieldBit.StripByteCounts); if (!fieldSet(FieldBit.RowsPerStrip)) m_dir.td_rowsperstrip = m_dir.td_imagelength; return true; } private void missingRequired(string tagname) { const string module = "missingRequired"; ErrorExt(this, m_clientdata, module, "{0}: TIFF directory is missing required \"{1}\" field", m_name, tagname); } private int fetchFailed(TiffDirEntry dir) { ErrorExt(this, m_clientdata, m_name, "Error fetching data for field \"{0}\"", FieldWithTag(dir.tdir_tag).Name); return 0; } private static int readDirectoryFind(TiffDirEntry[] dir, short dircount, TiffTag tagid) { for (short n = 0; n < dircount; n++) { if (dir[n].tdir_tag == tagid) return n; } return -1; } /// /// Checks the directory offset against the list of already seen directory /// offsets. /// /// This is a trick to prevent IFD looping. The one can /// create TIFF file with looped directory pointers. We will maintain a /// list of already seen directories and check every IFD offset against /// that list. private bool checkDirOffset(uint diroff) { if (diroff == 0) { // no more directories return false; } for (short n = 0; n < m_dirnumber && m_dirlist != null; n++) { if (m_dirlist[n] == diroff) return false; } m_dirnumber++; if (m_dirnumber > m_dirlistsize) { // XXX: Reduce memory allocation granularity of the dirlist array. uint[] new_dirlist = Realloc(m_dirlist, m_dirnumber - 1, 2 * m_dirnumber); m_dirlistsize = 2 * m_dirnumber; m_dirlist = new_dirlist; } m_dirlist[m_dirnumber - 1] = diroff; return true; } /// /// Reads IFD structure from the specified offset. /// /// The number of fields in the directory or 0 if failed. private short fetchDirectory(uint diroff, out TiffDirEntry[] pdir, out uint nextdiroff) { const string module = "fetchDirectory"; m_diroff = diroff; nextdiroff = 0; short dircount; TiffDirEntry[] dir = null; pdir = null; if (!seekOK(m_diroff)) { ErrorExt(this, m_clientdata, module, "{0}: Seek error accessing TIFF directory", m_name); return 0; } if (!readShortOK(out dircount)) { ErrorExt(this, m_clientdata, module, "{0}: Can not read TIFF directory count", m_name); return 0; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabShort(ref dircount); dir = new TiffDirEntry[dircount]; if (!readDirEntryOk(dir, dircount)) { ErrorExt(this, m_clientdata, module, "{0}: Can not read TIFF directory", m_name); return 0; } // Read offset to next directory for sequential scans. int temp; readIntOK(out temp); nextdiroff = (uint)temp; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { temp = (int)nextdiroff; SwabLong(ref temp); nextdiroff = (uint)temp; } pdir = dir; return dircount; } /* * Fetch and set the SubjectDistance EXIF tag. */ private bool fetchSubjectDistance(TiffDirEntry dir) { if (dir.tdir_count != 1 || dir.tdir_type != TiffType.Rational) { Tiff.WarningExt(this, m_clientdata, m_name, "incorrect count or type for SubjectDistance, tag ignored"); return false; } bool ok = false; byte[] b = new byte[2 * sizeof(int)]; int read = fetchData(dir, b); if (read != 0) { int[] l = new int[2]; l[0] = readInt(b, 0); l[1] = readInt(b, sizeof(int)); float v; if (cvtRational(dir, l[0], l[1], out v)) { /* * XXX: Numerator -1 means that we have infinite * distance. Indicate that with a negative floating point * SubjectDistance value. */ ok = SetField(dir.tdir_tag, (l[0] != -1) ? v : -v); } } return ok; } /* * Check the count field of a directory * entry against a known value. The caller * is expected to skip/ignore the tag if * there is a mismatch. */ private bool checkDirCount(TiffDirEntry dir, int count) { if (count > dir.tdir_count) { WarningExt(this, m_clientdata, m_name, "incorrect count for field \"{0}\" ({1}, expecting {2}); tag ignored", FieldWithTag(dir.tdir_tag).Name, dir.tdir_count, count); return false; } else if (count < dir.tdir_count) { WarningExt(this, m_clientdata, m_name, "incorrect count for field \"{0}\" ({1}, expecting {2}); tag trimmed", FieldWithTag(dir.tdir_tag).Name, dir.tdir_count, count); return true; } return true; } /// /// Fetches a contiguous directory item. /// private int fetchData(TiffDirEntry dir, byte[] buffer) { int width = DataWidth(dir.tdir_type); int count = (int)dir.tdir_count * width; // Check for overflow. if (dir.tdir_count == 0 || width == 0 || (count / width) != dir.tdir_count) fetchFailed(dir); if (!seekOK(dir.tdir_offset)) fetchFailed(dir); if (!readOK(buffer, count)) fetchFailed(dir); if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { switch (dir.tdir_type) { case TiffType.Short: case TiffType.SShort: short[] s = ByteArrayToShorts(buffer, 0, count); SwabArrayOfShort(s, dir.tdir_count); ShortsToByteArray(s, 0, dir.tdir_count, buffer, 0); break; case TiffType.Long: case TiffType.SLong: case TiffType.Float: int[] l = ByteArrayToInts(buffer, 0, count); SwabArrayOfLong(l, dir.tdir_count); IntsToByteArray(l, 0, dir.tdir_count, buffer, 0); break; case TiffType.Rational: case TiffType.SRational: int[] r = ByteArrayToInts(buffer, 0, count); SwabArrayOfLong(r, 2 * dir.tdir_count); IntsToByteArray(r, 0, 2 * dir.tdir_count, buffer, 0); break; case TiffType.Double: swab64BitData(buffer, 0, count); break; } } return count; } /// /// Fetches an ASCII item from the file. /// private int fetchString(TiffDirEntry dir, out string cp) { byte[] bytes = null; if (dir.tdir_count <= 4) { int l = (int)dir.tdir_offset; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabLong(ref l); bytes = new byte[sizeof(int)]; writeInt(l, bytes, 0); cp = Latin1Encoding.GetString(bytes, 0, dir.tdir_count); return 1; } bytes = new byte[dir.tdir_count]; int res = fetchData(dir, bytes); cp = Latin1Encoding.GetString(bytes, 0, dir.tdir_count); return res; } /* * Convert numerator+denominator to float. */ private bool cvtRational(TiffDirEntry dir, int num, int denom, out float rv) { if (denom == 0) { ErrorExt(this, m_clientdata, m_name, "{0}: Rational with zero denominator (num = {1})", FieldWithTag(dir.tdir_tag).Name, num); rv = float.NaN; return false; } else { rv = ((float)num / (float)denom); return true; } } /* * Fetch a rational item from the file * at offset off and return the value * as a floating point number. */ private float fetchRational(TiffDirEntry dir) { byte[] bytes = new byte[sizeof(int) * 2]; int read = fetchData(dir, bytes); if (read != 0) { int[] l = new int[2]; l[0] = readInt(bytes, 0); l[1] = readInt(bytes, sizeof(int)); float v; bool res = cvtRational(dir, l[0], l[1], out v); if (res) return v; } return 1.0f; } /// /// Fetch a single floating point value from the offset field and /// return it as a native float. /// private float fetchFloat(TiffDirEntry dir) { int l = extractData(dir); return BitConverter.ToSingle(BitConverter.GetBytes(l), 0); } /// /// Fetches an array of Byte or SByte values. /// private bool fetchByteArray(TiffDirEntry dir, byte[] v) { if (dir.tdir_count <= 4) { // Extract data from offset field. int count = dir.tdir_count; if (m_header.tiff_magic == TIFF_BIGENDIAN) { if (count == 4) v[3] = (byte)(dir.tdir_offset & 0xff); if (count >= 3) v[2] = (byte)((dir.tdir_offset >> 8) & 0xff); if (count >= 2) v[1] = (byte)((dir.tdir_offset >> 16) & 0xff); if (count >= 1) v[0] = (byte)(dir.tdir_offset >> 24); } else { if (count == 4) v[3] = (byte)(dir.tdir_offset >> 24); if (count >= 3) v[2] = (byte)((dir.tdir_offset >> 16) & 0xff); if (count >= 2) v[1] = (byte)((dir.tdir_offset >> 8) & 0xff); if (count >= 1) v[0] = (byte)(dir.tdir_offset & 0xff); } return true; } return (fetchData(dir, v) != 0); } /// /// Fetch an array of Short or SShort values. /// private bool fetchShortArray(TiffDirEntry dir, short[] v) { if (dir.tdir_count <= 2) { int count = dir.tdir_count; if (m_header.tiff_magic == TIFF_BIGENDIAN) { if (count == 2) v[1] = (short)(dir.tdir_offset & 0xffff); if (count >= 1) v[0] = (short)(dir.tdir_offset >> 16); } else { if (count == 2) v[1] = (short)(dir.tdir_offset >> 16); if (count >= 1) v[0] = (short)(dir.tdir_offset & 0xffff); } return true; } int cc = dir.tdir_count * sizeof(short); byte[] b = new byte[cc]; int read = fetchData(dir, b); if (read != 0) Buffer.BlockCopy(b, 0, v, 0, b.Length); return (read != 0); } /* * Fetch a pair of Short or Byte values. Some tags may have either Byte * or Short type and this function works with both ones. */ private bool fetchShortPair(TiffDirEntry dir) { /* * Prevent overflowing arrays below by performing a sanity * check on tdir_count, this should never be greater than two. */ if (dir.tdir_count > 2) { WarningExt(this, m_clientdata, m_name, "unexpected count for field \"{0}\", {1}, expected 2; ignored", FieldWithTag(dir.tdir_tag).Name, dir.tdir_count); return false; } switch (dir.tdir_type) { case TiffType.Byte: case TiffType.SByte: byte[] bytes = new byte[4]; return fetchByteArray(dir, bytes) && SetField(dir.tdir_tag, bytes[0], bytes[1]); case TiffType.Short: case TiffType.SShort: short[] shorts = new short[2]; return fetchShortArray(dir, shorts) && SetField(dir.tdir_tag, shorts[0], shorts[1]); } return false; } /// /// Fetches an array of Long or SLong values. /// private bool fetchLongArray(TiffDirEntry dir, int[] v) { if (dir.tdir_count == 1) { v[0] = (int)dir.tdir_offset; return true; } int cc = dir.tdir_count * sizeof(int); byte[] b = new byte[cc]; int read = fetchData(dir, b); if (read != 0) Buffer.BlockCopy(b, 0, v, 0, b.Length); return (read != 0); } /// /// Fetch an array of Rational or SRational values. /// private bool fetchRationalArray(TiffDirEntry dir, float[] v) { Debug.Assert(sizeof(float) == sizeof(int)); bool ok = false; byte[] l = new byte[dir.tdir_count * DataWidth(dir.tdir_type)]; if (fetchData(dir, l) != 0) { int offset = 0; int[] pair = new int[2]; for (int i = 0; i < dir.tdir_count; i++) { pair[0] = readInt(l, offset); offset += sizeof(int); pair[1] = readInt(l, offset); offset += sizeof(int); ok = cvtRational(dir, pair[0], pair[1], out v[i]); if (!ok) break; } } return ok; } /// /// Fetches an array of Float values. /// private bool fetchFloatArray(TiffDirEntry dir, float[] v) { if (dir.tdir_count == 1) { v[0] = BitConverter.ToSingle(BitConverter.GetBytes(dir.tdir_offset), 0); return true; } int w = DataWidth(dir.tdir_type); int cc = dir.tdir_count * w; byte[] b = new byte[cc]; int read = fetchData(dir, b); if (read != 0) { int byteOffset = 0; for (int i = 0; i < read / 4; i++) { v[i] = BitConverter.ToSingle(b, byteOffset); byteOffset += 4; } } return (read != 0); } /// /// Fetches an array of Double values. /// private bool fetchDoubleArray(TiffDirEntry dir, double[] v) { int w = DataWidth(dir.tdir_type); int cc = dir.tdir_count * w; byte[] b = new byte[cc]; int read = fetchData(dir, b); if (read != 0) { int byteOffset = 0; for (int i = 0; i < read / 8; i++) { v[i] = BitConverter.ToDouble(b, byteOffset); byteOffset += 8; } } return (read != 0); } /// /// Fetches an array of Any values. /// /// The actual values are returned as doubles which should be /// able hold all the types. Note in particular that we assume that the /// double return value vector is large enough to read in any /// fundamental type. We use that vector as a buffer to read in the base /// type vector and then convert it in place to double (from end to /// front of course). private bool fetchAnyArray(TiffDirEntry dir, double[] v) { int i = 0; bool res = false; switch (dir.tdir_type) { case TiffType.Byte: case TiffType.SByte: byte[] b = new byte[dir.tdir_count]; res = fetchByteArray(dir, b); if (res) { for (i = dir.tdir_count - 1; i >= 0; i--) v[i] = b[i]; } if (!res) return false; break; case TiffType.Short: case TiffType.SShort: short[] u = new short[dir.tdir_count]; res = fetchShortArray(dir, u); if (res) { for (i = dir.tdir_count - 1; i >= 0; i--) v[i] = u[i]; } if (!res) return false; break; case TiffType.Long: case TiffType.SLong: int[] l = new int[dir.tdir_count]; res = fetchLongArray(dir, l); if (res) { for (i = dir.tdir_count - 1; i >= 0; i--) v[i] = l[i]; } if (!res) return false; break; case TiffType.Rational: case TiffType.SRational: float[] r = new float[dir.tdir_count]; res = fetchRationalArray(dir, r); if (res) { for (i = dir.tdir_count - 1; i >= 0; i--) v[i] = r[i]; } if (!res) return false; break; case TiffType.Float: float[] f = new float[dir.tdir_count]; res = fetchFloatArray(dir, f); if (res) { for (i = dir.tdir_count - 1; i >= 0; i--) v[i] = f[i]; } if (!res) return false; break; case TiffType.Double: return fetchDoubleArray(dir, v); default: // NoType // ASCII // Undefined ErrorExt(this, m_clientdata, m_name, "cannot read TIFF_ANY type {0} for field \"{1}\"", dir.tdir_type, FieldWithTag(dir.tdir_tag).Name); return false; } return true; } /// /// Fetches a tag that is not handled by special case code. /// private bool fetchNormalTag(TiffDirEntry dir) { bool ok = false; TiffFieldInfo fip = FieldWithTag(dir.tdir_tag); if (dir.tdir_count > 1) { switch (dir.tdir_type) { case TiffType.Byte: case TiffType.SByte: byte[] bytes = new byte[dir.tdir_count]; ok = fetchByteArray(dir, bytes); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, bytes); else ok = SetField(dir.tdir_tag, bytes); } break; case TiffType.Short: case TiffType.SShort: short[] shorts = new short[dir.tdir_count]; ok = fetchShortArray(dir, shorts); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, shorts); else ok = SetField(dir.tdir_tag, shorts); } break; case TiffType.Long: case TiffType.SLong: int[] ints = new int[dir.tdir_count]; ok = fetchLongArray(dir, ints); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, ints); else ok = SetField(dir.tdir_tag, ints); } break; case TiffType.Rational: case TiffType.SRational: float[] rs = new float[dir.tdir_count]; ok = fetchRationalArray(dir, rs); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, rs); else ok = SetField(dir.tdir_tag, rs); } break; case TiffType.Float: float[] fs = new float[dir.tdir_count]; ok = fetchFloatArray(dir, fs); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, fs); else ok = SetField(dir.tdir_tag, fs); } break; case TiffType.Double: double[] ds = new double[dir.tdir_count]; ok = fetchDoubleArray(dir, ds); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, ds); else ok = SetField(dir.tdir_tag, ds); } break; case TiffType.ASCII: case TiffType.Undefined: // bit of a cheat... // Some vendors write strings w/o the trailing null // byte, so always append one just in case. string cp; ok = fetchString(dir, out cp) != 0; if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, dir.tdir_count, cp); else ok = SetField(dir.tdir_tag, cp); } break; } } else if (checkDirCount(dir, 1)) { int v32 = 0; // singleton value switch (dir.tdir_type) { case TiffType.Byte: case TiffType.SByte: case TiffType.Short: case TiffType.SShort: // If the tag is also acceptable as a Long or SLong // then SetField will expect an int parameter // passed to it. // // NB: We use FieldWithTag here knowing that // it returns us the first entry in the table // for the tag and that that entry is for the // widest potential data type the tag may have. TiffType type = fip.Type; if (type != TiffType.Long && type != TiffType.SLong) { short v = (short)extractData(dir); if (fip.PassCount) { short[] a = new short[1]; a[0] = v; ok = SetField(dir.tdir_tag, 1, a); } else ok = SetField(dir.tdir_tag, v); break; } v32 = extractData(dir); if (fip.PassCount) { int[] a = new int[1]; a[0] = (int)v32; ok = SetField(dir.tdir_tag, 1, a); } else ok = SetField(dir.tdir_tag, v32); break; case TiffType.Long: case TiffType.SLong: v32 = extractData(dir); if (fip.PassCount) { int[] a = new int[1]; a[0] = (int)v32; ok = SetField(dir.tdir_tag, 1, a); } else ok = SetField(dir.tdir_tag, v32); break; case TiffType.Rational: case TiffType.SRational: case TiffType.Float: float f = (dir.tdir_type == TiffType.Float ? fetchFloat(dir) : fetchRational(dir)); if (fip.PassCount) { float[] a = new float[1]; a[0] = f; ok = SetField(dir.tdir_tag, 1, a); } else ok = SetField(dir.tdir_tag, f); break; case TiffType.Double: double[] ds = new double[1]; ok = fetchDoubleArray(dir, ds); if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, 1, ds); else ok = SetField(dir.tdir_tag, ds[0]); } break; case TiffType.ASCII: case TiffType.Undefined: // bit of a cheat... string c; ok = fetchString(dir, out c) != 0; if (ok) { if (fip.PassCount) ok = SetField(dir.tdir_tag, 1, c); else ok = SetField(dir.tdir_tag, c); } break; } } return ok; } /// /// Fetches samples/pixel short values for the specified tag and verify /// that all values are the same. /// private bool fetchPerSampleShorts(TiffDirEntry dir, out short pl) { pl = 0; short samples = m_dir.td_samplesperpixel; bool status = false; if (checkDirCount(dir, samples)) { short[] v = new short[dir.tdir_count]; if (fetchShortArray(dir, v)) { int check_count = dir.tdir_count; if (samples < check_count) check_count = samples; bool failed = false; for (ushort i = 1; i < check_count; i++) { if (v[i] != v[0]) { ErrorExt(this, m_clientdata, m_name, "Cannot handle different per-sample values for field \"{0}\"", FieldWithTag(dir.tdir_tag).Name); failed = true; break; } } if (!failed) { pl = v[0]; status = true; } } } return status; } /// /// Fetches samples/pixel long values for the specified tag and verify /// that all values are the same. /// private bool fetchPerSampleLongs(TiffDirEntry dir, out int pl) { pl = 0; short samples = m_dir.td_samplesperpixel; bool status = false; if (checkDirCount(dir, samples)) { int[] v = new int[dir.tdir_count]; if (fetchLongArray(dir, v)) { int check_count = dir.tdir_count; if (samples < check_count) check_count = samples; bool failed = false; for (ushort i = 1; i < check_count; i++) { if (v[i] != v[0]) { ErrorExt(this, m_clientdata, m_name, "Cannot handle different per-sample values for field \"{0}\"", FieldWithTag(dir.tdir_tag).Name); failed = true; break; } } if (!failed) { pl = (int)v[0]; status = true; } } } return status; } /// /// Fetches samples/pixel Any values for the specified tag and verify /// that all values are the same. /// private bool fetchPerSampleAnys(TiffDirEntry dir, out double pl) { pl = 0; short samples = m_dir.td_samplesperpixel; bool status = false; if (checkDirCount(dir, samples)) { double[] v = new double[dir.tdir_count]; if (fetchAnyArray(dir, v)) { int check_count = dir.tdir_count; if (samples < check_count) check_count = samples; bool failed = false; for (ushort i = 1; i < check_count; i++) { if (v[i] != v[0]) { ErrorExt(this, m_clientdata, m_name, "Cannot handle different per-sample values for field \"{0}\"", FieldWithTag(dir.tdir_tag).Name); failed = true; break; } } if (!failed) { pl = v[0]; status = true; } } } return status; } /// /// Fetches a set of offsets or lengths. /// /// While this routine says "strips", in fact it's also used /// for tiles. private bool fetchStripThing(TiffDirEntry dir, int nstrips, ref int[] lpp) { checkDirCount(dir, nstrips); // Allocate space for strip information. if (lpp == null) lpp = new int[nstrips]; else Array.Clear(lpp, 0, lpp.Length); bool status = false; if (dir.tdir_type == TiffType.Short) { // Handle short -> int expansion. short[] dp = new short[dir.tdir_count]; status = fetchShortArray(dir, dp); if (status) { for (int i = 0; i < nstrips && i < dir.tdir_count; i++) lpp[i] = dp[i]; } } else if (nstrips != dir.tdir_count) { // Special case to correct length int[] dp = new int[dir.tdir_count]; status = fetchLongArray(dir, dp); if (status) { for (int i = 0; i < nstrips && i < dir.tdir_count; i++) lpp[i] = dp[i]; } } else { status = fetchLongArray(dir, lpp); } return status; } private bool fetchStripThing(TiffDirEntry dir, int nstrips, ref uint[] lpp) { int[] temp = null; if (lpp != null) temp = new int[lpp.Length]; bool res = fetchStripThing(dir, nstrips, ref temp); if (res) { if (lpp == null) lpp = new uint[temp.Length]; Buffer.BlockCopy(temp, 0, lpp, 0, temp.Length * sizeof(uint)); } return res; } /// /// Fetches and sets the RefBlackWhite tag. /// private bool fetchRefBlackWhite(TiffDirEntry dir) { if (dir.tdir_type == TiffType.Rational) return fetchNormalTag(dir); // Handle Long's for backward compatibility. int[] cp = new int[dir.tdir_count]; bool ok = fetchLongArray(dir, cp); if (ok) { float[] fp = new float[dir.tdir_count]; for (int i = 0; i < dir.tdir_count; i++) fp[i] = (float)cp[i]; ok = SetField(dir.tdir_tag, fp); } return ok; } /// /// Replace a single strip (tile) of uncompressed data with multiple /// strips (tiles), each approximately 8Kbytes. /// /// This is useful for dealing with large images or for /// dealing with machines with a limited amount of memory. private void chopUpSingleUncompressedStrip() { uint bytecount = m_dir.td_stripbytecount[0]; uint offset = m_dir.td_stripoffset[0]; // Make the rows hold at least one scanline, but fill specified // amount of data if possible. int rowbytes = VTileSize(1); uint stripbytes; int rowsperstrip; if (rowbytes > STRIP_SIZE_DEFAULT) { stripbytes = (uint)rowbytes; rowsperstrip = 1; } else if (rowbytes > 0) { rowsperstrip = STRIP_SIZE_DEFAULT / rowbytes; stripbytes = (uint)(rowbytes * rowsperstrip); } else { return; } // never increase the number of strips in an image if (rowsperstrip >= m_dir.td_rowsperstrip) return; uint nstrips = howMany(bytecount, stripbytes); if (nstrips == 0) { // something is wonky, do nothing. return; } uint[] newcounts = new uint[nstrips]; uint[] newoffsets = new uint[nstrips]; // Fill the strip information arrays with new bytecounts and offsets // that reflect the broken-up format. for (int strip = 0; strip < nstrips; strip++) { if (stripbytes > bytecount) stripbytes = bytecount; newcounts[strip] = stripbytes; newoffsets[strip] = offset; offset += stripbytes; bytecount -= stripbytes; } // Replace old single strip info with multi-strip info. m_dir.td_nstrips = (int)nstrips; m_dir.td_stripsperimage = (int)nstrips; SetField(TiffTag.RowsPerStrip, rowsperstrip); m_dir.td_stripbytecount = newcounts; m_dir.td_stripoffset = newoffsets; m_dir.td_stripbytecountsorted = true; } internal static int roundUp(int x, int y) { return (howMany(x, y) * y); } internal static int howMany(int x, int y) { long res = (((long)x + ((long)y - 1)) / (long)y); if (res > int.MaxValue) return 0; return (int)res; } internal static uint howMany(uint x, uint y) { long res = (((long)x + ((long)y - 1)) / (long)y); if (res > uint.MaxValue) return 0; return (uint)res; } /// /// NB: THIS ARRAY IS ASSUMED TO BE SORTED BY TAG. /// If a tag can have both Long and Short types then the Long must /// be placed before the Short for writing to work properly. /// /// NOTE: The second field (field_readcount) and third field /// (field_writecount) sometimes use the values /// TiffFieldInfo.Variable (-1), TiffFieldInfo.Variable2 (-3) /// and TiffFieldInfo.Spp (-2). These values should be used but /// would throw off the formatting of the code, so please /// interpret the -1, -2 and -3 values accordingly. /// static TiffFieldInfo[] tiffFieldInfo = { new TiffFieldInfo(TiffTag.SubFileType, 1, 1, TiffType.Long, FieldBit.SubFileType, true, false, "SubfileType"), /* XXX Short for compatibility w/ old versions of the library */ new TiffFieldInfo(TiffTag.SubFileType, 1, 1, TiffType.Short, FieldBit.SubFileType, true, false, "SubfileType"), new TiffFieldInfo(TiffTag.OSubFileType, 1, 1, TiffType.Short, FieldBit.SubFileType, true, false, "OldSubfileType"), new TiffFieldInfo(TiffTag.ImageWidth, 1, 1, TiffType.Long, FieldBit.ImageDimensions, false, false, "ImageWidth"), new TiffFieldInfo(TiffTag.ImageWidth, 1, 1, TiffType.Short, FieldBit.ImageDimensions, false, false, "ImageWidth"), new TiffFieldInfo(TiffTag.ImageLength, 1, 1, TiffType.Long, FieldBit.ImageDimensions, true, false, "ImageLength"), new TiffFieldInfo(TiffTag.ImageLength, 1, 1, TiffType.Short, FieldBit.ImageDimensions, true, false, "ImageLength"), new TiffFieldInfo(TiffTag.BitsPerSample, -1, -1, TiffType.Short, FieldBit.BitsPerSample, false, false, "BitsPerSample"), /* XXX Long for compatibility with some broken TIFF writers */ new TiffFieldInfo(TiffTag.BitsPerSample, -1, -1, TiffType.Long, FieldBit.BitsPerSample, false, false, "BitsPerSample"), new TiffFieldInfo(TiffTag.Compression, -1, 1, TiffType.Short, FieldBit.Compression, false, false, "Compression"), /* XXX Long for compatibility with some broken TIFF writers */ new TiffFieldInfo(TiffTag.Compression, -1, 1, TiffType.Long, FieldBit.Compression, false, false, "Compression"), new TiffFieldInfo(TiffTag.Photometric, 1, 1, TiffType.Short, FieldBit.Photometric, false, false, "PhotometricInterpretation"), /* XXX Long for compatibility with some broken TIFF writers */ new TiffFieldInfo(TiffTag.Photometric, 1, 1, TiffType.Long, FieldBit.Photometric, false, false, "PhotometricInterpretation"), new TiffFieldInfo(TiffTag.Threshholding, 1, 1, TiffType.Short, FieldBit.Thresholding, true, false, "Threshholding"), new TiffFieldInfo(TiffTag.CellWidth, 1, 1, TiffType.Short, FieldBit.Ignore, true, false, "CellWidth"), new TiffFieldInfo(TiffTag.CellLength, 1, 1, TiffType.Short, FieldBit.Ignore, true, false, "CellLength"), new TiffFieldInfo(TiffTag.FillOrder, 1, 1, TiffType.Short, FieldBit.FillOrder, false, false, "BitOrder"), new TiffFieldInfo(TiffTag.DocumentName, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "DocumentName"), new TiffFieldInfo(TiffTag.ImageDescription, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "ImageDescription"), new TiffFieldInfo(TiffTag.Manufacturer, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "Make"), new TiffFieldInfo(TiffTag.Model, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "Model"), new TiffFieldInfo(TiffTag.StripOffsets, -1, -1, TiffType.Long, FieldBit.StripOffsets, false, false, "StripOffsets"), new TiffFieldInfo(TiffTag.StripOffsets, -1, -1, TiffType.Short, FieldBit.StripOffsets, false, false, "StripOffsets"), new TiffFieldInfo(TiffTag.Orientation, 1, 1, TiffType.Short, FieldBit.Orientation, false, false, "Orientation"), new TiffFieldInfo(TiffTag.SamplesPerPixel, 1, 1, TiffType.Short, FieldBit.SamplesPerPixel, false, false, "SamplesPerPixel"), new TiffFieldInfo(TiffTag.RowsPerStrip, 1, 1, TiffType.Long, FieldBit.RowsPerStrip, false, false, "RowsPerStrip"), new TiffFieldInfo(TiffTag.RowsPerStrip, 1, 1, TiffType.Short, FieldBit.RowsPerStrip, false, false, "RowsPerStrip"), new TiffFieldInfo(TiffTag.StripByteCounts, -1, -1, TiffType.Long, FieldBit.StripByteCounts, false, false, "StripByteCounts"), new TiffFieldInfo(TiffTag.StripByteCounts, -1, -1, TiffType.Short, FieldBit.StripByteCounts, false, false, "StripByteCounts"), new TiffFieldInfo(TiffTag.MinSampleValue, -2, -1, TiffType.Short, FieldBit.MinSampleValue, true, false, "MinSampleValue"), new TiffFieldInfo(TiffTag.MaxSampleValue, -2, -1, TiffType.Short, FieldBit.MaxSampleValue, true, false, "MaxSampleValue"), new TiffFieldInfo(TiffTag.XResolution, 1, 1, TiffType.Rational, FieldBit.Resolution, true, false, "XResolution"), new TiffFieldInfo(TiffTag.YResolution, 1, 1, TiffType.Rational, FieldBit.Resolution, true, false, "YResolution"), new TiffFieldInfo(TiffTag.PlanarConfig, 1, 1, TiffType.Short, FieldBit.PlanarConfig, false, false, "PlanarConfiguration"), new TiffFieldInfo(TiffTag.PageName, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "PageName"), new TiffFieldInfo(TiffTag.XPosition, 1, 1, TiffType.Rational, FieldBit.Position, true, false, "XPosition"), new TiffFieldInfo(TiffTag.YPosition, 1, 1, TiffType.Rational, FieldBit.Position, true, false, "YPosition"), new TiffFieldInfo(TiffTag.FreeOffsets, -1, -1, TiffType.Long, FieldBit.Ignore, false, false, "FreeOffsets"), new TiffFieldInfo(TiffTag.FreeByteCounts, -1, -1, TiffType.Long, FieldBit.Ignore, false, false, "FreeByteCounts"), new TiffFieldInfo(TiffTag.GrayResponseUnit, 1, 1, TiffType.Short, FieldBit.Ignore, true, false, "CurveAccuracy"), new TiffFieldInfo(TiffTag.GrayResponseCurve, -1, -1, TiffType.Short, FieldBit.Ignore, true, false, "GrayResponseCurve"), new TiffFieldInfo(TiffTag.ResolutionUnit, 1, 1, TiffType.Short, FieldBit.ResolutionUnit, true, false, "ResolutionUnit"), new TiffFieldInfo(TiffTag.PageNumber, 2, 2, TiffType.Short, FieldBit.PageNumber, true, false, "PageNumber"), new TiffFieldInfo(TiffTag.ColorResponseUnit, 1, 1, TiffType.Short, FieldBit.Ignore, true, false, "CurveAccuracy"), new TiffFieldInfo(TiffTag.TransferFunction, -1, -1, TiffType.Short, FieldBit.TransferFunction, true, false, "TransferFunction"), new TiffFieldInfo(TiffTag.Software, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "Software"), new TiffFieldInfo(TiffTag.DateTime, 20, 20, TiffType.ASCII, FieldBit.Custom, true, false, "DateTime"), new TiffFieldInfo(TiffTag.Artist, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "Artist"), new TiffFieldInfo(TiffTag.HostComputer, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "HostComputer"), new TiffFieldInfo(TiffTag.WhitePoint, 2, 2, TiffType.Rational, FieldBit.Custom, true, false, "WhitePoint"), new TiffFieldInfo(TiffTag.PrimaryChromaticities, 6, 6, TiffType.Rational, FieldBit.Custom, true, false, "PrimaryChromaticities"), new TiffFieldInfo(TiffTag.Colormap, -1, -1, TiffType.Short, FieldBit.ColorMap, true, false, "ColorMap"), new TiffFieldInfo(TiffTag.HalfToneHints, 2, 2, TiffType.Short, FieldBit.HalftoneHints, true, false, "HalftoneHints"), new TiffFieldInfo(TiffTag.TileWidth, 1, 1, TiffType.Long, FieldBit.TileDimensions, false, false, "TileWidth"), new TiffFieldInfo(TiffTag.TileWidth, 1, 1, TiffType.Short, FieldBit.TileDimensions, false, false, "TileWidth"), new TiffFieldInfo(TiffTag.TileLength, 1, 1, TiffType.Long, FieldBit.TileDimensions, false, false, "TileLength"), new TiffFieldInfo(TiffTag.TileLength, 1, 1, TiffType.Short, FieldBit.TileDimensions, false, false, "TileLength"), new TiffFieldInfo(TiffTag.TileOffsets, -1, 1, TiffType.Long, FieldBit.StripOffsets, false, false, "TileOffsets"), new TiffFieldInfo(TiffTag.TileByteCounts, -1, 1, TiffType.Long, FieldBit.StripByteCounts, false, false, "TileByteCounts"), new TiffFieldInfo(TiffTag.TileByteCounts, -1, 1, TiffType.Short, FieldBit.StripByteCounts, false, false, "TileByteCounts"), new TiffFieldInfo(TiffTag.SubImageDescriptor, -1, -1, TiffType.IFD, FieldBit.SubIFD, true, true, "SubIFD"), new TiffFieldInfo(TiffTag.SubImageDescriptor, -1, -1, TiffType.Long, FieldBit.SubIFD, true, true, "SubIFD"), new TiffFieldInfo(TiffTag.InkSet, 1, 1, TiffType.Short, FieldBit.Custom, false, false, "InkSet"), new TiffFieldInfo(TiffTag.InkNames, -1, -1, TiffType.ASCII, FieldBit.InkNames, true, true, "InkNames"), new TiffFieldInfo(TiffTag.NumberOfInks, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "NumberOfInks"), new TiffFieldInfo(TiffTag.DotRange, 2, 2, TiffType.Short, FieldBit.Custom, false, false, "DotRange"), new TiffFieldInfo(TiffTag.DotRange, 2, 2, TiffType.Byte, FieldBit.Custom, false, false, "DotRange"), new TiffFieldInfo(TiffTag.TargetPrinter, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "TargetPrinter"), new TiffFieldInfo(TiffTag.ExtraSamples, -1, -1, TiffType.Short, FieldBit.ExtraSamples, false, true, "ExtraSamples"), /* XXX for bogus Adobe Photoshop v2.5 files */ new TiffFieldInfo(TiffTag.ExtraSamples, -1, -1, TiffType.Byte, FieldBit.ExtraSamples, false, true, "ExtraSamples"), new TiffFieldInfo(TiffTag.SampleFormat, -1, -1, TiffType.Short, FieldBit.SampleFormat, false, false, "SampleFormat"), new TiffFieldInfo(TiffTag.SMinSampleValue, -2, -1, TiffType.Any, FieldBit.SMinSampleValue, true, false, "SMinSampleValue"), new TiffFieldInfo(TiffTag.SMaxSampleValue, -2, -1, TiffType.Any, FieldBit.SMaxSampleValue, true, false, "SMaxSampleValue"), new TiffFieldInfo(TiffTag.ClipPath, -1, -3, TiffType.Byte, FieldBit.Custom, false, true, "ClipPath"), new TiffFieldInfo(TiffTag.XClipPathUnits, 1, 1, TiffType.SLong, FieldBit.Custom, false, false, "XClipPathUnits"), new TiffFieldInfo(TiffTag.XClipPathUnits, 1, 1, TiffType.SShort, FieldBit.Custom, false, false, "XClipPathUnits"), new TiffFieldInfo(TiffTag.XClipPathUnits, 1, 1, TiffType.SByte, FieldBit.Custom, false, false, "XClipPathUnits"), new TiffFieldInfo(TiffTag.YClipPathUnits, 1, 1, TiffType.SLong, FieldBit.Custom, false, false, "YClipPathUnits"), new TiffFieldInfo(TiffTag.YClipPathUnits, 1, 1, TiffType.SShort, FieldBit.Custom, false, false, "YClipPathUnits"), new TiffFieldInfo(TiffTag.YClipPathUnits, 1, 1, TiffType.SByte, FieldBit.Custom, false, false, "YClipPathUnits"), new TiffFieldInfo(TiffTag.YCBCRCOEFFICIENTS, 3, 3, TiffType.Rational, FieldBit.Custom, false, false, "YCbCrCoefficients"), new TiffFieldInfo(TiffTag.YCBCRSUBSAMPLING, 2, 2, TiffType.Short, FieldBit.YCbCrSubsampling, false, false, "YCbCrSubsampling"), new TiffFieldInfo(TiffTag.YCBCRPOSITIONING, 1, 1, TiffType.Short, FieldBit.YCbCrPositioning, false, false, "YCbCrPositioning"), new TiffFieldInfo(TiffTag.REFERENCEBLACKWHITE, 6, 6, TiffType.Rational, FieldBit.RefBlackWhite, true, false, "ReferenceBlackWhite"), /* XXX temporarily accept Long for backwards compatibility */ new TiffFieldInfo(TiffTag.REFERENCEBLACKWHITE, 6, 6, TiffType.Long, FieldBit.RefBlackWhite, true, false, "ReferenceBlackWhite"), new TiffFieldInfo(TiffTag.XMLPACKET, -3, -3, TiffType.Byte, FieldBit.Custom, false, true, "XMLPacket"), /* begin SGI tags */ new TiffFieldInfo(TiffTag.MATTEING, 1, 1, TiffType.Short, FieldBit.ExtraSamples, false, false, "Matteing"), new TiffFieldInfo(TiffTag.DATATYPE, -2, -1, TiffType.Short, FieldBit.SampleFormat, false, false, "DataType"), new TiffFieldInfo(TiffTag.IMAGEDEPTH, 1, 1, TiffType.Long, FieldBit.ImageDepth, false, false, "ImageDepth"), new TiffFieldInfo(TiffTag.IMAGEDEPTH, 1, 1, TiffType.Short, FieldBit.ImageDepth, false, false, "ImageDepth"), new TiffFieldInfo(TiffTag.TILEDEPTH, 1, 1, TiffType.Long, FieldBit.TileDepth, false, false, "TileDepth"), new TiffFieldInfo(TiffTag.TILEDEPTH, 1, 1, TiffType.Short, FieldBit.TileDepth, false, false, "TileDepth"), /* end SGI tags */ /* begin Pixar tags */ new TiffFieldInfo(TiffTag.PIXAR_IMAGEFULLWIDTH, 1, 1, TiffType.Long, FieldBit.Custom, true, false, "ImageFullWidth"), new TiffFieldInfo(TiffTag.PIXAR_IMAGEFULLLENGTH, 1, 1, TiffType.Long, FieldBit.Custom, true, false, "ImageFullLength"), new TiffFieldInfo(TiffTag.PIXAR_TEXTUREFORMAT, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "TextureFormat"), new TiffFieldInfo(TiffTag.PIXAR_WRAPMODES, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "TextureWrapModes"), new TiffFieldInfo(TiffTag.PIXAR_FOVCOT, 1, 1, TiffType.Float, FieldBit.Custom, true, false, "FieldOfViewCotangent"), new TiffFieldInfo(TiffTag.PIXAR_MATRIX_WORLDTOSCREEN, 16, 16, TiffType.Float, FieldBit.Custom, true, false, "MatrixWorldToScreen"), new TiffFieldInfo(TiffTag.PIXAR_MATRIX_WORLDTOCAMERA, 16, 16, TiffType.Float, FieldBit.Custom, true, false, "MatrixWorldToCamera"), new TiffFieldInfo(TiffTag.COPYRIGHT, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "Copyright"), /* end Pixar tags */ new TiffFieldInfo(TiffTag.RICHTIFFIPTC, -3, -3, TiffType.Long, FieldBit.Custom, false, true, "RichTIFFIPTC"), new TiffFieldInfo(TiffTag.PHOTOSHOP, -3, -3, TiffType.Byte, FieldBit.Custom, false, true, "Photoshop"), new TiffFieldInfo(TiffTag.EXIFIFD, 1, 1, TiffType.Long, FieldBit.Custom, false, false, "EXIFIFDOffset"), new TiffFieldInfo(TiffTag.ICCPROFILE, -3, -3, TiffType.Undefined, FieldBit.Custom, false, true, "ICC Profile"), new TiffFieldInfo(TiffTag.GPSIFD, 1, 1, TiffType.Long, FieldBit.Custom, false, false, "GPSIFDOffset"), new TiffFieldInfo(TiffTag.STONITS, 1, 1, TiffType.Double, FieldBit.Custom, false, false, "StoNits"), new TiffFieldInfo(TiffTag.INTEROPERABILITYIFD, 1, 1, TiffType.Long, FieldBit.Custom, false, false, "InteroperabilityIFDOffset"), /* begin DNG tags */ new TiffFieldInfo(TiffTag.DNGVERSION, 4, 4, TiffType.Byte, FieldBit.Custom, false, false, "DNGVersion"), new TiffFieldInfo(TiffTag.DNGBACKWARDVERSION, 4, 4, TiffType.Byte, FieldBit.Custom, false, false, "DNGBackwardVersion"), new TiffFieldInfo(TiffTag.UNIQUECAMERAMODEL, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "UniqueCameraModel"), new TiffFieldInfo(TiffTag.LOCALIZEDCAMERAMODEL, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "LocalizedCameraModel"), new TiffFieldInfo(TiffTag.LOCALIZEDCAMERAMODEL, -1, -1, TiffType.Byte, FieldBit.Custom, true, true, "LocalizedCameraModel"), new TiffFieldInfo(TiffTag.CFAPLANECOLOR, -1, -1, TiffType.Byte, FieldBit.Custom, false, true, "CFAPlaneColor"), new TiffFieldInfo(TiffTag.CFALAYOUT, 1, 1, TiffType.Short, FieldBit.Custom, false, false, "CFALayout"), new TiffFieldInfo(TiffTag.LINEARIZATIONTABLE, -1, -1, TiffType.Short, FieldBit.Custom, false, true, "LinearizationTable"), new TiffFieldInfo(TiffTag.BLACKLEVELREPEATDIM, 2, 2, TiffType.Short, FieldBit.Custom, false, false, "BlackLevelRepeatDim"), new TiffFieldInfo(TiffTag.BLACKLEVEL, -1, -1, TiffType.Long, FieldBit.Custom, false, true, "BlackLevel"), new TiffFieldInfo(TiffTag.BLACKLEVEL, -1, -1, TiffType.Short, FieldBit.Custom, false, true, "BlackLevel"), new TiffFieldInfo(TiffTag.BLACKLEVEL, -1, -1, TiffType.Rational, FieldBit.Custom, false, true, "BlackLevel"), new TiffFieldInfo(TiffTag.BLACKLEVELDELTAH, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "BlackLevelDeltaH"), new TiffFieldInfo(TiffTag.BLACKLEVELDELTAV, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "BlackLevelDeltaV"), new TiffFieldInfo(TiffTag.WHITELEVEL, -2, -2, TiffType.Long, FieldBit.Custom, false, false, "WhiteLevel"), new TiffFieldInfo(TiffTag.WHITELEVEL, -2, -2, TiffType.Short, FieldBit.Custom, false, false, "WhiteLevel"), new TiffFieldInfo(TiffTag.DEFAULTSCALE, 2, 2, TiffType.Rational, FieldBit.Custom, false, false, "DefaultScale"), new TiffFieldInfo(TiffTag.BESTQUALITYSCALE, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "BestQualityScale"), new TiffFieldInfo(TiffTag.DEFAULTCROPORIGIN, 2, 2, TiffType.Long, FieldBit.Custom, false, false, "DefaultCropOrigin"), new TiffFieldInfo(TiffTag.DEFAULTCROPORIGIN, 2, 2, TiffType.Short, FieldBit.Custom, false, false, "DefaultCropOrigin"), new TiffFieldInfo(TiffTag.DEFAULTCROPORIGIN, 2, 2, TiffType.Rational, FieldBit.Custom, false, false, "DefaultCropOrigin"), new TiffFieldInfo(TiffTag.DEFAULTCROPSIZE, 2, 2, TiffType.Long, FieldBit.Custom, false, false, "DefaultCropSize"), new TiffFieldInfo(TiffTag.DEFAULTCROPSIZE, 2, 2, TiffType.Short, FieldBit.Custom, false, false, "DefaultCropSize"), new TiffFieldInfo(TiffTag.DEFAULTCROPSIZE, 2, 2, TiffType.Rational, FieldBit.Custom, false, false, "DefaultCropSize"), new TiffFieldInfo(TiffTag.COLORMATRIX1, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "ColorMatrix1"), new TiffFieldInfo(TiffTag.COLORMATRIX2, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "ColorMatrix2"), new TiffFieldInfo(TiffTag.CAMERACALIBRATION1, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "CameraCalibration1"), new TiffFieldInfo(TiffTag.CAMERACALIBRATION2, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "CameraCalibration2"), new TiffFieldInfo(TiffTag.REDUCTIONMATRIX1, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "ReductionMatrix1"), new TiffFieldInfo(TiffTag.REDUCTIONMATRIX2, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "ReductionMatrix2"), new TiffFieldInfo(TiffTag.ANALOGBALANCE, -1, -1, TiffType.Rational, FieldBit.Custom, false, true, "AnalogBalance"), new TiffFieldInfo(TiffTag.ASSHOTNEUTRAL, -1, -1, TiffType.Short, FieldBit.Custom, false, true, "AsShotNeutral"), new TiffFieldInfo(TiffTag.ASSHOTNEUTRAL, -1, -1, TiffType.Rational, FieldBit.Custom, false, true, "AsShotNeutral"), new TiffFieldInfo(TiffTag.ASSHOTWHITEXY, 2, 2, TiffType.Rational, FieldBit.Custom, false, false, "AsShotWhiteXY"), new TiffFieldInfo(TiffTag.BASELINEEXPOSURE, 1, 1, TiffType.SRational, FieldBit.Custom, false, false, "BaselineExposure"), new TiffFieldInfo(TiffTag.BASELINENOISE, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "BaselineNoise"), new TiffFieldInfo(TiffTag.BASELINESHARPNESS, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "BaselineSharpness"), new TiffFieldInfo(TiffTag.BAYERGREENSPLIT, 1, 1, TiffType.Long, FieldBit.Custom, false, false, "BayerGreenSplit"), new TiffFieldInfo(TiffTag.LINEARRESPONSELIMIT, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "LinearResponseLimit"), new TiffFieldInfo(TiffTag.CAMERASERIALNUMBER, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "CameraSerialNumber"), new TiffFieldInfo(TiffTag.LENSINFO, 4, 4, TiffType.Rational, FieldBit.Custom, false, false, "LensInfo"), new TiffFieldInfo(TiffTag.CHROMABLURRADIUS, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "ChromaBlurRadius"), new TiffFieldInfo(TiffTag.ANTIALIASSTRENGTH, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "AntiAliasStrength"), new TiffFieldInfo(TiffTag.SHADOWSCALE, 1, 1, TiffType.Rational, FieldBit.Custom, false, false, "ShadowScale"), new TiffFieldInfo(TiffTag.DNGPRIVATEDATA, -1, -1, TiffType.Byte, FieldBit.Custom, false, true, "DNGPrivateData"), new TiffFieldInfo(TiffTag.MAKERNOTESAFETY, 1, 1, TiffType.Short, FieldBit.Custom, false, false, "MakerNoteSafety"), new TiffFieldInfo(TiffTag.CALIBRATIONILLUMINANT1, 1, 1, TiffType.Short, FieldBit.Custom, false, false, "CalibrationIlluminant1"), new TiffFieldInfo(TiffTag.CALIBRATIONILLUMINANT2, 1, 1, TiffType.Short, FieldBit.Custom, false, false, "CalibrationIlluminant2"), new TiffFieldInfo(TiffTag.RAWDATAUNIQUEID, 16, 16, TiffType.Byte, FieldBit.Custom, false, false, "RawDataUniqueID"), new TiffFieldInfo(TiffTag.ORIGINALRAWFILENAME, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "OriginalRawFileName"), new TiffFieldInfo(TiffTag.ORIGINALRAWFILENAME, -1, -1, TiffType.Byte, FieldBit.Custom, true, true, "OriginalRawFileName"), new TiffFieldInfo(TiffTag.ORIGINALRAWFILEDATA, -1, -1, TiffType.Undefined, FieldBit.Custom, false, true, "OriginalRawFileData"), new TiffFieldInfo(TiffTag.ACTIVEAREA, 4, 4, TiffType.Long, FieldBit.Custom, false, false, "ActiveArea"), new TiffFieldInfo(TiffTag.ACTIVEAREA, 4, 4, TiffType.Short, FieldBit.Custom, false, false, "ActiveArea"), new TiffFieldInfo(TiffTag.MASKEDAREAS, -1, -1, TiffType.Long, FieldBit.Custom, false, true, "MaskedAreas"), new TiffFieldInfo(TiffTag.ASSHOTICCPROFILE, -1, -1, TiffType.Undefined, FieldBit.Custom, false, true, "AsShotICCProfile"), new TiffFieldInfo(TiffTag.ASSHOTPREPROFILEMATRIX, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "AsShotPreProfileMatrix"), new TiffFieldInfo(TiffTag.CURRENTICCPROFILE, -1, -1, TiffType.Undefined, FieldBit.Custom, false, true, "CurrentICCProfile"), new TiffFieldInfo(TiffTag.CURRENTPREPROFILEMATRIX, -1, -1, TiffType.SRational, FieldBit.Custom, false, true, "CurrentPreProfileMatrix"), /* end DNG tags */ }; static TiffFieldInfo[] exifFieldInfo = { new TiffFieldInfo(TiffTag.EXIF_EXPOSURETIME, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "ExposureTime"), new TiffFieldInfo(TiffTag.EXIF_FNUMBER, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "FNumber"), new TiffFieldInfo(TiffTag.EXIF_EXPOSUREPROGRAM, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "ExposureProgram"), new TiffFieldInfo(TiffTag.EXIF_SPECTRALSENSITIVITY, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "SpectralSensitivity"), new TiffFieldInfo(TiffTag.EXIF_ISOSPEEDRATINGS, -1, -1, TiffType.Short, FieldBit.Custom, true, true, "ISOSpeedRatings"), new TiffFieldInfo(TiffTag.EXIF_OECF, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "OptoelectricConversionFactor"), new TiffFieldInfo(TiffTag.EXIF_EXIFVERSION, 4, 4, TiffType.Undefined, FieldBit.Custom, true, false, "ExifVersion"), new TiffFieldInfo(TiffTag.EXIF_DATETIMEORIGINAL, 20, 20, TiffType.ASCII, FieldBit.Custom, true, false, "DateTimeOriginal"), new TiffFieldInfo(TiffTag.EXIF_DATETIMEDIGITIZED, 20, 20, TiffType.ASCII, FieldBit.Custom, true, false, "DateTimeDigitized"), new TiffFieldInfo(TiffTag.EXIF_COMPONENTSCONFIGURATION, 4, 4, TiffType.Undefined, FieldBit.Custom, true, false, "ComponentsConfiguration"), new TiffFieldInfo(TiffTag.EXIF_COMPRESSEDBITSPERPIXEL, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "CompressedBitsPerPixel"), new TiffFieldInfo(TiffTag.EXIF_SHUTTERSPEEDVALUE, 1, 1, TiffType.SRational, FieldBit.Custom, true, false, "ShutterSpeedValue"), new TiffFieldInfo(TiffTag.EXIF_APERTUREVALUE, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "ApertureValue"), new TiffFieldInfo(TiffTag.EXIF_BRIGHTNESSVALUE, 1, 1, TiffType.SRational, FieldBit.Custom, true, false, "BrightnessValue"), new TiffFieldInfo(TiffTag.EXIF_EXPOSUREBIASVALUE, 1, 1, TiffType.SRational, FieldBit.Custom, true, false, "ExposureBiasValue"), new TiffFieldInfo(TiffTag.EXIF_MAXAPERTUREVALUE, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "MaxApertureValue"), new TiffFieldInfo(TiffTag.EXIF_SUBJECTDISTANCE, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "SubjectDistance"), new TiffFieldInfo(TiffTag.EXIF_METERINGMODE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "MeteringMode"), new TiffFieldInfo(TiffTag.EXIF_LIGHTSOURCE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "LightSource"), new TiffFieldInfo(TiffTag.EXIF_FLASH, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "Flash"), new TiffFieldInfo(TiffTag.EXIF_FOCALLENGTH, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "FocalLength"), new TiffFieldInfo(TiffTag.EXIF_SUBJECTAREA, -1, -1, TiffType.Short, FieldBit.Custom, true, true, "SubjectArea"), new TiffFieldInfo(TiffTag.EXIF_MAKERNOTE, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "MakerNote"), new TiffFieldInfo(TiffTag.EXIF_USERCOMMENT, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "UserComment"), new TiffFieldInfo(TiffTag.EXIF_SUBSECTIME, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "SubSecTime"), new TiffFieldInfo(TiffTag.EXIF_SUBSECTIMEORIGINAL, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "SubSecTimeOriginal"), new TiffFieldInfo(TiffTag.EXIF_SUBSECTIMEDIGITIZED, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "SubSecTimeDigitized"), new TiffFieldInfo(TiffTag.EXIF_FLASHPIXVERSION, 4, 4, TiffType.Undefined, FieldBit.Custom, true, false, "FlashpixVersion"), new TiffFieldInfo(TiffTag.EXIF_COLORSPACE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "ColorSpace"), new TiffFieldInfo(TiffTag.EXIF_PIXELXDIMENSION, 1, 1, TiffType.Long, FieldBit.Custom, true, false, "PixelXDimension"), new TiffFieldInfo(TiffTag.EXIF_PIXELXDIMENSION, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "PixelXDimension"), new TiffFieldInfo(TiffTag.EXIF_PIXELYDIMENSION, 1, 1, TiffType.Long, FieldBit.Custom, true, false, "PixelYDimension"), new TiffFieldInfo(TiffTag.EXIF_PIXELYDIMENSION, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "PixelYDimension"), new TiffFieldInfo(TiffTag.EXIF_RELATEDSOUNDFILE, 13, 13, TiffType.ASCII, FieldBit.Custom, true, false, "RelatedSoundFile"), new TiffFieldInfo(TiffTag.EXIF_FLASHENERGY, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "FlashEnergy"), new TiffFieldInfo(TiffTag.EXIF_SPATIALFREQUENCYRESPONSE, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "SpatialFrequencyResponse"), new TiffFieldInfo(TiffTag.EXIF_FOCALPLANEXRESOLUTION, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "FocalPlaneXResolution"), new TiffFieldInfo(TiffTag.EXIF_FOCALPLANEYRESOLUTION, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "FocalPlaneYResolution"), new TiffFieldInfo(TiffTag.EXIF_FOCALPLANERESOLUTIONUNIT, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "FocalPlaneResolutionUnit"), new TiffFieldInfo(TiffTag.EXIF_SUBJECTLOCATION, 2, 2, TiffType.Short, FieldBit.Custom, true, false, "SubjectLocation"), new TiffFieldInfo(TiffTag.EXIF_EXPOSUREINDEX, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "ExposureIndex"), new TiffFieldInfo(TiffTag.EXIF_SENSINGMETHOD, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "SensingMethod"), new TiffFieldInfo(TiffTag.EXIF_FILESOURCE, 1, 1, TiffType.Undefined, FieldBit.Custom, true, false, "FileSource"), new TiffFieldInfo(TiffTag.EXIF_SCENETYPE, 1, 1, TiffType.Undefined, FieldBit.Custom, true, false, "SceneType"), new TiffFieldInfo(TiffTag.EXIF_CFAPATTERN, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "CFAPattern"), new TiffFieldInfo(TiffTag.EXIF_CUSTOMRENDERED, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "CustomRendered"), new TiffFieldInfo(TiffTag.EXIF_EXPOSUREMODE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "ExposureMode"), new TiffFieldInfo(TiffTag.EXIF_WHITEBALANCE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "WhiteBalance"), new TiffFieldInfo(TiffTag.EXIF_DIGITALZOOMRATIO, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "DigitalZoomRatio"), new TiffFieldInfo(TiffTag.EXIF_FOCALLENGTHIN35MMFILM, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "FocalLengthIn35mmFilm"), new TiffFieldInfo(TiffTag.EXIF_SCENECAPTURETYPE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "SceneCaptureType"), new TiffFieldInfo(TiffTag.EXIF_GAINCONTROL, 1, 1, TiffType.Rational, FieldBit.Custom, true, false, "GainControl"), new TiffFieldInfo(TiffTag.EXIF_CONTRAST, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "Contrast"), new TiffFieldInfo(TiffTag.EXIF_SATURATION, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "Saturation"), new TiffFieldInfo(TiffTag.EXIF_SHARPNESS, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "Sharpness"), new TiffFieldInfo(TiffTag.EXIF_DEVICESETTINGDESCRIPTION, -1, -1, TiffType.Undefined, FieldBit.Custom, true, true, "DeviceSettingDescription"), new TiffFieldInfo(TiffTag.EXIF_SUBJECTDISTANCERANGE, 1, 1, TiffType.Short, FieldBit.Custom, true, false, "SubjectDistanceRange"), new TiffFieldInfo(TiffTag.EXIF_IMAGEUNIQUEID, 33, 33, TiffType.ASCII, FieldBit.Custom, true, false, "ImageUniqueID") }; private static TiffFieldInfo[] getFieldInfo(out int size) { size = tiffFieldInfo.Length; return tiffFieldInfo; } private static TiffFieldInfo[] getExifFieldInfo(out int size) { size = exifFieldInfo.Length; return exifFieldInfo; } private void setupFieldInfo(TiffFieldInfo[] info, int n) { m_nfields = 0; MergeFieldInfo(info, n); } /* * Return nearest TiffDataType to the sample type of an image. */ private TiffType sampleToTagType() { int bps = howMany8(m_dir.td_bitspersample); switch (m_dir.td_sampleformat) { case SampleFormat.IEEEFloat: return (bps == 4 ? TiffType.Float : TiffType.Double); case SampleFormat.Int: return (bps <= 1 ? TiffType.SByte : bps <= 2 ? TiffType.SShort : TiffType.SLong); case SampleFormat.UInt: return (bps <= 1 ? TiffType.Byte : bps <= 2 ? TiffType.Short : TiffType.Long); case SampleFormat.UnTyped: return TiffType.Undefined; } return TiffType.Undefined; } private static TiffFieldInfo createAnonFieldInfo(TiffTag tag, TiffType field_type) { TiffFieldInfo fld = new TiffFieldInfo(tag, TiffFieldInfo.Variable2, TiffFieldInfo.Variable2, field_type, FieldBit.Custom, true, true, null); // note that this name is a special sign to Close() and // setupFieldInfo() to free the field fld.Name = string.Format(CultureInfo.InvariantCulture, "Tag {0}", tag); return fld; } /* * Return size of TiffDataType in bytes. * * XXX: We need a separate function to determine the space needed * to store the value. For TiffType.Rational values DataWidth() * returns 8, but we use 4-byte float to represent rationals. */ internal static int dataSize(TiffType type) { switch (type) { case TiffType.Byte: case TiffType.SByte: case TiffType.ASCII: case TiffType.Undefined: return 1; case TiffType.Short: case TiffType.SShort: return 2; case TiffType.Long: case TiffType.SLong: case TiffType.Float: case TiffType.IFD: case TiffType.Rational: case TiffType.SRational: return 4; case TiffType.Double: return 8; default: return 0; } } /* is tag value normal or pseudo */ internal static bool isPseudoTag(TiffTag t) { return ((int)t > 0xffff); } private bool isFillOrder(BitOrder o) { TiffFlags order = (TiffFlags)o; return ((m_flags & order) == order); } private static int BITn(int n) { return (1 << (n & 0x1f)); } /* * Return true / false according to whether or not * it is permissible to set the tag's value. * Note that we allow ImageLength to be changed * so that we can append and extend to images. * Any other tag may not be altered once writing * has commenced, unless its value has no effect * on the format of the data that is written. */ private bool okToChangeTag(TiffTag tag) { TiffFieldInfo fip = FindFieldInfo(tag, TiffType.Any); if (fip == null) { // unknown tag ErrorExt(this, m_clientdata, "SetField", "{0}: Unknown {1}tag {2}", m_name, isPseudoTag(tag) ? "pseudo-" : "", tag); return false; } if (tag != TiffTag.ImageLength && (m_flags & TiffFlags.BeenWriting) == TiffFlags.BeenWriting && !fip.OkToChange) { // Consult info table to see if tag can be changed after we've // started writing. We only allow changes to those tags that // don't / shouldn't affect the compression and / or format of // the data. ErrorExt(this, m_clientdata, "SetField", "{0}: Cannot modify tag \"{1}\" while writing", m_name, fip.Name); return false; } return true; } /* * Setup a default directory structure. */ private void setupDefaultDirectory() { int tiffFieldInfoCount; TiffFieldInfo[] tiffFieldInfo = getFieldInfo(out tiffFieldInfoCount); setupFieldInfo(tiffFieldInfo, tiffFieldInfoCount); m_dir = new TiffDirectory(); m_postDecodeMethod = PostDecodeMethodType.pdmNone; m_foundfield = null; m_tagmethods = m_defaultTagMethods; /* * Give client code a chance to install their own * tag extensions & methods, prior to compression overloads. */ if (m_extender != null) m_extender(this); SetField(TiffTag.Compression, Compression.None); /* * NB: The directory is marked dirty as a result of setting * up the default compression scheme. However, this really * isn't correct -- we want DirtyDirect to be set only * if the user does something. We could just do the setup * by hand, but it seems better to use the normal mechanism * (i.e. SetField). */ m_flags &= ~TiffFlags.DirtyDirect; /* * we clear the IsTiled flag when setting up a new directory. * Should we also be clearing stuff like InSubIFD? */ m_flags &= ~TiffFlags.IsTiled; /* * Clear other directory-specific fields. */ m_tilesize = -1; m_scanlinesize = -1; } private bool advanceDirectory(ref uint nextdir, out long off) { off = 0; const string module = "advanceDirectory"; short dircount; if (!seekOK(nextdir) || !readShortOK(out dircount)) { ErrorExt(this, m_clientdata, module, "{0}: Error fetching directory count", m_name); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabShort(ref dircount); off = seekFile(dircount * TiffDirEntry.SizeInBytes, SeekOrigin.Current); if (!readUIntOK(out nextdir)) { ErrorExt(this, m_clientdata, module, "{0}: Error fetching directory link", m_name); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabUInt(ref nextdir); return true; } internal static void setString(out string cpp, string cp) { cpp = cp; } internal static void setShortArray(out short[] wpp, short[] wp, int n) { wpp = new short[n]; for (int i = 0; i < n; i++) wpp[i] = wp[i]; } internal static void setLongArray(out int[] lpp, int[] lp, int n) { lpp = new int[n]; for (int i = 0; i < n; i++) lpp[i] = lp[i]; } internal static void setFloatArray(out float[] fpp, float[] fp, int n) { fpp = new float[n]; for (int i = 0; i < n; i++) fpp[i] = fp[i]; } internal bool fieldSet(int field) { return ((m_dir.td_fieldsset[field / 32] & BITn(field)) != 0); } internal void setFieldBit(int field) { m_dir.td_fieldsset[field / 32] |= BITn(field); } internal void clearFieldBit(int field) { m_dir.td_fieldsset[field / 32] &= ~BITn(field); } /// /// Compression schemes statically built into the library. /// private void setupBuiltInCodecs() { // change initial syntax of m_builtInCodecs, maintains easier. // San Chen m_builtInCodecs = new TiffCodec[] { new TiffCodec(this, (Compression)(-1), "Not configured"), new DumpModeCodec(this, Compression.None, "None"), new LZWCodec(this, Compression.LZW, "LZW"), new PackBitsCodec(this, Compression.PackBits, "PackBits"), new TiffCodec(this, Compression.ThunderScan, "ThunderScan"), new TiffCodec(this, Compression.NeXT, "NeXT"), new JpegCodec(this, Compression.JPEG, "JPEG"), new OJpegCodec(this, Compression.OJPEG, "Old-style JPEG"), new CCITTCodec(this, Compression.CCITTRLE, "CCITT RLE"), new CCITTCodec(this, Compression.CCITTRLEW, "CCITT RLE/W"), new CCITTCodec(this, Compression.CCITTFAX3, "CCITT Group 3"), new CCITTCodec(this, Compression.CCITTFAX4, "CCITT Group 4"), new TiffCodec(this, Compression.JBIG, "ISO JBIG"), new DeflateCodec(this, Compression.Deflate, "Deflate"), new DeflateCodec(this, Compression.AdobeDeflate, "AdobeDeflate"), new TiffCodec(this, Compression.PixarLog, "PixarLog"), new TiffCodec(this, Compression.SGILOG, "SGILog"), new TiffCodec(this, Compression.SGILOG24, "SGILog24"), null, }; } private static bool defaultTransferFunction(TiffDirectory td) { short[][] tf = td.td_transferfunction; tf[0] = null; tf[1] = null; tf[2] = null; if (td.td_bitspersample >= sizeof(int) * 8 - 2) return false; int n = 1 << td.td_bitspersample; tf[0] = new short[n]; tf[0][0] = 0; for (int i = 1; i < n; i++) { double t = (double)i / ((double)n - 1.0); tf[0][i] = (short)Math.Floor(65535.0 * Math.Pow(t, 2.2) + 0.5); } if (td.td_samplesperpixel - td.td_extrasamples > 1) { tf[1] = new short[n]; Buffer.BlockCopy(tf[0], 0, tf[1], 0, tf[0].Length * sizeof(short)); tf[2] = new short[n]; Buffer.BlockCopy(tf[0], 0, tf[2], 0, tf[0].Length * sizeof(short)); } return true; } private static void defaultRefBlackWhite(TiffDirectory td) { td.td_refblackwhite = new float[6]; if (td.td_photometric == Photometric.YCBCR) { // YCbCr (Class Y) images must have the ReferenceBlackWhite tag set. Fix the // broken images, which lacks that tag. td.td_refblackwhite[0] = 0.0F; td.td_refblackwhite[1] = td.td_refblackwhite[3] = td.td_refblackwhite[5] = 255.0F; td.td_refblackwhite[2] = td.td_refblackwhite[4] = 128.0F; } else { // Assume RGB (Class R) for (int i = 0; i < 3; i++) { td.td_refblackwhite[2 * i + 0] = 0; td.td_refblackwhite[2 * i + 1] = (float)((1L << td.td_bitspersample) - 1L); } } } internal static int readInt(byte[] buffer, int offset) { int value = buffer[offset++] & 0xFF; value += (buffer[offset++] & 0xFF) << 8; value += (buffer[offset++] & 0xFF) << 16; value += buffer[offset++] << 24; return value; } internal static void writeInt(int value, byte[] buffer, int offset) { buffer[offset++] = (byte)value; buffer[offset++] = (byte)(value >> 8); buffer[offset++] = (byte)(value >> 16); buffer[offset++] = (byte)(value >> 24); } internal static short readShort(byte[] buffer, int offset) { short value = (short)(buffer[offset] & 0xFF); value += (short)((buffer[offset + 1] & 0xFF) << 8); return value; } internal static void fprintf(Stream fd, string format, params object[] list) { string s = string.Format(CultureInfo.InvariantCulture, format, list); byte[] bytes = Latin1Encoding.GetBytes(s); fd.Write(bytes, 0, bytes.Length); } private static string encodeOctalString(byte value) { //convert to int, for cleaner syntax below. int x = value; //return octal encoding \ddd of the character value. return string.Format(CultureInfo.InvariantCulture, @"\{0}{1}{2}", (x >> 6) & 7, (x >> 3) & 7, x & 7); } /// /// Delegate for LibTiff.Net extender method /// /// An instance of the class. /// /// Extender method is usually used for registering custom tags. /// To setup extender method that will be called upon creation of /// each instance of object please use /// method. /// public delegate void TiffExtendProc(Tiff tif); /// /// Delegate for a method used to image decoded spans. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The array of black and white run lengths (white then black). /// The zero-based offset in array at /// which current row's run begins. /// The zero-based offset in array at /// which next row's run begins. /// The width in pixels of the row. /// /// To override the default method used to image decoded spans please set /// tag with an instance of this delegate. /// /// Fill methods can assume the array has room for at least /// runs and can overwrite data in the /// array as needed (e.g. to append zero runs to bring the count up to a nice multiple). /// public delegate void FaxFillFunc( byte[] buffer, int offset, int[] runs, int thisRunOffset, int nextRunOffset, int width); /// /// Gets the library version string. /// /// The library version string. public static string GetVersion() { return string.Format(CultureInfo.InvariantCulture, "LibTiff.Net, Version {0}\nCopyright (C) 2008-2011, Bit Miracle.", AssemblyVersion); } /// /// Gets the version of the library's assembly. /// /// The version of the library's assembly. public static string AssemblyVersion { get { return "Some version, but it doesn't really matter."; } } /// /// Gets the R component from ABGR value returned by /// ReadRGBAImage. /// /// The ABGR value. /// The R component from ABGR value. public static int GetR(int abgr) { return (abgr & 0xff); } /// /// Gets the G component from ABGR value returned by /// ReadRGBAImage. /// /// The ABGR value. /// The G component from ABGR value. public static int GetG(int abgr) { return ((abgr >> 8) & 0xff); } /// /// Gets the B component from ABGR value returned by /// ReadRGBAImage. /// /// The ABGR value. /// The B component from ABGR value. public static int GetB(int abgr) { return ((abgr >> 16) & 0xff); } /// /// Gets the A component from ABGR value returned by /// ReadRGBAImage. /// /// The ABGR value. /// The A component from ABGR value. public static int GetA(int abgr) { return ((abgr >> 24) & 0xff); } /// /// Retrieves the codec registered for the specified compression scheme. /// /// The compression scheme. /// The codec registered for the specified compression scheme or null /// if there is no codec registered for the given scheme. /// /// /// LibTiff.Net supports a variety of compression schemes implemented by software codecs. /// Each codec adheres to a modular interface that provides for the decoding and encoding /// of image data; as well as some other methods for initialization, setup, cleanup, and /// the control of default strip and tile sizes. Codecs are identified by the associated /// value of the .Compression tag. /// /// /// Other compression schemes may be registered. Registered schemes can also override the /// built-in versions provided by the library. /// /// public TiffCodec FindCodec(Compression scheme) { for (codecList list = m_registeredCodecs; list != null; list = list.next) { if (list.codec.m_scheme == scheme) return list.codec; } for (int i = 0; m_builtInCodecs[i] != null; i++) { TiffCodec codec = m_builtInCodecs[i]; if (codec.m_scheme == scheme) return codec; } return null; } /// /// Adds specified codec to a list of registered codec. /// /// The codec to register. /// /// This method can be used to augment or override the set of codecs available to an /// application. If the is for a scheme that already has a /// registered codec then it is overridden and any images with data encoded with this /// compression scheme will be decoded using the supplied codec. /// public void RegisterCodec(TiffCodec codec) { if (codec == null) throw new ArgumentNullException("codec"); codecList list = new codecList(); list.codec = codec; list.next = m_registeredCodecs; m_registeredCodecs = list; } /// /// Removes specified codec from a list of registered codecs. /// /// The codec to remove from a list of registered codecs. public void UnRegisterCodec(TiffCodec codec) { if (m_registeredCodecs == null) return; codecList temp; if (m_registeredCodecs.codec == codec) { temp = m_registeredCodecs.next; m_registeredCodecs = temp; return; } for (codecList list = m_registeredCodecs; list != null; list = list.next) { if (list.next != null) { if (list.next.codec == codec) { temp = list.next.next; list.next = temp; return; } } } ErrorExt(this, 0, "UnRegisterCodec", "Cannot remove compression scheme {0}; not registered", codec.m_name); } /// /// Checks whether library has working codec for the specific compression scheme. /// /// The scheme to check. /// /// true if the codec is configured and working; otherwise, false. /// public bool IsCodecConfigured(Compression scheme) { TiffCodec codec = FindCodec(scheme); if (codec == null) return false; if (codec.CanEncode != false || codec.CanDecode != false) return true; return false; } /// /// Retrieves an array of configured codecs, both built-in and registered by user. /// /// An array of configured codecs. public TiffCodec[] GetConfiguredCodecs() { int totalCodecs = 0; for (int i = 0; m_builtInCodecs[i] != null; i++) { if (m_builtInCodecs[i] != null && IsCodecConfigured(m_builtInCodecs[i].m_scheme)) totalCodecs++; } for (codecList cd = m_registeredCodecs; cd != null; cd = cd.next) totalCodecs++; TiffCodec[] codecs = new TiffCodec[totalCodecs]; int codecPos = 0; for (codecList cd = m_registeredCodecs; cd != null; cd = cd.next) codecs[codecPos++] = cd.codec; for (int i = 0; m_builtInCodecs[i] != null; i++) { if (m_builtInCodecs[i] != null && IsCodecConfigured(m_builtInCodecs[i].m_scheme)) codecs[codecPos++] = m_builtInCodecs[i]; } return codecs; } /// /// Allocates new byte array of specified size and copies data from the existing to /// the new array. /// /// The existing array. /// The number of elements in new array. /// /// The new byte array of specified size with data from the existing array. /// /// Allocates new array of specified size and copies data from the existing to /// the new array. internal static byte[] Realloc(byte[] array, int size) { byte[] newArray = new byte[size]; if (array != null) { int copyLength = Math.Min(array.Length, size); Buffer.BlockCopy(array, 0, newArray, 0, copyLength); } return newArray; } /// /// Allocates new integer array of specified size and copies data from the existing to /// the new array. /// /// The existing array. /// The number of elements in new array. /// /// The new integer array of specified size with data from the existing array. /// /// Size of the array is in elements, not bytes. private static int[] Realloc(int[] array, int size) { int[] newArray = new int[size]; if (array != null) { int copyLength = Math.Min(array.Length, size); Buffer.BlockCopy(array, 0, newArray, 0, copyLength * sizeof(int)); } return newArray; } /// /// Compares specified number of elements in two arrays. /// /// The first array to compare. /// The second array to compare. /// The number of elements to compare. /// /// The difference between compared elements or 0 if all elements are equal. /// private static int Compare(short[] first, short[] second, int elementCount) { for (int i = 0; i < elementCount; i++) { if (first[i] != second[i]) return first[i] - second[i]; } return 0; } /// /// Initializes new instance of class and opens a TIFF file for /// reading or writing. /// /// The name of the file to open. /// The open mode. Specifies if the file is to be opened for /// reading ("r"), writing ("w"), or appending ("a") and, optionally, whether to override /// certain default aspects of library operation (see remarks). /// The new instance of class if specified file is /// successfully opened; otherwise, null. /// /// /// opens a TIFF file whose name is . When /// a file is opened for appending, existing data will not be touched; instead new data /// will be written as additional subfiles. If an existing file is opened for writing, /// all previous data is overwritten. /// /// /// If a file is opened for reading, the first TIFF directory in the file is automatically /// read (see for reading directories other than the first). If /// a file is opened for writing or appending, a default directory is automatically /// created for writing subsequent data. This directory has all the default values /// specified in TIFF Revision 6.0: BitsPerSample = 1, ThreshHolding = Threshold.BILevel /// (bilevel art scan), BitOrder = BigEndian (most significant bit of each data byte is /// filled first), Orientation = TopLeft (the 0th row represents the visual top of the /// image, and the 0th column represents the visual left hand side), SamplesPerPixel = 1, /// RowsPerStrip = infinity, ResolutionUnit = Inch, and Compression = None. To alter /// these values, or to define values for additional fields, must /// be used. /// /// /// The parameter can include the following flags in addition to /// the "r", "w", and "a" flags. Note however that option flags must follow the /// read-write-append specification. /// /// /// FlagDescription /// l /// When creating a new file force information be written with Little-BitOrder /// byte order (but see below). /// b /// When creating a new file force information be written with Big-BitOrder /// byte order (but see below). /// L /// Force image data that is read or written to be treated with bits filled /// from Least Significant Bit (LSB) to Most Significant Bit (MSB). Note that this is the /// opposite to the way the library has worked from its inception. /// B /// Force image data that is read or written to be treated with bits filled /// from Most Significant Bit (MSB) to Least Significant Bit (LSB); this is the /// default. /// H /// Force image data that is read or written to be treated with bits filled /// in the same order as the native CPU. /// C /// Enable the use of "strip chopping" when reading images that are comprised /// of a single strip or tile of uncompressed data. Strip chopping is a mechanism by which /// the library will automatically convert the single-strip image to multiple strips, each /// of which has about 8 Kilobytes of data. This facility can be useful in reducing the /// amount of memory used to read an image because the library normally reads each strip /// in its entirety. Strip chopping does however alter the apparent contents of the image /// because when an image is divided into multiple strips it looks as though the /// underlying file contains multiple separate strips. The default behaviour is to enable /// strip chopping. /// c /// Disable the use of strip chopping when reading images. /// h /// Read TIFF header only, do not load the first image directory. That could /// be useful in case of the broken first directory. We can open the file and proceed to /// the other directories. /// /// By default the library will create new files with the native byte-order of the CPU on /// which the application is run. This ensures optimal performance and is portable to any /// application that conforms to the TIFF specification. To force the library to use a /// specific byte-order when creating a new file the "b" and "l" option flags may be /// included in the parameter; for example, "wb" or "wl". /// The use of the "l" and "b" flags is strongly discouraged. These flags are /// provided solely because numerous vendors do not correctly support TIFF; they only /// support one of the two byte orders. It is strongly recommended that you not use this /// feature except to deal with busted apps that write invalid TIFF. /// The "L", "B", and "H" flags are intended for applications that can optimize /// operations on data by using a particular bit order. By default the library returns /// data in BigEndian bit order. Returning data in the bit order of the native CPU makes the /// most sense but also requires applications to check the value of the /// tag; something they probably do not do right now. /// The "c" option permits applications that only want to look at the tags, for /// example, to get the unadulterated TIFF tag information. /// public static Tiff Open(string fileName, string mode) { const string module = "Open"; FileMode fileMode; FileAccess fileAccess; getMode(mode, module, out fileMode, out fileAccess); FileStream stream = null; try { if (fileAccess == FileAccess.Read) stream = File.Open(fileName, fileMode, fileAccess, FileShare.Read); else stream = File.Open(fileName, fileMode, fileAccess); } catch (Exception e) { Error(module, "Failed to open '{0}'. {1}", fileName, e.Message); return null; } Tiff tif = ClientOpen(fileName, mode, stream, new TiffStream()); if (tif == null) stream.Dispose(); else tif.m_fileStream = stream; return tif; } public static Tiff Open(Stream s) { Tiff tif = ClientOpen("Temp.tiff", "r", s, new TiffStream()); tif.m_fileStream = s; return tif; } //public static Tiff OpenForWrite(Stream s) //{ // Tiff tif = ClientOpen("Temp.tiff", "w", s, new TiffStream()); // tif.m_fileStream = s; // return tif; //} /// /// Initializes new instance of class and opens a stream with TIFF data /// for reading or writing. /// /// The name for the new instance of class. /// The open mode. Specifies if the file is to be opened for /// reading ("r"), writing ("w"), or appending ("a") and, optionally, whether to override /// certain default aspects of library operation (see remarks for /// method for the list of the mode flags). /// Some client data. This data is passed as parameter to every /// method of the object specified by the /// parameter. /// An instance of the class to use for /// reading, writing and seeking of TIFF data. /// The new instance of class if stream is successfully /// opened; otherwise, null. /// /// /// This method can be used to read TIFF data from sources other than file. When custom /// stream class derived from is used it is possible to read (or /// write) TIFF data that reside in memory, database, etc. /// /// Please note, that is an arbitrary string used as /// ID for the created . It's not required to be a file name or anything /// meaningful at all. /// /// Please read remarks for method for the list of option flags that /// can be specified in parameter. /// /// public static Tiff ClientOpen(string name, string mode, object clientData, TiffStream stream) { const string module = "ClientOpen"; if (mode == null || mode.Length == 0) { ErrorExt(null, clientData, module, "{0}: mode string should contain at least one char", name); return null; } FileMode fileMode; FileAccess fileAccess; int m = getMode(mode, module, out fileMode, out fileAccess); Tiff tif = new Tiff(); tif.m_name = name; tif.m_mode = m & ~(O_CREAT | O_TRUNC); tif.m_curdir = -1; // non-existent directory tif.m_curoff = 0; tif.m_curstrip = -1; // invalid strip tif.m_row = -1; // read/write pre-increment tif.m_clientdata = clientData; if (stream == null) { ErrorExt(tif, clientData, module, "TiffStream is null pointer."); return null; } tif.m_stream = stream; // setup default state tif.m_currentCodec = tif.m_builtInCodecs[0]; // Default is to return data BigEndian and enable the use of // strip chopping when a file is opened read-only. tif.m_flags = TiffFlags.BigEndian; if (m == O_RDONLY || m == O_RDWR) tif.m_flags |= STRIPCHOP_DEFAULT; // Process library-specific flags in the open mode string. // See remarks for Open method for the list of supported flags. int modelength = mode.Length; for (int i = 0; i < modelength; i++) { switch (mode[i]) { case 'b': if ((m & O_CREAT) != 0) tif.m_flags |= TiffFlags.Swab; break; case 'l': break; case 'B': tif.m_flags = (tif.m_flags & ~TiffFlags.FillOrder) | TiffFlags.BigEndian; break; case 'L': tif.m_flags = (tif.m_flags & ~TiffFlags.FillOrder) | TiffFlags.LittleEndian; break; case 'H': tif.m_flags = (tif.m_flags & ~TiffFlags.FillOrder) | TiffFlags.LittleEndian; break; case 'C': if (m == O_RDONLY) tif.m_flags |= TiffFlags.StripChop; break; case 'c': if (m == O_RDONLY) tif.m_flags &= ~TiffFlags.StripChop; break; case 'h': tif.m_flags |= TiffFlags.HeaderOnly; break; } } // Read in TIFF header. if ((tif.m_mode & O_TRUNC) != 0 || !tif.readHeaderOk(ref tif.m_header)) { if (tif.m_mode == O_RDONLY) { ErrorExt(tif, tif.m_clientdata, name, "Cannot read TIFF header"); return null; } // Setup header and write. if ((tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) tif.m_header.tiff_magic = TIFF_BIGENDIAN; else tif.m_header.tiff_magic = TIFF_LITTLEENDIAN; tif.m_header.tiff_version = TIFF_VERSION; if ((tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabShort(ref tif.m_header.tiff_version); tif.m_header.tiff_diroff = 0; // filled in later tif.seekFile(0, SeekOrigin.Begin); if (!tif.writeHeaderOK(tif.m_header)) { ErrorExt(tif, tif.m_clientdata, name, "Error writing TIFF header"); tif.m_mode = O_RDONLY; return null; } // Setup the byte order handling. tif.initOrder(tif.m_header.tiff_magic); // Setup default directory. tif.setupDefaultDirectory(); tif.m_diroff = 0; tif.m_dirlist = null; tif.m_dirlistsize = 0; tif.m_dirnumber = 0; return tif; } // Setup the byte order handling. if (tif.m_header.tiff_magic != TIFF_BIGENDIAN && tif.m_header.tiff_magic != TIFF_LITTLEENDIAN && tif.m_header.tiff_magic != MDI_LITTLEENDIAN) { ErrorExt(tif, tif.m_clientdata, name, "Not a TIFF or MDI file, bad magic number {0} (0x{1:x})", tif.m_header.tiff_magic, tif.m_header.tiff_magic); tif.m_mode = O_RDONLY; return null; } tif.initOrder(tif.m_header.tiff_magic); // Swap header if required. if ((tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) { SwabShort(ref tif.m_header.tiff_version); SwabUInt(ref tif.m_header.tiff_diroff); } // Now check version (if needed, it's been byte-swapped). // Note that this isn't actually a version number, it's a // magic number that doesn't change (stupid). if (tif.m_header.tiff_version == TIFF_BIGTIFF_VERSION) { ErrorExt(tif, tif.m_clientdata, name, "This is a BigTIFF file. This format not supported\nby this version of LibTiff.Net."); tif.m_mode = O_RDONLY; return null; } if (tif.m_header.tiff_version != TIFF_VERSION) { ErrorExt(tif, tif.m_clientdata, name, "Not a TIFF file, bad version number {0} (0x{1:x})", tif.m_header.tiff_version, tif.m_header.tiff_version); tif.m_mode = O_RDONLY; return null; } tif.m_flags |= TiffFlags.MyBuffer; tif.m_rawcp = 0; tif.m_rawdata = null; tif.m_rawdatasize = 0; // Sometimes we do not want to read the first directory (for example, // it may be broken) and want to proceed to other directories. I this // case we use the HeaderOnly flag to open file and return // immediately after reading TIFF header. if ((tif.m_flags & TiffFlags.HeaderOnly) == TiffFlags.HeaderOnly) return tif; // Setup initial directory. switch (mode[0]) { case 'r': tif.m_nextdiroff = tif.m_header.tiff_diroff; if (tif.ReadDirectory()) { tif.m_rawcc = -1; tif.m_flags |= TiffFlags.BufferSetup; return tif; } break; case 'a': // New directories are automatically append to the end of // the directory chain when they are written out (see WriteDirectory). tif.setupDefaultDirectory(); return tif; } tif.m_mode = O_RDONLY; return null; } /// /// Closes a previously opened TIFF file. /// /// /// This method closes a file or stream that was previously opened with /// or . Any buffered data are flushed to the file/stream, /// including the contents of the current directory (if modified); and all resources /// are reclaimed. /// public void Close() { Flush(); m_stream.Close(m_clientdata); if (m_fileStream != null) m_fileStream.Close(); } /// /// Frees and releases all resources allocated by this . /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Gets the number of elements in the custom tag list. /// /// The number of elements in the custom tag list. public int GetTagListCount() { return m_dir.td_customValueCount; } /// /// Retrieves the custom tag with specified index. /// /// The zero-based index of a custom tag to retrieve. /// The custom tag with specified index. public int GetTagListEntry(int index) { if (index < 0 || index >= m_dir.td_customValueCount) return -1; else return (int)m_dir.td_customValues[index].info.Tag; } /// /// Merges given field information to existing one. /// /// The array of objects. /// The number of items to use from the array. public void MergeFieldInfo(TiffFieldInfo[] info, int count) { m_foundfield = null; if (m_nfields > 0) m_fieldinfo = Realloc(m_fieldinfo, m_nfields, m_nfields + count); else m_fieldinfo = new TiffFieldInfo[count]; for (int i = 0; i < count; i++) { TiffFieldInfo fip = FindFieldInfo(info[i].Tag, info[i].Type); // only add definitions that aren't already present if (fip == null) { m_fieldinfo[m_nfields] = info[i]; m_nfields++; } } // Sort the field info by tag number IComparer myComparer = new TagCompare(); Array.Sort(m_fieldinfo, 0, m_nfields, myComparer); } /// /// Retrieves field information for the specified tag. /// /// The tag to retrieve field information for. /// The tiff data type to use us additional filter. /// The field information for specified tag with specified type or null if /// the field information wasn't found. public TiffFieldInfo FindFieldInfo(TiffTag tag, TiffType type) { if (m_foundfield != null && m_foundfield.Tag == tag && (type == TiffType.Any || type == m_foundfield.Type)) { return m_foundfield; } // If we are invoked with no field information, then just return. if (m_fieldinfo == null) return null; m_foundfield = null; foreach (TiffFieldInfo info in m_fieldinfo) { if (info != null && info.Tag == tag && (type == TiffType.Any || type == info.Type)) { m_foundfield = info; break; } } return m_foundfield; } /// /// Retrieves field information for the tag with specified name. /// /// The name of the tag to retrieve field information for. /// The tiff data type to use us additional filter. /// The field information for specified tag with specified type or null if /// the field information wasn't found. public TiffFieldInfo FindFieldInfoByName(string name, TiffType type) { if (m_foundfield != null && m_foundfield.Name == name && (type == TiffType.Any || type == m_foundfield.Type)) { return m_foundfield; } // If we are invoked with no field information, then just return. if (m_fieldinfo == null) return null; m_foundfield = null; foreach (TiffFieldInfo info in m_fieldinfo) { if (info != null && info.Name == name && (type == TiffType.Any || type == info.Type)) { m_foundfield = info; break; } } return m_foundfield; } /// /// Retrieves field information for the specified tag. /// /// The tag to retrieve field information for. /// The field information for specified tag or null if /// the field information wasn't found. public TiffFieldInfo FieldWithTag(TiffTag tag) { TiffFieldInfo fip = FindFieldInfo(tag, TiffType.Any); if (fip != null) return fip; ErrorExt(this, m_clientdata, "FieldWithTag", "Internal error, unknown tag 0x{0:x}", tag); Debug.Assert(false); return null; } /// /// Retrieves field information for the tag with specified name. /// /// The name of the tag to retrieve field information for. /// The field information for specified tag or null if /// the field information wasn't found. public TiffFieldInfo FieldWithName(string name) { TiffFieldInfo fip = FindFieldInfoByName(name, TiffType.Any); if (fip != null) return fip; ErrorExt(this, m_clientdata, "FieldWithName", "Internal error, unknown tag {0}", name); Debug.Assert(false); return null; } /// /// Gets the currently used tag methods. /// /// The currently used tag methods. public TiffTagMethods GetTagMethods() { return m_tagmethods; } /// /// Sets the new tag methods to use. /// /// Tag methods. /// The previously used tag methods. public TiffTagMethods SetTagMethods(TiffTagMethods methods) { TiffTagMethods prevTagMethods = m_tagmethods; if (methods != null) m_tagmethods = methods; return prevTagMethods; } /// /// Gets the extra information with specified name associated with this . /// /// Name of the extra information to retrieve. /// The extra information with specified name associated with /// this or null if extra information with specified /// name was not found. public object GetClientInfo(string name) { // should get copy clientInfoLink link = m_clientinfo; while (link != null && link.name != name) link = link.next; if (link != null) return link.data; return null; } /// /// Associates extra information with this . /// /// The information to associate with this . /// The name (label) of the information. /// If there is already an extra information with the name specified by /// it will be replaced by the information specified by /// . public void SetClientInfo(object data, string name) { clientInfoLink link = m_clientinfo; // Do we have an existing link with this name? If so, just set it. while (link != null && link.name != name) link = link.next; if (link != null) { link.data = data; return; } // Create a new link. link = new clientInfoLink(); link.next = m_clientinfo; link.name = name; link.data = data; m_clientinfo = link; } /// /// Flushes pending writes to an open TIFF file. /// /// true if succeeded; otherwise, false /// causes any pending writes for the specified file /// (including writes for the current directory) to be done. In normal operation this call /// is never needed − the library automatically does any flushing required. /// /// public bool Flush() { if (m_mode != O_RDONLY) { if (!FlushData()) return false; if ((m_flags & TiffFlags.DirtyDirect) == TiffFlags.DirtyDirect && !WriteDirectory()) return false; } return true; } /// /// Flushes any pending image data for the specified file to be written out. /// /// true if succeeded; otherwise, false /// flushes any pending image data for the specified file /// to be written out; directory-related data are not flushed. In normal operation this /// call is never needed − the library automatically does any flushing required. /// /// public bool FlushData() { if ((m_flags & TiffFlags.BeenWriting) != TiffFlags.BeenWriting) return false; if ((m_flags & TiffFlags.PostEncode) == TiffFlags.PostEncode) { m_flags &= ~TiffFlags.PostEncode; if (!m_currentCodec.PostEncode()) return false; } return flushData1(); } /// /// Gets the value(s) of a tag in an open TIFF file. /// /// The tag. /// The value(s) of a tag in an open TIFF file as array of /// objects or null if there is no such tag set. /// /// /// returns the value(s) of a tag or pseudo-tag associated with the /// current directory of the opened TIFF file. The tag is identified by /// . The type and number of values returned is dependent on the /// tag being requested. You may want to consult /// "Well-known tags and their /// value(s) data types" to become familiar with exact data types and calling /// conventions required for each tag supported by the library. /// /// /// A pseudo-tag is a parameter that is used to control the operation of the library but /// whose value is not read or written to the underlying file. /// /// /// public FieldValue[] GetField(TiffTag tag) { TiffFieldInfo fip = FindFieldInfo(tag, TiffType.Any); if (fip != null && (isPseudoTag(tag) || fieldSet(fip.Bit))) return m_tagmethods.GetField(this, tag); return null; } /// /// Gets the value(s) of a tag in an open TIFF file or default value(s) of a tag if a tag /// is not defined in the current directory and it has a default value(s). /// /// The tag. /// /// The value(s) of a tag in an open TIFF file as array of /// objects or null if there is no such tag set and /// tag has no default value. /// /// /// /// returns the value(s) of a tag or pseudo-tag associated /// with the current directory of the opened TIFF file or default value(s) of a tag if a /// tag is not defined in the current directory and it has a default value(s). The tag is /// identified by . The type and number of values returned is /// dependent on the tag being requested. You may want to consult /// "Well-known tags and their /// value(s) data types" to become familiar with exact data types and calling /// conventions required for each tag supported by the library. /// /// /// A pseudo-tag is a parameter that is used to control the operation of the library but /// whose value is not read or written to the underlying file. /// /// /// public FieldValue[] GetFieldDefaulted(TiffTag tag) { TiffDirectory td = m_dir; FieldValue[] result = GetField(tag); if (result != null) return result; switch (tag) { case TiffTag.SubFileType: result = new FieldValue[1]; result[0].Set(td.td_subfiletype); break; case TiffTag.BitsPerSample: result = new FieldValue[1]; result[0].Set(td.td_bitspersample); break; case TiffTag.Threshholding: result = new FieldValue[1]; result[0].Set(td.td_threshholding); break; case TiffTag.FillOrder: result = new FieldValue[1]; result[0].Set(td.td_fillorder); break; case TiffTag.Orientation: result = new FieldValue[1]; result[0].Set(td.td_orientation); break; case TiffTag.SamplesPerPixel: result = new FieldValue[1]; result[0].Set(td.td_samplesperpixel); break; case TiffTag.RowsPerStrip: result = new FieldValue[1]; result[0].Set(td.td_rowsperstrip); break; case TiffTag.MinSampleValue: result = new FieldValue[1]; result[0].Set(td.td_minsamplevalue); break; case TiffTag.MaxSampleValue: result = new FieldValue[1]; result[0].Set(td.td_maxsamplevalue); break; case TiffTag.PlanarConfig: result = new FieldValue[1]; result[0].Set(td.td_planarconfig); break; case TiffTag.ResolutionUnit: result = new FieldValue[1]; result[0].Set(td.td_resolutionunit); break; case TiffTag.Predictor: CodecWithPredictor sp = m_currentCodec as CodecWithPredictor; if (sp != null) { result = new FieldValue[1]; result[0].Set(sp.GetPredictorValue()); } break; case TiffTag.DotRange: result = new FieldValue[2]; result[0].Set(0); result[1].Set((1 << td.td_bitspersample) - 1); break; case TiffTag.InkSet: result = new FieldValue[1]; result[0].Set(InkSet.CMYK); break; case TiffTag.NumberOfInks: result = new FieldValue[1]; result[0].Set(4); break; case TiffTag.ExtraSamples: result = new FieldValue[2]; result[0].Set(td.td_extrasamples); result[1].Set(td.td_sampleinfo); break; case TiffTag.MATTEING: result = new FieldValue[1]; result[0].Set((td.td_extrasamples == 1 && td.td_sampleinfo[0] == ExtraSample.AssociatedAlpha)); break; case TiffTag.TILEDEPTH: result = new FieldValue[1]; result[0].Set(td.td_tiledepth); break; case TiffTag.DATATYPE: result = new FieldValue[1]; result[0].Set(td.td_sampleformat - 1); break; case TiffTag.SampleFormat: result = new FieldValue[1]; result[0].Set(td.td_sampleformat); break; case TiffTag.IMAGEDEPTH: result = new FieldValue[1]; result[0].Set(td.td_imagedepth); break; case TiffTag.YCBCRCOEFFICIENTS: { // defaults are from CCIR Recommendation 601-1 float[] ycbcrcoeffs = new float[3]; ycbcrcoeffs[0] = 0.299f; ycbcrcoeffs[1] = 0.587f; ycbcrcoeffs[2] = 0.114f; result = new FieldValue[1]; result[0].Set(ycbcrcoeffs); break; } case TiffTag.YCBCRSUBSAMPLING: result = new FieldValue[2]; result[0].Set(td.td_ycbcrsubsampling[0]); result[1].Set(td.td_ycbcrsubsampling[1]); break; case TiffTag.YCBCRPOSITIONING: result = new FieldValue[1]; result[0].Set(td.td_ycbcrpositioning); break; case TiffTag.WhitePoint: { // TIFF 6.0 specification tells that it is no default value for the // WhitePoint, but AdobePhotoshop TIFF Technical Note tells that it // should be CIE D50. float[] whitepoint = new float[2]; whitepoint[0] = D50_X0 / (D50_X0 + D50_Y0 + D50_Z0); whitepoint[1] = D50_Y0 / (D50_X0 + D50_Y0 + D50_Z0); result = new FieldValue[1]; result[0].Set(whitepoint); break; } case TiffTag.TransferFunction: if (td.td_transferfunction[0] == null && !defaultTransferFunction(td)) { ErrorExt(this, m_clientdata, m_name, "No space for \"TransferFunction\" tag"); return null; } result = new FieldValue[3]; result[0].Set(td.td_transferfunction[0]); if (td.td_samplesperpixel - td.td_extrasamples > 1) { result[1].Set(td.td_transferfunction[1]); result[2].Set(td.td_transferfunction[2]); } break; case TiffTag.REFERENCEBLACKWHITE: if (td.td_refblackwhite == null) defaultRefBlackWhite(td); result = new FieldValue[1]; result[0].Set(td.td_refblackwhite); break; } return result; } /// /// Reads the contents of the next TIFF directory in an open TIFF file/stream and makes /// it the current directory. /// /// true if directory was successfully read; otherwise, false if an /// error was encountered, or if there are no more directories to be read. /// Directories are read sequentially. /// Applications only need to call to read multiple /// subfiles in a single TIFF file/stream - the first directory in a file/stream is /// automatically read when or is called. /// /// The images that have a single uncompressed strip or tile of data are automatically /// treated as if they were made up of multiple strips or tiles of approximately 8 /// kilobytes each. This operation is done only in-memory; it does not alter the contents /// of the file/stream. However, the construction of the "chopped strips" is visible to /// the application through the number of strips returned by /// or the number of tiles returned by . /// public bool ReadDirectory() { const string module = "ReadDirectory"; m_diroff = m_nextdiroff; if (m_diroff == 0) { // no more directories return false; } // Check whether we have the last offset or bad offset (IFD looping). if (!checkDirOffset(m_nextdiroff)) return false; // Cleanup any previous compression state. m_currentCodec.Cleanup(); m_curdir++; TiffDirEntry[] dir; short dircount = fetchDirectory(m_nextdiroff, out dir, out m_nextdiroff); if (dircount == 0) { ErrorExt(this, m_clientdata, module, "{0}: Failed to read directory at offset {1}", m_name, m_nextdiroff); return false; } // reset before new dir m_flags &= ~TiffFlags.BeenWriting; // Setup default value and then make a pass over the fields to check type and tag // information, and to extract info required to size data structures. A second pass is // made afterwards to read in everthing not taken in the first pass. // free any old stuff and reinit FreeDirectory(); setupDefaultDirectory(); // Electronic Arts writes gray-scale TIFF files without a PlanarConfiguration // directory entry. Thus we setup a default value here, even though the TIFF spec says // there is no default value. SetField(TiffTag.PlanarConfig, PlanarConfig.Contig); // Sigh, we must make a separate pass through the directory for the following reason: // // We must process the Compression tag in the first pass in order to merge in // codec-private tag definitions (otherwise we may get complaints about unknown tags). // However, the Compression tag may be dependent on the SamplesPerPixel tag value // because older TIFF specs permited Compression to be written as a // SamplesPerPixel-count tag entry. Thus if we don't first figure out the correct // SamplesPerPixel tag value then we may end up ignoring the Compression tag value // because it has an incorrect count value (if the true value of SamplesPerPixel is not 1). // // It sure would have been nice if Aldus had really thought this stuff through carefully. for (int i = 0; i < dircount; i++) { TiffDirEntry dp = dir[i]; if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { short temp = (short)dp.tdir_tag; SwabShort(ref temp); dp.tdir_tag = (TiffTag)(ushort)temp; temp = (short)dp.tdir_type; SwabShort(ref temp); dp.tdir_type = (TiffType)temp; SwabLong(ref dp.tdir_count); SwabUInt(ref dp.tdir_offset); } if (dp.tdir_tag == TiffTag.SamplesPerPixel) { if (!fetchNormalTag(dir[i])) return false; dp.tdir_tag = TiffTag.Ignore; } } // First real pass over the directory. int fix = 0; bool diroutoforderwarning = false; bool haveunknowntags = false; for (int i = 0; i < dircount; i++) { if (dir[i].tdir_tag == TiffTag.Ignore) continue; if (fix >= m_nfields) fix = 0; // Silicon Beach (at least) writes unordered directory tags (violating the spec). // Handle it here, but be obnoxious (maybe they'll fix it?). if (dir[i].tdir_tag < m_fieldinfo[fix].Tag) { if (!diroutoforderwarning) { WarningExt(this, m_clientdata, module, "{0}: invalid TIFF directory; tags are not sorted in ascending order", m_name); diroutoforderwarning = true; } fix = 0; // O(n^2) } while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag) fix++; if (fix >= m_nfields || m_fieldinfo[fix].Tag != dir[i].tdir_tag) { // Unknown tag ... we'll deal with it below haveunknowntags = true; continue; } // null out old tags that we ignore. if (m_fieldinfo[fix].Bit == FieldBit.Ignore) { dir[i].tdir_tag = TiffTag.Ignore; continue; } // Check data type. TiffFieldInfo fip = m_fieldinfo[fix]; while (dir[i].tdir_type != fip.Type && fix < m_nfields) { if (fip.Type == TiffType.Any) { // wildcard break; } fip = m_fieldinfo[++fix]; if (fix >= m_nfields || fip.Tag != dir[i].tdir_tag) { WarningExt(this, m_clientdata, module, "{0}: wrong data type {1} for \"{2}\"; tag ignored", m_name, dir[i].tdir_type, m_fieldinfo[fix - 1].Name); dir[i].tdir_tag = TiffTag.Ignore; continue; } } // Check count if known in advance. if (fip.ReadCount != TiffFieldInfo.Variable && fip.ReadCount != TiffFieldInfo.Variable2) { int expected = fip.ReadCount; if (fip.ReadCount == TiffFieldInfo.Spp) expected = m_dir.td_samplesperpixel; if (!checkDirCount(dir[i], expected)) { dir[i].tdir_tag = TiffTag.Ignore; continue; } } switch (dir[i].tdir_tag) { case TiffTag.Compression: // The 5.0 spec says the Compression tag has one value, // while earlier specs say it has one value per sample. // Because of this, we accept the tag if one value is supplied. if (dir[i].tdir_count == 1) { int v = extractData(dir[i]); if (!SetField(dir[i].tdir_tag, v)) return false; break; // XXX: workaround for broken TIFFs } else if (dir[i].tdir_type == TiffType.Long) { int v; if (!fetchPerSampleLongs(dir[i], out v) || !SetField(dir[i].tdir_tag, v)) return false; } else { short iv; if (!fetchPerSampleShorts(dir[i], out iv) || !SetField(dir[i].tdir_tag, iv)) return false; } dir[i].tdir_tag = TiffTag.Ignore; break; case TiffTag.StripOffsets: case TiffTag.StripByteCounts: case TiffTag.TileOffsets: case TiffTag.TileByteCounts: setFieldBit(fip.Bit); break; case TiffTag.ImageWidth: case TiffTag.ImageLength: case TiffTag.IMAGEDEPTH: case TiffTag.TileLength: case TiffTag.TileWidth: case TiffTag.TILEDEPTH: case TiffTag.PlanarConfig: case TiffTag.RowsPerStrip: case TiffTag.ExtraSamples: if (!fetchNormalTag(dir[i])) return false; dir[i].tdir_tag = TiffTag.Ignore; break; } } // If we saw any unknown tags, make an extra pass over the directory to deal with // them. This must be done separately because the tags could have become known when we // registered a codec after finding the Compression tag. In a correctly-sorted // directory there's no problem because Compression will come before any codec-private // tags, but if the sorting is wrong that might not hold. if (haveunknowntags) { fix = 0; for (int i = 0; i < dircount; i++) { if (dir[i].tdir_tag == TiffTag.Ignore) continue; if (fix >= m_nfields || dir[i].tdir_tag < m_fieldinfo[fix].Tag) { // O(n^2) fix = 0; } while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag) fix++; if (fix >= m_nfields || m_fieldinfo[fix].Tag != dir[i].tdir_tag) { Tiff.WarningExt(this, m_clientdata, module, "{0}: unknown field with tag {1} (0x{2:x}) encountered", m_name, (ushort)dir[i].tdir_tag, (ushort)dir[i].tdir_tag); TiffFieldInfo[] arr = new TiffFieldInfo[1]; arr[0] = createAnonFieldInfo(dir[i].tdir_tag, dir[i].tdir_type); MergeFieldInfo(arr, 1); fix = 0; while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag) fix++; } // Check data type. TiffFieldInfo fip = m_fieldinfo[fix]; while (dir[i].tdir_type != fip.Type && fix < m_nfields) { if (fip.Type == TiffType.Any) { // wildcard break; } fip = m_fieldinfo[++fix]; if (fix >= m_nfields || fip.Tag != dir[i].tdir_tag) { Tiff.WarningExt(this, m_clientdata, module, "{0}: wrong data type {1} for \"{2}\"; tag ignored", m_name, dir[i].tdir_type, m_fieldinfo[fix - 1].Name); dir[i].tdir_tag = TiffTag.Ignore; break; } } } } // XXX: OJPEG hack. // If a) compression is OJPEG, b) planarconfig tag says it's separate, c) strip // offsets/bytecounts tag are both present and d) both contain exactly one value, then // we consistently find that the buggy implementation of the buggy compression scheme // matches contig planarconfig best. So we 'fix-up' the tag here if ((m_dir.td_compression == Compression.OJPEG) && (m_dir.td_planarconfig == PlanarConfig.Separate)) { int dpIndex = readDirectoryFind(dir, dircount, TiffTag.StripOffsets); if (dpIndex != -1 && dir[dpIndex].tdir_count == 1) { dpIndex = readDirectoryFind(dir, dircount, TiffTag.StripByteCounts); if (dpIndex != -1 && dir[dpIndex].tdir_count == 1) { m_dir.td_planarconfig = PlanarConfig.Contig; WarningExt(this, m_clientdata, "ReadDirectory", "Planarconfig tag value assumed incorrect, assuming data is contig instead of chunky"); } } } // Allocate directory structure and setup defaults. if (!fieldSet(FieldBit.ImageDimensions)) { missingRequired("ImageLength"); return false; } // Setup appropriate structures (by strip or by tile) if (!fieldSet(FieldBit.TileDimensions)) { m_dir.td_nstrips = NumberOfStrips(); m_dir.td_tilewidth = m_dir.td_imagewidth; m_dir.td_tilelength = m_dir.td_rowsperstrip; m_dir.td_tiledepth = m_dir.td_imagedepth; m_flags &= ~TiffFlags.IsTiled; } else { m_dir.td_nstrips = NumberOfTiles(); m_flags |= TiffFlags.IsTiled; } if (m_dir.td_nstrips == 0) { ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero number of {1}", m_name, IsTiled() ? "tiles" : "strips"); return false; } m_dir.td_stripsperimage = m_dir.td_nstrips; if (m_dir.td_planarconfig == PlanarConfig.Separate) m_dir.td_stripsperimage /= m_dir.td_samplesperpixel; if (!fieldSet(FieldBit.StripOffsets)) { if ((m_dir.td_compression == Compression.OJPEG) && !IsTiled() && (m_dir.td_nstrips == 1)) { // XXX: OJPEG hack. // If a) compression is OJPEG, b) it's not a tiled TIFF, and c) the number of // strips is 1, then we tolerate the absence of stripoffsets tag, because, // presumably, all required data is in the JpegInterchangeFormat stream. setFieldBit(FieldBit.StripOffsets); } else { missingRequired(IsTiled() ? "TileOffsets" : "StripOffsets"); return false; } } // Second pass: extract other information. for (int i = 0; i < dircount; i++) { if (dir[i].tdir_tag == TiffTag.Ignore) continue; switch (dir[i].tdir_tag) { case TiffTag.MinSampleValue: case TiffTag.MaxSampleValue: case TiffTag.BitsPerSample: case TiffTag.DATATYPE: case TiffTag.SampleFormat: // The 5.0 spec says the Compression tag has one value, while earlier // specs say it has one value per sample. Because of this, we accept the // tag if one value is supplied. // // The MinSampleValue, MaxSampleValue, BitsPerSample DataType and // SampleFormat tags are supposed to be written as one value/sample, but // some vendors incorrectly write one value only - so we accept that as // well (yech). Other vendors write correct value for NumberOfSamples, but // incorrect one for BitsPerSample and friends, and we will read this too. if (dir[i].tdir_count == 1) { int v = extractData(dir[i]); if (!SetField(dir[i].tdir_tag, v)) return false; // XXX: workaround for broken TIFFs } else if (dir[i].tdir_tag == TiffTag.BitsPerSample && dir[i].tdir_type == TiffType.Long) { int v; if (!fetchPerSampleLongs(dir[i], out v) || !SetField(dir[i].tdir_tag, v)) return false; } else { short iv; if (!fetchPerSampleShorts(dir[i], out iv) || !SetField(dir[i].tdir_tag, iv)) return false; } break; case TiffTag.SMinSampleValue: case TiffTag.SMaxSampleValue: double dv; if (!fetchPerSampleAnys(dir[i], out dv) || !SetField(dir[i].tdir_tag, dv)) return false; break; case TiffTag.StripOffsets: case TiffTag.TileOffsets: if (!fetchStripThing(dir[i], m_dir.td_nstrips, ref m_dir.td_stripoffset)) return false; break; case TiffTag.StripByteCounts: case TiffTag.TileByteCounts: if (!fetchStripThing(dir[i], m_dir.td_nstrips, ref m_dir.td_stripbytecount)) return false; break; case TiffTag.Colormap: case TiffTag.TransferFunction: { // TransferFunction can have either 1x or 3x data values; // Colormap can have only 3x items. int v = 1 << m_dir.td_bitspersample; if (dir[i].tdir_tag == TiffTag.Colormap || dir[i].tdir_count != v) { if (!checkDirCount(dir[i], 3 * v)) break; } byte[] cp = new byte[dir[i].tdir_count * sizeof(short)]; if (fetchData(dir[i], cp) != 0) { int c = 1 << m_dir.td_bitspersample; if (dir[i].tdir_count == c) { // This deals with there being only one array to apply to all samples. short[] u = ByteArrayToShorts(cp, 0, dir[i].tdir_count * sizeof(short)); SetField(dir[i].tdir_tag, u, u, u); } else { v *= sizeof(short); short[] u0 = ByteArrayToShorts(cp, 0, v); short[] u1 = ByteArrayToShorts(cp, v, v); short[] u2 = ByteArrayToShorts(cp, 2 * v, v); SetField(dir[i].tdir_tag, u0, u1, u2); } } break; } case TiffTag.PageNumber: case TiffTag.HalfToneHints: case TiffTag.YCBCRSUBSAMPLING: case TiffTag.DotRange: fetchShortPair(dir[i]); break; case TiffTag.REFERENCEBLACKWHITE: fetchRefBlackWhite(dir[i]); break; // BEGIN REV 4.0 COMPATIBILITY case TiffTag.OSubFileType: FileType ft = 0; switch ((SubFileType)extractData(dir[i])) { case SubFileType.ReducedSizeImage: ft = FileType.ReducedResImage; break; case SubFileType.Page: ft = FileType.Page; break; } if (ft != 0) SetField(TiffTag.SubFileType, ft); break; // END REV 4.0 COMPATIBILITY default: fetchNormalTag(dir[i]); break; } } // OJPEG hack: // - If a) compression is OJPEG, and b) photometric tag is missing, then we // consistently find that photometric should be YCbCr // - If a) compression is OJPEG, and b) photometric tag says it's RGB, then we // consistently find that the buggy implementation of the buggy compression scheme // matches photometric YCbCr instead. // - If a) compression is OJPEG, and b) bitspersample tag is missing, then we // consistently find bitspersample should be 8. // - If a) compression is OJPEG, b) samplesperpixel tag is missing, and c) photometric // is RGB or YCbCr, then we consistently find samplesperpixel should be 3 // - If a) compression is OJPEG, b) samplesperpixel tag is missing, and c) photometric // is MinIsWhite or MinIsBlack, then we consistently find samplesperpixel should be 3 if (m_dir.td_compression == Compression.OJPEG) { if (!fieldSet(FieldBit.Photometric)) { WarningExt(this, m_clientdata, "ReadDirectory", "Photometric tag is missing, assuming data is YCbCr"); if (!SetField(TiffTag.Photometric, Photometric.YCBCR)) return false; } else if (m_dir.td_photometric == Photometric.RGB) { m_dir.td_photometric = Photometric.YCBCR; WarningExt(this, m_clientdata, "ReadDirectory", "Photometric tag value assumed incorrect, assuming data is YCbCr instead of RGB"); } if (!fieldSet(FieldBit.BitsPerSample)) { WarningExt(this, m_clientdata, "ReadDirectory", "BitsPerSample tag is missing, assuming 8 bits per sample"); if (!SetField(TiffTag.BitsPerSample, 8)) return false; } if (!fieldSet(FieldBit.SamplesPerPixel)) { if ((m_dir.td_photometric == Photometric.RGB) || (m_dir.td_photometric == Photometric.YCBCR)) { WarningExt(this, m_clientdata, "ReadDirectory", "SamplesPerPixel tag is missing, assuming correct SamplesPerPixel value is 3"); if (!SetField(TiffTag.SamplesPerPixel, 3)) return false; } else if ((m_dir.td_photometric == Photometric.MinIsWhite) || (m_dir.td_photometric == Photometric.MinIsBlack)) { WarningExt(this, m_clientdata, "ReadDirectory", "SamplesPerPixel tag is missing, assuming correct SamplesPerPixel value is 1"); if (!SetField(TiffTag.SamplesPerPixel, 1)) return false; } } } // Verify Palette image has a Colormap. if (m_dir.td_photometric == Photometric.Palette && !fieldSet(FieldBit.ColorMap)) { missingRequired("Colormap"); return false; } // OJPEG hack: // We do no further messing with strip/tile offsets/bytecounts in OJPEG TIFFs if (m_dir.td_compression != Compression.OJPEG) { // Attempt to deal with a missing StripByteCounts tag. if (!fieldSet(FieldBit.StripByteCounts)) { // Some manufacturers violate the spec by not giving the size of the strips. // In this case, assume there is one uncompressed strip of data. if ((m_dir.td_planarconfig == PlanarConfig.Contig && m_dir.td_nstrips > 1) || (m_dir.td_planarconfig == PlanarConfig.Separate && m_dir.td_nstrips != m_dir.td_samplesperpixel)) { missingRequired("StripByteCounts"); return false; } WarningExt(this, m_clientdata, module, "{0}: TIFF directory is missing required \"{1}\" field, calculating from imagelength", m_name, FieldWithTag(TiffTag.StripByteCounts).Name); if (!estimateStripByteCounts(dir, dircount)) return false; } else if (m_dir.td_nstrips == 1 && m_dir.td_stripoffset[0] != 0 && byteCountLooksBad(m_dir)) { // XXX: Plexus (and others) sometimes give a value of zero for a tag when // they don't know what the correct value is! Try and handle the simple case // of estimating the size of a one strip image. WarningExt(this, m_clientdata, module, "{0}: Bogus \"{1}\" field, ignoring and calculating from imagelength", m_name, FieldWithTag(TiffTag.StripByteCounts).Name); if (!estimateStripByteCounts(dir, dircount)) return false; } else if (m_dir.td_planarconfig == PlanarConfig.Contig && m_dir.td_nstrips > 2 && m_dir.td_compression == Compression.None && m_dir.td_stripbytecount[0] != m_dir.td_stripbytecount[1]) { // XXX: Some vendors fill StripByteCount array with absolutely wrong values // (it can be equal to StripOffset array, for example). Catch this case here. WarningExt(this, m_clientdata, module, "{0}: Wrong \"{1}\" field, ignoring and calculating from imagelength", m_name, FieldWithTag(TiffTag.StripByteCounts).Name); if (!estimateStripByteCounts(dir, dircount)) return false; } } dir = null; if (!fieldSet(FieldBit.MaxSampleValue)) m_dir.td_maxsamplevalue = (short)((1 << m_dir.td_bitspersample) - 1); // Setup default compression scheme. // XXX: We can optimize checking for the strip bounds using the sorted bytecounts // array. See also comments for appendToStrip() function. if (m_dir.td_nstrips > 1) { m_dir.td_stripbytecountsorted = true; for (int strip = 1; strip < m_dir.td_nstrips; strip++) { if (m_dir.td_stripoffset[strip - 1] > m_dir.td_stripoffset[strip]) { m_dir.td_stripbytecountsorted = false; break; } } } if (!fieldSet(FieldBit.Compression)) SetField(TiffTag.Compression, Compression.None); // Some manufacturers make life difficult by writing large amounts of uncompressed // data as a single strip. This is contrary to the recommendations of the spec. The // following makes an attempt at breaking such images into strips closer to the // recommended 8k bytes. A side effect, however, is that the RowsPerStrip tag value // may be changed. if (m_dir.td_nstrips == 1 && m_dir.td_compression == Compression.None && (m_flags & TiffFlags.StripChop) == TiffFlags.StripChop && (m_flags & TiffFlags.IsTiled) != TiffFlags.IsTiled) { chopUpSingleUncompressedStrip(); } // Reinitialize i/o since we are starting on a new directory. m_row = -1; m_curstrip = -1; m_col = -1; m_curtile = -1; m_tilesize = -1; m_scanlinesize = ScanlineSize(); if (m_scanlinesize == 0) { ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero scanline size", m_name); return false; } if (IsTiled()) { m_tilesize = TileSize(); if (m_tilesize == 0) { ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero tile size", m_name); return false; } } else { if (StripSize() == 0) { ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero strip size", m_name); return false; } } return true; } /// /// Reads a custom directory from the arbitrary offset within file/stream. /// /// The directory offset. /// The array of objects to merge to /// existing field information. /// The number of items to use from /// the array. /// true if a custom directory was read successfully; /// otherwise, false public bool ReadCustomDirectory(long offset, TiffFieldInfo[] info, int count) { const string module = "ReadCustomDirectory"; setupFieldInfo(info, count); uint dummyNextDirOff; TiffDirEntry[] dir; short dircount = fetchDirectory((uint)offset, out dir, out dummyNextDirOff); if (dircount == 0) { ErrorExt(this, m_clientdata, module, "{0}: Failed to read custom directory at offset {1}", m_name, offset); return false; } FreeDirectory(); m_dir = new TiffDirectory(); int fix = 0; for (short i = 0; i < dircount; i++) { if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) { short temp = (short)dir[i].tdir_tag; SwabShort(ref temp); dir[i].tdir_tag = (TiffTag)(ushort)temp; temp = (short)dir[i].tdir_type; SwabShort(ref temp); dir[i].tdir_type = (TiffType)temp; SwabLong(ref dir[i].tdir_count); SwabUInt(ref dir[i].tdir_offset); } if (fix >= m_nfields || dir[i].tdir_tag == TiffTag.Ignore) continue; while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag) fix++; if (fix >= m_nfields || m_fieldinfo[fix].Tag != dir[i].tdir_tag) { WarningExt(this, m_clientdata, module, "{0}: unknown field with tag {1} (0x{2:x}) encountered", m_name, (ushort)dir[i].tdir_tag, (ushort)dir[i].tdir_tag); TiffFieldInfo[] arr = new TiffFieldInfo[1]; arr[0] = createAnonFieldInfo(dir[i].tdir_tag, dir[i].tdir_type); MergeFieldInfo(arr, 1); fix = 0; while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag) fix++; } // null out old tags that we ignore. if (m_fieldinfo[fix].Bit == FieldBit.Ignore) { dir[i].tdir_tag = TiffTag.Ignore; continue; } // Check data type. TiffFieldInfo fip = m_fieldinfo[fix]; while (dir[i].tdir_type != fip.Type && fix < m_nfields) { if (fip.Type == TiffType.Any) { // wildcard break; } fip = m_fieldinfo[++fix]; if (fix >= m_nfields || fip.Tag != dir[i].tdir_tag) { WarningExt(this, m_clientdata, module, "{0}: wrong data type {1} for \"{2}\"; tag ignored", m_name, dir[i].tdir_type, m_fieldinfo[fix - 1].Name); dir[i].tdir_tag = TiffTag.Ignore; continue; } } // Check count if known in advance. if (fip.ReadCount != TiffFieldInfo.Variable && fip.ReadCount != TiffFieldInfo.Variable2) { int expected = fip.ReadCount; if (fip.ReadCount == TiffFieldInfo.Spp) expected = m_dir.td_samplesperpixel; if (!checkDirCount(dir[i], expected)) { dir[i].tdir_tag = TiffTag.Ignore; continue; } } // EXIF tags which need to be specifically processed. switch (dir[i].tdir_tag) { case TiffTag.EXIF_SUBJECTDISTANCE: fetchSubjectDistance(dir[i]); break; default: fetchNormalTag(dir[i]); break; } } return true; } /// /// Reads an EXIF directory from the given offset within file/stream. /// /// The directory offset. /// true if an EXIF directory was read successfully; /// otherwise, false public bool ReadEXIFDirectory(long offset) { int exifFieldInfoCount; TiffFieldInfo[] exifFieldInfo = getExifFieldInfo(out exifFieldInfoCount); return ReadCustomDirectory(offset, exifFieldInfo, exifFieldInfoCount); } /// /// Calculates the size in bytes of a row of data as it would be returned in a call to /// , or as it would be /// expected in a call to . /// /// The size in bytes of a row of data. /// ScanlineSize calculates size for one sample plane only. Please use /// if you want to get size in bytes of a complete /// decoded and packed raster scanline. /// public int ScanlineSize() { int scanline; if (m_dir.td_planarconfig == PlanarConfig.Contig) { if (m_dir.td_photometric == Photometric.YCBCR && !IsUpSampled()) { FieldValue[] result = GetFieldDefaulted(TiffTag.YCBCRSUBSAMPLING); short ycbcrsubsampling0 = result[0].ToShort(); if (ycbcrsubsampling0 == 0) { ErrorExt(this, m_clientdata, m_name, "Invalid YCbCr subsampling"); return 0; } scanline = roundUp(m_dir.td_imagewidth, ycbcrsubsampling0); scanline = howMany8(multiply(scanline, m_dir.td_bitspersample, "ScanlineSize")); return summarize(scanline, multiply(2, scanline / ycbcrsubsampling0, "VStripSize"), "VStripSize"); } else { scanline = multiply(m_dir.td_imagewidth, m_dir.td_samplesperpixel, "ScanlineSize"); } } else { scanline = m_dir.td_imagewidth; } return howMany8(multiply(scanline, m_dir.td_bitspersample, "ScanlineSize")); } /// /// Calculates the size in bytes of a complete decoded and packed raster scanline. /// /// The size in bytes of a complete decoded and packed raster scanline. /// The value returned by RasterScanlineSize may be different from the /// value returned by if data is stored as separate /// planes ( = .Separate). /// public int RasterScanlineSize() { int scanline = multiply(m_dir.td_bitspersample, m_dir.td_imagewidth, "RasterScanlineSize"); if (m_dir.td_planarconfig == PlanarConfig.Contig) { scanline = multiply(scanline, m_dir.td_samplesperpixel, "RasterScanlineSize"); return howMany8(scanline); } return multiply(howMany8(scanline), m_dir.td_samplesperpixel, "RasterScanlineSize"); } /// /// Computes the number of rows for a reasonable-sized strip according to the current /// settings of the , /// and tags and any compression-specific requirements. /// /// The esimated value (may be zero). /// The number of rows for a reasonable-sized strip according to the current /// tag settings and compression-specific requirements. /// If the parameter is non-zero, then it is taken /// as an estimate of the desired strip size and adjusted according to any /// compression-specific requirements. The value returned by DefaultStripSize is /// typically used to define the tag. If there is no /// any unusual requirements DefaultStripSize tries to create strips that have /// approximately 8 kilobytes of uncompressed data. public int DefaultStripSize(int estimate) { return m_currentCodec.DefStripSize(estimate); } /// /// Computes the number of bytes in a row-aligned strip. /// /// The number of bytes in a row-aligned strip /// /// /// StripSize returns the equivalent size for a strip of data as it would be /// returned in a call to or as it would be expected in a /// call to . /// /// If the value of the field corresponding to is /// larger than the recorded , then the strip size is /// truncated to reflect the actual space required to hold the strip. /// public int StripSize() { int rps = m_dir.td_rowsperstrip; if (rps > m_dir.td_imagelength) rps = m_dir.td_imagelength; return VStripSize(rps); } /// /// Computes the number of bytes in a row-aligned strip with specified number of rows. /// /// The number of rows in a strip. /// /// The number of bytes in a row-aligned strip with specified number of rows. public int VStripSize(int rowCount) { if (rowCount == -1) rowCount = m_dir.td_imagelength; if (m_dir.td_planarconfig == PlanarConfig.Contig && m_dir.td_photometric == Photometric.YCBCR && !IsUpSampled()) { // Packed YCbCr data contain one Cb+Cr for every // HorizontalSampling * VerticalSampling Y values. // Must also roundup width and height when calculating since images that are not // a multiple of the horizontal/vertical subsampling area include YCbCr data for // the extended image. FieldValue[] result = GetFieldDefaulted(TiffTag.YCBCRSUBSAMPLING); short ycbcrsubsampling0 = result[0].ToShort(); short ycbcrsubsampling1 = result[1].ToShort(); int samplingarea = ycbcrsubsampling0 * ycbcrsubsampling1; if (samplingarea == 0) { ErrorExt(this, m_clientdata, m_name, "Invalid YCbCr subsampling"); return 0; } int w = roundUp(m_dir.td_imagewidth, ycbcrsubsampling0); int scanline = howMany8(multiply(w, m_dir.td_bitspersample, "VStripSize")); rowCount = roundUp(rowCount, ycbcrsubsampling1); // NB: don't need howMany here 'cuz everything is rounded scanline = multiply(rowCount, scanline, "VStripSize"); return summarize(scanline, multiply(2, scanline / samplingarea, "VStripSize"), "VStripSize"); } return multiply(rowCount, ScanlineSize(), "VStripSize"); } /// /// Computes the number of bytes in a raw (i.e. not decoded) strip. /// /// The zero-based index of a strip. /// The number of bytes in a raw strip. public long RawStripSize(int strip) { long bytecount = m_dir.td_stripbytecount[strip]; if (bytecount <= 0) { ErrorExt(this, m_clientdata, m_name, "{0}: Invalid strip byte count, strip {1}", bytecount, strip); bytecount = -1; } return bytecount; } /// /// Computes which strip contains the specified coordinates (row, plane). /// /// The row. /// The sample plane. /// The number of the strip that contains the specified coordinates. /// /// A valid strip number is always returned; out-of-range coordinate values are clamped to /// the bounds of the image. The parameter is always used in /// calculating a strip. The parameter is used only if data are /// organized in separate planes /// ( = .Separate). /// public int ComputeStrip(int row, short plane) { int strip = 0; if (m_dir.td_rowsperstrip != -1) strip = row / m_dir.td_rowsperstrip; if (m_dir.td_planarconfig == PlanarConfig.Separate) { if (plane >= m_dir.td_samplesperpixel) { ErrorExt(this, m_clientdata, m_name, "{0}: Sample out of range, max {1}", plane, m_dir.td_samplesperpixel); return 0; } strip += plane * m_dir.td_stripsperimage; } return strip; } /// /// Retrives the number of strips in the image. /// /// The number of strips in the image. public int NumberOfStrips() { int nstrips = (m_dir.td_rowsperstrip == -1 ? 1 : howMany(m_dir.td_imagelength, m_dir.td_rowsperstrip)); if (m_dir.td_planarconfig == PlanarConfig.Separate) nstrips = multiply(nstrips, m_dir.td_samplesperpixel, "NumberOfStrips"); return nstrips; } /// /// Computes the pixel width and height of a reasonable-sized tile suitable for setting /// up the and tags. /// /// The proposed tile width upon the call / tile width to use /// after the call. /// The proposed tile height upon the call / tile height to use /// after the call. /// If the and values passed /// in are non-zero, then they are adjusted to reflect any compression-specific /// requirements. The returned width and height are constrained to be a multiple of /// 16 pixels to conform with the TIFF specification. public void DefaultTileSize(ref int width, ref int height) { m_currentCodec.DefTileSize(ref width, ref height); } /// /// Compute the number of bytes in a row-aligned tile. /// /// The number of bytes in a row-aligned tile. /// TileSize returns the equivalent size for a tile of data as it would be /// returned in a call to or as it would be expected in a /// call to . /// public int TileSize() { return VTileSize(m_dir.td_tilelength); } /// /// Computes the number of bytes in a row-aligned tile with specified number of rows. /// /// The number of rows in a tile. /// /// The number of bytes in a row-aligned tile with specified number of rows. public int VTileSize(int rowCount) { if (m_dir.td_tilelength == 0 || m_dir.td_tilewidth == 0 || m_dir.td_tiledepth == 0) return 0; int tilesize; if (m_dir.td_planarconfig == PlanarConfig.Contig && m_dir.td_photometric == Photometric.YCBCR && !IsUpSampled()) { // Packed YCbCr data contain one Cb+Cr for every // HorizontalSampling * VerticalSampling Y values. // Must also roundup width and height when calculating since images that are not a // multiple of the horizontal/vertical subsampling area include YCbCr data for // the extended image. int w = roundUp(m_dir.td_tilewidth, m_dir.td_ycbcrsubsampling[0]); int rowsize = howMany8(multiply(w, m_dir.td_bitspersample, "VTileSize")); int samplingarea = m_dir.td_ycbcrsubsampling[0] * m_dir.td_ycbcrsubsampling[1]; if (samplingarea == 0) { ErrorExt(this, m_clientdata, m_name, "Invalid YCbCr subsampling"); return 0; } rowCount = roundUp(rowCount, m_dir.td_ycbcrsubsampling[1]); // NB: don't need howMany here 'cuz everything is rounded tilesize = multiply(rowCount, rowsize, "VTileSize"); tilesize = summarize(tilesize, multiply(2, tilesize / samplingarea, "VTileSize"), "VTileSize"); } else { tilesize = multiply(rowCount, TileRowSize(), "VTileSize"); } return multiply(tilesize, m_dir.td_tiledepth, "VTileSize"); } /// /// Computes the number of bytes in a raw (i.e. not decoded) tile. /// /// The zero-based index of a tile. /// The number of bytes in a raw tile. public long RawTileSize(int tile) { // yes, one method for raw tile and strip sizes return RawStripSize(tile); } /// /// Compute the number of bytes in each row of a tile. /// /// The number of bytes in each row of a tile. public int TileRowSize() { if (m_dir.td_tilelength == 0 || m_dir.td_tilewidth == 0) return 0; int rowsize = multiply(m_dir.td_bitspersample, m_dir.td_tilewidth, "TileRowSize"); if (m_dir.td_planarconfig == PlanarConfig.Contig) rowsize = multiply(rowsize, m_dir.td_samplesperpixel, "TileRowSize"); return howMany8(rowsize); } /// /// Computes which tile contains the specified coordinates (x, y, z, plane). /// /// The x-coordinate. /// The y-coordinate. /// The z-coordinate. /// The sample plane. /// The number of the tile that contains the specified coordinates. /// /// A valid tile number is always returned; out-of-range coordinate values are /// clamped to the bounds of the image. The and /// parameters are always used in calculating a tile. The parameter /// is used if the image is deeper than 1 slice ( > 1). /// The parameter is used only if data are organized in separate /// planes ( = .Separate). /// public int ComputeTile(int x, int y, int z, short plane) { if (m_dir.td_imagedepth == 1) z = 0; int dx = m_dir.td_tilewidth; if (dx == -1) dx = m_dir.td_imagewidth; int dy = m_dir.td_tilelength; if (dy == -1) dy = m_dir.td_imagelength; int dz = m_dir.td_tiledepth; if (dz == -1) dz = m_dir.td_imagedepth; int tile = 1; if (dx != 0 && dy != 0 && dz != 0) { int xpt = howMany(m_dir.td_imagewidth, dx); int ypt = howMany(m_dir.td_imagelength, dy); int zpt = howMany(m_dir.td_imagedepth, dz); if (m_dir.td_planarconfig == PlanarConfig.Separate) tile = (xpt * ypt * zpt) * plane + (xpt * ypt) * (z / dz) + xpt * (y / dy) + x / dx; else tile = (xpt * ypt) * (z / dz) + xpt * (y / dy) + x / dx; } return tile; } /// /// Checks whether the specified (x, y, z, plane) coordinates are within the bounds of /// the image. /// /// The x-coordinate. /// The y-coordinate. /// The z-coordinate. /// The sample plane. /// true if the specified coordinates are within the bounds of the image; /// otherwise, false. /// The parameter is checked against the value of the /// tag. The parameter is checked /// against the value of the tag. The /// parameter is checked against the value of the tag /// (if defined). The parameter is checked against the value of /// the tag if the data are organized in separate /// planes. public bool CheckTile(int x, int y, int z, short plane) { if (x >= m_dir.td_imagewidth) { ErrorExt(this, m_clientdata, m_name, "{0}: Col out of range, max {1}", x, m_dir.td_imagewidth - 1); return false; } if (y >= m_dir.td_imagelength) { ErrorExt(this, m_clientdata, m_name, "{0}: Row out of range, max {1}", y, m_dir.td_imagelength - 1); return false; } if (z >= m_dir.td_imagedepth) { ErrorExt(this, m_clientdata, m_name, "{0}: Depth out of range, max {1}", z, m_dir.td_imagedepth - 1); return false; } if (m_dir.td_planarconfig == PlanarConfig.Separate && plane >= m_dir.td_samplesperpixel) { ErrorExt(this, m_clientdata, m_name, "{0}: Sample out of range, max {1}", plane, m_dir.td_samplesperpixel - 1); return false; } return true; } /// /// Retrives the number of tiles in the image. /// /// The number of tiles in the image. public int NumberOfTiles() { int dx = m_dir.td_tilewidth; if (dx == -1) dx = m_dir.td_imagewidth; int dy = m_dir.td_tilelength; if (dy == -1) dy = m_dir.td_imagelength; int dz = m_dir.td_tiledepth; if (dz == -1) dz = m_dir.td_imagedepth; int ntiles; if (dx == 0 || dy == 0 || dz == 0) { ntiles = 0; } else { ntiles = multiply( multiply(howMany(m_dir.td_imagewidth, dx), howMany(m_dir.td_imagelength, dy), "NumberOfTiles"), howMany(m_dir.td_imagedepth, dz), "NumberOfTiles"); } if (m_dir.td_planarconfig == PlanarConfig.Separate) ntiles = multiply(ntiles, m_dir.td_samplesperpixel, "NumberOfTiles"); return ntiles; } /// /// Returns the custom client data associated with this . /// /// The custom client data associated with this . public object Clientdata() { return m_clientdata; } /// /// Asscociates a custom data with this . /// /// The data to associate. /// The previously associated data. public object SetClientdata(object data) { object prev = m_clientdata; m_clientdata = data; return prev; } /// /// Gets the mode with which the underlying file or stream was opened. /// /// The mode with which the underlying file or stream was opened. public int GetMode() { return m_mode; } /// /// Sets the new mode for the underlying file or stream. /// /// The new mode for the underlying file or stream. /// The previous mode with which the underlying file or stream was opened. public int SetMode(int mode) { int prevMode = m_mode; m_mode = mode; return prevMode; } /// /// Gets the value indicating whether the image data of this has a /// tiled organization. /// /// /// true if the image data of this has a tiled organization or /// false if the image data of this is organized in strips. /// public bool IsTiled() { return ((m_flags & TiffFlags.IsTiled) == TiffFlags.IsTiled); } /// /// Gets the value indicating whether the image data was in a different byte-order than /// the host computer. /// /// true if the image data was in a different byte-order than the host /// computer or false if the TIFF file/stream and local host byte-orders are the /// same. /// /// Note that , , /// and /// methods already /// normally perform byte swapping to local host order if needed. /// /// Also note that and do not /// perform byte swapping to local host order. /// public bool IsByteSwapped() { return ((m_flags & TiffFlags.Swab) == TiffFlags.Swab); } /// /// Gets the value indicating whether the image data returned through the read interface /// methods is being up-sampled. /// /// /// true if the data is returned up-sampled; otherwise, false. /// /// The value returned by this method can be useful to applications that want to /// calculate I/O buffer sizes to reflect this usage (though the usual strip and tile size /// routines already do this). public bool IsUpSampled() { return ((m_flags & TiffFlags.UpSampled) == TiffFlags.UpSampled); } /// /// Gets the value indicating whether the image data is being returned in MSB-to-LSB /// bit order. /// /// /// true if the data is being returned in MSB-to-LSB bit order (i.e with bit 0 as /// the most significant bit); otherwise, false. /// public bool IsMSB2LSB() { return isFillOrder(BitOrder.BigEndian); } /// /// Gets the value indicating whether given image data was written in big-endian order. /// /// /// true if given image data was written in big-endian order; otherwise, false. /// public bool IsBigEndian() { return (m_header.tiff_magic == TIFF_BIGENDIAN); } /// /// Gets the tiff stream. /// /// The tiff stream. public TiffStream GetStream() { return m_stream; } /// /// Gets the current row that is being read or written. /// /// The current row that is being read or written. /// The current row is updated each time a read or write is done. public int CurrentRow() { return m_row; } /// /// Gets the zero-based index of the current directory. /// /// The zero-based index of the current directory. /// The zero-based index returned by this method is suitable for use with /// the method. /// public short CurrentDirectory() { return m_curdir; } /// /// Gets the number of directories in a file. /// /// The number of directories in a file. public short NumberOfDirectories() { uint nextdir = m_header.tiff_diroff; short n = 0; long dummyOff; while (nextdir != 0 && advanceDirectory(ref nextdir, out dummyOff)) n++; return n; } /// /// Retrieves the file/stream offset of the current directory. /// /// The file/stream offset of the current directory. public long CurrentDirOffset() { return m_diroff; } /// /// Gets the current strip that is being read or written. /// /// The current strip that is being read or written. /// The current strip is updated each time a read or write is done. public int CurrentStrip() { return m_curstrip; } /// /// Gets the current tile that is being read or written. /// /// The current tile that is being read or written. /// The current tile is updated each time a read or write is done. public int CurrentTile() { return m_curtile; } /// /// Sets up the data buffer used to read raw (encoded) data from a file. /// /// The data buffer. /// The buffer size. /// /// /// This method is provided for client-control of the I/O buffers used by the library. /// Applications need never use this method; it's provided only for "intelligent clients" /// that wish to optimize memory usage and/or eliminate potential copy operations that can /// occur when working with images that have data stored without compression. /// /// /// If the is null, then a buffer of appropriate size is /// allocated by the library. Otherwise, the caller must guarantee that the buffer is /// large enough to hold any individual strip of raw data. /// /// public void ReadBufferSetup(byte[] buffer, int size) { Debug.Assert((m_flags & TiffFlags.NoReadRaw) != TiffFlags.NoReadRaw); m_rawdata = null; if (buffer != null) { m_rawdatasize = size; m_rawdata = buffer; m_flags &= ~TiffFlags.MyBuffer; } else { m_rawdatasize = roundUp(size, 1024); if (m_rawdatasize > 0) { m_rawdata = new byte[m_rawdatasize]; } else { Tiff.ErrorExt(this, m_clientdata, "ReadBufferSetup", "{0}: No space for data buffer at scanline {1}", m_name, m_row); m_rawdatasize = 0; } m_flags |= TiffFlags.MyBuffer; } } /// /// Sets up the data buffer used to write raw (encoded) data to a file. /// /// The data buffer. /// The buffer size. /// /// /// This method is provided for client-control of the I/O buffers used by the library. /// Applications need never use this method; it's provided only for "intelligent clients" /// that wish to optimize memory usage and/or eliminate potential copy operations that can /// occur when working with images that have data stored without compression. /// /// /// If the is -1 then the buffer size is selected to hold a /// complete tile or strip, or at least 8 kilobytes, whichever is greater. If the /// is null, then a buffer of appropriate size is /// allocated by the library. /// /// public void WriteBufferSetup(byte[] buffer, int size) { if (m_rawdata != null) { if ((m_flags & TiffFlags.MyBuffer) == TiffFlags.MyBuffer) m_flags &= ~TiffFlags.MyBuffer; m_rawdata = null; } if (size == -1) { size = (IsTiled() ? m_tilesize : StripSize()); // Make raw data buffer at least 8K if (size < 8 * 1024) size = 8 * 1024; // force allocation buffer = null; } if (buffer == null) { buffer = new byte[size]; m_flags |= TiffFlags.MyBuffer; } else { m_flags &= ~TiffFlags.MyBuffer; } m_rawdata = buffer; m_rawdatasize = size; m_rawcc = 0; m_rawcp = 0; m_flags |= TiffFlags.BufferSetup; } /// /// Setups the strips. /// /// true if setup successfully; otherwise, false public bool SetupStrips() { if (IsTiled()) m_dir.td_stripsperimage = isUnspecified(FieldBit.TileDimensions) ? m_dir.td_samplesperpixel : NumberOfTiles(); else m_dir.td_stripsperimage = isUnspecified(FieldBit.RowsPerStrip) ? m_dir.td_samplesperpixel : NumberOfStrips(); m_dir.td_nstrips = m_dir.td_stripsperimage; if (m_dir.td_planarconfig == PlanarConfig.Separate) m_dir.td_stripsperimage /= m_dir.td_samplesperpixel; m_dir.td_stripoffset = new uint[m_dir.td_nstrips]; m_dir.td_stripbytecount = new uint[m_dir.td_nstrips]; setFieldBit(FieldBit.StripOffsets); setFieldBit(FieldBit.StripByteCounts); return true; } /// /// Verifies that file/stream is writable and that the directory information is /// setup properly. /// /// If set to true then ability to write tiles will be verified; /// otherwise, ability to write strips will be verified. /// The name of the calling method. /// true if file/stream is writeable and the directory information is /// setup properly; otherwise, false public bool WriteCheck(bool tiles, string method) { if (m_mode == O_RDONLY) { ErrorExt(this, m_clientdata, method, "{0}: File not open for writing", m_name); return false; } if (tiles ^ IsTiled()) { ErrorExt(this, m_clientdata, m_name, tiles ? "Can not write tiles to a stripped image" : "Can not write scanlines to a tiled image"); return false; } // On the first write verify all the required information has been setup and // initialize any data structures that had to wait until directory information was set. // Note that a lot of our work is assumed to remain valid because we disallow any of // the important parameters from changing after we start writing (i.e. once // BeenWriting is set, SetField will only allow the image's length to be changed). if (!fieldSet(FieldBit.ImageDimensions)) { ErrorExt(this, m_clientdata, method, "{0}: Must set \"ImageWidth\" before writing data", m_name); return false; } if (m_dir.td_samplesperpixel == 1) { // PlanarConfiguration is irrelevant in case of single band images and need not // be included. We will set it anyway, because this field is used in other parts // of library even in the single band case. if (!fieldSet(FieldBit.PlanarConfig)) m_dir.td_planarconfig = PlanarConfig.Contig; } else { if (!fieldSet(FieldBit.PlanarConfig)) { ErrorExt(this, m_clientdata, method, "{0}: Must set \"PlanarConfiguration\" before writing data", m_name); return false; } } if (m_dir.td_stripoffset == null && !SetupStrips()) { m_dir.td_nstrips = 0; ErrorExt(this, m_clientdata, method, "{0}: No space for {1} arrays", m_name, IsTiled() ? "tile" : "strip"); return false; } m_tilesize = IsTiled() ? TileSize() : -1; m_scanlinesize = ScanlineSize(); m_flags |= TiffFlags.BeenWriting; return true; } /// /// Releases storage associated with current directory. /// public void FreeDirectory() { if (m_dir != null) { clearFieldBit(FieldBit.YCbCrSubsampling); clearFieldBit(FieldBit.YCbCrPositioning); m_dir = null; } } /// /// Creates a new directory within file/stream. /// /// The newly created directory will not exist on the file/stream till /// , , /// or is called. public void CreateDirectory() { // Should we automatically call WriteDirectory() // if the current one is dirty? setupDefaultDirectory(); m_diroff = 0; m_nextdiroff = 0; m_curoff = 0; m_row = -1; m_curstrip = -1; } /// /// Returns an indication of whether the current directory is the last directory /// in the file. /// /// true if current directory is the last directory in the file; /// otherwise, false. public bool LastDirectory() { return (m_nextdiroff == 0); } /// /// Sets the directory with specified number as the current directory. /// /// The zero-based number of the directory to set as the /// current directory. /// true if the specified directory was set as current successfully; /// otherwise, false /// SetDirectory changes the current directory and reads its contents with /// . public bool SetDirectory(short number) { uint nextdir = m_header.tiff_diroff; short n; for (n = number; n > 0 && nextdir != 0; n--) { long dummyOff; if (!advanceDirectory(ref nextdir, out dummyOff)) return false; } m_nextdiroff = nextdir; // Set curdir to the actual directory index. The -1 is because // ReadDirectory will increment m_curdir after successfully reading // the directory. m_curdir = (short)(number - n - 1); // Reset m_dirnumber counter and start new list of seen directories. // We need this to prevent IFD loops. m_dirnumber = 0; return ReadDirectory(); } /// /// Sets the directory at specified file/stream offset as the current directory. /// /// The offset from the beginnig of the file/stream to the directory /// to set as the current directory. /// true if the directory at specified file offset was set as current /// successfully; otherwise, false /// SetSubDirectory acts like , except the /// directory is specified as a file offset instead of an index; this is required for /// accessing subdirectories linked through a SubIFD tag (e.g. thumbnail images). public bool SetSubDirectory(long offset) { m_nextdiroff = (uint)offset; // Reset m_dirnumber counter and start new list of seen directories. // We need this to prevent IFD loops. m_dirnumber = 0; return ReadDirectory(); } /// /// Unlinks the specified directory from the directory chain. /// /// The zero-based number of the directory to unlink. /// true if directory was unlinked successfully; otherwise, false. /// UnlinkDirectory does not removes directory bytes from the file/stream. /// It only makes them unused. public bool UnlinkDirectory(short number) { const string module = "UnlinkDirectory"; if (m_mode == O_RDONLY) { ErrorExt(this, m_clientdata, module, "Can not unlink directory in read-only file"); return false; } // Go to the directory before the one we want // to unlink and nab the offset of the link // field we'll need to patch. uint nextdir = m_header.tiff_diroff; long off = sizeof(short) + sizeof(short); for (int n = number - 1; n > 0; n--) { if (nextdir == 0) { ErrorExt(this, m_clientdata, module, "Directory {0} does not exist", number); return false; } if (!advanceDirectory(ref nextdir, out off)) return false; } // Advance to the directory to be unlinked and fetch the offset of the directory // that follows. long dummyOff; if (!advanceDirectory(ref nextdir, out dummyOff)) return false; // Go back and patch the link field of the preceding directory to point to the // offset of the directory that follows. seekFile(off, SeekOrigin.Begin); if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabUInt(ref nextdir); if (!writeIntOK((int)nextdir)) { ErrorExt(this, m_clientdata, module, "Error writing directory link"); return false; } // Leave directory state setup safely. We don't have facilities for doing inserting // and removing directories, so it's safest to just invalidate everything. This means // that the caller can only append to the directory chain. m_currentCodec.Cleanup(); if ((m_flags & TiffFlags.MyBuffer) == TiffFlags.MyBuffer && m_rawdata != null) { m_rawdata = null; m_rawcc = 0; } m_flags &= ~(TiffFlags.BeenWriting | TiffFlags.BufferSetup | TiffFlags.PostEncode); FreeDirectory(); setupDefaultDirectory(); m_diroff = 0; // force link on next write m_nextdiroff = 0; // next write must be at end m_curoff = 0; m_row = -1; m_curstrip = -1; return true; } /// /// Sets the value(s) of a tag in a TIFF file/stream open for writing. /// /// The tag. /// The tag value(s). /// true if tag value(s) were set successfully; otherwise, false. /// /// SetField sets the value of a tag or pseudo-tag in the current directory /// associated with the open TIFF file/stream. To set the value of a field the file/stream /// must have been previously opened for writing with or /// ; pseudo-tags can be set whether the file was opened for /// reading or writing. The tag is identified by . /// The type and number of values in is dependent on the tag /// being set. You may want to consult /// "Well-known tags and their /// value(s) data types" to become familiar with exact data types and calling /// conventions required for each tag supported by the library. /// /// A pseudo-tag is a parameter that is used to control the operation of the library but /// whose value is not read or written to the underlying file. /// /// The field will be written to the file when/if the directory structure is updated. /// public bool SetField(TiffTag tag, params object[] value) { if (okToChangeTag(tag)) return m_tagmethods.SetField(this, tag, FieldValue.FromParams(value)); return false; } /// /// Writes the contents of the current directory to the file and setup to create a new /// subfile (page) in the same file. /// /// true if the current directory was written successfully; /// otherwise, false /// Applications only need to call WriteDirectory when writing multiple /// subfiles (pages) to a single TIFF file. WriteDirectory is automatically called /// by and to write a modified directory if the /// file is open for writing. public bool WriteDirectory() { return writeDirectory(true); } /// /// Writes the current state of the TIFF directory into the file to make what is currently /// in the file/stream readable. /// /// true if the current directory was rewritten successfully; /// otherwise, false /// Unlike , CheckpointDirectory does not free /// up the directory data structures in memory, so they can be updated (as strips/tiles /// are written) and written again. Reading such a partial file you will at worst get a /// TIFF read error for the first strip/tile encountered that is incomplete, but you will /// at least get all the valid data in the file before that. When the file is complete, /// just use as usual to finish it off cleanly. public bool CheckpointDirectory() { // Setup the strips arrays, if they haven't already been. if (m_dir.td_stripoffset == null) SetupStrips(); bool rc = writeDirectory(false); SetWriteOffset(seekFile(0, SeekOrigin.End)); return rc; } /// /// Rewrites the contents of the current directory to the file and setup to create a new /// subfile (page) in the same file. /// /// true if the current directory was rewritten successfully; /// otherwise, false /// The RewriteDirectory operates similarly to , /// but can be called with directories previously read or written that already have an /// established location in the file. It will rewrite the directory, but instead of place /// it at it's old location (as would) it will place them at /// the end of the file, correcting the pointer from the preceeding directory or file /// header to point to it's new location. This is particularly important in cases where /// the size of the directory and pointed to data has grown, so it won’t fit in the space /// available at the old location. Note that this will result in the loss of the /// previously used directory space. public bool RewriteDirectory() { const string module = "RewriteDirectory"; // We don't need to do anything special if it hasn't been written. if (m_diroff == 0) return WriteDirectory(); // Find and zero the pointer to this directory, so that linkDirectory will cause it to // be added after this directories current pre-link. // Is it the first directory in the file? if (m_header.tiff_diroff == m_diroff) { m_header.tiff_diroff = 0; m_diroff = 0; seekFile(TiffHeader.TIFF_MAGIC_SIZE + TiffHeader.TIFF_VERSION_SIZE, SeekOrigin.Begin); if (!writeIntOK((int)m_header.tiff_diroff)) { ErrorExt(this, m_clientdata, m_name, "Error updating TIFF header"); return false; } } else { uint nextdir = m_header.tiff_diroff; do { short dircount; if (!seekOK(nextdir) || !readShortOK(out dircount)) { ErrorExt(this, m_clientdata, module, "Error fetching directory count"); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabShort(ref dircount); seekFile(dircount * TiffDirEntry.SizeInBytes, SeekOrigin.Current); if (!readUIntOK(out nextdir)) { ErrorExt(this, m_clientdata, module, "Error fetching directory link"); return false; } if ((m_flags & TiffFlags.Swab) == TiffFlags.Swab) SwabUInt(ref nextdir); } while (nextdir != m_diroff && nextdir != 0); // get current offset long off = seekFile(0, SeekOrigin.Current); seekFile(off - sizeof(int), SeekOrigin.Begin); m_diroff = 0; if (!writeIntOK((int)m_diroff)) { ErrorExt(this, m_clientdata, module, "Error writing directory link"); return false; } } // Now use WriteDirectory() normally. return WriteDirectory(); } /// /// Prints formatted description of the contents of the current directory to the /// specified stream. /// /// /// Prints formatted description of the contents of the current directory to the /// specified stream possibly using specified print options. /// /// The stream. public void PrintDirectory(Stream stream) { PrintDirectory(stream, TiffPrintFlags.None); } /// /// Prints formatted description of the contents of the current directory to the /// specified stream using specified print (formatting) options. /// /// The stream. /// The print (formatting) options. public void PrintDirectory(Stream stream, TiffPrintFlags flags) { const string EndOfLine = "\r\n"; fprintf(stream, "TIFF Directory at offset 0x{0:x} ({1})" + EndOfLine, m_diroff, m_diroff); if (fieldSet(FieldBit.SubFileType)) { fprintf(stream, " Subfile Type:"); string sep = " "; if ((m_dir.td_subfiletype & FileType.ReducedResImage) != 0) { fprintf(stream, "{0}reduced-resolution image", sep); sep = "/"; } if ((m_dir.td_subfiletype & FileType.Page) != 0) { fprintf(stream, "{0}multi-page document", sep); sep = "/"; } if ((m_dir.td_subfiletype & FileType.Mask) != 0) fprintf(stream, "{0}transparency mask", sep); fprintf(stream, " ({0} = 0x{1:x})" + EndOfLine, m_dir.td_subfiletype, m_dir.td_subfiletype); } if (fieldSet(FieldBit.ImageDimensions)) { fprintf(stream, " Image Width: {0} Image Length: {1}", m_dir.td_imagewidth, m_dir.td_imagelength); if (fieldSet(FieldBit.ImageDepth)) fprintf(stream, " Image Depth: {0}", m_dir.td_imagedepth); fprintf(stream, EndOfLine); } if (fieldSet(FieldBit.TileDimensions)) { fprintf(stream, " Tile Width: {0} Tile Length: {1}", m_dir.td_tilewidth, m_dir.td_tilelength); if (fieldSet(FieldBit.TileDepth)) fprintf(stream, " Tile Depth: {0}", m_dir.td_tiledepth); fprintf(stream, EndOfLine); } if (fieldSet(FieldBit.Resolution)) { fprintf(stream, " Resolution: {0:G}, {1:G}", m_dir.td_xresolution, m_dir.td_yresolution); if (fieldSet(FieldBit.ResolutionUnit)) { switch (m_dir.td_resolutionunit) { case ResolutionUnit.None: fprintf(stream, " (unitless)"); break; case ResolutionUnit.Inch: fprintf(stream, " pixels/inch"); break; case ResolutionUnit.Centimeter: fprintf(stream, " pixels/cm"); break; default: fprintf(stream, " (unit {0} = 0x{1:x})", m_dir.td_resolutionunit, m_dir.td_resolutionunit); break; } } fprintf(stream, EndOfLine); } if (fieldSet(FieldBit.Position)) fprintf(stream, " Position: {0:G}, {1:G}" + EndOfLine, m_dir.td_xposition, m_dir.td_yposition); if (fieldSet(FieldBit.BitsPerSample)) fprintf(stream, " Bits/Sample: {0}" + EndOfLine, m_dir.td_bitspersample); if (fieldSet(FieldBit.SampleFormat)) { fprintf(stream, " Sample Format: "); switch (m_dir.td_sampleformat) { case SampleFormat.UnTyped: fprintf(stream, "void" + EndOfLine); break; case SampleFormat.Int: fprintf(stream, "signed integer" + EndOfLine); break; case SampleFormat.UInt: fprintf(stream, "unsigned integer" + EndOfLine); break; case SampleFormat.IEEEFloat: fprintf(stream, "IEEE floating point" + EndOfLine); break; case SampleFormat.COMPLEXINT: fprintf(stream, "complex signed integer" + EndOfLine); break; case SampleFormat.ComplexIEEEFloat: fprintf(stream, "complex IEEE floating point" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_sampleformat, m_dir.td_sampleformat); break; } } if (fieldSet(FieldBit.Compression)) { TiffCodec c = FindCodec(m_dir.td_compression); fprintf(stream, " Compression Scheme: "); if (c != null) fprintf(stream, "{0}" + EndOfLine, c.m_name); else fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_compression, m_dir.td_compression); } if (fieldSet(FieldBit.Photometric)) { fprintf(stream, " Photometric Interpretation: "); if ((int)m_dir.td_photometric < photoNames.Length) fprintf(stream, "{0}" + EndOfLine, photoNames[(int)m_dir.td_photometric]); else { switch (m_dir.td_photometric) { case Photometric.LogL: fprintf(stream, "CIE Log2(L)" + EndOfLine); break; case Photometric.LogLUV: fprintf(stream, "CIE Log2(L) (u',v')" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_photometric, m_dir.td_photometric); break; } } } if (fieldSet(FieldBit.ExtraSamples) && m_dir.td_extrasamples != 0) { fprintf(stream, " Extra Samples: {0}<", m_dir.td_extrasamples); string sep = ""; for (short i = 0; i < m_dir.td_extrasamples; i++) { switch (m_dir.td_sampleinfo[i]) { case ExtraSample.UnSpecified: fprintf(stream, "{0}unspecified", sep); break; case ExtraSample.AssociatedAlpha: fprintf(stream, "{0}assoc-alpha", sep); break; case ExtraSample.UnAssociatedAlpha: fprintf(stream, "{0}unassoc-alpha", sep); break; default: fprintf(stream, "{0}{1} (0x{2:x})", sep, m_dir.td_sampleinfo[i], m_dir.td_sampleinfo[i]); break; } sep = ", "; } fprintf(stream, ">" + EndOfLine); } if (fieldSet(FieldBit.InkNames)) { fprintf(stream, " Ink Names: "); string[] names = m_dir.td_inknames.Split(new char[] { '\0' }); for (int i = 0; i < names.Length; i++) { printAscii(stream, names[i]); fprintf(stream, ", "); } fprintf(stream, EndOfLine); } if (fieldSet(FieldBit.Thresholding)) { fprintf(stream, " Thresholding: "); switch (m_dir.td_threshholding) { case Threshold.BILevel: fprintf(stream, "bilevel art scan" + EndOfLine); break; case Threshold.HalfTone: fprintf(stream, "halftone or dithered scan" + EndOfLine); break; case Threshold.ErrorDiffuse: fprintf(stream, "error diffused" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_threshholding, m_dir.td_threshholding); break; } } if (fieldSet(FieldBit.FillOrder)) { fprintf(stream, " BitOrder: "); switch (m_dir.td_fillorder) { case BitOrder.BigEndian: fprintf(stream, "msb-to-lsb" + EndOfLine); break; case BitOrder.LittleEndian: fprintf(stream, "lsb-to-msb" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_fillorder, m_dir.td_fillorder); break; } } if (fieldSet(FieldBit.YCbCrSubsampling)) { // For hacky reasons (see JpegCodecTagMethods.JPEGFixupTestSubsampling method), // we need to fetch this rather than trust what is in our structures. FieldValue[] result = GetField(TiffTag.YCBCRSUBSAMPLING); short subsampling0 = result[0].ToShort(); short subsampling1 = result[1].ToShort(); fprintf(stream, " YCbCr Subsampling: {0}, {1}" + EndOfLine, subsampling0, subsampling1); } if (fieldSet(FieldBit.YCbCrPositioning)) { fprintf(stream, " YCbCr Positioning: "); switch (m_dir.td_ycbcrpositioning) { case YCbCrPosition.Centered: fprintf(stream, "centered" + EndOfLine); break; case YCbCrPosition.CoSited: fprintf(stream, "cosited" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_ycbcrpositioning, m_dir.td_ycbcrpositioning); break; } } if (fieldSet(FieldBit.HalftoneHints)) fprintf(stream, " Halftone Hints: light {0} dark {1}" + EndOfLine, m_dir.td_halftonehints[0], m_dir.td_halftonehints[1]); if (fieldSet(FieldBit.Orientation)) { fprintf(stream, " Orientation: "); if ((int)m_dir.td_orientation < orientNames.Length) fprintf(stream, "{0}" + EndOfLine, orientNames[(int)m_dir.td_orientation]); else fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_orientation, m_dir.td_orientation); } if (fieldSet(FieldBit.SamplesPerPixel)) fprintf(stream, " Samples/Pixel: {0}" + EndOfLine, m_dir.td_samplesperpixel); if (fieldSet(FieldBit.RowsPerStrip)) { fprintf(stream, " Rows/Strip: "); if (m_dir.td_rowsperstrip == -1) fprintf(stream, "(infinite)" + EndOfLine); else fprintf(stream, "{0}" + EndOfLine, m_dir.td_rowsperstrip); } if (fieldSet(FieldBit.MinSampleValue)) fprintf(stream, " Min Sample Value: {0}" + EndOfLine, m_dir.td_minsamplevalue); if (fieldSet(FieldBit.MaxSampleValue)) fprintf(stream, " Max Sample Value: {0}" + EndOfLine, m_dir.td_maxsamplevalue); if (fieldSet(FieldBit.SMinSampleValue)) fprintf(stream, " SMin Sample Value: {0:G}" + EndOfLine, m_dir.td_sminsamplevalue); if (fieldSet(FieldBit.SMaxSampleValue)) fprintf(stream, " SMax Sample Value: {0:G}" + EndOfLine, m_dir.td_smaxsamplevalue); if (fieldSet(FieldBit.PlanarConfig)) { fprintf(stream, " Planar Configuration: "); switch (m_dir.td_planarconfig) { case PlanarConfig.Contig: fprintf(stream, "single image plane" + EndOfLine); break; case PlanarConfig.Separate: fprintf(stream, "separate image planes" + EndOfLine); break; default: fprintf(stream, "{0} (0x{1:x})" + EndOfLine, m_dir.td_planarconfig, m_dir.td_planarconfig); break; } } if (fieldSet(FieldBit.PageNumber)) fprintf(stream, " Page Number: {0}-{1}" + EndOfLine, m_dir.td_pagenumber[0], m_dir.td_pagenumber[1]); if (fieldSet(FieldBit.ColorMap)) { fprintf(stream, " Color Map: "); if ((flags & TiffPrintFlags.Colormap) != 0) { fprintf(stream, "" + EndOfLine); int n = 1 << m_dir.td_bitspersample; for (int l = 0; l < n; l++) fprintf(stream, " {0,5}: {1,5} {2,5} {3,5}" + EndOfLine, l, m_dir.td_colormap[0][l], m_dir.td_colormap[1][l], m_dir.td_colormap[2][l]); } else fprintf(stream, "(present)" + EndOfLine); } if (fieldSet(FieldBit.TransferFunction)) { fprintf(stream, " Transfer Function: "); if ((flags & TiffPrintFlags.Curves) != 0) { fprintf(stream, "" + EndOfLine); int n = 1 << m_dir.td_bitspersample; for (int l = 0; l < n; l++) { fprintf(stream, " {0,2}: {0,5}", l, m_dir.td_transferfunction[0][l]); for (short i = 1; i < m_dir.td_samplesperpixel; i++) fprintf(stream, " {0,5}", m_dir.td_transferfunction[i][l]); fprintf(stream, "" + EndOfLine); } } else fprintf(stream, "(present)" + EndOfLine); } if (fieldSet(FieldBit.SubIFD) && m_dir.td_subifd != null) { fprintf(stream, " SubIFD Offsets:"); for (short i = 0; i < m_dir.td_nsubifd; i++) fprintf(stream, " {0,5}", m_dir.td_subifd[i]); fprintf(stream, "" + EndOfLine); } // Custom tag support. int count = GetTagListCount(); for (int i = 0; i < count; i++) { TiffTag tag = (TiffTag)GetTagListEntry(i); TiffFieldInfo fip = FieldWithTag(tag); if (fip == null) continue; byte[] raw_data = null; int value_count; if (fip.PassCount) { FieldValue[] result = GetField(tag); if (result == null) continue; value_count = result[0].ToInt(); raw_data = result[1].ToByteArray(); } else { if (fip.ReadCount == TiffFieldInfo.Variable || fip.ReadCount == TiffFieldInfo.Variable2) { value_count = 1; } else if (fip.ReadCount == TiffFieldInfo.Spp) { value_count = m_dir.td_samplesperpixel; } else { value_count = fip.ReadCount; } if ((fip.Type == TiffType.ASCII || fip.ReadCount == TiffFieldInfo.Variable || fip.ReadCount == TiffFieldInfo.Variable2 || fip.ReadCount == TiffFieldInfo.Spp || value_count > 1) && fip.Tag != TiffTag.PageNumber && fip.Tag != TiffTag.HalfToneHints && fip.Tag != TiffTag.YCBCRSUBSAMPLING && fip.Tag != TiffTag.DotRange) { FieldValue[] result = GetField(tag); if (result == null) continue; raw_data = result[0].ToByteArray(); } else if (fip.Tag != TiffTag.PageNumber && fip.Tag != TiffTag.HalfToneHints && fip.Tag != TiffTag.YCBCRSUBSAMPLING && fip.Tag != TiffTag.DotRange) { raw_data = new byte[dataSize(fip.Type) * value_count]; FieldValue[] result = GetField(tag); if (result == null) continue; raw_data = result[0].ToByteArray(); } else { // XXX: Should be fixed and removed, see the notes // related to PageNumber, HalfToneHints, // YCBCRSUBSAMPLING and DotRange tags raw_data = new byte[dataSize(fip.Type) * value_count]; FieldValue[] result = GetField(tag); if (result == null) continue; byte[] first = result[0].ToByteArray(); byte[] second = result[1].ToByteArray(); Buffer.BlockCopy(first, 0, raw_data, 0, first.Length); Buffer.BlockCopy(second, 0, raw_data, dataSize(fip.Type), second.Length); } } // Catch the tags which needs to be specially handled and // pretty print them. If tag not handled in prettyPrintField() // fall down and print it as any other tag. if (prettyPrintField(stream, tag, value_count, raw_data)) continue; else printField(stream, fip, value_count, raw_data); } m_tagmethods.PrintDir(this, stream, flags); if ((flags & TiffPrintFlags.Strips) != 0 && fieldSet(FieldBit.StripOffsets)) { fprintf(stream, " {0} {1}:" + EndOfLine, m_dir.td_nstrips, IsTiled() ? "Tiles" : "Strips"); for (int s = 0; s < m_dir.td_nstrips; s++) fprintf(stream, " {0,3}: [{0,8}, {0,8}]" + EndOfLine, s, m_dir.td_stripoffset[s], m_dir.td_stripbytecount[s]); } } /// /// Reads and decodes a scanline of data from an open TIFF file/stream. /// /// /// Reads and decodes a scanline of data from an open TIFF file/stream. /// /// The buffer to place read and decoded image data to. /// The zero-based index of scanline (row) to read. /// /// true if image data were read and decoded successfully; otherwise, false /// /// /// /// ReadScanline reads the data for the specified into the /// user supplied data buffer . The data are returned /// decompressed and, in the native byte- and bit-ordering, but are otherwise packed /// (see further below). The must be large enough to hold an /// entire scanline of data. Applications should call the /// to find out the size (in bytes) of a scanline buffer. Applications should use /// or /// and specify correct sample plane if /// image data are organized in separate planes /// ( = .Separate). /// /// /// The library attempts to hide bit- and byte-ordering differences between the image and /// the native machine by converting data to the native machine order. Bit reversal is /// done if the value of tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// /// public bool ReadScanline(byte[] buffer, int row) { return ReadScanline(buffer, 0, row, 0); } /// /// Reads and decodes a scanline of data from an open TIFF file/stream. /// /// The buffer to place read and decoded image data to. /// The zero-based index of scanline (row) to read. /// The zero-based index of the sample plane. /// /// true if image data were read and decoded successfully; otherwise, false /// /// /// /// ReadScanline reads the data for the specified and /// specified sample plane into the user supplied data buffer /// . The data are returned decompressed and, in the native /// byte- and bit-ordering, but are otherwise packed (see further below). The /// must be large enough to hold an entire scanline of data. /// Applications should call the to find out the size (in /// bytes) of a scanline buffer. Applications may use /// or specify 0 for /// parameter if image data is contiguous (i.e not organized in separate planes, /// = .Contig). /// /// /// The library attempts to hide bit- and byte-ordering differences between the image and /// the native machine by converting data to the native machine order. Bit reversal is /// done if the value of tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// /// public bool ReadScanline(byte[] buffer, int row, short plane) { return ReadScanline(buffer, 0, row, plane); } /// /// Reads and decodes a scanline of data from an open TIFF file/stream. /// /// The buffer to place read and decoded image data to. /// The zero-based byte offset in at which /// to begin storing read and decoded bytes. /// The zero-based index of scanline (row) to read. /// The zero-based index of the sample plane. /// /// true if image data were read and decoded successfully; otherwise, false /// /// /// /// ReadScanline reads the data for the specified and /// specified sample plane into the user supplied data buffer /// . The data are returned decompressed and, in the native /// byte- and bit-ordering, but are otherwise packed (see further below). The /// must be large enough to hold an entire scanline of data. /// Applications should call the to find out the size (in /// bytes) of a scanline buffer. Applications may use /// or specify 0 for /// parameter if image data is contiguous (i.e not organized in separate planes, /// = .Contig). /// /// /// The library attempts to hide bit- and byte-ordering differences between the image and /// the native machine by converting data to the native machine order. Bit reversal is /// done if the value of tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// /// public bool ReadScanline(byte[] buffer, int offset, int row, short plane) { if (!checkRead(false)) return false; bool e = seek(row, plane); if (e) { // Decompress desired row into user buffer. e = m_currentCodec.DecodeRow(buffer, offset, m_scanlinesize, plane); // we are now poised at the beginning of the next row m_row = row + 1; if (e) postDecode(buffer, offset, m_scanlinesize); } return e; } /// /// Encodes and writes a scanline of data to an open TIFF file/stream. /// /// Encodes and writes a scanline of data to an open TIFF file/stream. /// The buffer with image data to be encoded and written. /// The zero-based index of scanline (row) to place encoded data at. /// /// true if image data were encoded and written successfully; otherwise, false /// /// /// /// WriteScanline encodes and writes to a file at the specified /// . Applications should use /// or /// and specify correct sample plane /// parameter if image data in a file/stream is organized in separate planes (i.e /// = .Separate). /// /// The data are assumed to be uncompressed and in the native bit- and byte-order of the /// host machine. The data written to the file is compressed according to the compression /// scheme of the current TIFF directory (see further below). If the current scanline is /// past the end of the current subfile, the value of /// tag is automatically increased to include the scanline (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. The library attempts to hide bit-ordering differences between the image and /// the native machine by converting data from the native machine order. /// /// Once data are written to a file/stream for the current directory, the values of /// certain tags may not be altered; see /// "Well-known tags and their /// value(s) data types" for more information. /// /// It is not possible to write scanlines to a file/stream that uses a tiled organization. /// The can be used to determine if the file/stream is organized as /// tiles or strips. /// public bool WriteScanline(byte[] buffer, int row) { return WriteScanline(buffer, 0, row, 0); } /// /// Encodes and writes a scanline of data to an open TIFF file/stream. /// /// The buffer with image data to be encoded and written. /// The zero-based index of scanline (row) to place encoded data at. /// The zero-based index of the sample plane. /// /// true if image data were encoded and written successfully; otherwise, false /// /// /// /// WriteScanline encodes and writes to a file at the specified /// and specified sample plane . /// Applications may use or specify 0 for /// parameter if image data in a file/stream is contiguous (i.e /// not organized in separate planes, /// = .Contig). /// /// The data are assumed to be uncompressed and in the native bit- and byte-order of the /// host machine. The data written to the file is compressed according to the compression /// scheme of the current TIFF directory (see further below). If the current scanline is /// past the end of the current subfile, the value of /// tag is automatically increased to include the scanline (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. The library attempts to hide bit-ordering differences between the image and /// the native machine by converting data from the native machine order. /// /// Once data are written to a file/stream for the current directory, the values of /// certain tags may not be altered; see /// "Well-known tags and their /// value(s) data types" for more information. /// /// It is not possible to write scanlines to a file/stream that uses a tiled organization. /// The can be used to determine if the file/stream is organized as /// tiles or strips. /// public bool WriteScanline(byte[] buffer, int row, short plane) { return WriteScanline(buffer, 0, row, plane); } /// /// Encodes and writes a scanline of data to an open TIFF file/stream. /// /// The buffer with image data to be encoded and written. /// The zero-based byte offset in at which /// to begin reading bytes. /// The zero-based index of scanline (row) to place encoded data at. /// The zero-based index of the sample plane. /// /// true if image data were encoded and written successfully; otherwise, false /// /// /// /// WriteScanline encodes and writes to a file at the specified /// and specified sample plane . /// Applications may use or specify 0 for /// parameter if image data in a file/stream is contiguous (i.e /// not organized in separate planes, /// = .Contig). /// /// The data are assumed to be uncompressed and in the native bit- and byte-order of the /// host machine. The data written to the file is compressed according to the compression /// scheme of the current TIFF directory (see further below). If the current scanline is /// past the end of the current subfile, the value of /// tag is automatically increased to include the scanline (except for /// = .Contig, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. The library attempts to hide bit-ordering differences between the image and /// the native machine by converting data from the native machine order. /// /// Once data are written to a file/stream for the current directory, the values of /// certain tags may not be altered; see /// "Well-known tags and their /// value(s) data types" for more information. /// /// It is not possible to write scanlines to a file/stream that uses a tiled organization. /// The can be used to determine if the file/stream is organized as /// tiles or strips. /// public bool WriteScanline(byte[] buffer, int offset, int row, short plane) { const string module = "WriteScanline"; if (!writeCheckStrips(module)) return false; // Handle delayed allocation of data buffer. This permits it to be // sized more intelligently (using directory information). bufferCheck(); // Extend image length if needed (but only for PlanarConfig.Contig). bool imagegrew = false; if (row >= m_dir.td_imagelength) { // extend image if (m_dir.td_planarconfig == PlanarConfig.Separate) { ErrorExt(this, m_clientdata, m_name, "Can not change \"ImageLength\" when using separate planes"); return false; } m_dir.td_imagelength = row + 1; imagegrew = true; } // Calculate strip and check for crossings. int strip; if (m_dir.td_planarconfig == PlanarConfig.Separate) { if (plane >= m_dir.td_samplesperpixel) { ErrorExt(this, m_clientdata, m_name, "{0}: Sample out of range, max {1}", plane, m_dir.td_samplesperpixel); return false; } if (m_dir.td_rowsperstrip != -1) strip = plane * m_dir.td_stripsperimage + row / m_dir.td_rowsperstrip; else strip = 0; } else { if (m_dir.td_rowsperstrip != -1) strip = row / m_dir.td_rowsperstrip; else strip = 0; } // Check strip array to make sure there's space. We don't support // dynamically growing files that have data organized in separate // bitplanes because it's too painful. In that case we require that // the imagelength be set properly before the first write (so that // the strips array will be fully allocated above). if (strip >= m_dir.td_nstrips && !growStrips(1)) return false; if (strip != m_curstrip) { // Changing strips - flush any data present. if (!FlushData()) return false; m_curstrip = (int)strip; // Watch out for a growing image. The value of strips/image // will initially be 1 (since it can't be deduced until the // imagelength is known). if (strip >= m_dir.td_stripsperimage && imagegrew) m_dir.td_stripsperimage = howMany(m_dir.td_imagelength, m_dir.td_rowsperstrip); m_row = (strip % m_dir.td_stripsperimage) * m_dir.td_rowsperstrip; if ((m_flags & TiffFlags.CoderSetup) != TiffFlags.CoderSetup) { if (!m_currentCodec.SetupEncode()) return false; m_flags |= TiffFlags.CoderSetup; } m_rawcc = 0; m_rawcp = 0; if (m_dir.td_stripbytecount[strip] > 0) { // if we are writing over existing tiles, zero length m_dir.td_stripbytecount[strip] = 0; // this forces appendToStrip() to do a seek m_curoff = 0; } if (!m_currentCodec.PreEncode(plane)) return false; m_flags |= TiffFlags.PostEncode; } // Ensure the write is either sequential or at the beginning of a // strip (or that we can randomly access the data - i.e. no encoding). if (row != m_row) { if (row < m_row) { // Moving backwards within the same strip: // backup to the start and then decode forward (below). m_row = (strip % m_dir.td_stripsperimage) * m_dir.td_rowsperstrip; m_rawcp = 0; } // Seek forward to the desired row. if (!m_currentCodec.Seek(row - m_row)) return false; m_row = row; } // swab if needed - note that source buffer will be altered postDecode(buffer, offset, m_scanlinesize); bool status = m_currentCodec.EncodeRow(buffer, offset, m_scanlinesize, plane); // we are now poised at the beginning of the next row m_row = row + 1; return status; } /// /// Reads the image and decodes it into RGBA format raster. /// /// /// Reads the image and decodes it into RGBA format raster. /// /// The raster width. /// The raster height. /// The raster (the buffer to place decoded image data to). /// true if the image was successfully read and converted; otherwise, /// false is returned if an error was encountered. /// /// ReadRGBAImage reads a strip- or tile-based image into memory, storing the /// result in the user supplied RGBA . The raster is assumed to /// be an array of times 32-bit entries, /// where must be less than or equal to the width of the image /// ( may be any non-zero size). If the raster dimensions are /// smaller than the image, the image data is cropped to the raster bounds. If the raster /// height is greater than that of the image, then the image data are placed in the lower /// part of the raster. Note that the raster is assumed to be organized such that the /// pixel at location (x, y) is [y * width + x]; with the raster /// origin in the lower-left hand corner. Please use /// if you /// want to specify another raster origin. /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// ReadRGBAImage converts non-8-bit images by scaling sample values. Palette, /// grayscale, bilevel, CMYK, and YCbCr images are converted to RGB transparently. Raster /// pixels are returned uncorrected by any colorimetry information present in the directory. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBAImage is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBAImage(int width, int height, int[] raster) { return ReadRGBAImage(width, height, raster, false); } /// /// Reads the image and decodes it into RGBA format raster. /// /// The raster width. /// The raster height. /// The raster (the buffer to place decoded image data to). /// if set to true then an error will terminate the /// operation; otherwise method will continue processing data until all the possible data /// in the image have been requested. /// /// true if the image was successfully read and converted; otherwise, false /// is returned if an error was encountered and stopOnError is false. /// /// /// ReadRGBAImage reads a strip- or tile-based image into memory, storing the /// result in the user supplied RGBA . The raster is assumed to /// be an array of times 32-bit entries, /// where must be less than or equal to the width of the image /// ( may be any non-zero size). If the raster dimensions are /// smaller than the image, the image data is cropped to the raster bounds. If the raster /// height is greater than that of the image, then the image data are placed in the lower /// part of the raster. Note that the raster is assumed to be organized such that the /// pixel at location (x, y) is [y * width + x]; with the raster /// origin in the lower-left hand corner. Please use /// if you /// want to specify another raster origin. /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// ReadRGBAImage converts non-8-bit images by scaling sample values. Palette, /// grayscale, bilevel, CMYK, and YCbCr images are converted to RGB transparently. Raster /// pixels are returned uncorrected by any colorimetry information present in the directory. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBAImage is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBAImage(int width, int height, int[] raster, bool stopOnError) { return ReadRGBAImageOriented(width, height, raster, Orientation.BottomLeft, stopOnError); } /// /// Reads the image and decodes it into RGBA format raster using specified raster origin. /// /// /// Reads the image and decodes it into RGBA format raster using specified raster origin. /// /// The raster width. /// The raster height. /// The raster (the buffer to place decoded image data to). /// The raster origin position. /// /// true if the image was successfully read and converted; otherwise, false /// is returned if an error was encountered. /// /// /// ReadRGBAImageOriented reads a strip- or tile-based image into memory, storing the /// result in the user supplied RGBA . The raster is assumed to /// be an array of times 32-bit entries, /// where must be less than or equal to the width of the image /// ( may be any non-zero size). If the raster dimensions are /// smaller than the image, the image data is cropped to the raster bounds. If the raster /// height is greater than that of the image, then the image data placement depends on /// . Note that the raster is assumed to be organized such /// that the pixel at location (x, y) is [y * width + x]; with /// the raster origin specified by parameter. /// /// When ReadRGBAImageOriented is used with .BottomLeft for /// the the produced result is the same as retuned by /// . /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// ReadRGBAImageOriented converts non-8-bit images by scaling sample values. /// Palette, grayscale, bilevel, CMYK, and YCbCr images are converted to RGB transparently. /// Raster pixels are returned uncorrected by any colorimetry information present in /// the directory. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBAImageOriented is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBAImageOriented(int width, int height, int[] raster, Orientation orientation) { return ReadRGBAImageOriented(width, height, raster, orientation, false); } /// /// Reads the image and decodes it into RGBA format raster using specified raster origin. /// /// The raster width. /// The raster height. /// The raster (the buffer to place decoded image data to). /// The raster origin position. /// if set to true then an error will terminate the /// operation; otherwise method will continue processing data until all the possible data /// in the image have been requested. /// /// true if the image was successfully read and converted; otherwise, false /// is returned if an error was encountered and stopOnError is false. /// /// /// ReadRGBAImageOriented reads a strip- or tile-based image into memory, storing the /// result in the user supplied RGBA . The raster is assumed to /// be an array of times 32-bit entries, /// where must be less than or equal to the width of the image /// ( may be any non-zero size). If the raster dimensions are /// smaller than the image, the image data is cropped to the raster bounds. If the raster /// height is greater than that of the image, then the image data placement depends on /// . Note that the raster is assumed to be organized such /// that the pixel at location (x, y) is [y * width + x]; with /// the raster origin specified by parameter. /// /// When ReadRGBAImageOriented is used with .BottomLeft for /// the the produced result is the same as retuned by /// . /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// ReadRGBAImageOriented converts non-8-bit images by scaling sample values. /// Palette, grayscale, bilevel, CMYK, and YCbCr images are converted to RGB transparently. /// Raster pixels are returned uncorrected by any colorimetry information present in /// the directory. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBAImageOriented is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBAImageOriented(int width, int height, int[] raster, Orientation orientation, bool stopOnError) { bool ok = false; string emsg; if (RGBAImageOK(out emsg)) { TiffRgbaImage img = TiffRgbaImage.Create(this, stopOnError, out emsg); if (img != null) { img.ReqOrientation = orientation; // XXX verify rwidth and rheight against image width and height ok = img.GetRaster(raster, (height - img.Height) * width, width, img.Height); } } else { ErrorExt(this, m_clientdata, FileName(), "{0}", emsg); ok = false; } return ok; } /// /// Reads a whole strip of a strip-based image, decodes it and converts it to RGBA format. /// /// The row. /// The RGBA raster. /// true if the strip was successfully read and converted; otherwise, /// false /// /// /// ReadRGBAStrip reads a single strip of a strip-based image into memory, storing /// the result in the user supplied RGBA . If specified strip is /// the last strip, then it will only contain the portion of the strip that is actually /// within the image space. The raster is assumed to be an array of width times /// rowsperstrip 32-bit entries, where width is the width of the image /// () and rowsperstrip is the maximum lines in a strip /// (). /// /// The value should be the row of the first row in the strip /// (strip * rowsperstrip, zero based). /// /// Note that the raster is assumed to be organized such that the pixel at location (x, y) /// is [y * width + x]; with the raster origin in the lower-left /// hand corner of the strip. That is bottom to top organization. When reading a partial /// last strip in the file the last line of the image will begin at the beginning of /// the buffer. /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// See for more details on how various image types are /// converted to RGBA values. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBAStrip's main advantage over the similar /// function is that for /// large images a single buffer capable of holding the whole image doesn't need to be /// allocated, only enough for one strip. The function does a /// similar operation for tiled images. /// /// ReadRGBAStrip is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBAStrip(int row, int[] raster) { if (IsTiled()) { ErrorExt(this, m_clientdata, FileName(), "Can't use ReadRGBAStrip() with tiled file."); return false; } FieldValue[] result = GetFieldDefaulted(TiffTag.RowsPerStrip); int rowsperstrip = result[0].ToInt(); if ((row % rowsperstrip) != 0) { ErrorExt(this, m_clientdata, FileName(), "Row passed to ReadRGBAStrip() must be first in a strip."); return false; } bool ok; string emsg; if (RGBAImageOK(out emsg)) { TiffRgbaImage img = TiffRgbaImage.Create(this, false, out emsg); if (img != null) { img.row_offset = row; img.col_offset = 0; int rows_to_read = rowsperstrip; if (row + rowsperstrip > img.Height) rows_to_read = img.Height - row; ok = img.GetRaster(raster, 0, img.Width, rows_to_read); return ok; } return true; } ErrorExt(this, m_clientdata, FileName(), "{0}", emsg); return false; } /// /// Reads a whole tile of a tile-based image, decodes it and converts it to RGBA format. /// /// The column. /// The row. /// The RGBA raster. /// true if the strip was successfully read and converted; otherwise, /// false /// /// ReadRGBATile reads a single tile of a tile-based image into memory, /// storing the result in the user supplied RGBA . The raster is /// assumed to be an array of width times length 32-bit entries, where width is the width /// of the tile () and length is the height of a tile /// (). /// /// The and values are the offsets from the /// top left corner of the image to the top left corner of the tile to be read. They must /// be an exact multiple of the tile width and length. /// /// Note that the raster is assumed to be organized such that the pixel at location (x, y) /// is [y * width + x]; with the raster origin in the lower-left /// hand corner of the tile. That is bottom to top organization. Edge tiles which partly /// fall off the image will be filled out with appropriate zeroed areas. /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// See for more details on how various image types are /// converted to RGBA values. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// ReadRGBATile's main advantage over the similar /// function is that for /// large images a single buffer capable of holding the whole image doesn't need to be /// allocated, only enough for one tile. The function does a /// similar operation for stripped images. /// /// ReadRGBATile is just a wrapper around the more general /// facilities. /// /// All error messages are directed to the current error handler. /// /// /// /// /// public bool ReadRGBATile(int col, int row, int[] raster) { // Verify that our request is legal - on a tile file, and on a tile boundary. if (!IsTiled()) { ErrorExt(this, m_clientdata, FileName(), "Can't use ReadRGBATile() with stripped file."); return false; } FieldValue[] result = GetFieldDefaulted(TiffTag.TileWidth); int tile_xsize = result[0].ToInt(); result = GetFieldDefaulted(TiffTag.TileLength); int tile_ysize = result[0].ToInt(); if ((col % tile_xsize) != 0 || (row % tile_ysize) != 0) { ErrorExt(this, m_clientdata, FileName(), "Row/col passed to ReadRGBATile() must be topleft corner of a tile."); return false; } // Setup the RGBA reader. string emsg; TiffRgbaImage img = TiffRgbaImage.Create(this, false, out emsg); if (!RGBAImageOK(out emsg) || img == null) { ErrorExt(this, m_clientdata, FileName(), "{0}", emsg); return false; } // The TiffRgbaImage.Get() function doesn't allow us to get off the edge of the // image, even to fill an otherwise valid tile. So we figure out how much we can read, // and fix up the tile buffer to a full tile configuration afterwards. int read_ysize; if (row + tile_ysize > img.Height) read_ysize = img.Height - row; else read_ysize = tile_ysize; int read_xsize; if (col + tile_xsize > img.Width) read_xsize = img.Width - col; else read_xsize = tile_xsize; // Read the chunk of imagery. img.row_offset = row; img.col_offset = col; bool ok = img.GetRaster(raster, 0, read_xsize, read_ysize); // If our read was incomplete we will need to fix up the tile by shifting the data // around as if a full tile of data is being returned. This is all the more // complicated because the image is organized in bottom to top format. if (read_xsize == tile_xsize && read_ysize == tile_ysize) return ok; for (int i_row = 0; i_row < read_ysize; i_row++) { Buffer.BlockCopy(raster, (read_ysize - i_row - 1) * read_xsize * sizeof(int), raster, (tile_ysize - i_row - 1) * tile_xsize * sizeof(int), read_xsize * sizeof(int)); Array.Clear(raster, (tile_ysize - i_row - 1) * tile_xsize + read_xsize, tile_xsize - read_xsize); } for (int i_row = read_ysize; i_row < tile_ysize; i_row++) Array.Clear(raster, (tile_ysize - i_row - 1) * tile_xsize, tile_xsize); return ok; } /// /// Check the image to see if it can be converted to RGBA format. /// /// The error message (if any) gets placed here. /// true if the image can be converted to RGBA format; otherwise, /// false is returned and contains the reason why it /// is being rejected. /// /// To convert the image to RGBA format please use /// , /// , /// or /// /// Convertible images should follow this rules: samples must be either 1, 2, 4, 8, or /// 16 bits; colorimetric samples/pixel must be either 1, 3, or 4 (i.e. SamplesPerPixel /// minus ExtraSamples). /// public bool RGBAImageOK(out string errorMsg) { errorMsg = null; if (!m_decodestatus) { errorMsg = "Sorry, requested compression method is not configured"; return false; } switch (m_dir.td_bitspersample) { case 1: case 2: case 4: case 8: case 16: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle images with {0}-bit samples", m_dir.td_bitspersample); return false; } int colorchannels = m_dir.td_samplesperpixel - m_dir.td_extrasamples; Photometric photometric = Photometric.RGB; FieldValue[] result = GetField(TiffTag.Photometric); if (result == null) { switch (colorchannels) { case 1: photometric = Photometric.MinIsBlack; break; case 3: photometric = Photometric.RGB; break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Missing needed {0} tag", TiffRgbaImage.photoTag); return false; } } else { // San Chen photometric = (Photometric)result[0].Value; } switch (photometric) { case Photometric.MinIsWhite: case Photometric.MinIsBlack: case Photometric.Palette: if (m_dir.td_planarconfig == PlanarConfig.Contig && m_dir.td_samplesperpixel != 1 && m_dir.td_bitspersample < 8) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle contiguous data with {0}={1}, and {2}={3} and Bits/Sample={4}", TiffRgbaImage.photoTag, photometric, "Samples/pixel", m_dir.td_samplesperpixel, m_dir.td_bitspersample); return false; } // We should likely validate that any extra samples are either to be ignored, // or are alpha, and if alpha we should try to use them. But for now we won't // bother with this. break; case Photometric.YCBCR: // TODO: if at all meaningful and useful, make more complete support check // here, or better still, refactor to let supporting code decide whether there // is support and what meaningfull error to return break; case Photometric.RGB: if (colorchannels < 3) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle RGB image with {0}={1}", "Color channels", colorchannels); return false; } break; case Photometric.Separated: result = GetFieldDefaulted(TiffTag.InkSet); InkSet inkset = (InkSet)result[0].ToByte(); if (inkset != InkSet.CMYK) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "InkSet", inkset); return false; } if (m_dir.td_samplesperpixel < 4) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "Samples/pixel", m_dir.td_samplesperpixel); return false; } break; case Photometric.LogL: if (m_dir.td_compression != Compression.SGILOG) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogL data must have {0}={1}", "Compression", Compression.SGILOG); return false; } break; case Photometric.LogLUV: if (m_dir.td_compression != Compression.SGILOG && m_dir.td_compression != Compression.SGILOG24) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogLuv data must have {0}={1} or {2}", "Compression", Compression.SGILOG, Compression.SGILOG24); return false; } if (m_dir.td_planarconfig != PlanarConfig.Contig) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle LogLuv images with {0}={1}", "Planarconfiguration", m_dir.td_planarconfig); return false; } break; case Photometric.CIELAB: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle image with {0}={1}", TiffRgbaImage.photoTag, photometric); return false; } return true; } /// /// Gets the name of the file or ID string for this . /// /// The name of the file or ID string for this . /// If this was created using method then /// value of fileName parameter of method is returned. If this /// was created using then value of /// name parameter of method is returned. public string FileName() { return m_name; } /// /// Sets the new ID string for this . /// /// The ID string for this . /// The previous file name or ID string for this . /// Please note, that is an arbitrary string used as /// ID for this . It's not required to be a file name or anything /// meaningful at all. public string SetFileName(string name) { string old_name = m_name; m_name = name; return old_name; } /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the . /// /// An instance of the class. Can be null. /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// /// Applications that desire to capture control in the event of an error should use /// to override the default error and warning handler. /// /// /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the . /// public static void Error(Tiff tif, string method, string format, params object[] args) { if (m_errorHandler == null) return; m_errorHandler.ErrorHandler(tif, method, format, args); m_errorHandler.ErrorHandlerExt(tif, null, method, format, args); } /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the . /// /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// /// Applications that desire to capture control in the event of an error should use /// to override the default error and warning handler. /// /// public static void Error(string method, string format, params object[] args) { Error(null, method, format, args); } /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the . /// /// An instance of the class. Can be null. /// The client data to be passed to error handler. /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// /// /// The parameter can be anything you want. It will be passed /// unchanged to the error handler. Default error handler does not use it. Only custom /// error handlers may make use of it. /// /// Applications that desire to capture control in the event of an error should use /// to override the default error and warning handler. /// /// /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the and passes client data to the error handler. /// public static void ErrorExt(Tiff tif, object clientData, string method, string format, params object[] args) { if (m_errorHandler == null) return; m_errorHandler.ErrorHandler(tif, method, format, args); m_errorHandler.ErrorHandlerExt(tif, clientData, method, format, args); } /// /// Invokes the library-wide error handling methods to (normally) write an error message /// to the . /// /// The client data to be passed to error handler. /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// /// /// The parameter can be anything you want. It will be passed /// unchanged to the error handler. Default error handler does not use it. Only custom /// error handlers may make use of it. /// /// Applications that desire to capture control in the event of an error should use /// to override the default error and warning handler. /// /// public static void ErrorExt(object clientData, string method, string format, params object[] args) { ErrorExt(null, clientData, method, format, args); } /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the . /// /// An instance of the class. Can be null. /// The method in which a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, /// if not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// /// Applications that desire to capture control in the event of a warning should use /// to override the default error and warning handler. /// /// /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the . /// public static void Warning(Tiff tif, string method, string format, params object[] args) { if (m_errorHandler == null) return; m_errorHandler.WarningHandler(tif, method, format, args); m_errorHandler.WarningHandlerExt(tif, null, method, format, args); } /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the . /// /// The method in which a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, /// if not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// /// Applications that desire to capture control in the event of a warning should use /// to override the default error and warning handler. /// /// public static void Warning(string method, string format, params object[] args) { Warning(null, method, format, args); } /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the and passes client data to the warning handler. /// /// An instance of the class. Can be null. /// The client data to be passed to warning handler. /// The method in which a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// /// /// The parameter can be anything you want. It will be passed /// unchanged to the warning handler. Default warning handler does not use it. Only custom /// warning handlers may make use of it. /// /// Applications that desire to capture control in the event of a warning should use /// to override the default error and warning handler. /// /// /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the and passes client data to the warning handler. /// public static void WarningExt(Tiff tif, object clientData, string method, string format, params object[] args) { if (m_errorHandler == null) return; m_errorHandler.WarningHandler(tif, method, format, args); m_errorHandler.WarningHandlerExt(tif, clientData, method, format, args); } /// /// Invokes the library-wide warning handling methods to (normally) write a warning message /// to the and passes client data to the warning handler. /// /// The client data to be passed to warning handler. /// The method in which a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// /// The parameter can be anything you want. It will be passed /// unchanged to the warning handler. Default warning handler does not use it. Only custom /// warning handlers may make use of it. /// /// Applications that desire to capture control in the event of a warning should use /// to override the default error and warning handler. /// /// public static void WarningExt(object clientData, string method, string format, params object[] args) { WarningExt(null, clientData, method, format, args); } /// /// Sets an instance of the class as custom library-wide /// error and warning handler. /// /// An instance of the class /// to set as custom library-wide error and warning handler. /// /// Previous error handler or null if there was no error handler set. /// public static TiffErrorHandler SetErrorHandler(TiffErrorHandler errorHandler) { TiffErrorHandler prev = m_errorHandler; m_errorHandler = errorHandler; return prev; } /// /// Sets the tag extender method. /// /// The tag extender method. /// Previous tag extender method. /// /// Extender method is called upon creation of each instance of object. /// public static TiffExtendProc SetTagExtender(TiffExtendProc extender) { TiffExtendProc prev = m_extender; m_extender = extender; return prev; } /// /// Reads and decodes a tile of data from an open TIFF file/stream. /// /// The buffer to place read and decoded image data to. /// The zero-based byte offset in at which /// to begin storing read and decoded bytes. /// The x-coordinate of the pixel within a tile to be read and decoded. /// The y-coordinate of the pixel within a tile to be read and decoded. /// The z-coordinate of the pixel within a tile to be read and decoded. /// The zero-based index of the sample plane. /// The number of bytes in the decoded tile or -1 if an error occurred. /// /// /// The tile to read and decode is selected by the (x, y, z, plane) coordinates (i.e. /// ReadTile returns the data for the tile containing the specified coordinates. /// The data placed in are returned decompressed and, typically, /// in the native byte- and bit-ordering, but are otherwise packed (see further below). /// The buffer must be large enough to hold an entire tile of data. Applications should /// call the to find out the size (in bytes) of a tile buffer. /// The and parameters are always used by /// ReadTile. The parameter is used if the image is deeper /// than 1 slice (a value of > 1). In other cases the /// value of is ignored. The parameter is /// used only if data are organized in separate planes /// ( = .Separate). In other /// cases the value of is ignored. /// /// The library attempts to hide bit- and byte-ordering differences between the image and /// the native machine by converting data to the native machine order. Bit reversal is /// done if the value of tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// public int ReadTile(byte[] buffer, int offset, int x, int y, int z, short plane) { if (!checkRead(true) || !CheckTile(x, y, z, plane)) return -1; return ReadEncodedTile(ComputeTile(x, y, z, plane), buffer, offset, -1); } /// /// Reads a tile of data from an open TIFF file/stream, decompresses it and places /// specified amount of decompressed bytes into the user supplied buffer. /// /// The zero-based index of the tile to read. /// The buffer to place decompressed tile bytes to. /// The zero-based byte offset in buffer at which to begin storing /// decompressed tile bytes. /// The maximum number of decompressed tile bytes to be stored /// to buffer. /// The actual number of bytes of data that were placed in buffer or -1 if an /// error was encountered. /// /// /// The value of is a "raw tile number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// To read a full tile of data the data buffer should typically be at least as /// large as the number returned by . If the -1 passed in /// parameter, the whole tile will be read. You should be sure /// you have enough space allocated for the buffer. /// The library attempts to hide bit- and byte-ordering differences between the /// image and the native machine by converting data to the native machine order. Bit /// reversal is done if the tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// public int ReadEncodedTile(int tile, byte[] buffer, int offset, int count) { if (!checkRead(true)) return -1; if (tile >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, m_name, "{0}: Tile out of range, max {1}", tile, m_dir.td_nstrips); return -1; } if (count == -1) count = m_tilesize; else if (count > m_tilesize) count = m_tilesize; if (fillTile(tile) && m_currentCodec.DecodeTile(buffer, offset, count, (short)(tile / m_dir.td_stripsperimage))) { postDecode(buffer, offset, count); return count; } return -1; } /// /// Reads the undecoded contents of a tile of data from an open TIFF file/stream and places /// specified amount of read bytes into the user supplied buffer. /// /// The zero-based index of the tile to read. /// The buffer to place read tile bytes to. /// The zero-based byte offset in buffer at which to begin storing /// read tile bytes. /// The maximum number of read tile bytes to be stored to buffer. /// The actual number of bytes of data that were placed in buffer or -1 if an /// error was encountered. /// /// /// The value of is a "raw tile number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// To read a full tile of data the data buffer should typically be at least as /// large as the number returned by . If the -1 passed in /// parameter, the whole tile will be read. You should be sure /// you have enough space allocated for the buffer. public int ReadRawTile(int tile, byte[] buffer, int offset, int count) { const string module = "ReadRawTile"; if (!checkRead(true)) return -1; if (tile >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, m_name, "{0}: Tile out of range, max {1}", tile, m_dir.td_nstrips); return -1; } if ((m_flags & TiffFlags.NoReadRaw) == TiffFlags.NoReadRaw) { ErrorExt(m_clientdata, m_name, "Compression scheme does not support access to raw uncompressed data"); return -1; } uint bytecount = m_dir.td_stripbytecount[tile]; if (count != -1 && (uint)count < bytecount) bytecount = (uint)count; return readRawTile1(tile, buffer, offset, (int)bytecount, module); } /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// The buffer with image data to be encoded and written. /// The x-coordinate of the pixel within a tile to be encoded and written. /// The y-coordinate of the pixel within a tile to be encoded and written. /// The z-coordinate of the pixel within a tile to be encoded and written. /// The zero-based index of the sample plane. /// /// The number of encoded and written bytes or -1 if an error occurred. /// /// /// /// The tile to place encoded data is selected by the (x, y, z, plane) coordinates (i.e. /// WriteTile writes data to the tile containing the specified coordinates. /// WriteTile (potentially) encodes the data and writes /// it to open file/stream. The buffer must contain an entire tile of data. Applications /// should call the to find out the size (in bytes) of a tile buffer. /// The and parameters are always used by /// WriteTile. The parameter is used if the image is deeper /// than 1 slice (a value of > 1). In other cases the /// value of is ignored. The parameter is /// used only if data are organized in separate planes /// ( = .Separate). In other /// cases the value of is ignored. /// /// A correct value for the tag must be setup before /// writing; WriteTile does not support automatically growing the image on /// each write (as does). /// public int WriteTile(byte[] buffer, int x, int y, int z, short plane) { return WriteTile(buffer, 0, x, y, z, plane); } /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// /// The buffer with image data to be encoded and written. /// The zero-based byte offset in at which /// to begin reading bytes to be encoded and written. /// The x-coordinate of the pixel within a tile to be encoded and written. /// The y-coordinate of the pixel within a tile to be encoded and written. /// The z-coordinate of the pixel within a tile to be encoded and written. /// The zero-based index of the sample plane. /// The number of encoded and written bytes or -1 if an error occurred. /// /// /// The tile to place encoded data is selected by the (x, y, z, plane) coordinates (i.e. /// WriteTile writes data to the tile containing the specified coordinates. /// WriteTile (potentially) encodes the data and writes /// it to open file/stream. The buffer must contain an entire tile of data. Applications /// should call the to find out the size (in bytes) of a tile buffer. /// The and parameters are always used by /// WriteTile. The parameter is used if the image is deeper /// than 1 slice (a value of > 1). In other cases the /// value of is ignored. The parameter is /// used only if data are organized in separate planes /// ( = .Separate). In other /// cases the value of is ignored. /// /// A correct value for the tag must be setup before /// writing; WriteTile does not support automatically growing the image on /// each write (as does). /// public int WriteTile(byte[] buffer, int offset, int x, int y, int z, short plane) { if (!CheckTile(x, y, z, plane)) return -1; // NB: A tile size of -1 is used instead of m_tilesize knowing that WriteEncodedTile // will clamp this to the tile size. This is done because the tile size may not be // defined until after the output buffer is setup in WriteBufferSetup. return WriteEncodedTile(ComputeTile(x, y, z, plane), buffer, offset, -1); } /// /// Reads a strip of data from an open TIFF file/stream, decompresses it and places /// specified amount of decompressed bytes into the user supplied buffer. /// /// The zero-based index of the strip to read. /// The buffer to place decompressed strip bytes to. /// The zero-based byte offset in buffer at which to begin storing /// decompressed strip bytes. /// The maximum number of decompressed strip bytes to be stored /// to buffer. /// The actual number of bytes of data that were placed in buffer or -1 if an /// error was encountered. /// /// /// The value of is a "raw strip number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to a /// strip index. /// To read a full strip of data the data buffer should typically be at least /// as large as the number returned by . If the -1 passed in /// parameter, the whole strip will be read. You should be sure /// you have enough space allocated for the buffer. /// The library attempts to hide bit- and byte-ordering differences between the /// image and the native machine by converting data to the native machine order. Bit /// reversal is done if the tag is opposite to the native /// machine bit order. 16- and 32-bit samples are automatically byte-swapped if the file /// was written with a byte order opposite to the native machine byte order. /// public int ReadEncodedStrip(int strip, byte[] buffer, int offset, int count) { if (!checkRead(false)) return -1; if (strip >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, m_name, "{0}: Strip out of range, max {1}", strip, m_dir.td_nstrips); return -1; } // Calculate the strip size according to the number of rows in the strip (check for // truncated last strip on any of the separations). int strips_per_sep; if (m_dir.td_rowsperstrip >= m_dir.td_imagelength) strips_per_sep = 1; else strips_per_sep = (m_dir.td_imagelength + m_dir.td_rowsperstrip - 1) / m_dir.td_rowsperstrip; int sep_strip = strip % strips_per_sep; int nrows = m_dir.td_imagelength % m_dir.td_rowsperstrip; if (sep_strip != strips_per_sep - 1 || nrows == 0) nrows = m_dir.td_rowsperstrip; int stripsize = VStripSize(nrows); if (count == -1) count = stripsize; else if (count > stripsize) count = stripsize; if (fillStrip(strip) && m_currentCodec.DecodeStrip(buffer, offset, count, (short)(strip / m_dir.td_stripsperimage))) { postDecode(buffer, offset, count); return count; } return -1; } /// /// Reads the undecoded contents of a strip of data from an open TIFF file/stream and /// places specified amount of read bytes into the user supplied buffer. /// /// The zero-based index of the strip to read. /// The buffer to place read bytes to. /// The zero-based byte offset in buffer at which to begin storing /// read bytes. /// The maximum number of read bytes to be stored to buffer. /// The actual number of bytes of data that were placed in buffer or -1 if an /// error was encountered. /// /// /// The value of is a "raw strip number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to a /// strip index. /// To read a full strip of data the data buffer should typically be at least /// as large as the number returned by . If the -1 passed in /// parameter, the whole strip will be read. You should be sure /// you have enough space allocated for the buffer. public int ReadRawStrip(int strip, byte[] buffer, int offset, int count) { const string module = "ReadRawStrip"; if (!checkRead(false)) return -1; if (strip >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, m_name, "{0}: Strip out of range, max {1}", strip, m_dir.td_nstrips); return -1; } if ((m_flags & TiffFlags.NoReadRaw) == TiffFlags.NoReadRaw) { ErrorExt(this, m_clientdata, m_name, "Compression scheme does not support access to raw uncompressed data"); return -1; } uint bytecount = m_dir.td_stripbytecount[strip]; if (bytecount <= 0) { ErrorExt(this, m_clientdata, m_name, "{0}: Invalid strip byte count, strip {1}", bytecount, strip); return -1; } if (count != -1 && (uint)count < bytecount) bytecount = (uint)count; return readRawStrip1(strip, buffer, offset, (int)bytecount, module); } /// /// Encodes and writes a strip of data to an open TIFF file/stream. /// /// The zero-based index of the strip to write. /// The buffer with image data to be encoded and written. /// The maximum number of strip bytes to be read from /// . /// /// The number of encoded and written bytes or -1 if an error occurred. /// /// Encodes and writes a strip of data to an open TIFF file/stream. /// /// /// WriteEncodedStrip encodes bytes of raw data from /// and append the result to the specified strip; replacing any /// previously written data. Note that the value of is a "raw /// strip number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to /// a strip index. /// /// If there is no space for the strip, the value of /// tag is automatically increased to include the strip (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. /// public int WriteEncodedStrip(int strip, byte[] buffer, int count) { return WriteEncodedStrip(strip, buffer, 0, count); } /// /// Encodes and writes a strip of data to an open TIFF file/stream. /// /// The zero-based index of the strip to write. /// The buffer with image data to be encoded and written. /// The zero-based byte offset in at which /// to begin reading bytes to be encoded and written. /// The maximum number of strip bytes to be read from /// . /// The number of encoded and written bytes or -1 if an error occurred. /// /// /// WriteEncodedStrip encodes bytes of raw data from /// and append the result to the specified strip; replacing any /// previously written data. Note that the value of is a "raw /// strip number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to /// a strip index. /// /// If there is no space for the strip, the value of /// tag is automatically increased to include the strip (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. /// public int WriteEncodedStrip(int strip, byte[] buffer, int offset, int count) { const string module = "WriteEncodedStrip"; if (!writeCheckStrips(module)) return -1; // Check strip array to make sure there's space. We don't support dynamically growing // files that have data organized in separate bitplanes because it's too painful. // In that case we require that the imagelength be set properly before the first write // (so that the strips array will be fully allocated above). if (strip >= m_dir.td_nstrips) { if (m_dir.td_planarconfig == PlanarConfig.Separate) { ErrorExt(this, m_clientdata, m_name, "Can not grow image by strips when using separate planes"); return -1; } if (!growStrips(1)) return -1; m_dir.td_stripsperimage = howMany(m_dir.td_imagelength, m_dir.td_rowsperstrip); } // Handle delayed allocation of data buffer. This permits it to be sized according to // the directory info. bufferCheck(); m_curstrip = strip; m_row = (strip % m_dir.td_stripsperimage) * m_dir.td_rowsperstrip; if ((m_flags & TiffFlags.CoderSetup) != TiffFlags.CoderSetup) { if (!m_currentCodec.SetupEncode()) return -1; m_flags |= TiffFlags.CoderSetup; } m_rawcc = 0; m_rawcp = 0; if (m_dir.td_stripbytecount[strip] > 0) { // this forces appendToStrip() to do a seek m_curoff = 0; } m_flags &= ~TiffFlags.PostEncode; short sample = (short)(strip / m_dir.td_stripsperimage); if (!m_currentCodec.PreEncode(sample)) return -1; // swab if needed - note that source buffer will be altered postDecode(buffer, offset, count); if (!m_currentCodec.EncodeStrip(buffer, offset, count, sample)) return 0; if (!m_currentCodec.PostEncode()) return -1; if (!isFillOrder(m_dir.td_fillorder) && (m_flags & TiffFlags.NoBitRev) != TiffFlags.NoBitRev) ReverseBits(m_rawdata, m_rawcc); if (m_rawcc > 0 && !appendToStrip(strip, m_rawdata, 0, m_rawcc)) return -1; m_rawcc = 0; m_rawcp = 0; return count; } /// /// Writes a strip of raw data to an open TIFF file/stream. /// /// Writes a strip of raw data to an open TIFF file/stream. /// The zero-based index of the strip to write. /// The buffer with raw image data to be written. /// The maximum number of strip bytes to be read from /// . /// /// The number of written bytes or -1 if an error occurred. /// /// /// /// WriteRawStrip appends bytes of raw data from /// to the specified strip; replacing any /// previously written data. Note that the value of is a "raw /// strip number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to /// a strip index. /// /// If there is no space for the strip, the value of /// tag is automatically increased to include the strip (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// public int WriteRawStrip(int strip, byte[] buffer, int count) { return WriteRawStrip(strip, buffer, 0, count); } /// /// Writes a strip of raw data to an open TIFF file/stream. /// /// The zero-based index of the strip to write. /// The buffer with raw image data to be written. /// The zero-based byte offset in at which /// to begin reading bytes to be written. /// The maximum number of strip bytes to be read from /// . /// The number of written bytes or -1 if an error occurred. /// /// /// WriteRawStrip appends bytes of raw data from /// to the specified strip; replacing any /// previously written data. Note that the value of is a "raw /// strip number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (row, plane) to /// a strip index. /// /// If there is no space for the strip, the value of /// tag is automatically increased to include the strip (except for /// = .Separate, where the /// tag cannot be changed once the first data are /// written). If the is increased, the values of /// and tags are /// similarly enlarged to reflect data written past the previous end of image. /// public int WriteRawStrip(int strip, byte[] buffer, int offset, int count) { const string module = "WriteRawStrip"; if (!writeCheckStrips(module)) return -1; // Check strip array to make sure there's space. We don't support dynamically growing // files that have data organized in separate bitplanes because it's too painful. // In that case we require that the imagelength be set properly before the first write // (so that the strips array will be fully allocated above). if (strip >= m_dir.td_nstrips) { if (m_dir.td_planarconfig == PlanarConfig.Separate) { ErrorExt(this, m_clientdata, m_name, "Can not grow image by strips when using separate planes"); return -1; } // Watch out for a growing image. The value of strips/image will initially be 1 // (since it can't be deduced until the imagelength is known). if (strip >= m_dir.td_stripsperimage) m_dir.td_stripsperimage = howMany(m_dir.td_imagelength, m_dir.td_rowsperstrip); if (!growStrips(1)) return -1; } m_curstrip = strip; m_row = (strip % m_dir.td_stripsperimage) * m_dir.td_rowsperstrip; return (appendToStrip(strip, buffer, offset, count) ? count : -1); } /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// The zero-based index of the tile to write. /// The buffer with image data to be encoded and written. /// The maximum number of tile bytes to be read from /// . /// /// The number of encoded and written bytes or -1 if an error occurred. /// /// /// WriteEncodedTile encodes bytes of raw data from /// and append the result to the end of the specified tile. Note /// that the value of is a "raw tile number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// /// There must be space for the data. The function clamps individual writes to a tile to /// the tile size, but does not (and can not) check that multiple writes to the same tile /// were performed. /// /// A correct value for the tag must be setup before /// writing; WriteEncodedTile does not support automatically growing the image on /// each write (as does). /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. /// public int WriteEncodedTile(int tile, byte[] buffer, int count) { return WriteEncodedTile(tile, buffer, 0, count); } /// /// Encodes and writes a tile of data to an open TIFF file/stream. /// /// The zero-based index of the tile to write. /// The buffer with image data to be encoded and written. /// The zero-based byte offset in at which /// to begin reading bytes to be encoded and written. /// The maximum number of tile bytes to be read from /// . /// The number of encoded and written bytes or -1 if an error occurred. /// /// /// WriteEncodedTile encodes bytes of raw data from /// and append the result to the end of the specified tile. Note /// that the value of is a "raw tile number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// /// There must be space for the data. The function clamps individual writes to a tile to /// the tile size, but does not (and can not) check that multiple writes to the same tile /// were performed. /// /// A correct value for the tag must be setup before /// writing; WriteEncodedTile does not support automatically growing the image on /// each write (as does). /// /// The library writes encoded data using the native machine byte order. Correctly /// implemented TIFF readers are expected to do any necessary byte-swapping to correctly /// process image data with value of tag greater /// than 8. /// public int WriteEncodedTile(int tile, byte[] buffer, int offset, int count) { const string module = "WriteEncodedTile"; if (!writeCheckTiles(module)) return -1; if (tile >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, module, "{0}: Tile {1} out of range, max {2}", m_name, tile, m_dir.td_nstrips); return -1; } // Handle delayed allocation of data buffer. This permits it to be sized more // intelligently (using directory information). bufferCheck(); m_curtile = tile; m_rawcc = 0; m_rawcp = 0; if (m_dir.td_stripbytecount[tile] > 0) { // this forces appendToStrip() to do a seek m_curoff = 0; } // Compute tiles per row & per column to compute current row and column m_row = (tile % howMany(m_dir.td_imagelength, m_dir.td_tilelength)) * m_dir.td_tilelength; m_col = (tile % howMany(m_dir.td_imagewidth, m_dir.td_tilewidth)) * m_dir.td_tilewidth; if ((m_flags & TiffFlags.CoderSetup) != TiffFlags.CoderSetup) { if (!m_currentCodec.SetupEncode()) return -1; m_flags |= TiffFlags.CoderSetup; } m_flags &= ~TiffFlags.PostEncode; short sample = (short)(tile / m_dir.td_stripsperimage); if (!m_currentCodec.PreEncode(sample)) return -1; // Clamp write amount to the tile size. This is mostly done so that callers can pass // in some large number (e.g. -1) and have the tile size used instead. if (count < 1 || count > m_tilesize) count = m_tilesize; // swab if needed - note that source buffer will be altered postDecode(buffer, offset, count); if (!m_currentCodec.EncodeTile(buffer, offset, count, sample)) return 0; if (!m_currentCodec.PostEncode()) return -1; if (!isFillOrder(m_dir.td_fillorder) && (m_flags & TiffFlags.NoBitRev) != TiffFlags.NoBitRev) ReverseBits(m_rawdata, m_rawcc); if (m_rawcc > 0 && !appendToStrip(tile, m_rawdata, 0, m_rawcc)) return -1; m_rawcc = 0; m_rawcp = 0; return count; } /// /// Writes a tile of raw data to an open TIFF file/stream. /// /// Writes a tile of raw data to an open TIFF file/stream. /// The zero-based index of the tile to write. /// The buffer with raw image data to be written. /// The maximum number of tile bytes to be read from /// . /// /// The number of written bytes or -1 if an error occurred. /// /// /// /// WriteRawTile appends bytes of raw data to the end of /// the specified tile. Note that the value of is a "raw tile /// number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// /// There must be space for the data. The function clamps individual writes to a tile to /// the tile size, but does not (and can not) check that multiple writes to the same tile /// were performed. /// /// A correct value for the tag must be setup before /// writing; WriteRawTile does not support automatically growing the image on /// each write (as does). /// public int WriteRawTile(int tile, byte[] buffer, int count) { return WriteRawTile(tile, buffer, 0, count); } /// /// Writes a tile of raw data to an open TIFF file/stream. /// /// The zero-based index of the tile to write. /// The buffer with raw image data to be written. /// The zero-based byte offset in at which /// to begin reading bytes to be written. /// The maximum number of tile bytes to be read from /// . /// The number of written bytes or -1 if an error occurred. /// /// /// WriteRawTile appends bytes of raw data to the end of /// the specified tile. Note that the value of is a "raw tile /// number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// ( = .Separate). /// automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// /// There must be space for the data. The function clamps individual writes to a tile to /// the tile size, but does not (and can not) check that multiple writes to the same tile /// were performed. /// /// A correct value for the tag must be setup before /// writing; WriteRawTile does not support automatically growing the image on /// each write (as does). /// public int WriteRawTile(int tile, byte[] buffer, int offset, int count) { const string module = "WriteRawTile"; if (!writeCheckTiles(module)) return -1; if (tile >= m_dir.td_nstrips) { ErrorExt(this, m_clientdata, module, "{0}: Tile {1} out of range, max {2}", m_name, tile, m_dir.td_nstrips); return -1; } return (appendToStrip(tile, buffer, offset, count) ? count : -1); } /// /// Sets the current write offset. /// /// The write offset. /// This should only be used to set the offset to a known previous location /// (very carefully), or to 0 so that the next write gets appended to the end of the file. /// public void SetWriteOffset(long offset) { m_curoff = (uint)offset; } /// /// Gets the number of bytes occupied by the item of given type. /// /// The type. /// The number of bytes occupied by the or 0 if unknown /// data type is supplied. public static int DataWidth(TiffType type) { switch (type) { case TiffType.NoType: case TiffType.Byte: case TiffType.ASCII: case TiffType.SByte: case TiffType.Undefined: return 1; case TiffType.Short: case TiffType.SShort: return 2; case TiffType.Long: case TiffType.SLong: case TiffType.Float: case TiffType.IFD: return 4; case TiffType.Rational: case TiffType.SRational: case TiffType.Double: return 8; default: // will return 0 for unknown types return 0; } } /// /// Swaps the bytes in a single 16-bit item. /// /// The value to swap bytes in. public static void SwabShort(ref short value) { byte[] bytes = new byte[2]; bytes[0] = (byte)value; bytes[1] = (byte)(value >> 8); byte temp = bytes[1]; bytes[1] = bytes[0]; bytes[0] = temp; value = (short)(bytes[0] & 0xFF); value += (short)((bytes[1] & 0xFF) << 8); } /// /// Swaps the bytes in a single 32-bit item. /// /// The value to swap bytes in. public static void SwabLong(ref int value) { byte[] bytes = new byte[4]; bytes[0] = (byte)value; bytes[1] = (byte)(value >> 8); bytes[2] = (byte)(value >> 16); bytes[3] = (byte)(value >> 24); byte temp = bytes[3]; bytes[3] = bytes[0]; bytes[0] = temp; temp = bytes[2]; bytes[2] = bytes[1]; bytes[1] = temp; value = bytes[0] & 0xFF; value += (bytes[1] & 0xFF) << 8; value += (bytes[2] & 0xFF) << 16; value += bytes[3] << 24; } /// /// Swaps the bytes in a single double-precision floating-point number. /// /// The value to swap bytes in. public static void SwabDouble(ref double value) { byte[] bytes = BitConverter.GetBytes(value); int[] ints = new int[2]; ints[0] = BitConverter.ToInt32(bytes, 0); ints[0] = BitConverter.ToInt32(bytes, sizeof(int)); SwabArrayOfLong(ints, 2); int temp = ints[0]; ints[0] = ints[1]; ints[1] = temp; Buffer.BlockCopy(BitConverter.GetBytes(ints[0]), 0, bytes, 0, sizeof(int)); Buffer.BlockCopy(BitConverter.GetBytes(ints[1]), 0, bytes, sizeof(int), sizeof(int)); value = BitConverter.ToDouble(bytes, 0); } /// /// Swaps the bytes in specified number of values in the array of 16-bit items. /// /// /// Swaps the bytes in specified number of values in the array of 16-bit items. /// /// The array to swap bytes in. /// The number of items to swap bytes in. public static void SwabArrayOfShort(short[] array, int count) { SwabArrayOfShort(array, 0, count); } /// /// Swaps the bytes in specified number of values in the array of 16-bit items starting at /// specified offset. /// /// The array to swap bytes in. /// The zero-based offset in at /// which to begin swapping bytes. /// The number of items to swap bytes in. public static void SwabArrayOfShort(short[] array, int offset, int count) { byte[] bytes = new byte[2]; for (int i = 0; i < count; i++, offset++) { bytes[0] = (byte)array[offset]; bytes[1] = (byte)(array[offset] >> 8); byte temp = bytes[1]; bytes[1] = bytes[0]; bytes[0] = temp; array[offset] = (short)(bytes[0] & 0xFF); array[offset] += (short)((bytes[1] & 0xFF) << 8); } } /// /// Swaps the bytes in specified number of values in the array of triples (24-bit items). /// /// /// Swaps the bytes in specified number of values in the array of triples (24-bit items). /// /// The array to swap bytes in. /// The number of items to swap bytes in. public static void SwabArrayOfTriples(byte[] array, int count) { SwabArrayOfTriples(array, 0, count); } /// /// Swaps the bytes in specified number of values in the array of triples (24-bit items) /// starting at specified offset. /// /// The array to swap bytes in. /// The zero-based offset in at /// which to begin swapping bytes. /// The number of items to swap bytes in. public static void SwabArrayOfTriples(byte[] array, int offset, int count) { // XXX unroll loop some while (count-- > 0) { byte t = array[offset + 2]; array[offset + 2] = array[offset]; array[offset] = t; offset += 3; } } /// /// Swaps the bytes in specified number of values in the array of 32-bit items. /// /// /// Swaps the bytes in specified number of values in the array of 32-bit items. /// /// The array to swap bytes in. /// The number of items to swap bytes in. public static void SwabArrayOfLong(int[] array, int count) { SwabArrayOfLong(array, 0, count); } /// /// Swaps the bytes in specified number of values in the array of 32-bit items /// starting at specified offset. /// /// The array to swap bytes in. /// The zero-based offset in at /// which to begin swapping bytes. /// The number of items to swap bytes in. public static void SwabArrayOfLong(int[] array, int offset, int count) { byte[] bytes = new byte[4]; for (int i = 0; i < count; i++, offset++) { bytes[0] = (byte)array[offset]; bytes[1] = (byte)(array[offset] >> 8); bytes[2] = (byte)(array[offset] >> 16); bytes[3] = (byte)(array[offset] >> 24); byte temp = bytes[3]; bytes[3] = bytes[0]; bytes[0] = temp; temp = bytes[2]; bytes[2] = bytes[1]; bytes[1] = temp; array[offset] = bytes[0] & 0xFF; array[offset] += (bytes[1] & 0xFF) << 8; array[offset] += (bytes[2] & 0xFF) << 16; array[offset] += bytes[3] << 24; } } /// /// Swaps the bytes in specified number of values in the array of double-precision /// floating-point numbers. /// /// /// Swaps the bytes in specified number of values in the array of double-precision /// floating-point numbers. /// /// The array to swap bytes in. /// The number of items to swap bytes in. public static void SwabArrayOfDouble(double[] array, int count) { SwabArrayOfDouble(array, 0, count); } /// /// Swaps the bytes in specified number of values in the array of double-precision /// floating-point numbers starting at specified offset. /// /// The array to swap bytes in. /// The zero-based offset in at /// which to begin swapping bytes. /// The number of items to swap bytes in. public static void SwabArrayOfDouble(double[] array, int offset, int count) { int[] ints = new int[count * sizeof(int) / sizeof(double)]; Buffer.BlockCopy(array, offset * sizeof(double), ints, 0, ints.Length * sizeof(int)); SwabArrayOfLong(ints, ints.Length); int pos = 0; while (count-- > 0) { int temp = ints[pos]; ints[pos] = ints[pos + 1]; ints[pos + 1] = temp; pos += 2; } Buffer.BlockCopy(ints, 0, array, offset * sizeof(double), ints.Length * sizeof(int)); } /// /// Replaces specified number of bytes in with the /// equivalent bit-reversed bytes. /// /// /// Replaces specified number of bytes in with the /// equivalent bit-reversed bytes. /// /// The buffer to replace bytes in. /// The number of bytes to process. /// /// This operation is performed with a lookup table, which can be retrieved using the /// method. /// public static void ReverseBits(byte[] buffer, int count) { ReverseBits(buffer, 0, count); } /// /// Replaces specified number of bytes in with the /// equivalent bit-reversed bytes starting at specified offset. /// /// The buffer to replace bytes in. /// The zero-based offset in at /// which to begin processing bytes. /// The number of bytes to process. /// /// This operation is performed with a lookup table, which can be retrieved using the /// method. /// public static void ReverseBits(byte[] buffer, int offset, int count) { for (; count > 8; count -= 8) { buffer[offset + 0] = TIFFBitRevTable[buffer[offset + 0]]; buffer[offset + 1] = TIFFBitRevTable[buffer[offset + 1]]; buffer[offset + 2] = TIFFBitRevTable[buffer[offset + 2]]; buffer[offset + 3] = TIFFBitRevTable[buffer[offset + 3]]; buffer[offset + 4] = TIFFBitRevTable[buffer[offset + 4]]; buffer[offset + 5] = TIFFBitRevTable[buffer[offset + 5]]; buffer[offset + 6] = TIFFBitRevTable[buffer[offset + 6]]; buffer[offset + 7] = TIFFBitRevTable[buffer[offset + 7]]; offset += 8; } while (count-- > 0) { buffer[offset] = TIFFBitRevTable[buffer[offset]]; offset++; } } /// /// Retrieves a bit reversal table. /// /// if set to true then bit reversal table will be /// retrieved; otherwise, the table that do not reverse bit values will be retrieved. /// The bit reversal table. /// If is false then the table that do not /// reverse bit values will be retrieved. It is a lookup table that can be used as an /// identity function; i.e. NoBitRevTable[n] == n. public static byte[] GetBitRevTable(bool reversed) { return (reversed ? TIFFBitRevTable : TIFFNoBitRevTable); } /// /// Converts a byte buffer into array of 32-bit values. /// /// The byte buffer. /// The zero-based offset in at /// which to begin converting bytes. /// The number of bytes to convert. /// The array of 32-bit values. public static int[] ByteArrayToInts(byte[] buffer, int offset, int count) { int intCount = count / sizeof(int); int[] integers = new int[intCount]; Buffer.BlockCopy(buffer, offset, integers, 0, intCount * sizeof(int)); return integers; } /// /// Converts array of 32-bit values into array of bytes. /// /// The array of 32-bit values. /// The zero-based offset in at /// which to begin converting bytes. /// The number of 32-bit values to convert. /// The byte array to store converted values at. /// The zero-based offset in at /// which to begin storing converted values. public static void IntsToByteArray(int[] source, int srcOffset, int srcCount, byte[] bytes, int offset) { Buffer.BlockCopy(source, srcOffset * sizeof(int), bytes, offset, srcCount * sizeof(int)); } /// /// Converts a byte buffer into array of 16-bit values. /// /// The byte buffer. /// The zero-based offset in at /// which to begin converting bytes. /// The number of bytes to convert. /// The array of 16-bit values. public static short[] ByteArrayToShorts(byte[] buffer, int offset, int count) { int shortCount = count / sizeof(short); short[] shorts = new short[shortCount]; Buffer.BlockCopy(buffer, offset, shorts, 0, shortCount * sizeof(short)); return shorts; } /// /// Converts array of 16-bit values into array of bytes. /// /// The array of 16-bit values. /// The zero-based offset in at /// which to begin converting bytes. /// The number of 16-bit values to convert. /// The byte array to store converted values at. /// The zero-based offset in at /// which to begin storing converted values. public static void ShortsToByteArray(short[] source, int srcOffset, int srcCount, byte[] bytes, int offset) { Buffer.BlockCopy(source, srcOffset * sizeof(short), bytes, offset, srcCount * sizeof(short)); } } #endregion #region Tiff2RGBAConverter public class TiffToRGBAConverter { public Compression m_compression = Compression.PackBits; public int m_rowsPerStrip = -1; public bool m_processByBlock; public bool m_noAlpha; public bool m_testFriendly; public bool tiffcvt(Tiff inImage, Tiff outImage) { FieldValue[] result = inImage.GetField(TiffTag.ImageWidth); if (result == null) return false; int width = result[0].ToInt(); result = inImage.GetField(TiffTag.ImageLength); if (result == null) return false; int height = result[0].ToInt(); copyField(inImage, outImage, TiffTag.SubFileType); outImage.SetField(TiffTag.ImageWidth, width); outImage.SetField(TiffTag.ImageLength, height); outImage.SetField(TiffTag.BitsPerSample, 8); outImage.SetField(TiffTag.Compression, m_compression); outImage.SetField(TiffTag.Photometric, Photometric.RGB); copyField(inImage, outImage, TiffTag.FillOrder); outImage.SetField(TiffTag.Orientation, Orientation.TopLeft); if (m_noAlpha) outImage.SetField(TiffTag.SamplesPerPixel, 3); else outImage.SetField(TiffTag.SamplesPerPixel, 4); if (!m_noAlpha) { short[] v = new short[1]; v[0] = (short)ExtraSample.AssociatedAlpha; outImage.SetField(TiffTag.ExtraSamples, 1, v); } copyField(inImage, outImage, TiffTag.XResolution); copyField(inImage, outImage, TiffTag.YResolution); copyField(inImage, outImage, TiffTag.ResolutionUnit); outImage.SetField(TiffTag.PlanarConfig, PlanarConfig.Contig); if (!m_testFriendly) outImage.SetField(TiffTag.Software, Tiff.GetVersion()); copyField(inImage, outImage, TiffTag.DocumentName); if (m_processByBlock && inImage.IsTiled()) return cvt_by_tile(inImage, outImage, width, height); else if (m_processByBlock) return cvt_by_strip(inImage, outImage, width, height); return cvt_whole_image(inImage, outImage, width, height); } private static void copyField(Tiff inImage, Tiff outImage, TiffTag tag) { FieldValue[] result = inImage.GetField(tag); if (result != null) outImage.SetField(tag, result[0]); } private static int multiply(int x, int y) { long res = (long)x * (long)y; if (res > int.MaxValue) return 0; return (int)res; } static bool cvt_by_tile(Tiff inImage, Tiff outImage, int width, int height) { int tile_width = 0; int tile_height = 0; FieldValue[] result = inImage.GetField(TiffTag.TileWidth); if (result != null) { tile_width = result[0].ToInt(); result = inImage.GetField(TiffTag.TileLength); if (result != null) tile_height = result[0].ToInt(); } if (result == null) { Tiff.Error(inImage.FileName(), "Source image not tiled"); return false; } outImage.SetField(TiffTag.TileWidth, tile_width); outImage.SetField(TiffTag.TileLength, tile_height); // Allocate tile buffer int raster_size = multiply(tile_width, tile_height); int rasterByteSize = multiply(raster_size, sizeof(int)); if (raster_size == 0 || rasterByteSize == 0) { Tiff.Error(inImage.FileName(), "Can't allocate buffer for raster of size {0}x{1}", tile_width, tile_height); return false; } int[] raster = new int[raster_size]; byte[] rasterBytes = new byte[rasterByteSize]; // Allocate a scanline buffer for swapping during the vertical mirroring pass. // (Request can't overflow given prior checks.) int[] wrk_line = new int[tile_width]; // Loop over the tiles. for (int row = 0; row < height; row += tile_height) { for (int col = 0; col < width; col += tile_width) { // Read the tile into an RGBA array if (!inImage.ReadRGBATile(col, row, raster)) return false; // For some reason the ReadRGBATile() function chooses the lower left corner // as the origin. Vertically mirror scanlines. for (int i_row = 0; i_row < tile_height / 2; i_row++) { int topIndex = tile_width * i_row * sizeof(int); int bottomIndex = tile_width * (tile_height - i_row - 1) * sizeof(int); Buffer.BlockCopy(raster, topIndex, wrk_line, 0, tile_width * sizeof(int)); Buffer.BlockCopy(raster, bottomIndex, raster, topIndex, tile_width * sizeof(int)); Buffer.BlockCopy(wrk_line, 0, raster, bottomIndex, tile_width * sizeof(int)); } // Write out the result in a tile. int tile = outImage.ComputeTile(col, row, 0, 0); Buffer.BlockCopy(raster, 0, rasterBytes, 0, rasterByteSize); if (outImage.WriteEncodedTile(tile, rasterBytes, rasterByteSize) == -1) return false; } } return true; } private bool cvt_by_strip(Tiff inImage, Tiff outImage, int width, int height) { FieldValue[] result = inImage.GetField(TiffTag.RowsPerStrip); if (result == null) { Tiff.Error(inImage.FileName(), "Source image not in strips"); return false; } m_rowsPerStrip = result[0].ToInt(); outImage.SetField(TiffTag.RowsPerStrip, m_rowsPerStrip); // Allocate strip buffer int raster_size = multiply(width, m_rowsPerStrip); int rasterByteSize = multiply(raster_size, sizeof(int)); if (raster_size == 0 || rasterByteSize == 0) { Tiff.Error(inImage.FileName(), "Can't allocate buffer for raster of size {0}x{1}", width, m_rowsPerStrip); return false; } int[] raster = new int[raster_size]; byte[] rasterBytes = new byte[rasterByteSize]; // Allocate a scanline buffer for swapping during the vertical mirroring pass. // (Request can't overflow given prior checks.) int[] wrk_line = new int[width]; // Loop over the strips. for (int row = 0; row < height; row += m_rowsPerStrip) { // Read the strip into an RGBA array if (!inImage.ReadRGBAStrip(row, raster)) return false; // Figure out the number of scanlines actually in this strip. int rows_to_write; if (row + m_rowsPerStrip > height) rows_to_write = height - row; else rows_to_write = m_rowsPerStrip; // For some reason the TIFFReadRGBAStrip() function chooses the lower left corner // as the origin. Vertically mirror scanlines. for (int i_row = 0; i_row < rows_to_write / 2; i_row++) { int topIndex = width * i_row * sizeof(int); int bottomIndex = width * (rows_to_write - i_row - 1) * sizeof(int); Buffer.BlockCopy(raster, topIndex, wrk_line, 0, width * sizeof(int)); Buffer.BlockCopy(raster, bottomIndex, raster, topIndex, width * sizeof(int)); Buffer.BlockCopy(wrk_line, 0, raster, bottomIndex, width * sizeof(int)); } // Write out the result in a strip int bytesToWrite = rows_to_write * width * sizeof(int); Buffer.BlockCopy(raster, 0, rasterBytes, 0, bytesToWrite); if (outImage.WriteEncodedStrip(row / m_rowsPerStrip, rasterBytes, bytesToWrite) == -1) return false; } return true; } /// /// Read the whole image into one big RGBA buffer and then write out /// strips from that. This is using the traditional TIFFReadRGBAImage() /// API that we trust. /// private bool cvt_whole_image(Tiff inImage, Tiff outImage, int width, int height) { int pixel_count = width * height; /* XXX: Check the integer overflow. */ if (width == 0 || height == 0 || (pixel_count / width) != height) { Tiff.Error(inImage.FileName(), "Malformed input file; can't allocate buffer for raster of {0}x{1} size", width, height); return false; } m_rowsPerStrip = outImage.DefaultStripSize(m_rowsPerStrip); outImage.SetField(TiffTag.RowsPerStrip, m_rowsPerStrip); int[] raster = new int[pixel_count]; /* Read the image in one chunk into an RGBA array */ if (!inImage.ReadRGBAImageOriented(width, height, raster, Orientation.TopLeft, false)) return false; /* * Do we want to strip away alpha components? */ byte[] rasterBytes; int rasterByteSize; if (m_noAlpha) { rasterByteSize = pixel_count * 3; rasterBytes = new byte[rasterByteSize]; for (int i = 0, rasterBytesPos = 0; i < pixel_count; i++) { byte[] bytes = BitConverter.GetBytes(raster[i]); rasterBytes[rasterBytesPos++] = bytes[0]; rasterBytes[rasterBytesPos++] = bytes[1]; rasterBytes[rasterBytesPos++] = bytes[2]; } } else { rasterByteSize = pixel_count * 4; rasterBytes = new byte[rasterByteSize]; Buffer.BlockCopy(raster, 0, rasterBytes, 0, rasterByteSize); } /* * Write out the result in strips */ for (int row = 0; row < height; row += m_rowsPerStrip) { int bytes_per_pixel; if (m_noAlpha) bytes_per_pixel = 3; else bytes_per_pixel = 4; int rows_to_write; if (row + m_rowsPerStrip > height) rows_to_write = height - row; else rows_to_write = m_rowsPerStrip; int offset = bytes_per_pixel * row * width; int count = bytes_per_pixel * rows_to_write * width; if (outImage.WriteEncodedStrip(row / m_rowsPerStrip, rasterBytes, offset, count) == -1) return false; } return true; } } #endregion #region TiffRGBAImage /// /// RGBA-style image support. Provides methods for decoding images into RGBA (or other) format. /// /// /// /// TiffRgbaImage provide a high-level interface through which TIFF images may be read /// into memory. Images may be strip- or tile-based and have a variety of different /// characteristics: bits/sample, samples/pixel, photometric, etc. The target raster format /// can be customized to a particular application's needs by installing custom methods that /// manipulate image data according to application requirements. /// /// The default usage for this class: check if an image can be processed using /// , construct an instance of /// TiffRgbaImage using and then read and decode an image into a /// target raster using . can be called /// multiple times to decode an image using different state parameters. If multiple images /// are to be displayed and there is not enough space for each of the decoded rasters, /// multiple instances of TiffRgbaImage can be managed and then calls can be made to /// as needed to display an image. /// /// To use the core support for reading and processing TIFF images, but write the resulting /// raster data in a different format one need only override the "put methods" used to store /// raster data. These methods are initially setup by to point to methods /// that pack raster data in the default ABGR pixel format. Two different methods are used /// according to the physical organization of the image data in the file: one for /// = .Contig (packed samples), /// and another for = .Separate /// (separated samples). Note that this mechanism can be used to transform the data before /// storing it in the raster. For example one can convert data to colormap indices for display /// on a colormap display. /// To setup custom "put" method please use property for contiguously /// packed samples and/or property for separated samples. /// /// The methods of TiffRgbaImage support the most commonly encountered flavors of TIFF. /// It is possible to extend this support by overriding the "get method" invoked by /// to read TIFF image data. Details of doing this are a bit involved, /// it is best to make a copy of an existing get method and modify it to suit the needs of an /// application. To setup custom "get" method please use property. /// public class TiffRgbaImage { internal const string photoTag = "PhotometricInterpretation"; /// /// image handle /// private Tiff tif; /// /// stop on read error /// private bool stoponerr; /// /// data is packed/separate /// private bool isContig; /// /// type of alpha data present /// private ExtraSample alpha; /// /// image width /// private int width; /// /// image height /// private int height; /// /// image bits/sample /// private short bitspersample; /// /// image samples/pixel /// private short samplesperpixel; /// /// image orientation /// private Orientation orientation; /// /// requested orientation /// private Orientation req_orientation; /// /// image photometric interp /// private Photometric photometric; /// /// colormap pallete /// private short[] redcmap; private short[] greencmap; private short[] bluecmap; private GetDelegate get; private PutContigDelegate putContig; private PutSeparateDelegate putSeparate; /// /// sample mapping array /// private byte[] Map; /// /// black and white map /// private int[][] BWmap; /// /// palette image map /// private int[][] PALmap; /// /// YCbCr conversion state /// private TiffYCbCrToRGB ycbcr; /// /// CIE L*a*b conversion state /// private TiffCIELabToRGB cielab; private static TiffDisplay display_sRGB = new TiffDisplay( // XYZ -> luminance matrix new float[] { 3.2410F, -1.5374F, -0.4986F }, new float[] { -0.9692F, 1.8760F, 0.0416F }, new float[] { 0.0556F, -0.2040F, 1.0570F }, 100.0F, 100.0F, 100.0F, // Light o/p for reference white 255, 255, 255, // Pixel values for ref. white 1.0F, 1.0F, 1.0F, // Residual light o/p for black pixel 2.4F, 2.4F, 2.4F // Gamma values for the three guns ); private const int A1 = 0xff << 24; // Helper constants used in Orientation tag handling private const int FLIP_VERTICALLY = 0x01; private const int FLIP_HORIZONTALLY = 0x02; internal int row_offset; internal int col_offset; /// /// Delegate for "put" method (the method that is called to pack pixel data in the raster) /// used when converting contiguously packed samples. /// /// An instance of the class. /// The raster (the buffer to place decoded image data to). /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The value that should be added to /// after each row processed. /// The x-coordinate of the first pixel in block of pixels to be decoded. /// The y-coordinate of the first pixel in block of pixels to be decoded. /// The block width. /// The block height. /// The buffer with image data. /// The zero-based byte offset in at /// which to begin reading image bytes. /// The value that should be added to /// after each row processed. /// /// The image reading and conversion methods invoke "put" methods to copy/image/whatever /// tiles of raw image data. A default set of methods is provided to convert/copy raw /// image data to 8-bit packed ABGR format rasters. Applications can supply alternate /// methods that unpack the data into a different format or, for example, unpack the data /// and draw the unpacked raster on the display. /// /// To setup custom "put" method for contiguously packed samples please use /// property. /// /// The is usually 0. It is greater than 0 if width of strip /// being converted is greater than image width or part of the tile being converted is /// outside the image (may be true for tiles on the right and bottom edge of the image). /// In other words, is used to make up for any padding on /// the end of each line of the buffer with image data. /// /// The is 0 if width of tile being converted is equal to /// image width and image data should not be flipped vertically. In other circumstances /// is used to make up for any padding on the end of each /// line of the raster and/or for flipping purposes. /// public delegate void PutContigDelegate( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift); /// /// Delegate for "put" method (the method that is called to pack pixel data in the raster) /// used when converting separated samples. /// /// An instance of the class. /// The raster (the buffer to place decoded image data to). /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The value that should be added to /// after each row processed. /// The x-coordinate of the first pixel in block of pixels to be decoded. /// The y-coordinate of the first pixel in block of pixels to be decoded. /// The block width. /// The block height. /// The buffer with image data. /// The zero-based byte offset in at /// which to begin reading image bytes that constitute first sample plane. /// The zero-based byte offset in at /// which to begin reading image bytes that constitute second sample plane. /// The zero-based byte offset in at /// which to begin reading image bytes that constitute third sample plane. /// The zero-based byte offset in at /// which to begin reading image bytes that constitute fourth sample plane. /// The value that should be added to , /// , and /// after each row processed. /// /// The image reading and conversion methods invoke "put" methods to copy/image/whatever /// tiles of raw image data. A default set of methods is provided to convert/copy raw /// image data to 8-bit packed ABGR format rasters. Applications can supply alternate /// methods that unpack the data into a different format or, for example, unpack the data /// and draw the unpacked raster on the display. /// /// To setup custom "put" method for separated samples please use /// property. /// /// The is usually 0. It is greater than 0 if width of strip /// being converted is greater than image width or part of the tile being converted is /// outside the image (may be true for tiles on the right and bottom edge of the image). /// In other words, is used to make up for any padding on /// the end of each line of the buffer with image data. /// /// The is 0 if width of tile being converted is equal to /// image width and image data should not be flipped vertically. In other circumstances /// is used to make up for any padding on the end of each /// line of the raster and/or for flipping purposes. /// public delegate void PutSeparateDelegate( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift); /// /// Delegate for "get" method (the method that is called to produce RGBA raster). /// /// An instance of the class. /// The raster (the buffer to place decoded image data to). /// The zero-based byte offset in at which /// to begin storing decoded bytes. /// The raster width. /// The raster height. /// true if the image was successfully read and decoded; otherwise, /// false. /// /// A default set of methods is provided to read and convert/copy raw image data to 8-bit /// packed ABGR format rasters. Applications can supply alternate method for this. /// /// To setup custom "get" method please use property. /// public delegate bool GetDelegate(TiffRgbaImage img, int[] raster, int offset, int width, int height); private TiffRgbaImage() { } /// /// Creates new instance of the class. /// /// /// The instance of the class used to retrieve /// image data. /// /// /// if set to true then an error will terminate the conversion; otherwise "get" /// methods will continue processing data until all the possible data in the image have /// been requested. /// /// The error message (if any) gets placed here. /// /// New instance of the class if the image specified /// by can be converted to RGBA format; otherwise, null is /// returned and contains the reason why it is being /// rejected. /// public static TiffRgbaImage Create(Tiff tif, bool stopOnError, out string errorMsg) { errorMsg = null; // Initialize to normal values TiffRgbaImage img = new TiffRgbaImage(); img.row_offset = 0; img.col_offset = 0; img.redcmap = null; img.greencmap = null; img.bluecmap = null; img.req_orientation = Orientation.BottomLeft; // It is the default img.tif = tif; img.stoponerr = stopOnError; FieldValue[] result = tif.GetFieldDefaulted(TiffTag.BitsPerSample); img.bitspersample = result[0].ToShort(); switch (img.bitspersample) { case 1: case 2: case 4: case 8: case 16: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle images with {0}-bit samples", img.bitspersample); return null; } img.alpha = 0; result = tif.GetFieldDefaulted(TiffTag.SamplesPerPixel); img.samplesperpixel = result[0].ToShort(); result = tif.GetFieldDefaulted(TiffTag.ExtraSamples); short extrasamples = result[0].ToShort(); byte[] sampleinfo = result[1].ToByteArray(); if (extrasamples >= 1) { switch ((ExtraSample)sampleinfo[0]) { case ExtraSample.UnSpecified: if (img.samplesperpixel > 3) { // Workaround for some images without correct info about alpha channel img.alpha = ExtraSample.AssociatedAlpha; } break; case ExtraSample.AssociatedAlpha: // data is pre-multiplied case ExtraSample.UnAssociatedAlpha: // data is not pre-multiplied img.alpha = (ExtraSample)sampleinfo[0]; break; } } if (Tiff.DEFAULT_EXTRASAMPLE_AS_ALPHA) { result = tif.GetField(TiffTag.Photometric); if (result == null) img.photometric = Photometric.MinIsWhite; if (extrasamples == 0 && img.samplesperpixel == 4 && img.photometric == Photometric.RGB) { img.alpha = ExtraSample.AssociatedAlpha; extrasamples = 1; } } int colorchannels = img.samplesperpixel - extrasamples; result = tif.GetFieldDefaulted(TiffTag.Compression); Compression compress = (Compression)result[0].ToInt(); result = tif.GetFieldDefaulted(TiffTag.PlanarConfig); PlanarConfig planarconfig = (PlanarConfig)result[0].ToShort(); result = tif.GetField(TiffTag.Photometric); if (result == null) { switch (colorchannels) { case 1: if (img.isCCITTCompression()) img.photometric = Photometric.MinIsWhite; else img.photometric = Photometric.MinIsBlack; break; case 3: img.photometric = Photometric.RGB; break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Missing needed {0} tag", photoTag); return null; } } else img.photometric = (Photometric)result[0].ToInt(); switch (img.photometric) { case Photometric.Palette: result = tif.GetField(TiffTag.Colormap); if (result == null) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Missing required \"Colormap\" tag"); return null; } short[] red_orig = result[0].ToShortArray(); short[] green_orig = result[1].ToShortArray(); short[] blue_orig = result[2].ToShortArray(); // copy the colormaps so we can modify them int n_color = (1 << img.bitspersample); img.redcmap = new short[n_color]; img.greencmap = new short[n_color]; img.bluecmap = new short[n_color]; Buffer.BlockCopy(red_orig, 0, img.redcmap, 0, n_color * sizeof(short)); Buffer.BlockCopy(green_orig, 0, img.greencmap, 0, n_color * sizeof(short)); Buffer.BlockCopy(blue_orig, 0, img.bluecmap, 0, n_color * sizeof(short)); if (planarconfig == PlanarConfig.Contig && img.samplesperpixel != 1 && img.bitspersample < 8) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle contiguous data with {0}={1}, and {2}={3} and Bits/Sample={4}", photoTag, img.photometric, "Samples/pixel", img.samplesperpixel, img.bitspersample); return null; } break; case Photometric.MinIsWhite: case Photometric.MinIsBlack: if (planarconfig == PlanarConfig.Contig && img.samplesperpixel != 1 && img.bitspersample < 8) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle contiguous data with {0}={1}, and {2}={3} and Bits/Sample={4}", photoTag, img.photometric, "Samples/pixel", img.samplesperpixel, img.bitspersample); return null; } break; case Photometric.YCBCR: // It would probably be nice to have a reality check here. if (planarconfig == PlanarConfig.Contig) { // can rely on LibJpeg.Net to convert to RGB // XXX should restore current state on exit switch (compress) { case Compression.JPEG: // TODO: when complete tests verify complete desubsampling and // YCbCr handling, remove use of JPEGCOLORMODE in favor of native // handling tif.SetField(TiffTag.JPEGCOLORMODE, JpegColorMode.RGB); img.photometric = Photometric.RGB; break; default: // do nothing break; } } // TODO: if at all meaningful and useful, make more complete support check // here, or better still, refactor to let supporting code decide whether there // is support and what meaningfull error to return break; case Photometric.RGB: if (colorchannels < 3) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle RGB image with {0}={1}", "Color channels", colorchannels); return null; } break; case Photometric.Separated: result = tif.GetFieldDefaulted(TiffTag.InkSet); InkSet inkset = (InkSet)result[0].ToByte(); if (inkset != InkSet.CMYK) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "InkSet", inkset); return null; } if (img.samplesperpixel < 4) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle separated image with {0}={1}", "Samples/pixel", img.samplesperpixel); return null; } break; case Photometric.LogL: if (compress != Compression.SGILOG) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogL data must have {0}={1}", "Compression", Compression.SGILOG); return null; } tif.SetField(TiffTag.SGILOGDATAFMT, 3); // 8-bit RGB monitor values. img.photometric = Photometric.MinIsBlack; // little white lie img.bitspersample = 8; break; case Photometric.LogLUV: if (compress != Compression.SGILOG && compress != Compression.SGILOG24) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, LogLuv data must have {0}={1} or {2}", "Compression", Compression.SGILOG, Compression.SGILOG24); return null; } if (planarconfig != PlanarConfig.Contig) { errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle LogLuv images with {0}={1}", "Planarconfiguration", planarconfig); return null; } tif.SetField(TiffTag.SGILOGDATAFMT, 3); // 8-bit RGB monitor values. img.photometric = Photometric.RGB; // little white lie img.bitspersample = 8; break; case Photometric.CIELAB: break; default: errorMsg = string.Format(CultureInfo.InvariantCulture, "Sorry, can not handle image with {0}={1}", photoTag, img.photometric); return null; } img.Map = null; img.BWmap = null; img.PALmap = null; img.ycbcr = null; img.cielab = null; result = tif.GetField(TiffTag.ImageWidth); img.width = result[0].ToInt(); result = tif.GetField(TiffTag.ImageLength); img.height = result[0].ToInt(); result = tif.GetFieldDefaulted(TiffTag.Orientation); img.orientation = (Orientation)result[0].ToByte(); img.isContig = !(planarconfig == PlanarConfig.Separate && colorchannels > 1); if (img.isContig) { if (!img.pickContigCase()) { errorMsg = "Sorry, can not handle image"; return null; } } else { if (!img.pickSeparateCase()) { errorMsg = "Sorry, can not handle image"; return null; } } return img; } /// /// Gets a value indicating whether image data has contiguous (packed) or separated samples. /// /// true if this image data has contiguous (packed) samples; otherwise, /// false. public bool IsContig { get { return isContig; } } /// /// Gets the type of alpha data present. /// /// The type of alpha data present. public ExtraSample Alpha { get { return alpha; } } /// /// Gets the image width. /// /// The image width. public int Width { get { return width; } } /// /// Gets the image height. /// /// The image height. public int Height { get { return height; } } /// /// Gets the image bits per sample count. /// /// The image bits per sample count. public short BitsPerSample { get { return bitspersample; } } /// /// Gets the image samples per pixel count. /// /// The image samples per pixel count. public short SamplesPerPixel { get { return samplesperpixel; } } /// /// Gets the image orientation. /// /// The image orientation. public Orientation Orientation { get { return orientation; } } /// /// Gets or sets the requested orientation. /// /// The requested orientation. /// The method uses this value when placing converted /// image data into raster buffer. public Orientation ReqOrientation { get { return req_orientation; } set { req_orientation = value; } } /// /// Gets the photometric interpretation of the image data. /// /// The photometric interpretation of the image data. public Photometric Photometric { get { return photometric; } } /// /// Gets or sets the "get" method (the method that is called to produce RGBA raster). /// /// The "get" method. public GetDelegate Get { get { return get; } set { get = value; } } /// /// Gets or sets the "put" method (the method that is called to pack pixel data in the /// raster) used when converting contiguously packed samples. /// /// The "put" method used when converting contiguously packed samples. public PutContigDelegate PutContig { get { return putContig; } set { putContig = value; } } /// /// Gets or sets the "put" method (the method that is called to pack pixel data in the /// raster) used when converting separated samples. /// /// The "put" method used when converting separated samples. public PutSeparateDelegate PutSeparate { get { return putSeparate; } set { putSeparate = value; } } /// /// Reads the underlaying TIFF image and decodes it into RGBA format raster. /// /// The raster (the buffer to place decoded image data to). /// The zero-based byte offset in at which /// to begin storing decoded bytes. /// The raster width. /// The raster height. /// true if the image was successfully read and decoded; otherwise, /// false. /// /// GetRaster reads image into memory using current "get" () method, /// storing the result in the user supplied RGBA using one of /// the "put" ( or ) methods. The raster /// is assumed to be an array of times /// 32-bit entries, where must be less than or equal to the width /// of the image ( may be any non-zero size). If the raster /// dimensions are smaller than the image, the image data is cropped to the raster bounds. /// If the raster height is greater than that of the image, then the image data placement /// depends on the value of property. Note that the raster is /// assumed to be organized such that the pixel at location (x, y) is /// [y * width + x]; with the raster origin specified by the /// value of property. /// /// Raster pixels are 8-bit packed red, green, blue, alpha samples. The /// , , , and /// should be used to access individual samples. Images without /// Associated Alpha matting information have a constant Alpha of 1.0 (255). /// /// GetRaster converts non-8-bit images by scaling sample values. Palette, /// grayscale, bilevel, CMYK, and YCbCr images are converted to RGB transparently. /// Raster pixels are returned uncorrected by any colorimetry information present in /// the directory. /// /// Samples must be either 1, 2, 4, 8, or 16 bits. Colorimetric samples/pixel must be /// either 1, 3, or 4 (i.e. SamplesPerPixel minus ExtraSamples). /// /// Palette image colormaps that appear to be incorrectly written as 8-bit values are /// automatically scaled to 16-bits. /// /// All error messages are directed to the current error handler. /// public bool GetRaster(int[] raster, int offset, int width, int height) { if (get == null) { Tiff.ErrorExt(tif, tif.m_clientdata, tif.FileName(), "No \"get\" method setup"); return false; } return get(this, raster, offset, width, height); } private static int PACK(int r, int g, int b) { return (r | (g << 8) | (b << 16) | A1); } private static int PACK4(int r, int g, int b, int a) { return (r | (g << 8) | (b << 16) | (a << 24)); } private static int W2B(short v) { return ((v >> 8) & 0xff); } private static int PACKW(short r, short g, short b) { return (W2B(r) | (W2B(g) << 8) | (W2B(b) << 16) | (int)A1); } private static int PACKW4(short r, short g, short b, short a) { return (W2B(r) | (W2B(g) << 8) | (W2B(b) << 16) | (W2B(a) << 24)); } /// /// Palette images with <= 8 bits/sample are handled with a table to avoid lots of shifts /// and masks. The table is setup so that put*cmaptile (below) can retrieve 8 / bitspersample /// pixel values simply by indexing into the table with one number. /// private void CMAP(int x, int i, ref int j) { PALmap[i][j++] = PACK(redcmap[x] & 0xff, greencmap[x] & 0xff, bluecmap[x] & 0xff); } /// /// Greyscale images with less than 8 bits/sample are handled with a table to avoid lots /// of shifts and masks. The table is setup so that put*bwtile (below) can retrieve /// 8 / bitspersample pixel values simply by indexing into the table with one number. /// private void GREY(int x, int i, ref int j) { int c = Map[x]; BWmap[i][j++] = PACK(c, c, c); } /// /// Get an tile-organized image that has /// PlanarConfiguration contiguous if SamplesPerPixel > 1 /// or /// SamplesPerPixel == 1 /// private static bool gtTileContig(TiffRgbaImage img, int[] raster, int offset, int width, int height) { byte[] buf = new byte[img.tif.TileSize()]; FieldValue[] result = img.tif.GetField(TiffTag.TileWidth); int tileWidth = result[0].ToInt(); result = img.tif.GetField(TiffTag.TileLength); int tileHeight = result[0].ToInt(); int flip = img.setorientation(); int y; int rasterShift; if ((flip & FLIP_VERTICALLY) != 0) { y = height - 1; rasterShift = -(tileWidth + width); } else { y = 0; rasterShift = -(tileWidth - width); } bool ret = true; for (int row = 0; row < height; ) { int rowstoread = tileHeight - (row + img.row_offset) % tileHeight; int nrow = (row + rowstoread > height ? height - row : rowstoread); for (int col = 0; col < width; col += tileWidth) { if (img.tif.ReadTile(buf, 0, col + img.col_offset, row + img.row_offset, 0, 0) < 0 && img.stoponerr) { ret = false; break; } int pos = ((row + img.row_offset) % tileHeight) * img.tif.TileRowSize(); if (col + tileWidth > width) { // Tile is clipped horizontally. Calculate visible portion and // skewing factors. int npix = width - col; int bufferShift = tileWidth - npix; img.putContig(img, raster, offset + y * width + col, rasterShift + bufferShift, col, y, npix, nrow, buf, pos, bufferShift); } else { img.putContig(img, raster, offset + y * width + col, rasterShift, col, y, tileWidth, nrow, buf, pos, 0); } } y += ((flip & FLIP_VERTICALLY) != 0 ? -nrow : nrow); row += nrow; } if ((flip & FLIP_HORIZONTALLY) != 0) { for (int line = 0; line < height; line++) { int left = offset + line * width; int right = left + width - 1; while (left < right) { int temp = raster[left]; raster[left] = raster[right]; raster[right] = temp; left++; right--; } } } return ret; } /// /// Get an tile-organized image that has /// SamplesPerPixel > 1 /// PlanarConfiguration separated /// We assume that all such images are RGB. /// private static bool gtTileSeparate(TiffRgbaImage img, int[] raster, int offset, int width, int height) { int tilesize = img.tif.TileSize(); byte[] buf = new byte[(img.alpha != 0 ? 4 : 3) * tilesize]; int p0 = 0; int p1 = p0 + tilesize; int p2 = p1 + tilesize; int pa = (img.alpha != 0 ? (p2 + tilesize) : -1); FieldValue[] result = img.tif.GetField(TiffTag.TileWidth); int tileWidth = result[0].ToInt(); result = img.tif.GetField(TiffTag.TileLength); int tileHeight = result[0].ToInt(); int flip = img.setorientation(); int y; int rasterShift; if ((flip & FLIP_VERTICALLY) != 0) { y = height - 1; rasterShift = -(tileWidth + width); } else { y = 0; rasterShift = -(tileWidth - width); } bool ret = true; for (int row = 0; row < height; ) { int rowstoread = tileHeight - (row + img.row_offset) % tileHeight; int nrow = (row + rowstoread > height ? height - row : rowstoread); for (int col = 0; col < width; col += tileWidth) { if (img.tif.ReadTile(buf, p0, col + img.col_offset, row + img.row_offset, 0, 0) < 0 && img.stoponerr) { ret = false; break; } if (img.tif.ReadTile(buf, p1, col + img.col_offset, row + img.row_offset, 0, 1) < 0 && img.stoponerr) { ret = false; break; } if (img.tif.ReadTile(buf, p2, col + img.col_offset, row + img.row_offset, 0, 2) < 0 && img.stoponerr) { ret = false; break; } if (img.alpha != 0) { if (img.tif.ReadTile(buf, pa, col + img.col_offset, row + img.row_offset, 0, 3) < 0 && img.stoponerr) { ret = false; break; } } int pos = ((row + img.row_offset) % tileHeight) * img.tif.TileRowSize(); if (col + tileWidth > width) { // Tile is clipped horizontally. // Calculate visible portion and skewing factors. int npix = width - col; int bufferShift = tileWidth - npix; img.putSeparate(img, raster, offset + y * width + col, rasterShift + bufferShift, col, y, npix, nrow, buf, p0 + pos, p1 + pos, p2 + pos, img.alpha != 0 ? (pa + pos) : -1, bufferShift); } else { img.putSeparate(img, raster, offset + y * width + col, rasterShift, col, y, tileWidth, nrow, buf, p0 + pos, p1 + pos, p2 + pos, img.alpha != 0 ? (pa + pos) : -1, 0); } } y += ((flip & FLIP_VERTICALLY) != 0 ? -nrow : nrow); row += nrow; } if ((flip & FLIP_HORIZONTALLY) != 0) { for (int line = 0; line < height; line++) { int left = offset + line * width; int right = left + width - 1; while (left < right) { int temp = raster[left]; raster[left] = raster[right]; raster[right] = temp; left++; right--; } } } return ret; } /// /// Get a strip-organized image that has /// PlanarConfiguration contiguous if SamplesPerPixel > 1 /// or /// SamplesPerPixel == 1 /// private static bool gtStripContig(TiffRgbaImage img, int[] raster, int offset, int width, int height) { byte[] buf = new byte[img.tif.StripSize()]; int flip = img.setorientation(); int y; int rasterShift; if ((flip & FLIP_VERTICALLY) != 0) { y = height - 1; rasterShift = -(width + width); } else { y = 0; rasterShift = -(width - width); } FieldValue[] result = img.tif.GetFieldDefaulted(TiffTag.RowsPerStrip); int rowsperstrip = result[0].ToInt(); if (rowsperstrip == -1) { // San Chen // HACK: should be UInt32.MaxValue rowsperstrip = Int32.MaxValue; } result = img.tif.GetFieldDefaulted(TiffTag.YCBCRSUBSAMPLING); short subsamplingver = result[1].ToShort(); int scanline = img.tif.newScanlineSize(); int bufferShift = (width < img.width ? img.width - width : 0); bool ret = true; for (int row = 0; row < height; ) { int rowstoread = rowsperstrip - (row + img.row_offset) % rowsperstrip; int nrow = (row + rowstoread > height ? height - row : rowstoread); int nrowsub = nrow; if ((nrowsub % subsamplingver) != 0) nrowsub += subsamplingver - nrowsub % subsamplingver; if (img.tif.ReadEncodedStrip(img.tif.ComputeStrip(row + img.row_offset, 0), buf, 0, ((row + img.row_offset) % rowsperstrip + nrowsub) * scanline) < 0 && img.stoponerr) { ret = false; break; } int pos = ((row + img.row_offset) % rowsperstrip) * scanline; img.putContig(img, raster, offset + y * width, rasterShift, 0, y, width, nrow, buf, pos, bufferShift); y += (flip & FLIP_VERTICALLY) != 0 ? -nrow : nrow; row += nrow; } if ((flip & FLIP_HORIZONTALLY) != 0) { for (int line = 0; line < height; line++) { int left = offset + line * width; int right = left + width - 1; while (left < right) { int temp = raster[left]; raster[left] = raster[right]; raster[right] = temp; left++; right--; } } } return ret; } /// /// Get a strip-organized image with /// SamplesPerPixel > 1 /// PlanarConfiguration separated /// We assume that all such images are RGB. /// private static bool gtStripSeparate(TiffRgbaImage img, int[] raster, int offset, int width, int height) { int stripsize = img.tif.StripSize(); byte[] buf = new byte[(img.alpha != 0 ? 4 : 3) * stripsize]; int p0 = 0; int p1 = p0 + stripsize; int p2 = p1 + stripsize; int pa = p2 + stripsize; pa = (img.alpha != 0 ? (p2 + stripsize) : -1); int flip = img.setorientation(); int y; int rasterShift; if ((flip & FLIP_VERTICALLY) != 0) { y = height - 1; rasterShift = -(width + width); } else { y = 0; rasterShift = -(width - width); } FieldValue[] result = img.tif.GetFieldDefaulted(TiffTag.RowsPerStrip); int rowsperstrip = result[0].ToInt(); int scanline = img.tif.ScanlineSize(); int bufferShift = (width < img.width ? img.width - width : 0); bool ret = true; for (int row = 0; row < height; ) { int rowstoread = rowsperstrip - (row + img.row_offset) % rowsperstrip; int nrow = (row + rowstoread > height ? height - row : rowstoread); int offset_row = row + img.row_offset; if (img.tif.ReadEncodedStrip(img.tif.ComputeStrip(offset_row, 0), buf, p0, ((row + img.row_offset) % rowsperstrip + nrow) * scanline) < 0 && img.stoponerr) { ret = false; break; } if (img.tif.ReadEncodedStrip(img.tif.ComputeStrip(offset_row, 1), buf, p1, ((row + img.row_offset) % rowsperstrip + nrow) * scanline) < 0 && img.stoponerr) { ret = false; break; } if (img.tif.ReadEncodedStrip(img.tif.ComputeStrip(offset_row, 2), buf, p2, ((row + img.row_offset) % rowsperstrip + nrow) * scanline) < 0 && img.stoponerr) { ret = false; break; } if (img.alpha != 0) { if ((img.tif.ReadEncodedStrip(img.tif.ComputeStrip(offset_row, 3), buf, pa, ((row + img.row_offset) % rowsperstrip + nrow) * scanline) < 0 && img.stoponerr)) { ret = false; break; } } int pos = ((row + img.row_offset) % rowsperstrip) * scanline; img.putSeparate(img, raster, offset + y * width, rasterShift, 0, y, width, nrow, buf, p0 + pos, p1 + pos, p2 + pos, img.alpha != 0 ? (pa + pos) : -1, bufferShift); y += (flip & FLIP_VERTICALLY) != 0 ? -nrow : nrow; row += nrow; } if ((flip & FLIP_HORIZONTALLY) != 0) { for (int line = 0; line < height; line++) { int left = offset + line * width; int right = left + width - 1; while (left < right) { int temp = raster[left]; raster[left] = raster[right]; raster[right] = temp; left++; right--; } } } return ret; } private bool isCCITTCompression() { FieldValue[] result = tif.GetField(TiffTag.Compression); Compression compress = (Compression)result[0].ToInt(); return (compress == Compression.CCITTFAX3 || compress == Compression.CCITTFAX4 || compress == Compression.CCITTRLE || compress == Compression.CCITTRLEW); } private int setorientation() { switch (orientation) { case Orientation.TopLeft: case Orientation.LeftTop: if (req_orientation == Orientation.TopRight || req_orientation == Orientation.RightTop) return FLIP_HORIZONTALLY; else if (req_orientation == Orientation.BottomRight || req_orientation == Orientation.RightBottom) return FLIP_HORIZONTALLY | FLIP_VERTICALLY; else if (req_orientation == Orientation.BottomLeft || req_orientation == Orientation.LeftBottom) return FLIP_VERTICALLY; return 0; case Orientation.TopRight: case Orientation.RightTop: if (req_orientation == Orientation.TopLeft || req_orientation == Orientation.LeftTop) return FLIP_HORIZONTALLY; else if (req_orientation == Orientation.BottomRight || req_orientation == Orientation.RightBottom) return FLIP_VERTICALLY; else if (req_orientation == Orientation.BottomLeft || req_orientation == Orientation.LeftBottom) return FLIP_HORIZONTALLY | FLIP_VERTICALLY; return 0; case Orientation.BottomRight: case Orientation.RightBottom: if (req_orientation == Orientation.TopLeft || req_orientation == Orientation.LeftTop) return FLIP_HORIZONTALLY | FLIP_VERTICALLY; else if (req_orientation == Orientation.TopRight || req_orientation == Orientation.RightTop) return FLIP_VERTICALLY; else if (req_orientation == Orientation.BottomLeft || req_orientation == Orientation.LeftBottom) return FLIP_HORIZONTALLY; return 0; case Orientation.BottomLeft: case Orientation.LeftBottom: if (req_orientation == Orientation.TopLeft || req_orientation == Orientation.LeftTop) return FLIP_VERTICALLY; else if (req_orientation == Orientation.TopRight || req_orientation == Orientation.RightTop) return FLIP_HORIZONTALLY | FLIP_VERTICALLY; else if (req_orientation == Orientation.BottomRight || req_orientation == Orientation.RightBottom) return FLIP_HORIZONTALLY; return 0; } return 0; } /// /// Select the appropriate conversion routine for packed data. /// private bool pickContigCase() { get = tif.IsTiled() ? new GetDelegate(gtTileContig) : new GetDelegate(gtStripContig); putContig = null; switch (photometric) { case Photometric.RGB: switch (bitspersample) { case 8: if (alpha == ExtraSample.AssociatedAlpha) putContig = putRGBAAcontig8bittile; else if (alpha == ExtraSample.UnAssociatedAlpha) putContig = putRGBUAcontig8bittile; else putContig = putRGBcontig8bittile; break; case 16: if (alpha == ExtraSample.AssociatedAlpha) putContig = putRGBAAcontig16bittile; else if (alpha == ExtraSample.UnAssociatedAlpha) putContig = putRGBUAcontig16bittile; else putContig = putRGBcontig16bittile; break; } break; case Photometric.Separated: if (buildMap()) { if (bitspersample == 8) { if (Map == null) putContig = putRGBcontig8bitCMYKtile; else putContig = putRGBcontig8bitCMYKMaptile; } } break; case Photometric.Palette: if (buildMap()) { switch (bitspersample) { case 8: putContig = put8bitcmaptile; break; case 4: putContig = put4bitcmaptile; break; case 2: putContig = put2bitcmaptile; break; case 1: putContig = put1bitcmaptile; break; } } break; case Photometric.MinIsWhite: case Photometric.MinIsBlack: if (buildMap()) { switch (bitspersample) { case 16: putContig = put16bitbwtile; break; case 8: putContig = putgreytile; break; case 4: putContig = put4bitbwtile; break; case 2: putContig = put2bitbwtile; break; case 1: putContig = put1bitbwtile; break; } } break; case Photometric.YCBCR: if (bitspersample == 8) { if (initYCbCrConversion()) { // The 6.0 spec says that subsampling must be one of 1, 2, or 4, and // that vertical subsampling must always be <= horizontal subsampling; // so there are only a few possibilities and we just enumerate the cases. // Joris: added support for the [1, 2] case, nonetheless, to accommodate // some OJPEG files FieldValue[] result = tif.GetFieldDefaulted(TiffTag.YCBCRSUBSAMPLING); short SubsamplingHor = result[0].ToShort(); short SubsamplingVer = result[1].ToShort(); switch (((ushort)SubsamplingHor << 4) | (ushort)SubsamplingVer) { case 0x44: putContig = putcontig8bitYCbCr44tile; break; case 0x42: putContig = putcontig8bitYCbCr42tile; break; case 0x41: putContig = putcontig8bitYCbCr41tile; break; case 0x22: putContig = putcontig8bitYCbCr22tile; break; case 0x21: putContig = putcontig8bitYCbCr21tile; break; case 0x12: putContig = putcontig8bitYCbCr12tile; break; case 0x11: putContig = putcontig8bitYCbCr11tile; break; } } } break; case Photometric.CIELAB: if (buildMap()) { if (bitspersample == 8) putContig = initCIELabConversion(); } break; } return (putContig != null); } /// /// Select the appropriate conversion routine for unpacked data. /// NB: we assume that unpacked single channel data is directed to the "packed routines. /// private bool pickSeparateCase() { get = tif.IsTiled() ? new GetDelegate(gtTileSeparate) : new GetDelegate(gtStripSeparate); putSeparate = null; switch (photometric) { case Photometric.RGB: switch (bitspersample) { case 8: if (alpha == ExtraSample.AssociatedAlpha) putSeparate = putRGBAAseparate8bittile; else if (alpha == ExtraSample.UnAssociatedAlpha) putSeparate = putRGBUAseparate8bittile; else putSeparate = putRGBseparate8bittile; break; case 16: if (alpha == ExtraSample.AssociatedAlpha) putSeparate = putRGBAAseparate16bittile; else if (alpha == ExtraSample.UnAssociatedAlpha) putSeparate = putRGBUAseparate16bittile; else putSeparate = putRGBseparate16bittile; break; } break; case Photometric.YCBCR: if ((bitspersample == 8) && (samplesperpixel == 3)) { if (initYCbCrConversion()) { FieldValue[] result = tif.GetFieldDefaulted(TiffTag.YCBCRSUBSAMPLING); short hs = result[0].ToShort(); short vs = result[0].ToShort(); switch (((ushort)hs << 4) | (ushort)vs) { case 0x11: putSeparate = putseparate8bitYCbCr11tile; break; // TODO: add other cases here } } } break; } return (putSeparate != null); } private bool initYCbCrConversion() { if (ycbcr == null) ycbcr = new TiffYCbCrToRGB(); FieldValue[] result = tif.GetFieldDefaulted(TiffTag.YCBCRCOEFFICIENTS); float[] luma = result[0].ToFloatArray(); result = tif.GetFieldDefaulted(TiffTag.REFERENCEBLACKWHITE); float[] refBlackWhite = result[0].ToFloatArray(); ycbcr.Init(luma, refBlackWhite); return true; } private PutContigDelegate initCIELabConversion() { if (cielab == null) cielab = new TiffCIELabToRGB(); FieldValue[] result = tif.GetFieldDefaulted(TiffTag.WhitePoint); float[] whitePoint = result[0].ToFloatArray(); float[] refWhite = new float[3]; refWhite[1] = 100.0F; refWhite[0] = whitePoint[0] / whitePoint[1] * refWhite[1]; refWhite[2] = (1.0F - whitePoint[0] - whitePoint[1]) / whitePoint[1] * refWhite[1]; cielab.Init(display_sRGB, refWhite); return putcontig8bitCIELab; } /// /// Construct any mapping table used by the associated put method. /// private bool buildMap() { switch (photometric) { case Photometric.RGB: case Photometric.YCBCR: case Photometric.Separated: if (bitspersample == 8) break; if (!setupMap()) return false; break; case Photometric.MinIsBlack: case Photometric.MinIsWhite: if (!setupMap()) return false; break; case Photometric.Palette: // Convert 16-bit colormap to 8-bit // (unless it looks like an old-style 8-bit colormap). if (checkcmap() == 16) cvtcmap(); else Tiff.WarningExt(tif, tif.m_clientdata, tif.FileName(), "Assuming 8-bit colormap"); // Use mapping table and colormap to construct unpacking // tables for samples < 8 bits. if (bitspersample <= 8 && !makecmap()) return false; break; } return true; } /// /// Construct a mapping table to convert from the range of the data samples to [0, 255] - /// for display. This process also handles inverting B&W images when needed. /// private bool setupMap() { int range = (1 << bitspersample) - 1; // treat 16 bit the same as eight bit if (bitspersample == 16) range = 255; Map = new byte[range + 1]; if (photometric == Photometric.MinIsWhite) { for (int x = 0; x <= range; x++) Map[x] = (byte)(((range - x) * 255) / range); } else { for (int x = 0; x <= range; x++) Map[x] = (byte)((x * 255) / range); } if (bitspersample <= 16 && (photometric == Photometric.MinIsBlack || photometric == Photometric.MinIsWhite)) { // Use photometric mapping table to construct unpacking tables for samples <= 8 bits. if (!makebwmap()) return false; // no longer need Map Map = null; } return true; } private int checkcmap() { int r = 0; int g = 0; int b = 0; int n = 1 << bitspersample; while (n-- > 0) { if (redcmap[r] >= 256 || greencmap[g] >= 256 || bluecmap[b] >= 256) return 16; r++; g++; b++; } return 8; } private void cvtcmap() { for (int i = (1 << bitspersample) - 1; i >= 0; i--) { redcmap[i] = (short)(redcmap[i] >> 8); greencmap[i] = (short)(greencmap[i] >> 8); bluecmap[i] = (short)(bluecmap[i] >> 8); } } private bool makecmap() { int nsamples = 8 / bitspersample; PALmap = new int[256][]; for (int i = 0; i < 256; i++) PALmap[i] = new int[nsamples]; for (int i = 0; i < 256; i++) { int j = 0; switch (bitspersample) { case 1: CMAP(i >> 7, i, ref j); CMAP((i >> 6) & 1, i, ref j); CMAP((i >> 5) & 1, i, ref j); CMAP((i >> 4) & 1, i, ref j); CMAP((i >> 3) & 1, i, ref j); CMAP((i >> 2) & 1, i, ref j); CMAP((i >> 1) & 1, i, ref j); CMAP(i & 1, i, ref j); break; case 2: CMAP(i >> 6, i, ref j); CMAP((i >> 4) & 3, i, ref j); CMAP((i >> 2) & 3, i, ref j); CMAP(i & 3, i, ref j); break; case 4: CMAP(i >> 4, i, ref j); CMAP(i & 0xf, i, ref j); break; case 8: CMAP(i, i, ref j); break; } } return true; } private bool makebwmap() { int nsamples = 8 / bitspersample; if (nsamples == 0) nsamples = 1; BWmap = new int[256][]; for (int i = 0; i < 256; i++) BWmap[i] = new int[nsamples]; for (int i = 0; i < 256; i++) { int j = 0; switch (bitspersample) { case 1: GREY(i >> 7, i, ref j); GREY((i >> 6) & 1, i, ref j); GREY((i >> 5) & 1, i, ref j); GREY((i >> 4) & 1, i, ref j); GREY((i >> 3) & 1, i, ref j); GREY((i >> 2) & 1, i, ref j); GREY((i >> 1) & 1, i, ref j); GREY(i & 1, i, ref j); break; case 2: GREY(i >> 6, i, ref j); GREY((i >> 4) & 3, i, ref j); GREY((i >> 2) & 3, i, ref j); GREY(i & 3, i, ref j); break; case 4: GREY(i >> 4, i, ref j); GREY(i & 0xf, i, ref j); break; case 8: case 16: GREY(i, i, ref j); break; } } return true; } /// /// YCbCr -> RGB conversion and packing routines. /// private void YCbCrtoRGB(out int dst, int Y, int Cb, int Cr) { int r, g, b; ycbcr.YCbCrtoRGB(Y, Cb, Cr, out r, out g, out b); dst = PACK(r, g, b); } /////////////////////////////////////////////////////////////////////////////////////////// // The following routines move decoded data returned from the TIFF library into rasters // filled with packed ABGR pixels // // The routines have been created according to the most important cases and optimized. // pickTileContigCase and pickTileSeparateCase analyze the parameters and select the // appropriate "put" routine to use. /////////////////////////////////////////////////////////////////////////////////////////// // Contiguous cases // /// /// 8-bit palette => colormap/RGB /// private static void put8bitcmaptile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] PALmap = img.PALmap; int samplesperpixel = img.samplesperpixel; while (height-- > 0) { for (x = width; x-- > 0; ) { raster[rasterOffset] = PALmap[buffer[offset]][0]; rasterOffset++; offset += samplesperpixel; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 4-bit palette => colormap/RGB /// private static void put4bitcmaptile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] PALmap = img.PALmap; bufferShift /= 2; while (height-- > 0) { int[] bw = null; int _x; for (_x = width; _x >= 2; _x -= 2) { bw = PALmap[buffer[offset]]; offset++; for (int rc = 0; rc < 2; rc++) { raster[rasterOffset] = bw[rc]; rasterOffset++; } } if (_x != 0) { bw = PALmap[buffer[offset]]; offset++; raster[rasterOffset] = bw[0]; rasterOffset++; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 2-bit palette => colormap/RGB /// private static void put2bitcmaptile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] PALmap = img.PALmap; bufferShift /= 4; while (height-- > 0) { int[] bw = null; int _x; for (_x = width; _x >= 4; _x -= 4) { bw = PALmap[buffer[offset]]; offset++; for (int rc = 0; rc < 4; rc++) { raster[rasterOffset] = bw[rc]; rasterOffset++; } } if (_x > 0) { bw = PALmap[buffer[offset]]; offset++; if (_x <= 3 && _x > 0) { for (int i = 0; i < _x; i++) { raster[rasterOffset] = bw[i]; rasterOffset++; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 1-bit palette => colormap/RGB /// private static void put1bitcmaptile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] PALmap = img.PALmap; bufferShift /= 8; while (height-- > 0) { int[] bw; int bwPos = 0; int _x; for (_x = width; _x >= 8; _x -= 8) { bw = PALmap[buffer[offset++]]; bwPos = 0; for (int i = 0; i < 8; i++) raster[rasterOffset++] = bw[bwPos++]; } if (_x > 0) { bw = PALmap[buffer[offset++]]; bwPos = 0; if (_x <= 7 && _x > 0) { for (int i = 0; i < _x; i++) raster[rasterOffset++] = bw[bwPos++]; } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit greyscale => colormap/RGB /// private static void putgreytile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; int[][] BWmap = img.BWmap; while (height-- > 0) { for (x = width; x-- > 0; ) { raster[rasterOffset] = BWmap[buffer[offset]][0]; rasterOffset++; offset += samplesperpixel; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 16-bit greyscale => colormap/RGB /// private static void put16bitbwtile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; int[][] BWmap = img.BWmap; while (height-- > 0) { short[] wp = Tiff.ByteArrayToShorts(buffer, offset, buffer.Length - offset); int wpPos = 0; for (x = width; x-- > 0; ) { // use high order byte of 16bit value raster[rasterOffset] = BWmap[(wp[wpPos] & 0xffff) >> 8][0]; rasterOffset++; offset += 2 * samplesperpixel; wpPos += samplesperpixel; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 1-bit bilevel => colormap/RGB /// private static void put1bitbwtile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] BWmap = img.BWmap; bufferShift /= 8; while (height-- > 0) { int[] bw = null; int _x; for (_x = width; _x >= 8; _x -= 8) { bw = BWmap[buffer[offset]]; offset++; for (int rc = 0; rc < 8; rc++) { raster[rasterOffset] = bw[rc]; rasterOffset++; } } if (_x > 0) { bw = BWmap[buffer[offset]]; offset++; if (_x <= 7 && _x > 0) { for (int i = 0; i < _x; i++) { raster[rasterOffset] = bw[i]; rasterOffset++; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 2-bit greyscale => colormap/RGB /// private static void put2bitbwtile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] BWmap = img.BWmap; bufferShift /= 4; while (height-- > 0) { int[] bw = null; int _x; for (_x = width; _x >= 4; _x -= 4) { bw = BWmap[buffer[offset]]; offset++; for (int rc = 0; rc < 4; rc++) { raster[rasterOffset] = bw[rc]; rasterOffset++; } } if (_x > 0) { bw = BWmap[buffer[offset]]; offset++; if (_x <= 3 && _x > 0) { for (int i = 0; i < _x; i++) { raster[rasterOffset] = bw[i]; rasterOffset++; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 4-bit greyscale => colormap/RGB /// private static void put4bitbwtile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int[][] BWmap = img.BWmap; bufferShift /= 2; while (height-- > 0) { int[] bw = null; int _x; for (_x = width; _x >= 2; _x -= 2) { bw = BWmap[buffer[offset]]; offset++; for (int rc = 0; rc < 2; rc++) { raster[rasterOffset] = bw[rc]; rasterOffset++; } } if (_x != 0) { bw = BWmap[buffer[offset]]; offset++; raster[rasterOffset] = bw[0]; rasterOffset++; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit packed samples, no Map => RGB /// private static void putRGBcontig8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; while (height-- > 0) { int _x; for (_x = width; _x >= 8; _x -= 8) { for (int rc = 0; rc < 8; rc++) { raster[rasterOffset] = PACK(buffer[offset], buffer[offset + 1], buffer[offset + 2]); rasterOffset++; offset += samplesperpixel; } } if (_x > 0) { if (_x <= 7 && _x > 0) { for (int i = _x; i > 0; i--) { raster[rasterOffset] = PACK(buffer[offset], buffer[offset + 1], buffer[offset + 2]); rasterOffset++; offset += samplesperpixel; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit packed samples => RGBA w/ associated alpha (known to have Map == null) /// private static void putRGBAAcontig8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; while (height-- > 0) { int _x; for (_x = width; _x >= 8; _x -= 8) { for (int rc = 0; rc < 8; rc++) { raster[rasterOffset] = PACK4(buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]); rasterOffset++; offset += samplesperpixel; } } if (_x > 0) { if (_x <= 7 && _x > 0) { for (int i = _x; i > 0; i--) { raster[rasterOffset] = PACK4(buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]); rasterOffset++; offset += samplesperpixel; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit packed samples => RGBA w/ unassociated alpha (known to have Map == null) /// private static void putRGBUAcontig8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; while (height-- > 0) { for (x = width; x-- > 0; ) { int a = buffer[offset + 3]; int r = (buffer[offset] * a + 127) / 255; int g = (buffer[offset + 1] * a + 127) / 255; int b = (buffer[offset + 2] * a + 127) / 255; raster[rasterOffset] = PACK4(r, g, b, a); rasterOffset++; offset += samplesperpixel; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 16-bit packed samples => RGB /// private static void putRGBcontig16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; short[] wp = Tiff.ByteArrayToShorts(buffer, offset, buffer.Length); int wpPos = 0; while (height-- > 0) { for (x = width; x-- > 0; ) { raster[rasterOffset] = PACKW(wp[wpPos], wp[wpPos + 1], wp[wpPos + 2]); rasterOffset++; wpPos += samplesperpixel; } rasterOffset += rasterShift; wpPos += bufferShift; } } /// /// 16-bit packed samples => RGBA w/ associated alpha (known to have Map == null) /// private static void putRGBAAcontig16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; short[] wp = Tiff.ByteArrayToShorts(buffer, offset, buffer.Length); int wpPos = 0; bufferShift *= samplesperpixel; while (height-- > 0) { for (x = width; x-- > 0; ) { raster[rasterOffset] = PACKW4(wp[wpPos], wp[wpPos + 1], wp[wpPos + 2], wp[wpPos + 3]); rasterOffset++; wpPos += samplesperpixel; } rasterOffset += rasterShift; wpPos += bufferShift; } } /// /// 16-bit packed samples => RGBA w/ unassociated alpha (known to have Map == null) /// private static void putRGBUAcontig16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; short[] wp = Tiff.ByteArrayToShorts(buffer, offset, buffer.Length); int wpPos = 0; while (height-- > 0) { for (x = width; x-- > 0; ) { int a = W2B(wp[wpPos + 3]); int r = (W2B(wp[wpPos]) * a + 127) / 255; int g = (W2B(wp[wpPos + 1]) * a + 127) / 255; int b = (W2B(wp[wpPos + 2]) * a + 127) / 255; raster[rasterOffset] = PACK4(r, g, b, a); rasterOffset++; wpPos += samplesperpixel; } rasterOffset += rasterShift; wpPos += bufferShift; } } /// /// 8-bit packed CMYK samples w/o Map => RGB. /// NB: The conversion of CMYK->RGB is *very* crude. /// private static void putRGBcontig8bitCMYKtile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; bufferShift *= samplesperpixel; while (height-- > 0) { int _x; for (_x = width; _x >= 8; _x -= 8) { for (int rc = 0; rc < 8; rc++) { short k = (short)(255 - buffer[offset + 3]); short r = (short)((k * (255 - buffer[offset])) / 255); short g = (short)((k * (255 - buffer[offset + 1])) / 255); short b = (short)((k * (255 - buffer[offset + 2])) / 255); raster[rasterOffset] = PACK(r, g, b); rasterOffset++; offset += samplesperpixel; } } if (_x > 0) { if (_x <= 7 && _x > 0) { for (int i = _x; i > 0; i--) { short k = (short)(255 - buffer[offset + 3]); short r = (short)((k * (255 - buffer[offset])) / 255); short g = (short)((k * (255 - buffer[offset + 1])) / 255); short b = (short)((k * (255 - buffer[offset + 2])) / 255); raster[rasterOffset] = PACK(r, g, b); rasterOffset++; offset += samplesperpixel; } } } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit packed CIE L*a*b 1976 samples => RGB /// private static void putcontig8bitCIELab( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { bufferShift *= 3; while (height-- > 0) { for (x = width; x-- > 0; ) { float X, Y, Z; img.cielab.CIELabToXYZ(buffer[offset], (sbyte)buffer[offset + 1], (sbyte)buffer[offset + 2], out X, out Y, out Z); int r, g, b; img.cielab.XYZToRGB(X, Y, Z, out r, out g, out b); raster[rasterOffset] = PACK(r, g, b); rasterOffset++; offset += 3; } rasterOffset += rasterShift; offset += bufferShift; } } /// /// 8-bit packed YCbCr samples w/ 2,2 subsampling => RGB /// private static void putcontig8bitYCbCr22tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { bufferShift = (bufferShift / 2) * 6; int rasterOffset2 = rasterOffset + width + rasterShift; while (height >= 2) { x = width; while (x >= 2) { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 2], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 1], buffer[offset + 3], Cb, Cr); rasterOffset += 2; rasterOffset2 += 2; offset += 6; x -= 2; } if (x == 1) { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 2], Cb, Cr); rasterOffset++; rasterOffset2++; offset += 6; } rasterOffset += rasterShift * 2 + width; rasterOffset2 += rasterShift * 2 + width; offset += bufferShift; height -= 2; } if (height == 1) { x = width; while (x >= 2) { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); rasterOffset += 2; rasterOffset2 += 2; offset += 6; x -= 2; } if (x == 1) { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); } } } /// /// 8-bit packed YCbCr samples w/ 2,1 subsampling => RGB /// private static void putcontig8bitYCbCr21tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { bufferShift = (bufferShift * 4) / 2; do { x = width >> 1; do { int Cb = buffer[offset + 2]; int Cr = buffer[offset + 3]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); rasterOffset += 2; offset += 4; } while (--x != 0); if ((width & 1) != 0) { int Cb = buffer[offset + 2]; int Cr = buffer[offset + 3]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); rasterOffset += 1; offset += 4; } rasterOffset += rasterShift; offset += bufferShift; } while (--height != 0); } /// /// 8-bit packed YCbCr samples w/ 4,4 subsampling => RGB /// private static void putcontig8bitYCbCr44tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int rasterOffset1 = rasterOffset + width + rasterShift; int rasterOffset2 = rasterOffset1 + width + rasterShift; int rasterOffset3 = rasterOffset2 + width + rasterShift; int incr = 3 * width + 4 * rasterShift; // adjust bufferShift bufferShift = (bufferShift * 18) / 4; if ((height & 3) == 0 && (width & 3) == 0) { for (; height >= 4; height -= 4) { x = width >> 2; do { int Cb = buffer[offset + 16]; int Cr = buffer[offset + 17]; img.YCbCrtoRGB(out raster[rasterOffset], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 3], buffer[offset + 3], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset1 + 0], buffer[offset + 4], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset1 + 1], buffer[offset + 5], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset1 + 2], buffer[offset + 6], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset1 + 3], buffer[offset + 7], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 8], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 1], buffer[offset + 9], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 2], buffer[offset + 10], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 3], buffer[offset + 11], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset3 + 0], buffer[offset + 12], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset3 + 1], buffer[offset + 13], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset3 + 2], buffer[offset + 14], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset3 + 3], buffer[offset + 15], Cb, Cr); rasterOffset += 4; rasterOffset1 += 4; rasterOffset2 += 4; rasterOffset3 += 4; offset += 18; } while (--x != 0); rasterOffset += incr; rasterOffset1 += incr; rasterOffset2 += incr; rasterOffset3 += incr; offset += bufferShift; } } else { while (height > 0) { for (x = width; x > 0; ) { int Cb = buffer[offset + 16]; int Cr = buffer[offset + 17]; bool h_goOn = false; bool x_goOn = false; // order of if's is important if (x < 1 || x > 3) { // order of if's is important h_goOn = false; if (height < 1 || height > 3) { img.YCbCrtoRGB(out raster[rasterOffset3 + 3], buffer[offset + 15], Cb, Cr); h_goOn = true; } if (height == 3 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset2 + 3], buffer[offset + 11], Cb, Cr); h_goOn = true; } if (height == 2 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset1 + 3], buffer[offset + 7], Cb, Cr); h_goOn = true; } if (height == 1 || h_goOn) img.YCbCrtoRGB(out raster[rasterOffset + 3], buffer[offset + 3], Cb, Cr); x_goOn = true; } if (x == 3 || x_goOn) { // order of if's is important h_goOn = false; if (height < 1 || height > 3) { img.YCbCrtoRGB(out raster[rasterOffset3 + 2], buffer[offset + 14], Cb, Cr); h_goOn = true; } if (height == 3 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset2 + 2], buffer[offset + 10], Cb, Cr); h_goOn = true; } if (height == 2 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset1 + 2], buffer[offset + 6], Cb, Cr); h_goOn = true; } if (height == 1 || h_goOn) img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); x_goOn = true; } if (x == 2 || x_goOn) { // order of if's is important h_goOn = false; if (height < 1 || height > 3) { img.YCbCrtoRGB(out raster[rasterOffset3 + 1], buffer[offset + 13], Cb, Cr); h_goOn = true; } if (height == 3 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset2 + 1], buffer[offset + 9], Cb, Cr); h_goOn = true; } if (height == 2 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset1 + 1], buffer[offset + 5], Cb, Cr); h_goOn = true; } if (height == 1 || h_goOn) img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); } if (x == 1 || x_goOn) { // order of if's is important h_goOn = false; if (height < 1 || height > 3) { img.YCbCrtoRGB(out raster[rasterOffset3 + 0], buffer[offset + 12], Cb, Cr); h_goOn = true; } if (height == 3 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 8], Cb, Cr); h_goOn = true; } if (height == 2 || h_goOn) { img.YCbCrtoRGB(out raster[rasterOffset1 + 0], buffer[offset + 4], Cb, Cr); h_goOn = true; } if (height == 1 || h_goOn) img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); } if (x < 4) { rasterOffset += x; rasterOffset1 += x; rasterOffset2 += x; rasterOffset3 += x; x = 0; } else { rasterOffset += 4; rasterOffset1 += 4; rasterOffset2 += 4; rasterOffset3 += 4; x -= 4; } offset += 18; } if (height <= 4) break; height -= 4; rasterOffset += incr; rasterOffset1 += incr; rasterOffset2 += incr; rasterOffset3 += incr; offset += bufferShift; } } } /// /// 8-bit packed YCbCr samples w/ 4,2 subsampling => RGB /// private static void putcontig8bitYCbCr42tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int rasterOffset2 = rasterOffset + width + rasterShift; int incr = 2 * rasterShift + width; bufferShift = (bufferShift * 10) / 4; if ((height & 3) == 0 && (width & 1) == 0) { for (; height >= 2; height -= 2) { x = width >> 2; do { int Cb = buffer[offset + 8]; int Cr = buffer[offset + 9]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 3], buffer[offset + 3], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 4], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 1], buffer[offset + 5], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 2], buffer[offset + 6], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 3], buffer[offset + 7], Cb, Cr); rasterOffset += 4; rasterOffset2 += 4; offset += 10; } while (--x != 0); rasterOffset += incr; rasterOffset2 += incr; offset += bufferShift; } } else { while (height > 0) { for (x = width; x > 0; ) { int Cb = buffer[offset + 8]; int Cr = buffer[offset + 9]; bool x_goOn = false; if (x < 1 || x > 3) { if (height != 1) img.YCbCrtoRGB(out raster[rasterOffset2 + 3], buffer[offset + 7], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 3], buffer[offset + 3], Cb, Cr); x_goOn = true; } if (x == 3 || x_goOn) { if (height != 1) img.YCbCrtoRGB(out raster[rasterOffset2 + 2], buffer[offset + 6], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); x_goOn = true; } if (x == 2 || x_goOn) { if (height != 1) img.YCbCrtoRGB(out raster[rasterOffset2 + 1], buffer[offset + 5], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); x_goOn = true; } if (x == 1 || x_goOn) { if (height != 1) img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 4], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); } if (x < 4) { rasterOffset += x; rasterOffset2 += x; x = 0; } else { rasterOffset += 4; rasterOffset2 += 4; x -= 4; } offset += 10; } if (height <= 2) break; height -= 2; rasterOffset += incr; rasterOffset2 += incr; offset += bufferShift; } } } /// /// 8-bit packed YCbCr samples w/ 4,1 subsampling => RGB /// private static void putcontig8bitYCbCr41tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { // XXX adjust bufferShift do { x = width >> 2; do { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset + 3], buffer[offset + 3], Cb, Cr); rasterOffset += 4; offset += 6; } while (--x != 0); if ((width & 3) != 0) { int Cb = buffer[offset + 4]; int Cr = buffer[offset + 5]; int xx = width & 3; if (xx == 3) img.YCbCrtoRGB(out raster[rasterOffset + 2], buffer[offset + 2], Cb, Cr); if (xx == 3 || xx == 2) img.YCbCrtoRGB(out raster[rasterOffset + 1], buffer[offset + 1], Cb, Cr); if (xx == 3 || xx == 2 || xx == 1) img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); rasterOffset += xx; offset += 6; } rasterOffset += rasterShift; offset += bufferShift; } while (--height != 0); } /// /// 8-bit packed YCbCr samples w/ no subsampling => RGB /// private static void putcontig8bitYCbCr11tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { bufferShift *= 3; do { x = width; // was x = w >> 1; patched 2000/09/25 warmerda@home.com do { int Cb = buffer[offset + 1]; int Cr = buffer[offset + 2]; img.YCbCrtoRGB(out raster[rasterOffset], buffer[offset + 0], Cb, Cr); rasterOffset++; offset += 3; } while (--x != 0); rasterOffset += rasterShift; offset += bufferShift; } while (--height != 0); } /// /// 8-bit packed YCbCr samples w/ 1,2 subsampling => RGB /// private static void putcontig8bitYCbCr12tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { bufferShift = (bufferShift / 2) * 4; int rasterOffset2 = rasterOffset + width + rasterShift; while (height >= 2) { x = width; do { int Cb = buffer[offset + 2]; int Cr = buffer[offset + 3]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); img.YCbCrtoRGB(out raster[rasterOffset2 + 0], buffer[offset + 1], Cb, Cr); rasterOffset++; rasterOffset2++; offset += 4; } while (--x != 0); rasterOffset += rasterShift * 2 + width; rasterOffset2 += rasterShift * 2 + width; offset += bufferShift; height -= 2; } if (height == 1) { x = width; do { int Cb = buffer[offset + 2]; int Cr = buffer[offset + 3]; img.YCbCrtoRGB(out raster[rasterOffset + 0], buffer[offset + 0], Cb, Cr); rasterOffset++; offset += 4; } while (--x != 0); } } /////////////////////////////////////////////////////////////////////////////////////////// // Separated cases // /// /// 8-bit unpacked samples => RGB /// private static void putRGBseparate8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { while (height-- > 0) { int _x; for (_x = width; _x >= 8; _x -= 8) { for (int rc = 0; rc < 8; rc++) { raster[rasterOffset] = PACK(buffer[offset1], buffer[offset2], buffer[offset3]); rasterOffset++; offset1++; offset2++; offset3++; } } if (_x > 0) { if (_x <= 7 && _x > 0) { for (int i = _x; i > 0; i--) { raster[rasterOffset] = PACK(buffer[offset1], buffer[offset2], buffer[offset3]); rasterOffset++; offset1++; offset2++; offset3++; } } } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; rasterOffset += rasterShift; } } /// /// 8-bit unpacked samples => RGBA w/ associated alpha /// private static void putRGBAAseparate8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { while (height-- > 0) { int _x; for (_x = width; _x >= 8; _x -= 8) { for (int rc = 0; rc < 8; rc++) { raster[rasterOffset] = PACK4(buffer[offset1], buffer[offset2], buffer[offset3], buffer[offset4]); rasterOffset++; offset1++; offset2++; offset3++; offset4++; } } if (_x > 0) { if (_x <= 7 && _x > 0) { for (int i = _x; i > 0; i--) { raster[rasterOffset] = PACK4(buffer[offset1], buffer[offset2], buffer[offset3], buffer[offset4]); rasterOffset++; offset1++; offset2++; offset3++; offset4++; } } } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; offset4 += bufferShift; rasterOffset += rasterShift; } } /// /// 8-bit unpacked samples => RGBA w/ unassociated alpha /// private static void putRGBUAseparate8bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { while (height-- > 0) { for (x = width; x-- > 0; ) { int av = buffer[offset4]; int rv = (buffer[offset1] * av + 127) / 255; int gv = (buffer[offset2] * av + 127) / 255; int bv = (buffer[offset3] * av + 127) / 255; raster[rasterOffset] = PACK4(rv, gv, bv, av); rasterOffset++; offset1++; offset2++; offset3++; offset4++; } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; offset4 += bufferShift; rasterOffset += rasterShift; } } /// /// 16-bit unpacked samples => RGB /// private static void putRGBseparate16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { short[] wrgba = Tiff.ByteArrayToShorts(buffer, 0, buffer.Length); offset1 /= sizeof(short); offset2 /= sizeof(short); offset3 /= sizeof(short); while (height-- > 0) { for (x = 0; x < width; x++) { raster[rasterOffset] = PACKW(wrgba[offset1], wrgba[offset2], wrgba[offset3]); rasterOffset++; offset1++; offset2++; offset3++; } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; rasterOffset += rasterShift; } } /// /// 16-bit unpacked samples => RGBA w/ associated alpha /// private static void putRGBAAseparate16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { short[] wrgba = Tiff.ByteArrayToShorts(buffer, 0, buffer.Length); offset1 /= sizeof(short); offset2 /= sizeof(short); offset3 /= sizeof(short); offset4 /= sizeof(short); while (height-- > 0) { for (x = 0; x < width; x++) { raster[rasterOffset] = PACKW4(wrgba[offset1], wrgba[offset2], wrgba[offset3], wrgba[offset4]); rasterOffset++; offset1++; offset2++; offset3++; offset4++; } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; offset4 += bufferShift; rasterOffset += rasterShift; } } /// /// 16-bit unpacked samples => RGBA w/ unassociated alpha /// private static void putRGBUAseparate16bittile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { short[] wrgba = Tiff.ByteArrayToShorts(buffer, 0, buffer.Length); offset1 /= sizeof(short); offset2 /= sizeof(short); offset3 /= sizeof(short); offset4 /= sizeof(short); while (height-- > 0) { for (x = width; x-- > 0; ) { int a = W2B(wrgba[offset4]); int r = (W2B(wrgba[offset1]) * a + 127) / 255; int g = (W2B(wrgba[offset2]) * a + 127) / 255; int b = (W2B(wrgba[offset3]) * a + 127) / 255; raster[rasterOffset] = PACK4(r, g, b, a); rasterOffset++; offset1++; offset2++; offset3++; offset4++; } offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; offset4 += bufferShift; rasterOffset += rasterShift; } } /// /// 8-bit packed YCbCr samples w/ no subsampling => RGB /// private static void putseparate8bitYCbCr11tile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset1, int offset2, int offset3, int offset4, int bufferShift) { while (height-- > 0) { x = width; do { int r, g, b; img.ycbcr.YCbCrtoRGB(buffer[offset1], buffer[offset2], buffer[offset3], out r, out g, out b); raster[rasterOffset] = PACK(r, g, b); rasterOffset++; offset1++; offset2++; offset3++; } while (--x != 0); offset1 += bufferShift; offset2 += bufferShift; offset3 += bufferShift; rasterOffset += rasterShift; } } /////////////////////////////////////////////////////////////////////////////////////////// // Untested methods // // This methods are untested (and, probably, should be deleted). // // pickContigCase implicitly *excludes* putRGBcontig8bitCMYKMaptile from possible "put" // methods when it requires images to have Photometric.Separated *and* 8-bit // samples *and* a Map to use putRGBcontig8bitCMYKMaptile. The problem is: // no Map is ever built for Photometric.Separated *and* 8-bit samples. /// /// 8-bit packed CMYK samples w/Map => RGB /// NB: The conversion of CMYK->RGB is *very* crude. /// private static void putRGBcontig8bitCMYKMaptile( TiffRgbaImage img, int[] raster, int rasterOffset, int rasterShift, int x, int y, int width, int height, byte[] buffer, int offset, int bufferShift) { int samplesperpixel = img.samplesperpixel; byte[] Map = img.Map; bufferShift *= samplesperpixel; while (height-- > 0) { for (x = width; x-- > 0; ) { short k = (short)(255 - buffer[offset + 3]); short r = (short)((k * (255 - buffer[offset])) / 255); short g = (short)((k * (255 - buffer[offset + 1])) / 255); short b = (short)((k * (255 - buffer[offset + 2])) / 255); raster[rasterOffset] = PACK(Map[r], Map[g], Map[b]); rasterOffset++; offset += samplesperpixel; } offset += bufferShift; rasterOffset += rasterShift; } } } #endregion #region Enums #region BitOrder /// /// Data order within a byte.
/// Possible values for .FillOrder tag. ///
public enum BitOrder { /// /// Most significant -> least. /// BigEndian = 1, /// /// Least significant -> most. /// LittleEndian = 2, } #endregion #region CleanFaxData /// /// Regenerated line info.
/// Possible values for .CleanFaxData tag. ///
public enum CleanFaxData { /// /// No errors detected. /// Clean = 0, /// /// Receiver regenerated lines. /// Regenerated = 1, /// /// Uncorrected errors exist. /// UnClean = 2, } #endregion #region CurveAccuracy /// /// Color curve accuracy.
/// Possible values for .ColorResponseUnit tag. ///
public enum CurveAccuracy { /// /// Tenths of a unit. /// Tenths = 1, /// /// Hundredths of a unit. /// Hundredths = 2, /// /// Thousandths of a unit. /// Thousandths = 3, /// /// Ten-thousandths of a unit. /// TenThousandths = 4, /// /// Hundred-thousandths. /// HundredThousandths = 5, } #endregion #region Compression /// /// Compression scheme.
/// Possible values for .Compression tag. ///
public enum Compression { /// /// Dump mode. /// None = 1, /// /// CCITT modified Huffman RLE. /// CCITTRLE = 2, /// /// CCITT Group 3 fax encoding. /// CCITTFAX3 = 3, /// /// CCITT T.4 (TIFF 6 name for CCITT Group 3 fax encoding). /// CCITT_T4 = 3, /// /// CCITT Group 4 fax encoding. /// CCITTFAX4 = 4, /// /// CCITT T.6 (TIFF 6 name for CCITT Group 4 fax encoding). /// CCITT_T6 = 4, /// /// Lempel-Ziv & Welch. /// LZW = 5, /// /// Original JPEG / Old-style JPEG (6.0). /// OJPEG = 6, /// /// JPEG DCT compression. Introduced post TIFF rev 6.0. /// JPEG = 7, /// /// NeXT 2-bit RLE. /// NeXT = 32766, /// /// CCITT RLE. /// CCITTRLEW = 32771, /// /// Macintosh RLE. /// PackBits = 32773, /// /// ThunderScan RLE. /// ThunderScan = 32809, /// /// IT8 CT w/padding. Reserved for ANSI IT8 TIFF/IT. /// IT8CTPAD = 32895, /// /// IT8 Linework RLE. Reserved for ANSI IT8 TIFF/IT. /// IT8LW = 32896, /// /// IT8 Monochrome picture. Reserved for ANSI IT8 TIFF/IT. /// IT8MP = 32897, /// /// IT8 Binary line art. Reserved for ANSI IT8 TIFF/IT. /// IT8BL = 32898, /// /// Pixar commanded 10bit LZW. Reserved for Pixar. /// PixarFilm = 32908, /// /// Pixar commanded 11bit ZIP. Reserved for Pixar. /// PixarLog = 32909, /// /// Deflate compression. /// Deflate = 32946, /// /// Deflate compression, as recognized by Adobe. /// AdobeDeflate = 8, /// /// Kodak DCS encoding. /// Reserved for Oceana Matrix (dev@oceana.com). /// DCS = 32947, /// /// ISO JBIG. /// JBIG = 34661, /// /// SGI Log Luminance RLE. /// SGILOG = 34676, /// /// SGI Log 24-bit packed. /// SGILOG24 = 34677, /// /// Leadtools JPEG2000. /// JP2000 = 34712, } #endregion #region ExtraSample /// /// Information about extra samples.
/// Possible values for .ExtraSamples tag. ///
public enum ExtraSample { /// /// Unspecified data. /// UnSpecified = 0, /// /// Associated alpha data. /// AssociatedAlpha = 1, /// /// Unassociated alpha data. /// UnAssociatedAlpha = 2, } #endregion #region FaxMode /// /// Group 3/4 format control.
/// Possible values for .FAXMODE tag. ///
public enum FaxMode { /// /// Default, include RTC. /// Classic = 0x0000, /// /// No RTC at end of data. /// NoRTC = 0x0001, /// /// No EOL code at end of row. /// NoEOL = 0x0002, /// /// Byte align row. /// ByteAlign = 0x0004, /// /// Word align row. /// WordAlign = 0x0008, /// /// TIFF Class F. /// ClassF = NoRTC, } #endregion #region FileType /// /// Sub-file data descriptor.
/// Possible values for .SubFileType tag. ///
public enum FileType { /// /// Reduced resolution version. /// ReducedResImage = 0x1, /// /// One page of many. /// Page = 0x2, /// /// Transparency mask. /// Mask = 0x4 } #endregion #region Group3Option /// /// Options for CCITT Group 3/4 fax encoding.
/// Possible values for .Group3Options / TiffTag.T4Options and /// TiffTag.Group4Options / TiffTag.T6Options tags. ///
public enum Group3Option { /// /// Unknown (uninitialized). /// Unknown = -1, /// /// 2-dimensional coding. /// Encoding2D = 0x1, /// /// Data not compressed. /// UnCompressed = 0x2, /// /// Fill to byte boundary. /// FillBits = 0x4, } #endregion #region InkSet /// /// Inks in separated image.
/// Possible values for .InkSet tag. ///
public enum InkSet { /// /// Cyan-magenta-yellow-black color. /// CMYK = 1, /// /// Multi-ink or hi-fi color. /// MultiInk = 2, } #endregion #region JpegColorMode /// /// Auto RGB<=>YCbCr convert.
/// Possible values for .JPEGCOLORMODE tag. ///
public enum JpegColorMode { /// /// No conversion (default). /// Raw = 0x0000, /// /// Do auto conversion. /// RGB = 0x0001, } #endregion #region JpegProc /// /// JPEG processing algorithm.
/// Possible values for .JPEGProc tag. ///
public enum JpegProc { /// /// Baseline sequential. /// Baseline = 1, /// /// Huffman coded lossless. /// Lossless = 14, } #endregion #region JpegTablesMode /// /// Jpeg Tables Mode.
/// Possible values for .JPEGTABLESMODE tag. ///
public enum JpegTablesMode { /// /// None. /// None = 0, /// /// Include quantization tables. /// Quant = 0x0001, /// /// Include Huffman tables. /// Huff = 0x0002, } #endregion #region Orientation /// /// Image orientation.
/// Possible values for .Orientation tag. ///
public enum Orientation { /// /// Row 0 top, Column 0 lhs. /// TopLeft = 1, /// /// Row 0 top, Column 0 rhs. /// TopRight = 2, /// /// Row 0 bottom, Column 0 rhs. /// BottomRight = 3, /// /// Row 0 bottom, Column 0 lhs. /// BottomLeft = 4, /// /// Row 0 lhs, Column 0 top. /// LeftTop = 5, /// /// Row 0 rhs, Column 0 top. /// RightTop = 6, /// /// Row 0 rhs, Column 0 bottom. /// RightBottom = 7, /// /// Row 0 lhs, Column 0 bottom. /// LeftBottom = 8, } #endregion #region Photometric /// /// Photometric interpretation.
/// Possible values for .Photometric tag. ///
public enum Photometric { /// /// Min value is white. /// MinIsWhite = 0, /// /// Min value is black. /// MinIsBlack = 1, /// /// RGB color model. /// RGB = 2, /// /// Color map indexed. /// Palette = 3, /// /// [obsoleted by TIFF rev. 6.0] Holdout mask. /// Mask = 4, /// /// Color separations. /// Separated = 5, /// /// CCIR 601. /// YCBCR = 6, /// /// 1976 CIE L*a*b*. /// CIELAB = 8, /// /// ICC L*a*b*. Introduced post TIFF rev 6.0 by Adobe TIFF Technote 4. /// ICCLAB = 9, /// /// ITU L*a*b*. /// ITULAB = 10, /// /// CIE Log2(L). /// LogL = 32844, /// /// CIE Log2(L) (u',v'). /// LogLUV = 32845, } #endregion #region PlanarConfig /// /// Storage organization.
/// Possible values for .PlanarConfig tag. ///
public enum PlanarConfig { /// /// Unknown (uninitialized). /// Unknown = 0, /// /// Single image plane. /// Contig = 1, /// /// Separate planes of data. /// Separate = 2 } #endregion #region PredictionScheme /// /// Prediction scheme w/ LZW.
/// Possible values for .Predictor tag. ///
public enum PredictionScheme { /// /// No prediction scheme used. /// None = 1, /// /// Horizontal differencing. /// Horizontal = 2, /// /// Floating point predictor. /// FloatingPoint = 3, } #endregion #region ResolutionUnit /// /// Units of resolutions.
/// Possible values for .ResolutionUnit tag. ///
public enum ResolutionUnit { /// /// No meaningful units. /// None = 1, /// /// English. /// Inch = 2, /// /// Metric. /// Centimeter = 3, } #endregion #region SampleFormat /// /// Data sample format.
/// Possible values for .SampleFormat tag. ///
public enum SampleFormat { /// /// Unsigned integer data /// UInt = 1, /// /// Signed integer data /// Int = 2, /// /// IEEE floating point data /// IEEEFloat = 3, /// /// Un-typed data /// UnTyped = 4, /// /// Complex signed int /// COMPLEXINT = 5, /// /// Complex IEEE floating /// ComplexIEEEFloat = 6, } #endregion #region SubFileType /// /// Kind of data in subfile.
/// Possible values for .OSubFileType tag. ///
public enum SubFileType { /// /// Full resolution image data. /// Image = 1, /// /// Reduced size image data. /// ReducedSizeImage = 2, /// /// One page of many. /// Page = 3 } #endregion #region Threshold /// /// Thresholding used on data.
/// Possible values for .Threshholding tag. ///
public enum Threshold { /// /// B&W art scan. /// BILevel = 1, /// /// Dithered scan. /// HalfTone = 2, /// /// Usually Floyd-Steinberg. /// ErrorDiffuse = 3, } #endregion #region TiffPrintFlags /// /// Flags that can be passed to /// method to control printing of data structures that are potentially very large. /// /// More than one flag can be used. Bit-or these flags to enable printing /// multiple items. [Flags] public enum TiffPrintFlags { /// /// no extra info /// None = 0x0, /// /// strips/tiles info /// Strips = 0x1, /// /// color/gray response curves /// Curves = 0x2, /// /// colormap /// Colormap = 0x4, /// /// JPEG Q matrices /// JPEGQTables = 0x100, /// /// JPEG AC tables /// JPEGACTABLES = 0x200, /// /// JPEG DC tables /// JPEGDCTables = 0x200, } #endregion #region TiffTag /// /// TIFF tag definitions. /// /// /// Joris Van Damme maintains /// /// TIFF Tag Reference, good source of tag information. It's an overview of known TIFF /// Tags with properties, short description, and other useful information. /// public enum TiffTag { /// /// Tag placeholder /// Ignore = 0, /// /// Subfile data descriptor. /// For the list of possible values, see . /// SubFileType = 254, /// /// [obsoleted by TIFF rev. 5.0]
/// Kind of data in subfile. For the list of possible values, see . ///
OSubFileType = 255, /// /// Image width in pixels. /// ImageWidth = 256, /// /// Image height in pixels. /// ImageLength = 257, /// /// Bits per channel (sample). /// BitsPerSample = 258, /// /// Data compression technique. /// For the list of possible values, see . /// Compression = 259, /// /// Photometric interpretation. /// For the list of possible values, see . /// Photometric = 262, /// /// [obsoleted by TIFF rev. 5.0]
/// Thresholding used on data. For the list of possible values, see . ///
Threshholding = 263, /// /// [obsoleted by TIFF rev. 5.0]
/// Dithering matrix width. ///
CellWidth = 264, /// /// [obsoleted by TIFF rev. 5.0]
/// Dithering matrix height. ///
CellLength = 265, /// /// Data order within a byte. /// For the list of possible values, see . /// FillOrder = 266, /// /// Name of document which holds for image. /// DocumentName = 269, /// /// Information about image. /// ImageDescription = 270, /// /// Scanner manufacturer name. /// Manufacturer = 271, /// /// Scanner model name/number. /// Model = 272, /// /// Offsets to data strips. /// StripOffsets = 273, /// /// [obsoleted by TIFF rev. 5.0]
/// Image orientation. For the list of possible values, see . ///
Orientation = 274, /// /// Samples per pixel. /// SamplesPerPixel = 277, /// /// Rows per strip of data. /// RowsPerStrip = 278, /// /// Bytes counts for strips. /// StripByteCounts = 279, /// /// [obsoleted by TIFF rev. 5.0]
/// Minimum sample value. ///
MinSampleValue = 280, /// /// [obsoleted by TIFF rev. 5.0]
/// Maximum sample value. ///
MaxSampleValue = 281, /// /// Pixels/resolution in x. /// XResolution = 282, /// /// Pixels/resolution in y. /// YResolution = 283, /// /// Storage organization. /// For the list of possible values, see . /// PlanarConfig = 284, /// /// Page name image is from. /// PageName = 285, /// /// X page offset of image lhs. /// XPosition = 286, /// /// Y page offset of image lhs. /// YPosition = 287, /// /// [obsoleted by TIFF rev. 5.0]
/// Byte offset to free block. ///
FreeOffsets = 288, /// /// [obsoleted by TIFF rev. 5.0]
/// Sizes of free blocks. ///
FreeByteCounts = 289, /// /// [obsoleted by TIFF rev. 6.0]
/// Gray scale curve accuracy. /// For the list of possible values, see . ///
GrayResponseUnit = 290, /// /// [obsoleted by TIFF rev. 6.0]
/// Gray scale response curve. ///
GrayResponseCurve = 291, /// /// Options for CCITT Group 3 fax encoding. 32 flag bits. /// For the list of possible values, see . /// Group3Options = 292, /// /// TIFF 6.0 proper name alias for Group3Options. /// T4Options = 292, /// /// Options for CCITT Group 4 fax encoding. 32 flag bits. /// For the list of possible values, see . /// Group4Options = 293, /// /// TIFF 6.0 proper name alias for Group4Options. /// T6Options = 293, /// /// Units of resolutions. /// For the list of possible values, see . /// ResolutionUnit = 296, /// /// Page numbers of multi-page. /// PageNumber = 297, /// /// [obsoleted by TIFF rev. 6.0]
/// Color curve accuracy. /// For the list of possible values, see . ///
ColorResponseUnit = 300, /// /// Colorimetry info. /// TransferFunction = 301, /// /// Name & release. /// Software = 305, /// /// Creation date and time. /// DateTime = 306, /// /// Creator of image. /// Artist = 315, /// /// Machine where created. /// HostComputer = 316, /// /// Prediction scheme w/ LZW. /// For the list of possible values, see . /// Predictor = 317, /// /// Image white point. /// WhitePoint = 318, /// /// Primary chromaticities. /// PrimaryChromaticities = 319, /// /// RGB map for pallette image. /// Colormap = 320, /// /// Highlight + shadow info. /// HalfToneHints = 321, /// /// Tile width in pixels. /// TileWidth = 322, /// /// Tile height in pixels. /// TileLength = 323, /// /// Offsets to data tiles. /// TileOffsets = 324, /// /// Byte counts for tiles. /// TileByteCounts = 325, /// /// Lines with wrong pixel count. /// BadFaxLines = 326, /// /// Regenerated line info. /// For the list of possible values, see . /// CleanFaxData = 327, /// /// Max consecutive bad lines. /// ConsecutiveBadFaxLines = 328, /// /// Subimage descriptors. /// SubImageDescriptor = 330, /// /// Inks in separated image. /// For the list of possible values, see . /// InkSet = 332, /// /// ASCII names of inks. /// InkNames = 333, /// /// Number of inks. /// NumberOfInks = 334, /// /// 0% and 100% dot codes. /// DotRange = 336, /// /// Separation target. /// TargetPrinter = 337, /// /// Information about extra samples. /// For the list of possible values, see . /// ExtraSamples = 338, /// /// Data sample format. /// For the list of possible values, see . /// SampleFormat = 339, /// /// Variable MinSampleValue. /// SMinSampleValue = 340, /// /// Variable MaxSampleValue. /// SMaxSampleValue = 341, /// /// ClipPath. Introduced post TIFF rev 6.0 by Adobe TIFF technote 2. /// ClipPath = 343, /// /// XClipPathUnits. Introduced post TIFF rev 6.0 by Adobe TIFF technote 2. /// XClipPathUnits = 344, /// /// YClipPathUnits. Introduced post TIFF rev 6.0 by Adobe TIFF technote 2. /// YClipPathUnits = 345, /// /// Indexed. Introduced post TIFF rev 6.0 by Adobe TIFF Technote 3. /// Indexed = 346, /// /// JPEG table stream. Introduced post TIFF rev 6.0. /// JpegTables = 347, /// /// OPI Proxy. Introduced post TIFF rev 6.0 by Adobe TIFF technote. /// OPIProxy = 351, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// JPEG processing algorithm. /// For the list of possible values, see . ///
JPEGProc = 512, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// Pointer to SOI marker. ///
JPEGIFOffset = 513, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// JFIF stream length ///
JPEGIFByteCount = 514, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// Restart interval length. ///
JPEGRestartInterval = 515, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// Lossless proc predictor. ///
JPEGLosslessPredictors = 517, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// Lossless point transform. ///
JPEGPointTransform = 518, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// Q matrice offsets. ///
JPEGQTables = 519, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// DCT table offsets. ///
JPEGDCTables = 520, /// /// [obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme]
/// AC coefficient offsets. ///
JPEGACTABLES = 521, /// /// RGB -> YCbCr transform. /// YCBCRCOEFFICIENTS = 529, /// /// YCbCr subsampling factors. /// YCBCRSUBSAMPLING = 530, /// /// Subsample positioning. /// For the list of possible values, see . /// YCBCRPOSITIONING = 531, /// /// Colorimetry info. /// REFERENCEBLACKWHITE = 532, /// /// XML packet. Introduced post TIFF rev 6.0 by Adobe XMP Specification, January 2004. /// XMLPACKET = 700, /// /// OPI ImageID. Introduced post TIFF rev 6.0 by Adobe TIFF technote. /// OPIIMAGEID = 32781, /// /// Image reference points. Private tag registered to Island Graphics. /// REFPTS = 32953, /// /// Region-xform tack point. Private tag registered to Island Graphics. /// REGIONTACKPOINT = 32954, /// /// Warp quadrilateral. Private tag registered to Island Graphics. /// REGIONWARPCORNERS = 32955, /// /// Affine transformation matrix. Private tag registered to Island Graphics. /// REGIONAFFINE = 32956, /// /// [obsoleted by TIFF rev. 6.0]
/// Use EXTRASAMPLE tag. Private tag registered to SGI. ///
MATTEING = 32995, /// /// [obsoleted by TIFF rev. 6.0]
/// Use SampleFormat tag. Private tag registered to SGI. ///
DATATYPE = 32996, /// /// Z depth of image. Private tag registered to SGI. /// IMAGEDEPTH = 32997, /// /// Z depth/data tile. Private tag registered to SGI. /// TILEDEPTH = 32998, /// /// Full image size in X. This tag is set when an image has been cropped out of a larger /// image. It reflect width of the original uncropped image. The XPosition tag can be used /// to determine the position of the smaller image in the larger one. /// Private tag registered to Pixar. /// PIXAR_IMAGEFULLWIDTH = 33300, /// /// Full image size in Y. This tag is set when an image has been cropped out of a larger /// image. It reflect height of the original uncropped image. The YPosition can be used /// to determine the position of the smaller image in the larger one. /// Private tag registered to Pixar. /// PIXAR_IMAGEFULLLENGTH = 33301, /// /// Texture map format. Used to identify special image modes and data used by Pixar's /// texture formats. Private tag registered to Pixar. /// PIXAR_TEXTUREFORMAT = 33302, /* t */ /// /// S&T wrap modes. Used to identify special image modes and data used by Pixar's /// texture formats. Private tag registered to Pixar. /// PIXAR_WRAPMODES = 33303, /// /// Cotan(fov) for env. maps. Used to identify special image modes and data used by /// Pixar's texture formats. Private tag registered to Pixar. /// PIXAR_FOVCOT = 33304, /// /// Used to identify special image modes and data used by Pixar's texture formats. /// Private tag registered to Pixar. /// PIXAR_MATRIX_WORLDTOSCREEN = 33305, /// /// Used to identify special image modes and data used by Pixar's texture formats. /// Private tag registered to Pixar. /// PIXAR_MATRIX_WORLDTOCAMERA = 33306, /// /// Device serial number. Private tag registered to Eastman Kodak. /// WRITERSERIALNUMBER = 33405, /// /// Copyright string. This tag is listed in the TIFF rev. 6.0 w/ unknown ownership. /// COPYRIGHT = 33432, /// /// IPTC TAG from RichTIFF specifications. /// RICHTIFFIPTC = 33723, /// /// Site name. Reserved for ANSI IT8 TIFF/IT. /// IT8SITE = 34016, /// /// Color seq. [RGB, CMYK, etc]. Reserved for ANSI IT8 TIFF/IT. /// IT8COLORSEQUENCE = 34017, /// /// DDES Header. Reserved for ANSI IT8 TIFF/IT. /// IT8HEADER = 34018, /// /// Raster scanline padding. Reserved for ANSI IT8 TIFF/IT. /// IT8RASTERPADDING = 34019, /// /// The number of bits in short run. Reserved for ANSI IT8 TIFF/IT. /// IT8BITSPERRUNLENGTH = 34020, /// /// The number of bits in long run. Reserved for ANSI IT8 TIFF/IT. /// IT8BITSPEREXTENDEDRUNLENGTH = 34021, /// /// LW colortable. Reserved for ANSI IT8 TIFF/IT. /// IT8COLORTABLE = 34022, /// /// BP/BL image color switch. Reserved for ANSI IT8 TIFF/IT. /// IT8IMAGECOLORINDICATOR = 34023, /// /// BP/BL bg color switch. Reserved for ANSI IT8 TIFF/IT. /// IT8BKGCOLORINDICATOR = 34024, /// /// BP/BL image color value. Reserved for ANSI IT8 TIFF/IT. /// IT8IMAGECOLORVALUE = 34025, /// /// BP/BL bg color value. Reserved for ANSI IT8 TIFF/IT. /// IT8BKGCOLORVALUE = 34026, /// /// MP pixel intensity value. Reserved for ANSI IT8 TIFF/IT. /// IT8PIXELINTENSITYRANGE = 34027, /// /// HC transparency switch. Reserved for ANSI IT8 TIFF/IT. /// IT8TRANSPARENCYINDICATOR = 34028, /// /// Color characterization table. Reserved for ANSI IT8 TIFF/IT. /// IT8COLORCHARACTERIZATION = 34029, /// /// HC usage indicator. Reserved for ANSI IT8 TIFF/IT. /// IT8HCUSAGE = 34030, /// /// Trapping indicator (untrapped = 0, trapped = 1). Reserved for ANSI IT8 TIFF/IT. /// IT8TRAPINDICATOR = 34031, /// /// CMYK color equivalents. /// IT8CMYKEQUIVALENT = 34032, /// /// Sequence Frame Count. Private tag registered to Texas Instruments. /// FRAMECOUNT = 34232, /// /// Private tag registered to Adobe for PhotoShop. /// PHOTOSHOP = 34377, /// /// Pointer to EXIF private directory. This tag is documented in EXIF specification. /// EXIFIFD = 34665, /// /// ICC profile data. ?? Private tag registered to Adobe. ?? /// ICCPROFILE = 34675, /// /// JBIG options. Private tag registered to Pixel Magic. /// JBIGOPTIONS = 34750, /// /// Pointer to GPS private directory. This tag is documented in EXIF specification. /// GPSIFD = 34853, /// /// Encoded Class 2 ses. params. Private tag registered to SGI. /// FAXRECVPARAMS = 34908, /// /// Received SubAddr string. Private tag registered to SGI. /// FAXSUBADDRESS = 34909, /// /// Receive time (secs). Private tag registered to SGI. /// FAXRECVTIME = 34910, /// /// Encoded fax ses. params, Table 2/T.30. Private tag registered to SGI. /// FAXDCS = 34911, /// /// Sample value to Nits. Private tag registered to SGI. /// STONITS = 37439, /// /// Private tag registered to FedEx. /// FEDEX_EDR = 34929, /// /// Pointer to Interoperability private directory. /// This tag is documented in EXIF specification. /// INTEROPERABILITYIFD = 40965, /// /// DNG version number. Introduced by Adobe DNG specification. /// DNGVERSION = 50706, /// /// DNG compatibility version. Introduced by Adobe DNG specification. /// DNGBACKWARDVERSION = 50707, /// /// Name for the camera model. Introduced by Adobe DNG specification. /// UNIQUECAMERAMODEL = 50708, /// /// Localized camera model name. Introduced by Adobe DNG specification. /// LOCALIZEDCAMERAMODEL = 50709, /// /// CFAPattern->LinearRaw space mapping. Introduced by Adobe DNG specification. /// CFAPLANECOLOR = 50710, /// /// Spatial layout of the CFA. Introduced by Adobe DNG specification. /// CFALAYOUT = 50711, /// /// Lookup table description. Introduced by Adobe DNG specification. /// LINEARIZATIONTABLE = 50712, /// /// Repeat pattern size for the BlackLevel tag. Introduced by Adobe DNG specification. /// BLACKLEVELREPEATDIM = 50713, /// /// Zero light encoding level. Introduced by Adobe DNG specification. /// BLACKLEVEL = 50714, /// /// Zero light encoding level differences (columns). Introduced by Adobe DNG specification. /// BLACKLEVELDELTAH = 50715, /// /// Zero light encoding level differences (rows). Introduced by Adobe DNG specification. /// BLACKLEVELDELTAV = 50716, /// /// Fully saturated encoding level. Introduced by Adobe DNG specification. /// WHITELEVEL = 50717, /// /// Default scale factors. Introduced by Adobe DNG specification. /// DEFAULTSCALE = 50718, /// /// Origin of the final image area. Introduced by Adobe DNG specification. /// DEFAULTCROPORIGIN = 50719, /// /// Size of the final image area. Introduced by Adobe DNG specification. /// DEFAULTCROPSIZE = 50720, /// /// XYZ->reference color space transformation matrix 1. /// Introduced by Adobe DNG specification. /// COLORMATRIX1 = 50721, /// /// XYZ->reference color space transformation matrix 2. /// Introduced by Adobe DNG specification. /// COLORMATRIX2 = 50722, /// /// Calibration matrix 1. Introduced by Adobe DNG specification. /// CAMERACALIBRATION1 = 50723, /// /// Calibration matrix 2. Introduced by Adobe DNG specification. /// CAMERACALIBRATION2 = 50724, /// /// Dimensionality reduction matrix 1. Introduced by Adobe DNG specification. /// REDUCTIONMATRIX1 = 50725, /// /// Dimensionality reduction matrix 2. Introduced by Adobe DNG specification. /// REDUCTIONMATRIX2 = 50726, /// /// Gain applied the stored raw values. Introduced by Adobe DNG specification. /// ANALOGBALANCE = 50727, /// /// Selected white balance in linear reference space. /// Introduced by Adobe DNG specification. /// ASSHOTNEUTRAL = 50728, /// /// Selected white balance in x-y chromaticity coordinates. /// Introduced by Adobe DNG specification. /// ASSHOTWHITEXY = 50729, /// /// How much to move the zero point. Introduced by Adobe DNG specification. /// BASELINEEXPOSURE = 50730, /// /// Relative noise level. Introduced by Adobe DNG specification. /// BASELINENOISE = 50731, /// /// Relative amount of sharpening. Introduced by Adobe DNG specification. /// BASELINESHARPNESS = 50732, /// /// How closely the values of the green pixels in the blue/green rows /// track the values of the green pixels in the red/green rows. /// Introduced by Adobe DNG specification. /// BAYERGREENSPLIT = 50733, /// /// Non-linear encoding range. Introduced by Adobe DNG specification. /// LINEARRESPONSELIMIT = 50734, /// /// Camera's serial number. Introduced by Adobe DNG specification. /// CAMERASERIALNUMBER = 50735, /// /// Information about the lens. /// LENSINFO = 50736, /// /// Chroma blur radius. Introduced by Adobe DNG specification. /// CHROMABLURRADIUS = 50737, /// /// Relative strength of the camera's anti-alias filter. /// Introduced by Adobe DNG specification. /// ANTIALIASSTRENGTH = 50738, /// /// Used by Adobe Camera Raw. Introduced by Adobe DNG specification. /// SHADOWSCALE = 50739, /// /// Manufacturer's private data. Introduced by Adobe DNG specification. /// DNGPRIVATEDATA = 50740, /// /// Whether the EXIF MakerNote tag is safe to preserve along with the rest of the EXIF data. /// Introduced by Adobe DNG specification. /// MAKERNOTESAFETY = 50741, /// /// Illuminant 1. Introduced by Adobe DNG specification. /// CALIBRATIONILLUMINANT1 = 50778, /// /// Illuminant 2. Introduced by Adobe DNG specification. /// CALIBRATIONILLUMINANT2 = 50779, /// /// Best quality multiplier. Introduced by Adobe DNG specification. /// BESTQUALITYSCALE = 50780, /// /// Unique identifier for the raw image data. Introduced by Adobe DNG specification. /// RAWDATAUNIQUEID = 50781, /// /// File name of the original raw file. Introduced by Adobe DNG specification. /// ORIGINALRAWFILENAME = 50827, /// /// Contents of the original raw file. Introduced by Adobe DNG specification. /// ORIGINALRAWFILEDATA = 50828, /// /// Active (non-masked) pixels of the sensor. Introduced by Adobe DNG specification. /// ACTIVEAREA = 50829, /// /// List of coordinates of fully masked pixels. Introduced by Adobe DNG specification. /// MASKEDAREAS = 50830, /// /// Used to map cameras's color space into ICC profile space. /// Introduced by Adobe DNG specification. /// ASSHOTICCPROFILE = 50831, /// /// Used to map cameras's color space into ICC profile space. /// Introduced by Adobe DNG specification. /// ASSHOTPREPROFILEMATRIX = 50832, /// /// Introduced by Adobe DNG specification. /// CURRENTICCPROFILE = 50833, /// /// Introduced by Adobe DNG specification. /// CURRENTPREPROFILEMATRIX = 50834, /// /// Undefined tag used by Eastman Kodak, hue shift correction data. /// DCSHUESHIFTVALUES = 65535, /// /// [pseudo tag. not written to file]
/// Group 3/4 format control. /// For the list of possible values, see . ///
FAXMODE = 65536, /// /// [pseudo tag. not written to file]
/// Compression quality level. Quality level is on the IJG 0-100 scale. Default value is 75. ///
JPEGQUALITY = 65537, /// /// [pseudo tag. not written to file]
/// Auto RGB<=>YCbCr convert. /// For the list of possible values, see . ///
JPEGCOLORMODE = 65538, /// /// [pseudo tag. not written to file]
/// For the list of possible values, see . /// Default is | . ///
JPEGTABLESMODE = 65539, /// /// [pseudo tag. not written to file]
/// G3/G4 fill function. ///
FAXFILLFUNC = 65540, /// /// [pseudo tag. not written to file]
/// PixarLogCodec I/O data sz. ///
PIXARLOGDATAFMT = 65549, /// /// [pseudo tag. not written to file]
/// Imager mode & filter. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSIMAGERTYPE = 65550, /// /// [pseudo tag. not written to file]
/// Interpolation mode. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSINTERPMODE = 65551, /// /// [pseudo tag. not written to file]
/// Color balance values. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSBALANCEARRAY = 65552, /// /// [pseudo tag. not written to file]
/// Color correction values. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSCORRECTMATRIX = 65553, /// /// [pseudo tag. not written to file]
/// Gamma value. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSGAMMA = 65554, /// /// [pseudo tag. not written to file]
/// Toe & shoulder points. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSTOESHOULDERPTS = 65555, /// /// [pseudo tag. not written to file]
/// Calibration file description. ///
DCSCALIBRATIONFD = 65556, /// /// [pseudo tag. not written to file]
/// Compression quality level. /// Quality level is on the ZLIB 1-9 scale. Default value is -1. ///
ZIPQUALITY = 65557, /// /// [pseudo tag. not written to file]
/// PixarLog uses same scale. ///
PIXARLOGQUALITY = 65558, /// /// [pseudo tag. not written to file]
/// Area of image to acquire. /// Allocated to Oceana Matrix (dev@oceana.com). ///
DCSCLIPRECTANGLE = 65559, /// /// [pseudo tag. not written to file]
/// SGILog user data format. ///
SGILOGDATAFMT = 65560, /// /// [pseudo tag. not written to file]
/// SGILog data encoding control. ///
SGILOGENCODE = 65561, /// /// Exposure time. /// EXIF_EXPOSURETIME = 33434, /// /// F number. /// EXIF_FNUMBER = 33437, /// /// Exposure program. /// EXIF_EXPOSUREPROGRAM = 34850, /// /// Spectral sensitivity. /// EXIF_SPECTRALSENSITIVITY = 34852, /// /// ISO speed rating. /// EXIF_ISOSPEEDRATINGS = 34855, /// /// Optoelectric conversion factor. /// EXIF_OECF = 34856, /// /// Exif version. /// EXIF_EXIFVERSION = 36864, /// /// Date and time of original data generation. /// EXIF_DATETIMEORIGINAL = 36867, /// /// Date and time of digital data generation. /// EXIF_DATETIMEDIGITIZED = 36868, /// /// Meaning of each component. /// EXIF_COMPONENTSCONFIGURATION = 37121, /// /// Image compression mode. /// EXIF_COMPRESSEDBITSPERPIXEL = 37122, /// /// Shutter speed. /// EXIF_SHUTTERSPEEDVALUE = 37377, /// /// Aperture. /// EXIF_APERTUREVALUE = 37378, /// /// Brightness. /// EXIF_BRIGHTNESSVALUE = 37379, /// /// Exposure bias. /// EXIF_EXPOSUREBIASVALUE = 37380, /// /// Maximum lens aperture. /// EXIF_MAXAPERTUREVALUE = 37381, /// /// Subject distance. /// EXIF_SUBJECTDISTANCE = 37382, /// /// Metering mode. /// EXIF_METERINGMODE = 37383, /// /// Light source. /// EXIF_LIGHTSOURCE = 37384, /// /// Flash. /// EXIF_FLASH = 37385, /// /// Lens focal length. /// EXIF_FOCALLENGTH = 37386, /// /// Subject area. /// EXIF_SUBJECTAREA = 37396, /// /// Manufacturer notes. /// EXIF_MAKERNOTE = 37500, /// /// User comments. /// EXIF_USERCOMMENT = 37510, /// /// DateTime subseconds. /// EXIF_SUBSECTIME = 37520, /// /// DateTimeOriginal subseconds. /// EXIF_SUBSECTIMEORIGINAL = 37521, /// /// DateTimeDigitized subseconds. /// EXIF_SUBSECTIMEDIGITIZED = 37522, /// /// Supported Flashpix version. /// EXIF_FLASHPIXVERSION = 40960, /// /// Color space information. /// EXIF_COLORSPACE = 40961, /// /// Valid image width. /// EXIF_PIXELXDIMENSION = 40962, /// /// Valid image height. /// EXIF_PIXELYDIMENSION = 40963, /// /// Related audio file. /// EXIF_RELATEDSOUNDFILE = 40964, /// /// Flash energy. /// EXIF_FLASHENERGY = 41483, /// /// Spatial frequency response. /// EXIF_SPATIALFREQUENCYRESPONSE = 41484, /// /// Focal plane X resolution. /// EXIF_FOCALPLANEXRESOLUTION = 41486, /// /// Focal plane Y resolution. /// EXIF_FOCALPLANEYRESOLUTION = 41487, /// /// Focal plane resolution unit. /// EXIF_FOCALPLANERESOLUTIONUNIT = 41488, /// /// Subject location. /// EXIF_SUBJECTLOCATION = 41492, /// /// Exposure index. /// EXIF_EXPOSUREINDEX = 41493, /// /// Sensing method. /// EXIF_SENSINGMETHOD = 41495, /// /// File source. /// EXIF_FILESOURCE = 41728, /// /// Scene type. /// EXIF_SCENETYPE = 41729, /// /// CFA pattern. /// EXIF_CFAPATTERN = 41730, /// /// Custom image processing. /// EXIF_CUSTOMRENDERED = 41985, /// /// Exposure mode. /// EXIF_EXPOSUREMODE = 41986, /// /// White balance. /// EXIF_WHITEBALANCE = 41987, /// /// Digital zoom ratio. /// EXIF_DIGITALZOOMRATIO = 41988, /// /// Focal length in 35 mm film. /// EXIF_FOCALLENGTHIN35MMFILM = 41989, /// /// Scene capture type. /// EXIF_SCENECAPTURETYPE = 41990, /// /// Gain control. /// EXIF_GAINCONTROL = 41991, /// /// Contrast. /// EXIF_CONTRAST = 41992, /// /// Saturation. /// EXIF_SATURATION = 41993, /// /// Sharpness. /// EXIF_SHARPNESS = 41994, /// /// Device settings description. /// EXIF_DEVICESETTINGDESCRIPTION = 41995, /// /// Subject distance range. /// EXIF_SUBJECTDISTANCERANGE = 41996, /// /// Unique image ID. /// EXIF_IMAGEUNIQUEID = 42016, } #endregion #region TiffType /// /// Tag data type. /// /// Note: RATIONALs are the ratio of two 32-bit integer values. public enum TiffType : short { /// /// Placeholder. /// NoType = 0, /// /// For field descriptor searching. /// Any = NoType, /// /// 8-bit unsigned integer. /// Byte = 1, /// /// 8-bit bytes with last byte null. /// ASCII = 2, /// /// 16-bit unsigned integer. /// Short = 3, /// /// 32-bit unsigned integer. /// Long = 4, /// /// 64-bit unsigned fraction. /// Rational = 5, /// /// 8-bit signed integer. /// SByte = 6, /// /// 8-bit untyped data. /// Undefined = 7, /// /// 16-bit signed integer. /// SShort = 8, /// /// 32-bit signed integer. /// SLong = 9, /// /// 64-bit signed fraction. /// SRational = 10, /// /// 32-bit IEEE floating point. /// Float = 11, /// /// 64-bit IEEE floating point. /// Double = 12, /// /// 32-bit unsigned integer (offset) /// IFD = 13 } #endregion #region YCbCrPosition /// /// Subsample positioning.
/// Possible values for .YCBCRPOSITIONING tag. ///
public enum YCbCrPosition { /// /// As in PostScript Level 2 /// Centered = 1, /// /// As in CCIR 601-1 /// CoSited = 2, } #endregion #endregion #region Internal #region CCITTCodec partial class CCITTCodec : TiffCodec { private static int[] m_faxMainTable = { 12,7,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 5,6,2, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 5,7,3, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 4,6,2, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 6,7,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 5,6,2, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 4,7,3, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 4,6,2, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0, 1,4,0, 3,1,0, 5,3,1, 3,1,0, 2,3,0, 3,1,0, 4,3,1, 3,1,0 }; private static int[] m_faxWhiteTable = { 12,11,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1792, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1856, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2112, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2368, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,1984, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1920, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2240, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2496, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 12,11,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1792, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1856, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2176, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2432, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2048, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,11,1920, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1472, 7,4,5, 7,8,43, 7,6,17, 9,9,1216, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,960, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,704, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2304, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,832, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1600, 7,4,5, 7,8,44, 7,6,17, 9,9,1344, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1088, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 0,0,0, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,39, 7,6,16, 9,8,576, 7,4,6, 7,7,19, 7,5,8, 7,8,55, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,45, 7,4,3, 7,5,11, 7,4,5, 7,8,53, 7,5,9, 9,8,448, 7,4,6, 7,8,35, 9,5,128, 7,8,51, 7,6,15, 7,8,63, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1536, 7,4,5, 7,8,43, 7,6,17, 9,9,1280, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,29, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,33, 9,5,128, 7,8,49, 7,6,14, 7,8,61, 7,4,4, 7,4,2, 7,4,7, 7,8,47, 7,4,3, 7,8,59, 7,4,5, 7,8,41, 7,6,16, 9,9,1024, 7,4,6, 7,8,31, 7,5,8, 7,8,57, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,9,768, 7,4,6, 7,8,37, 9,5,128, 7,7,25, 7,6,15, 9,8,320, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 11,12,2560, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,7,20, 9,5,128, 7,7,24, 7,6,14, 7,7,28, 7,4,4, 7,4,2, 7,4,7, 7,7,23, 7,4,3, 7,7,27, 7,4,5, 7,8,40, 7,6,16, 9,9,896, 7,4,6, 7,7,19, 7,5,8, 7,8,56, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,46, 7,4,3, 7,5,11, 7,4,5, 7,8,54, 7,5,9, 9,8,512, 7,4,6, 7,8,36, 9,5,128, 7,8,52, 7,6,15, 7,8,0, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 9,9,1728, 7,4,5, 7,8,44, 7,6,17, 9,9,1408, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,8,30, 7,4,3, 7,5,11, 7,4,5, 7,6,12, 7,5,9, 9,6,1664, 7,4,6, 7,8,34, 9,5,128, 7,8,50, 7,6,14, 7,8,62, 7,4,4, 7,4,2, 7,4,7, 7,8,48, 7,4,3, 7,8,60, 7,4,5, 7,8,42, 7,6,16, 9,9,1152, 7,4,6, 7,8,32, 7,5,8, 7,8,58, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7, 7,7,22, 7,4,3, 7,5,11, 7,4,5, 7,7,26, 7,5,9, 9,8,640, 7,4,6, 7,8,38, 9,5,128, 7,7,25, 7,6,15, 9,8,384, 7,4,4, 7,4,2, 7,4,7, 7,6,13, 7,4,3, 7,7,18, 7,4,5, 7,7,21, 7,6,17, 9,7,256, 7,4,6, 7,6,1, 7,5,8, 9,6,192, 9,5,64, 7,5,10, 7,4,4, 7,4,2, 7,4,7 }; private static int[] m_faxBlackTable = { 12,11,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1792, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,23, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,20, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,25, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,128, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,56, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,30, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1856, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,57, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,21, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,54, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,52, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,48, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2112, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,44, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,36, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,384, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,28, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,60, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,40, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2368, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,1984, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,50, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,34, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1664, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,26, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1408, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,32, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1920, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,61, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,42, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1024, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,768, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,62, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2240, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,46, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,38, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,512, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,19, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,24, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,22, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2496, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 12,11,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1792, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,23, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,20, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,25, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,192, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1280, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,31, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1856, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,58, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,21, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,896, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,640, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,49, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2176, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,45, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,37, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,448, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,29, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1536, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,41, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2432, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2048, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,51, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,35, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,320, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,27, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,59, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,33, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1920, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,256, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,43, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1152, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,55, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,63, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2304, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,47, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,39, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,53, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,19, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,24, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,22, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2560, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 12,11,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1792, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,23, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,20, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,25, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,128, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,56, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,30, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1856, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,57, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,21, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,54, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,52, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,48, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2112, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,44, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,36, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,384, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,28, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,60, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,40, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2368, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,1984, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,50, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,34, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1728, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,26, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1472, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,32, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1920, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,61, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,42, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1088, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,832, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,62, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2240, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,46, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,38, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,576, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,19, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,24, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,22, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2496, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 12,11,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1792, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,23, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,20, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,25, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,192, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1344, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,31, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1856, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,58, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,21, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,960, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,704, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,49, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2176, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,45, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,37, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,448, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,29, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1600, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,41, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2432, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,18, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,17, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2048, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,51, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,35, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,320, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,27, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,59, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,33, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,11,1920, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,12,256, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,43, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,13,1216, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,9,15, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,55, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,63, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2304, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,47, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,39, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,12,53, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 0,0,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,13, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,19, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,24, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,11,22, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 11,12,2560, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,10, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,16, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,10,0, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 10,10,64, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,9, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,11, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,8,14, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,6,8, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2, 8,7,12, 8,2,3, 8,3,1, 8,2,2, 8,4,6, 8,2,3, 8,3,4, 8,2,2, 8,5,7, 8,2,3, 8,3,1, 8,2,2, 8,4,5, 8,2,3, 8,3,4, 8,2,2 }; /* * Note that these tables are ordered such that the * index into the table is known to be either the * run length, or (run length / 64) + a fixed offset. * * NB: The G3CODE_INVALID entries are only used * during state generation (see mkg3states.c). */ private static short[] m_faxWhiteCodes = { 8, 0x35, 0, /* 0011 0101 */ 6, 0x7, 1, /* 0001 11 */ 4, 0x7, 2, /* 0111 */ 4, 0x8, 3, /* 1000 */ 4, 0xB, 4, /* 1011 */ 4, 0xC, 5, /* 1100 */ 4, 0xE, 6, /* 1110 */ 4, 0xF, 7, /* 1111 */ 5, 0x13, 8, /* 1001 1 */ 5, 0x14, 9, /* 1010 0 */ 5, 0x7, 10, /* 0011 1 */ 5, 0x8, 11, /* 0100 0 */ 6, 0x8, 12, /* 0010 00 */ 6, 0x3, 13, /* 0000 11 */ 6, 0x34, 14, /* 1101 00 */ 6, 0x35, 15, /* 1101 01 */ 6, 0x2A, 16, /* 1010 10 */ 6, 0x2B, 17, /* 1010 11 */ 7, 0x27, 18, /* 0100 111 */ 7, 0xC, 19, /* 0001 100 */ 7, 0x8, 20, /* 0001 000 */ 7, 0x17, 21, /* 0010 111 */ 7, 0x3, 22, /* 0000 011 */ 7, 0x4, 23, /* 0000 100 */ 7, 0x28, 24, /* 0101 000 */ 7, 0x2B, 25, /* 0101 011 */ 7, 0x13, 26, /* 0010 011 */ 7, 0x24, 27, /* 0100 100 */ 7, 0x18, 28, /* 0011 000 */ 8, 0x2, 29, /* 0000 0010 */ 8, 0x3, 30, /* 0000 0011 */ 8, 0x1A, 31, /* 0001 1010 */ 8, 0x1B, 32, /* 0001 1011 */ 8, 0x12, 33, /* 0001 0010 */ 8, 0x13, 34, /* 0001 0011 */ 8, 0x14, 35, /* 0001 0100 */ 8, 0x15, 36, /* 0001 0101 */ 8, 0x16, 37, /* 0001 0110 */ 8, 0x17, 38, /* 0001 0111 */ 8, 0x28, 39, /* 0010 1000 */ 8, 0x29, 40, /* 0010 1001 */ 8, 0x2A, 41, /* 0010 1010 */ 8, 0x2B, 42, /* 0010 1011 */ 8, 0x2C, 43, /* 0010 1100 */ 8, 0x2D, 44, /* 0010 1101 */ 8, 0x4, 45, /* 0000 0100 */ 8, 0x5, 46, /* 0000 0101 */ 8, 0xA, 47, /* 0000 1010 */ 8, 0xB, 48, /* 0000 1011 */ 8, 0x52, 49, /* 0101 0010 */ 8, 0x53, 50, /* 0101 0011 */ 8, 0x54, 51, /* 0101 0100 */ 8, 0x55, 52, /* 0101 0101 */ 8, 0x24, 53, /* 0010 0100 */ 8, 0x25, 54, /* 0010 0101 */ 8, 0x58, 55, /* 0101 1000 */ 8, 0x59, 56, /* 0101 1001 */ 8, 0x5A, 57, /* 0101 1010 */ 8, 0x5B, 58, /* 0101 1011 */ 8, 0x4A, 59, /* 0100 1010 */ 8, 0x4B, 60, /* 0100 1011 */ 8, 0x32, 61, /* 0011 0010 */ 8, 0x33, 62, /* 0011 0011 */ 8, 0x34, 63, /* 0011 0100 */ 5, 0x1B, 64, /* 1101 1 */ 5, 0x12, 128, /* 1001 0 */ 6, 0x17, 192, /* 0101 11 */ 7, 0x37, 256, /* 0110 111 */ 8, 0x36, 320, /* 0011 0110 */ 8, 0x37, 384, /* 0011 0111 */ 8, 0x64, 448, /* 0110 0100 */ 8, 0x65, 512, /* 0110 0101 */ 8, 0x68, 576, /* 0110 1000 */ 8, 0x67, 640, /* 0110 0111 */ 9, 0xCC, 704, /* 0110 0110 0 */ 9, 0xCD, 768, /* 0110 0110 1 */ 9, 0xD2, 832, /* 0110 1001 0 */ 9, 0xD3, 896, /* 0110 1001 1 */ 9, 0xD4, 960, /* 0110 1010 0 */ 9, 0xD5, 1024, /* 0110 1010 1 */ 9, 0xD6, 1088, /* 0110 1011 0 */ 9, 0xD7, 1152, /* 0110 1011 1 */ 9, 0xD8, 1216, /* 0110 1100 0 */ 9, 0xD9, 1280, /* 0110 1100 1 */ 9, 0xDA, 1344, /* 0110 1101 0 */ 9, 0xDB, 1408, /* 0110 1101 1 */ 9, 0x98, 1472, /* 0100 1100 0 */ 9, 0x99, 1536, /* 0100 1100 1 */ 9, 0x9A, 1600, /* 0100 1101 0 */ 6, 0x18, 1664, /* 0110 00 */ 9, 0x9B, 1728, /* 0100 1101 1 */ 11, 0x8, 1792, /* 0000 0001 000 */ 11, 0xC, 1856, /* 0000 0001 100 */ 11, 0xD, 1920, /* 0000 0001 101 */ 12, 0x12, 1984, /* 0000 0001 0010 */ 12, 0x13, 2048, /* 0000 0001 0011 */ 12, 0x14, 2112, /* 0000 0001 0100 */ 12, 0x15, 2176, /* 0000 0001 0101 */ 12, 0x16, 2240, /* 0000 0001 0110 */ 12, 0x17, 2304, /* 0000 0001 0111 */ 12, 0x1C, 2368, /* 0000 0001 1100 */ 12, 0x1D, 2432, /* 0000 0001 1101 */ 12, 0x1E, 2496, /* 0000 0001 1110 */ 12, 0x1F, 2560, /* 0000 0001 1111 */ 12, 0x1, G3CODE_EOL, /* 0000 0000 0001 */ 9, 0x1, G3CODE_INVALID, /* 0000 0000 1 */ 10, 0x1, G3CODE_INVALID, /* 0000 0000 01 */ 11, 0x1, G3CODE_INVALID, /* 0000 0000 001 */ 12, 0x0, G3CODE_INVALID, /* 0000 0000 0000 */ }; private static short[] m_faxBlackCodes = { 10, 0x37, 0, /* 0000 1101 11 */ 3, 0x2, 1, /* 010 */ 2, 0x3, 2, /* 11 */ 2, 0x2, 3, /* 10 */ 3, 0x3, 4, /* 011 */ 4, 0x3, 5, /* 0011 */ 4, 0x2, 6, /* 0010 */ 5, 0x3, 7, /* 0001 1 */ 6, 0x5, 8, /* 0001 01 */ 6, 0x4, 9, /* 0001 00 */ 7, 0x4, 10, /* 0000 100 */ 7, 0x5, 11, /* 0000 101 */ 7, 0x7, 12, /* 0000 111 */ 8, 0x4, 13, /* 0000 0100 */ 8, 0x7, 14, /* 0000 0111 */ 9, 0x18, 15, /* 0000 1100 0 */ 10, 0x17, 16, /* 0000 0101 11 */ 10, 0x18, 17, /* 0000 0110 00 */ 10, 0x8, 18, /* 0000 0010 00 */ 11, 0x67, 19, /* 0000 1100 111 */ 11, 0x68, 20, /* 0000 1101 000 */ 11, 0x6C, 21, /* 0000 1101 100 */ 11, 0x37, 22, /* 0000 0110 111 */ 11, 0x28, 23, /* 0000 0101 000 */ 11, 0x17, 24, /* 0000 0010 111 */ 11, 0x18, 25, /* 0000 0011 000 */ 12, 0xCA, 26, /* 0000 1100 1010 */ 12, 0xCB, 27, /* 0000 1100 1011 */ 12, 0xCC, 28, /* 0000 1100 1100 */ 12, 0xCD, 29, /* 0000 1100 1101 */ 12, 0x68, 30, /* 0000 0110 1000 */ 12, 0x69, 31, /* 0000 0110 1001 */ 12, 0x6A, 32, /* 0000 0110 1010 */ 12, 0x6B, 33, /* 0000 0110 1011 */ 12, 0xD2, 34, /* 0000 1101 0010 */ 12, 0xD3, 35, /* 0000 1101 0011 */ 12, 0xD4, 36, /* 0000 1101 0100 */ 12, 0xD5, 37, /* 0000 1101 0101 */ 12, 0xD6, 38, /* 0000 1101 0110 */ 12, 0xD7, 39, /* 0000 1101 0111 */ 12, 0x6C, 40, /* 0000 0110 1100 */ 12, 0x6D, 41, /* 0000 0110 1101 */ 12, 0xDA, 42, /* 0000 1101 1010 */ 12, 0xDB, 43, /* 0000 1101 1011 */ 12, 0x54, 44, /* 0000 0101 0100 */ 12, 0x55, 45, /* 0000 0101 0101 */ 12, 0x56, 46, /* 0000 0101 0110 */ 12, 0x57, 47, /* 0000 0101 0111 */ 12, 0x64, 48, /* 0000 0110 0100 */ 12, 0x65, 49, /* 0000 0110 0101 */ 12, 0x52, 50, /* 0000 0101 0010 */ 12, 0x53, 51, /* 0000 0101 0011 */ 12, 0x24, 52, /* 0000 0010 0100 */ 12, 0x37, 53, /* 0000 0011 0111 */ 12, 0x38, 54, /* 0000 0011 1000 */ 12, 0x27, 55, /* 0000 0010 0111 */ 12, 0x28, 56, /* 0000 0010 1000 */ 12, 0x58, 57, /* 0000 0101 1000 */ 12, 0x59, 58, /* 0000 0101 1001 */ 12, 0x2B, 59, /* 0000 0010 1011 */ 12, 0x2C, 60, /* 0000 0010 1100 */ 12, 0x5A, 61, /* 0000 0101 1010 */ 12, 0x66, 62, /* 0000 0110 0110 */ 12, 0x67, 63, /* 0000 0110 0111 */ 10, 0xF, 64, /* 0000 0011 11 */ 12, 0xC8, 128, /* 0000 1100 1000 */ 12, 0xC9, 192, /* 0000 1100 1001 */ 12, 0x5B, 256, /* 0000 0101 1011 */ 12, 0x33, 320, /* 0000 0011 0011 */ 12, 0x34, 384, /* 0000 0011 0100 */ 12, 0x35, 448, /* 0000 0011 0101 */ 13, 0x6C, 512, /* 0000 0011 0110 0 */ 13, 0x6D, 576, /* 0000 0011 0110 1 */ 13, 0x4A, 640, /* 0000 0010 0101 0 */ 13, 0x4B, 704, /* 0000 0010 0101 1 */ 13, 0x4C, 768, /* 0000 0010 0110 0 */ 13, 0x4D, 832, /* 0000 0010 0110 1 */ 13, 0x72, 896, /* 0000 0011 1001 0 */ 13, 0x73, 960, /* 0000 0011 1001 1 */ 13, 0x74, 1024, /* 0000 0011 1010 0 */ 13, 0x75, 1088, /* 0000 0011 1010 1 */ 13, 0x76, 1152, /* 0000 0011 1011 0 */ 13, 0x77, 1216, /* 0000 0011 1011 1 */ 13, 0x52, 1280, /* 0000 0010 1001 0 */ 13, 0x53, 1344, /* 0000 0010 1001 1 */ 13, 0x54, 1408, /* 0000 0010 1010 0 */ 13, 0x55, 1472, /* 0000 0010 1010 1 */ 13, 0x5A, 1536, /* 0000 0010 1101 0 */ 13, 0x5B, 1600, /* 0000 0010 1101 1 */ 13, 0x64, 1664, /* 0000 0011 0010 0 */ 13, 0x65, 1728, /* 0000 0011 0010 1 */ 11, 0x8, 1792, /* 0000 0001 000 */ 11, 0xC, 1856, /* 0000 0001 100 */ 11, 0xD, 1920, /* 0000 0001 101 */ 12, 0x12, 1984, /* 0000 0001 0010 */ 12, 0x13, 2048, /* 0000 0001 0011 */ 12, 0x14, 2112, /* 0000 0001 0100 */ 12, 0x15, 2176, /* 0000 0001 0101 */ 12, 0x16, 2240, /* 0000 0001 0110 */ 12, 0x17, 2304, /* 0000 0001 0111 */ 12, 0x1C, 2368, /* 0000 0001 1100 */ 12, 0x1D, 2432, /* 0000 0001 1101 */ 12, 0x1E, 2496, /* 0000 0001 1110 */ 12, 0x1F, 2560, /* 0000 0001 1111 */ 12, 0x1, G3CODE_EOL, /* 0000 0000 0001 */ 9, 0x1, G3CODE_INVALID, /* 0000 0000 1 */ 10, 0x1, G3CODE_INVALID, /* 0000 0000 01 */ 11, 0x1, G3CODE_INVALID, /* 0000 0000 001 */ 12, 0x0, G3CODE_INVALID, /* 0000 0000 0000 */ }; private static tableEntry m_horizcode = new tableEntry(3, 0x1, 0); /* 001 */ private static tableEntry m_passcode = new tableEntry(4, 0x1, 0); /* 0001 */ private static tableEntry[] m_vcodes = { new tableEntry(7, 0x03, 0), /* 0000 011 */ new tableEntry(6, 0x03, 0), /* 0000 11 */ new tableEntry(3, 0x03, 0), /* 011 */ new tableEntry(1, 0x1, 0), /* 1 */ new tableEntry(3, 0x2, 0), /* 010 */ new tableEntry(6, 0x02, 0), /* 0000 10 */ new tableEntry(7, 0x02, 0) /* 0000 010 */ }; private static int[] m_msbmask = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; private static byte[] m_zeroruns = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */ }; private static byte[] m_oneruns = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */ }; static byte[] fillMasks = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; public const int FIELD_BADFAXLINES = (FieldBit.Codec + 0); public const int FIELD_CLEANFAXDATA = (FieldBit.Codec + 1); public const int FIELD_BADFAXRUN = (FieldBit.Codec + 2); public const int FIELD_RECVPARAMS = (FieldBit.Codec + 3); public const int FIELD_SUBADDRESS = (FieldBit.Codec + 4); public const int FIELD_RECVTIME = (FieldBit.Codec + 5); public const int FIELD_FAXDCS = (FieldBit.Codec + 6); public const int FIELD_OPTIONS = (FieldBit.Codec + 7); internal FaxMode m_mode; /* operating mode */ internal Group3Option m_groupoptions; /* Group 3/4 options tag */ internal CleanFaxData m_cleanfaxdata; /* CleanFaxData tag */ internal int m_badfaxlines; /* BadFaxLines tag */ internal int m_badfaxrun; /* BadFaxRun tag */ internal int m_recvparams; /* encoded Class 2 session params */ internal string m_subaddress; /* subaddress string */ internal int m_recvtime; /* time spent receiving (secs) */ internal string m_faxdcs; /* Table 2/T.30 encoded session params */ /* Decoder state info */ internal Tiff.FaxFillFunc fill; /* fill routine */ private const int EOL_CODE = 0x001; /* EOL code value - 0000 0000 0000 1 */ /* finite state machine codes */ private const byte S_Null = 0; private const byte S_Pass = 1; private const byte S_Horiz = 2; private const byte S_V0 = 3; private const byte S_VR = 4; private const byte S_VL = 5; private const byte S_Ext = 6; private const byte S_TermW = 7; private const byte S_TermB = 8; private const byte S_MakeUpW = 9; private const byte S_MakeUpB = 10; private const byte S_MakeUp = 11; private const byte S_EOL = 12; /* status values returned instead of a run length */ private const short G3CODE_EOL = -1; /* NB: ACT_EOL - ACT_WRUNT */ private const short G3CODE_INVALID = -2; /* NB: ACT_INVALID - ACT_WRUNT */ private const short G3CODE_EOF = -3; /* end of input data */ private const short G3CODE_INCOMP = -4; /* incomplete run code */ /* * CCITT T.4 1D Huffman runlength codes and * related definitions. Given the small sizes * of these tables it does not seem * worthwhile to make code & length 8 bits. */ private struct tableEntry { public short length; /* bit length of g3 code */ public short code; /* g3 code */ public short runlen; /* run length in bits */ public tableEntry(short _length, short _code, short _runlen) { length = _length; code = _code; runlen = _runlen; } public static tableEntry FromArray(short[] array, int entryNumber) { int offset = entryNumber * 3; // we have 3 elements in entry return new tableEntry(array[offset], array[offset + 1], array[offset + 2]); } }; private struct faxTableEntry { public faxTableEntry(byte _State, byte _Width, int _Param) { State = _State; Width = _Width; Param = _Param; } public static faxTableEntry FromArray(int[] array, int entryNumber) { int offset = entryNumber * 3; // we have 3 elements in entry return new faxTableEntry((byte)array[offset], (byte)array[offset + 1], array[offset + 2]); } /* state table entry */ public byte State; /* see above */ public byte Width; /* width of code in bits */ public int Param; /* unsigned 32-bit run length in bits */ }; private enum Decoder { useFax3_1DDecoder, useFax3_2DDecoder, useFax4Decoder, useFax3RLEDecoder }; private enum Fax3Encoder { useFax1DEncoder, useFax2DEncoder }; private static TiffFieldInfo[] m_faxFieldInfo = { new TiffFieldInfo(TiffTag.FAXMODE, 0, 0, TiffType.Any, FieldBit.Pseudo, false, false, "FaxMode"), new TiffFieldInfo(TiffTag.FAXFILLFUNC, 0, 0, TiffType.Any, FieldBit.Pseudo, false, false, "FaxFillFunc"), new TiffFieldInfo(TiffTag.BadFaxLines, 1, 1, TiffType.Long, FIELD_BADFAXLINES, true, false, "BadFaxLines"), new TiffFieldInfo(TiffTag.BadFaxLines, 1, 1, TiffType.Short, FIELD_BADFAXLINES, true, false, "BadFaxLines"), new TiffFieldInfo(TiffTag.CleanFaxData, 1, 1, TiffType.Short, FIELD_CLEANFAXDATA, true, false, "CleanFaxData"), new TiffFieldInfo(TiffTag.ConsecutiveBadFaxLines, 1, 1, TiffType.Long, FIELD_BADFAXRUN, true, false, "ConsecutiveBadFaxLines"), new TiffFieldInfo(TiffTag.ConsecutiveBadFaxLines, 1, 1, TiffType.Short, FIELD_BADFAXRUN, true, false, "ConsecutiveBadFaxLines"), new TiffFieldInfo(TiffTag.FAXRECVPARAMS, 1, 1, TiffType.Long, FIELD_RECVPARAMS, true, false, "FaxRecvParams"), new TiffFieldInfo(TiffTag.FAXSUBADDRESS, -1, -1, TiffType.ASCII, FIELD_SUBADDRESS, true, false, "FaxSubAddress"), new TiffFieldInfo(TiffTag.FAXRECVTIME, 1, 1, TiffType.Long, FIELD_RECVTIME, true, false, "FaxRecvTime"), new TiffFieldInfo(TiffTag.FAXDCS, -1, -1, TiffType.ASCII, FIELD_FAXDCS, true, false, "FaxDcs"), }; private static TiffFieldInfo[] m_fax3FieldInfo = { new TiffFieldInfo(TiffTag.Group3Options, 1, 1, TiffType.Long, FIELD_OPTIONS, false, false, "Group3Options"), }; private static TiffFieldInfo[] m_fax4FieldInfo = { new TiffFieldInfo(TiffTag.Group4Options, 1, 1, TiffType.Long, FIELD_OPTIONS, false, false, "Group4Options"), }; private TiffTagMethods m_parentTagMethods; private TiffTagMethods m_tagMethods; private int m_rw_mode; /* O_RDONLY for decode, else encode */ private int m_rowbytes; /* bytes in a decoded scanline */ private int m_rowpixels; /* pixels in a scanline */ /* Decoder state info */ private Decoder m_decoder; private byte[] m_bitmap; /* bit reversal table */ private int m_data; /* current i/o byte/word */ private int m_bit; /* current i/o bit in byte */ private int m_EOLcnt; /* count of EOL codes recognized */ private int[] m_runs; /* b&w runs for current/previous row */ private int m_refruns; /* runs for reference line (index in m_runs) */ private int m_curruns; /* runs for current line (index in m_runs) */ private int m_a0; /* reference element */ private int m_RunLength; /* length of current run */ private int m_thisrun; /* current row's run array (index in m_runs) */ private int m_pa; /* place to stuff next run (index in m_runs) */ private int m_pb; /* next run in reference line (index in m_runs) */ /* Encoder state info */ private Fax3Encoder m_encoder; /* encoding state */ private bool m_encodingFax4; // if false, G3 will be used private byte[] m_refline; /* reference line for 2d decoding */ private int m_k; /* #rows left that can be 2d encoded */ private int m_maxk; /* max #rows that can be 2d encoded */ private int m_line; private byte[] m_buffer; // buffer with data to encode private int m_offset; // current position in m_buffer public CCITTCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { m_tagMethods = new CCITTCodecTagMethods(); } public override bool Init() { switch (m_scheme) { case Compression.CCITTRLE: return TIFFInitCCITTRLE(); case Compression.CCITTRLEW: return TIFFInitCCITTRLEW(); case Compression.CCITTFAX3: return TIFFInitCCITTFax3(); case Compression.CCITTFAX4: return TIFFInitCCITTFax4(); } return false; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } public override bool SetupDecode() { // same for all types return setupState(); } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// /// PreDecode is called after and before decoding. /// public override bool PreDecode(short plane) { m_bit = 0; // force initial read m_data = 0; m_EOLcnt = 0; // force initial scan for EOL // Decoder assumes lsb-to-msb bit order. Note that we select this here rather than in // setupState so that viewers can hold the image open, fiddle with the BitOrder tag // value, and then re-decode the image. Otherwise they'd need to close and open the // image to get the state reset. m_bitmap = Tiff.GetBitRevTable(m_tif.m_dir.td_fillorder != BitOrder.LittleEndian); if (m_refruns >= 0) { // init reference line to white m_runs[m_refruns] = m_rowpixels; m_runs[m_refruns + 1] = 0; } m_line = 0; return true; } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { switch (m_decoder) { case Decoder.useFax3_1DDecoder: return Fax3Decode1D(buffer, offset, count); case Decoder.useFax3_2DDecoder: return Fax3Decode2D(buffer, offset, count); case Decoder.useFax4Decoder: return Fax4Decode(buffer, offset, count); case Decoder.useFax3RLEDecoder: return Fax3DecodeRLE(buffer, offset, count); } return false; } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { return DecodeRow(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { return DecodeRow(buffer, offset, count, plane); } /// /// Setups the encoder part of the codec. /// /// /// true if this codec successfully setup its encoder part and can encode data; /// otherwise, false. /// /// /// SetupEncode is called once before /// . public override bool SetupEncode() { // same for all types return setupState(); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { m_bit = 8; m_data = 0; m_encoder = Fax3Encoder.useFax1DEncoder; /* * This is necessary for Group 4; otherwise it isn't * needed because the first scanline of each strip ends * up being copied into the refline. */ if (m_refline != null) Array.Clear(m_refline, 0, m_refline.Length); if (is2DEncoding()) { float res = m_tif.m_dir.td_yresolution; /* * The CCITT spec says that when doing 2d encoding, you * should only do it on K consecutive scanlines, where K * depends on the resolution of the image being encoded * (2 for <= 200 lpi, 4 for > 200 lpi). Since the directory * code initializes td_yresolution to 0, this code will * select a K of 2 unless the YResolution tag is set * appropriately. (Note also that we fudge a little here * and use 150 lpi to avoid problems with units conversion.) */ if (m_tif.m_dir.td_resolutionunit == ResolutionUnit.Centimeter) { /* convert to inches */ res *= 2.54f; } m_maxk = (res > 150 ? 4 : 2); m_k = m_maxk - 1; } else { m_maxk = 0; m_k = 0; } m_line = 0; return true; } /// /// Performs any actions after encoding required by the codec. /// /// /// true if all post-encode actions succeeded; otherwise, false /// /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public override bool PostEncode() { if (m_encodingFax4) return Fax4PostEncode(); return Fax3PostEncode(); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { if (m_encodingFax4) return Fax4Encode(buffer, offset, count); return Fax3Encode(buffer, offset, count); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { return EncodeRow(buffer, offset, count, plane); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { return EncodeRow(buffer, offset, count, plane); } /// /// Flushes any internal data buffers and terminates current operation. /// public override void Close() { if ((m_mode & FaxMode.NoRTC) == 0) { int code = EOL_CODE; int length = 12; if (is2DEncoding()) { bool b = ((code << 1) != 0) | (m_encoder == Fax3Encoder.useFax1DEncoder); if (b) code = 1; else code = 0; length++; } for (int i = 0; i < 6; i++) putBits(code, length); flushBits(); } } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public override void Cleanup() { m_tif.m_tagmethods = m_parentTagMethods; } private bool is2DEncoding() { return (m_groupoptions & Group3Option.Encoding2D) != 0; } /* * Update the value of b1 using the array * of runs for the reference line. */ private void CHECK_b1(ref int b1) { if (m_pa != m_thisrun) { while (b1 <= m_a0 && b1 < m_rowpixels) { b1 += m_runs[m_pb] + m_runs[m_pb + 1]; m_pb += 2; } } } private static void SWAP(ref int a, ref int b) { int x = a; a = b; b = x; } private static bool isLongAligned(int offset) { return (offset % sizeof(int) == 0); } private static bool isShortAligned(int offset) { return (offset % sizeof(short) == 0); } /* * The FILL macro must handle spans < 2*sizeof(int) bytes. * This is <8 bytes. */ private static void FILL(int n, byte[] cp, ref int offset, byte value) { const int max = 7; if (n <= max && n > 0) { for (int i = n; i > 0; i--) cp[offset + i - 1] = value; offset += n; } } /* * Bit-fill a row according to the white/black * runs generated during G3/G4 decoding. * The default run filler; made public for other decoders. */ private static void fax3FillRuns(byte[] buffer, int offset, int[] runs, int thisRunOffset, int nextRunOffset, int width) { if (((nextRunOffset - thisRunOffset) & 1) != 0) { runs[nextRunOffset] = 0; nextRunOffset++; } int x = 0; for (; thisRunOffset < nextRunOffset; thisRunOffset += 2) { int run = runs[thisRunOffset]; // should cast 'run' to unsigned in order to discard values bigger than int.MaxValue // for such value 'run' become negative and following condition is not met if ((uint)x + (uint)run > (uint)width || (uint)run > (uint)width) { runs[thisRunOffset] = width - x; run = runs[thisRunOffset]; } if (run != 0) { int cp = offset + (x >> 3); int bx = x & 7; if (run > 8 - bx) { if (bx != 0) { // align to byte boundary buffer[cp] &= (byte)(0xff << (8 - bx)); cp++; run -= 8 - bx; } int n = run >> 3; if (n != 0) { // multiple bytes to fill if ((n / sizeof(int)) > 1) { // Align to longword boundary and fill. for (; n != 0 && !isLongAligned(cp); n--) { buffer[cp] = 0x00; cp++; } int bytesToFill = n - (n % sizeof(int)); n -= bytesToFill; int stop = bytesToFill + cp; for (; cp < stop; cp++) buffer[cp] = 0; } FILL(n, buffer, ref cp, 0); run &= 7; } if (run != 0) buffer[cp] &= (byte)(0xff >> run); } else buffer[cp] &= (byte)(~(fillMasks[run] >> bx)); x += runs[thisRunOffset]; } run = runs[thisRunOffset + 1]; // should cast 'run' to unsigned in order to discard values bigger than int.MaxValue // for such value 'run' become negative and following condition is not met if ((uint)x + (uint)run > (uint)width || (uint)run > (uint)width) { runs[thisRunOffset + 1] = width - x; run = runs[thisRunOffset + 1]; } if (run != 0) { int cp = offset + (x >> 3); int bx = x & 7; if (run > 8 - bx) { if (bx != 0) { // align to byte boundary buffer[cp] |= (byte)(0xff >> bx); cp++; run -= 8 - bx; } int n = run >> 3; if (n != 0) { // multiple bytes to fill if ((n / sizeof(int)) > 1) { // Align to longword boundary and fill. for (; n != 0 && !isLongAligned(cp); n--) { buffer[cp] = 0xff; cp++; } int bytesToFill = n - (n % sizeof(int)); n -= bytesToFill; int stop = bytesToFill + cp; for (; cp < stop; cp++) buffer[cp] = 0xff; } FILL(n, buffer, ref cp, 0xff); run &= 7; } if (run != 0) buffer[cp] |= (byte)(0xff00 >> run); } else buffer[cp] |= (byte)(fillMasks[run] >> bx); x += runs[thisRunOffset + 1]; } } Debug.Assert(x == width); } /* * Find a span of ones or zeros using the supplied * table. The ``base'' of the bit string is supplied * along with the start+end bit indices. */ private static int find0span(byte[] bp, int bpOffset, int bs, int be) { int offset = bpOffset + (bs >> 3); /* * Check partial byte on lhs. */ int bits = be - bs; int n = bs & 7; int span = 0; if (bits > 0 && n != 0) { span = m_zeroruns[(bp[offset] << n) & 0xff]; if (span > 8 - n) { /* table value too generous */ span = 8 - n; } if (span > bits) { /* constrain span to bit range */ span = bits; } if (n + span < 8) { /* doesn't extend to edge of byte */ return span; } bits -= span; offset++; } if (bits >= (2 * 8 * sizeof(int))) { /* * Align to longword boundary and check longwords. */ while (!isLongAligned(offset)) { if (bp[offset] != 0x00) return (span + m_zeroruns[bp[offset]]); span += 8; bits -= 8; offset++; } while (bits >= 8 * sizeof(int)) { bool allZeros = true; for (int i = 0; i < sizeof(int); i++) { if (bp[offset + i] != 0) { allZeros = false; break; } } if (allZeros) { span += 8 * sizeof(int); bits -= 8 * sizeof(int); offset += sizeof(int); } else { break; } } } /* * Scan full bytes for all 0's. */ while (bits >= 8) { if (bp[offset] != 0x00) { /* end of run */ return (span + m_zeroruns[bp[offset]]); } span += 8; bits -= 8; offset++; } /* * Check partial byte on rhs. */ if (bits > 0) { n = m_zeroruns[bp[offset]]; span += (n > bits ? bits : n); } return span; } private static int find1span(byte[] bp, int bpOffset, int bs, int be) { int offset = bpOffset + (bs >> 3); /* * Check partial byte on lhs. */ int n = bs & 7; int span = 0; int bits = be - bs; if (bits > 0 && n != 0) { span = m_oneruns[(bp[offset] << n) & 0xff]; if (span > 8 - n) { /* table value too generous */ span = 8 - n; } if (span > bits) { /* constrain span to bit range */ span = bits; } if (n + span < 8) { /* doesn't extend to edge of byte */ return (span); } bits -= span; offset++; } if (bits >= (2 * 8 * sizeof(int))) { /* * Align to longword boundary and check longwords. */ while (!isLongAligned(offset)) { if (bp[offset] != 0xff) return (span + m_oneruns[bp[offset]]); span += 8; bits -= 8; offset++; } while (bits >= 8 * sizeof(int)) { bool allOnes = true; for (int i = 0; i < sizeof(int); i++) { if (bp[offset + i] != 0xff) { allOnes = false; break; } } if (allOnes) { span += 8 * sizeof(int); bits -= 8 * sizeof(int); offset += sizeof(int); } else { break; } } } /* * Scan full bytes for all 1's. */ while (bits >= 8) { if (bp[offset] != 0xff) { /* end of run */ return (span + m_oneruns[bp[offset]]); } span += 8; bits -= 8; offset++; } /* * Check partial byte on rhs. */ if (bits > 0) { n = m_oneruns[bp[offset]]; span += (n > bits ? bits : n); } return span; } /* * Return the offset of the next bit in the range * [bs..be] that is different from the specified * color. The end, be, is returned if no such bit * exists. */ private static int finddiff(byte[] bp, int bpOffset, int _bs, int _be, int _color) { if (_color != 0) return (_bs + find1span(bp, bpOffset, _bs, _be)); return (_bs + find0span(bp, bpOffset, _bs, _be)); } /* * Like finddiff, but also check the starting bit * against the end in case start > end. */ private static int finddiff2(byte[] bp, int bpOffset, int _bs, int _be, int _color) { if (_bs < _be) return finddiff(bp, bpOffset, _bs, _be, _color); return _be; } /* * Group 3 and Group 4 Decoding. */ /* * The following macros define the majority of the G3/G4 decoder * algorithm using the state tables defined elsewhere. To build * a decoder you need some setup code and some glue code. Note * that you may also need/want to change the way the NeedBits* * macros get input data if, for example, you know the data to be * decoded is properly aligned and oriented (doing so before running * the decoder can be a big performance win). * * Consult the decoder in the TIFF library for an idea of what you * need to define and setup to make use of these definitions. * * NB: to enable a debugging version of these macros define FAX3_DEBUG * before including this file. Trace output goes to stdout. */ private bool EndOfData() { return (m_tif.m_rawcp >= m_tif.m_rawcc); } private int GetBits(int n) { return (m_data & ((1 << n) - 1)); } private void ClrBits(int n) { m_bit -= n; m_data >>= n; } /* * Need <=8 or <=16 bits of input data. Unlike viewfax we * cannot use/assume a word-aligned, properly bit swizzled * input data set because data may come from an arbitrarily * aligned, read-only source such as a memory-mapped file. * Note also that the viewfax decoder does not check for * running off the end of the input data buffer. This is * possible for G3-encoded data because it prescans the input * data to count EOL markers, but can cause problems for G4 * data. In any event, we don't prescan and must watch for * running out of data since we can't permit the library to * scan past the end of the input data buffer. * * Finally, note that we must handle remaindered data at the end * of a strip specially. The coder asks for a fixed number of * bits when scanning for the next code. This may be more bits * than are actually present in the data stream. If we appear * to run out of data but still have some number of valid bits * remaining then we makeup the requested amount with zeros and * return successfully. If the returned data is incorrect then * we should be called again and get a premature EOF error; * otherwise we should get the right answer. */ private bool NeedBits8(int n) { if (m_bit < n) { if (EndOfData()) { if (m_bit == 0) { /* no valid bits */ return false; } m_bit = n; /* pad with zeros */ } else { m_data |= m_bitmap[m_tif.m_rawdata[m_tif.m_rawcp]] << m_bit; m_tif.m_rawcp++; m_bit += 8; } } return true; } private bool NeedBits16(int n) { if (m_bit < n) { if (EndOfData()) { if (m_bit == 0) { /* no valid bits */ return false; } m_bit = n; /* pad with zeros */ } else { m_data |= m_bitmap[m_tif.m_rawdata[m_tif.m_rawcp]] << m_bit; m_tif.m_rawcp++; m_bit += 8; if (m_bit < n) { if (EndOfData()) { /* NB: we know BitsAvail is non-zero here */ m_bit = n; /* pad with zeros */ } else { m_data |= m_bitmap[m_tif.m_rawdata[m_tif.m_rawcp]] << m_bit; m_tif.m_rawcp++; m_bit += 8; } } } } return true; } private bool LOOKUP8(out faxTableEntry TabEnt, int wid) { if (!NeedBits8(wid)) { TabEnt = new faxTableEntry(); return false; } TabEnt = faxTableEntry.FromArray(m_faxMainTable, GetBits(wid)); ClrBits(TabEnt.Width); return true; } private bool LOOKUP16(out faxTableEntry TabEnt, int wid, bool useBlack) { if (!NeedBits16(wid)) { TabEnt = new faxTableEntry(); return false; } if (useBlack) TabEnt = faxTableEntry.FromArray(m_faxBlackTable, GetBits(wid)); else TabEnt = faxTableEntry.FromArray(m_faxWhiteTable, GetBits(wid)); ClrBits(TabEnt.Width); return true; } /* * Synchronize input decoding at the start of each * row by scanning for an EOL (if appropriate) and * skipping any trash data that might be present * after a decoding error. Note that the decoding * done elsewhere that recognizes an EOL only consumes * 11 consecutive zero bits. This means that if EOLcnt * is non-zero then we still need to scan for the final flag * bit that is part of the EOL code. */ private bool SYNC_EOL() { if (m_EOLcnt == 0) { for (; ; ) { if (!NeedBits16(11)) return false; if (GetBits(11) == 0) break; ClrBits(1); } } for (; ; ) { if (!NeedBits8(8)) return false; if (GetBits(8) != 0) break; ClrBits(8); } while (GetBits(1) == 0) ClrBits(1); ClrBits(1); /* EOL bit */ m_EOLcnt = 0; /* reset EOL counter/flag */ return true; } /* * Setup G3/G4-related compression/decompression state * before data is processed. This routine is called once * per image -- it sets up different state based on whether * or not decoding or encoding is being done and whether * 1D- or 2D-encoded data is involved. */ private bool setupState() { if (m_tif.m_dir.td_bitspersample != 1) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Bits/sample must be 1 for Group 3/4 encoding/decoding"); return false; } /* * Calculate the scanline/tile widths. */ int rowbytes = 0; int rowpixels = 0; if (m_tif.IsTiled()) { rowbytes = m_tif.TileRowSize(); rowpixels = m_tif.m_dir.td_tilewidth; } else { rowbytes = m_tif.ScanlineSize(); rowpixels = m_tif.m_dir.td_imagewidth; } m_rowbytes = rowbytes; m_rowpixels = rowpixels; /* * Allocate any additional space required for decoding/encoding. */ bool needsRefLine = ((m_groupoptions & Group3Option.Encoding2D) != 0 || m_tif.m_dir.td_compression == Compression.CCITTFAX4); // Assure that allocation computations do not overflow. m_runs = null; int nruns = Tiff.roundUp(rowpixels, 32); if (needsRefLine) { long multiplied = (long)nruns * 2; if (multiplied > int.MaxValue) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Row pixels integer overflow (rowpixels {0})", rowpixels); return false; } else { nruns = (int)multiplied; } } if (nruns == 0 || ((long)nruns * 2) > int.MaxValue) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Row pixels integer overflow (rowpixels {0})", rowpixels); return false; } m_runs = new int[2 * nruns]; m_curruns = 0; if (needsRefLine) m_refruns = nruns; else m_refruns = -1; if (m_tif.m_dir.td_compression == Compression.CCITTFAX3 && is2DEncoding()) { /* NB: default is 1D routine */ m_decoder = Decoder.useFax3_2DDecoder; } if (needsRefLine) { /* 2d encoding */ /* * 2d encoding requires a scanline * buffer for the "reference line"; the * scanline against which delta encoding * is referenced. The reference line must * be initialized to be "white" (done elsewhere). */ m_refline = new byte[rowbytes + 1]; } else { /* 1d encoding */ m_refline = null; } return true; } /* * Routine for handling various errors/conditions. * Note how they are "glued into the decoder" by * overriding the definitions used by the decoder. */ private void Fax3Unexpected(string module) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: Bad code word at line {1} of {2} {3} (x {4})", m_tif.m_name, m_line, m_tif.IsTiled() ? "tile" : "strip", (m_tif.IsTiled() ? m_tif.m_curtile : m_tif.m_curstrip), m_a0); } private void Fax3Extension(string module) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: Uncompressed data (not supported) at line {1} of {2} {3} (x {4})", m_tif.m_name, m_line, m_tif.IsTiled() ? "tile" : "strip", (m_tif.IsTiled() ? m_tif.m_curtile : m_tif.m_curstrip), m_a0); } private void Fax3BadLength(string module) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "{0}: {1} at line {2} of {3} {4} (got {5}, expected {6})", m_tif.m_name, m_a0 < m_rowpixels ? "Premature EOL" : "Line length mismatch", m_line, m_tif.IsTiled() ? "tile" : "strip", (m_tif.IsTiled() ? m_tif.m_curtile : m_tif.m_curstrip), m_a0, m_rowpixels); } private void Fax3PrematureEOF(string module) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "{0}: Premature EOF at line {1} of {2} {3} (x {4})", m_tif.m_name, m_line, m_tif.IsTiled() ? "tile" : "strip", (m_tif.IsTiled() ? m_tif.m_curtile : m_tif.m_curstrip), m_a0); } /// /// Decode the requested amount of G3 1D-encoded data. /// private bool Fax3Decode1D(byte[] buffer, int offset, int count) { const string module = "Fax3Decode1D"; // current row's run array m_thisrun = m_curruns; while (count > 0) { m_a0 = 0; m_RunLength = 0; m_pa = m_thisrun; if (!SYNC_EOL()) { // premature EOF CLEANUP_RUNS(module); } else { bool expandSucceeded = EXPAND1D(module); if (expandSucceeded) { fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); offset += m_rowbytes; count -= m_rowbytes; m_line++; continue; } } // premature EOF fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); return false; } return true; } /// /// Decode the requested amount of G3 2D-encoded data. /// private bool Fax3Decode2D(byte[] buffer, int offset, int count) { const string module = "Fax3Decode2D"; while (count > 0) { m_a0 = 0; m_RunLength = 0; m_pa = m_curruns; m_thisrun = m_curruns; bool prematureEOF = false; if (!SYNC_EOL()) prematureEOF = true; if (!prematureEOF && !NeedBits8(1)) prematureEOF = true; if (!prematureEOF) { int is1D = GetBits(1); // 1D/2D-encoding tag bit ClrBits(1); m_pb = m_refruns; int b1 = m_runs[m_pb]; m_pb++; // next change on prev line bool expandSucceeded = false; if (is1D != 0) expandSucceeded = EXPAND1D(module); else expandSucceeded = EXPAND2D(module, b1); if (expandSucceeded) { fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); SETVALUE(0); // imaginary change for reference SWAP(ref m_curruns, ref m_refruns); offset += m_rowbytes; count -= m_rowbytes; m_line++; continue; } } else { // premature EOF CLEANUP_RUNS(module); } // premature EOF fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); return false; } return true; } /* * 1d-encode a row of pixels. The encoding is * a sequence of all-white or all-black spans * of pixels encoded with Huffman codes. */ private bool Fax3Encode1DRow() { int bs = 0; for (; ; ) { int span = find0span(m_buffer, m_offset, bs, m_rowpixels); /* white span */ putspan(span, false); bs += span; if (bs >= m_rowpixels) break; span = find1span(m_buffer, m_offset, bs, m_rowpixels); /* black span */ putspan(span, true); bs += span; if (bs >= m_rowpixels) break; } if ((m_mode & (FaxMode.ByteAlign | FaxMode.WordAlign)) != 0) { if (m_bit != 8) { /* byte-align */ flushBits(); } if ((m_mode & FaxMode.WordAlign) != 0 && !isShortAligned(m_tif.m_rawcp)) flushBits(); } return true; } /* * 2d-encode a row of pixels. Consult the CCITT * documentation for the algorithm. */ private bool Fax3Encode2DRow() { int a0 = 0; int a1 = (Fax3Encode2DRow_Pixel(m_buffer, m_offset, 0) != 0 ? 0 : finddiff(m_buffer, m_offset, 0, m_rowpixels, 0)); int b1 = (Fax3Encode2DRow_Pixel(m_refline, 0, 0) != 0 ? 0 : finddiff(m_refline, 0, 0, m_rowpixels, 0)); for (; ; ) { int b2 = finddiff2(m_refline, 0, b1, m_rowpixels, Fax3Encode2DRow_Pixel(m_refline, 0, b1)); if (b2 >= a1) { int d = b1 - a1; if (!(-3 <= d && d <= 3)) { /* horizontal mode */ int a2 = finddiff2(m_buffer, m_offset, a1, m_rowpixels, Fax3Encode2DRow_Pixel(m_buffer, m_offset, a1)); putcode(m_horizcode); if (a0 + a1 == 0 || Fax3Encode2DRow_Pixel(m_buffer, m_offset, a0) == 0) { putspan(a1 - a0, false); putspan(a2 - a1, true); } else { putspan(a1 - a0, true); putspan(a2 - a1, false); } a0 = a2; } else { /* vertical mode */ putcode(m_vcodes[d + 3]); a0 = a1; } } else { /* pass mode */ putcode(m_passcode); a0 = b2; } if (a0 >= m_rowpixels) break; a1 = finddiff(m_buffer, m_offset, a0, m_rowpixels, Fax3Encode2DRow_Pixel(m_buffer, m_offset, a0)); int color = Fax3Encode2DRow_Pixel(m_buffer, m_offset, a0); if (color == 0) color = 1; else color = 0; b1 = finddiff(m_refline, 0, a0, m_rowpixels, color); b1 = finddiff(m_refline, 0, b1, m_rowpixels, Fax3Encode2DRow_Pixel(m_buffer, m_offset, a0)); } return true; } private static int Fax3Encode2DRow_Pixel(byte[] buf, int bufOffset, int ix) { // some images caused out-of-bounds exception here. not sure why. maybe the images are // malformed or implementation is buggy. original libtiff does not produce exceptions // here. it's just read after the end of the buffer. // it's a fast fix (use last byte when requested any byte beyond buffer end) for // the problem that possibly should be reviewed. // (it's weird but produced output is byte-to-byte equal to libtiff's one) return (((buf[Math.Min(bufOffset + (ix >> 3), buf.Length - 1)]) >> (7 - (ix & 7))) & 1); } /// /// Encode a buffer of pixels. /// private bool Fax3Encode(byte[] buffer, int offset, int count) { m_buffer = buffer; m_offset = offset; while (count > 0) { if ((m_mode & FaxMode.NoEOL) == 0) Fax3PutEOL(); if (is2DEncoding()) { if (m_encoder == Fax3Encoder.useFax1DEncoder) { if (!Fax3Encode1DRow()) return false; m_encoder = Fax3Encoder.useFax2DEncoder; } else { if (!Fax3Encode2DRow()) return false; m_k--; } if (m_k == 0) { m_encoder = Fax3Encoder.useFax1DEncoder; m_k = m_maxk - 1; } else { Buffer.BlockCopy(m_buffer, m_offset, m_refline, 0, m_rowbytes); } } else { if (!Fax3Encode1DRow()) return false; } m_offset += m_rowbytes; count -= m_rowbytes; } return true; } private bool Fax3PostEncode() { if (m_bit != 8) flushBits(); return true; } private void InitCCITTFax3() { /* * Merge codec-specific tag information and * override parent get/set field methods. */ m_tif.MergeFieldInfo(m_faxFieldInfo, m_faxFieldInfo.Length); /* * Allocate state block so tag methods have storage to record values. */ m_rw_mode = m_tif.m_mode; m_parentTagMethods = m_tif.m_tagmethods; m_tif.m_tagmethods = m_tagMethods; m_groupoptions = 0; m_recvparams = 0; m_subaddress = null; m_faxdcs = null; if (m_rw_mode == Tiff.O_RDONLY) { // FIXME: improve for in place update m_tif.m_flags |= TiffFlags.NoBitRev; // decoder does bit reversal } m_runs = null; m_tif.SetField(TiffTag.FAXFILLFUNC, new Tiff.FaxFillFunc(fax3FillRuns)); m_refline = null; m_decoder = Decoder.useFax3_1DDecoder; m_encodingFax4 = false; } private bool TIFFInitCCITTFax3() { InitCCITTFax3(); m_tif.MergeFieldInfo(m_fax3FieldInfo, m_fax3FieldInfo.Length); /* * The default format is Class/F-style w/o RTC. */ return m_tif.SetField(TiffTag.FAXMODE, FaxMode.ClassF); } /* * CCITT Group 3 FAX Encoding. */ private void flushBits() { if (m_tif.m_rawcc >= m_tif.m_rawdatasize) m_tif.flushData1(); m_tif.m_rawdata[m_tif.m_rawcp] = (byte)m_data; m_tif.m_rawcp++; m_tif.m_rawcc++; m_data = 0; m_bit = 8; } /* * Write a variable-length bit-value to * the output stream. Values are * assumed to be at most 16 bits. */ private void putBits(int bits, int length) { while (length > m_bit) { m_data |= bits >> (length - m_bit); length -= m_bit; flushBits(); } m_data |= (bits & m_msbmask[length]) << (m_bit - length); m_bit -= length; if (m_bit == 0) flushBits(); } /* * Write a code to the output stream. */ private void putcode(tableEntry te) { putBits(te.code, te.length); } /* * Write the sequence of codes that describes * the specified span of zero's or one's. The * appropriate table that holds the make-up and * terminating codes is supplied. */ private void putspan(int span, bool useBlack) { short[] entries = null; if (useBlack) entries = m_faxBlackCodes; else entries = m_faxWhiteCodes; tableEntry te = tableEntry.FromArray(entries, 63 + (2560 >> 6)); while (span >= 2624) { putBits(te.code, te.length); span -= te.runlen; } if (span >= 64) { te = tableEntry.FromArray(entries, 63 + (span >> 6)); Debug.Assert(te.runlen == 64 * (span >> 6)); putBits(te.code, te.length); span -= te.runlen; } te = tableEntry.FromArray(entries, span); putBits(te.code, te.length); } /* * Write an EOL code to the output stream. The zero-fill * logic for byte-aligning encoded scanlines is handled * here. We also handle writing the tag bit for the next * scanline when doing 2d encoding. */ private void Fax3PutEOL() { if ((m_groupoptions & Group3Option.FillBits) != 0) { /* * Force bit alignment so EOL will terminate on * a byte boundary. That is, force the bit alignment * to 16-12 = 4 before putting out the EOL code. */ int align = 8 - 4; if (align != m_bit) { if (align > m_bit) align = m_bit + (8 - align); else align = m_bit - align; putBits(0, align); } } int code = EOL_CODE; int length = 12; if (is2DEncoding()) { code = (code << 1); if (m_encoder == Fax3Encoder.useFax1DEncoder) code++; length++; } putBits(code, length); } /* * Append a run to the run length array for the * current row and reset decoding state. */ private void SETVALUE(int x) { m_runs[m_pa] = m_RunLength + x; m_pa++; m_a0 += x; m_RunLength = 0; } /* * Cleanup the array of runs after decoding a row. * We adjust final runs to insure the user buffer is not * overwritten and/or undecoded area is white filled. */ private void CLEANUP_RUNS(string module) { if (m_RunLength != 0) SETVALUE(0); if (m_a0 != m_rowpixels) { Fax3BadLength(module); while (m_a0 > m_rowpixels && m_pa > m_thisrun) { m_pa--; m_a0 -= m_runs[m_pa]; } if (m_a0 < m_rowpixels) { if (m_a0 < 0) m_a0 = 0; if (((m_pa - m_thisrun) & 1) != 0) SETVALUE(0); SETVALUE(m_rowpixels - m_a0); } else if (m_a0 > m_rowpixels) { SETVALUE(m_rowpixels); SETVALUE(0); } } } private void handlePrematureEOFinExpand2D(string module) { Fax3PrematureEOF(module); CLEANUP_RUNS(module); } /* * Decode a line of 1D-encoded data. */ private bool EXPAND1D(string module) { faxTableEntry TabEnt; bool decodingDone = false; bool whiteDecodingDone = false; bool blackDecodingDone = false; for (; ; ) { for (; ; ) { if (!LOOKUP16(out TabEnt, 12, false)) { Fax3PrematureEOF(module); CLEANUP_RUNS(module); return false; } switch (TabEnt.State) { case S_EOL: m_rowpixels = 1; decodingDone = true; break; case S_TermW: SETVALUE(TabEnt.Param); whiteDecodingDone = true; break; case S_MakeUpW: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "WhiteTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (decodingDone || whiteDecodingDone) break; } if (decodingDone) break; if (m_a0 >= m_rowpixels) break; for (; ; ) { if (!LOOKUP16(out TabEnt, 13, true)) { Fax3PrematureEOF(module); CLEANUP_RUNS(module); return false; } switch (TabEnt.State) { case S_EOL: m_EOLcnt = 1; decodingDone = true; break; case S_TermB: SETVALUE(TabEnt.Param); blackDecodingDone = true; break; case S_MakeUpB: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "BlackTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (decodingDone || blackDecodingDone) break; } if (decodingDone) break; if (m_a0 >= m_rowpixels) break; if (m_runs[m_pa - 1] == 0 && m_runs[m_pa - 2] == 0) m_pa -= 2; whiteDecodingDone = false; blackDecodingDone = false; } CLEANUP_RUNS(module); return true; } /* * Expand a row of 2D-encoded data. */ private bool EXPAND2D(string module, int b1) { faxTableEntry TabEnt; bool decodingDone = false; while (m_a0 < m_rowpixels) { if (!LOOKUP8(out TabEnt, 7)) { handlePrematureEOFinExpand2D(module); return false; } switch (TabEnt.State) { case S_Pass: CHECK_b1(ref b1); b1 += m_runs[m_pb]; m_pb++; m_RunLength += b1 - m_a0; m_a0 = b1; b1 += m_runs[m_pb]; m_pb++; break; case S_Horiz: if (((m_pa - m_thisrun) & 1) != 0) { for (; ; ) { /* black first */ if (!LOOKUP16(out TabEnt, 13, true)) { handlePrematureEOFinExpand2D(module); return false; } bool doneWhite2d = false; switch (TabEnt.State) { case S_TermB: SETVALUE(TabEnt.Param); doneWhite2d = true; break; case S_MakeUpB: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "BlackTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (doneWhite2d || decodingDone) break; } if (decodingDone) break; for (; ; ) { /* then white */ if (!LOOKUP16(out TabEnt, 12, false)) { handlePrematureEOFinExpand2D(module); return false; } bool doneBlack2d = false; switch (TabEnt.State) { case S_TermW: SETVALUE(TabEnt.Param); doneBlack2d = true; break; case S_MakeUpW: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "WhiteTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (doneBlack2d || decodingDone) break; } if (decodingDone) break; } else { for (; ; ) { /* white first */ if (!LOOKUP16(out TabEnt, 12, false)) { handlePrematureEOFinExpand2D(module); return false; } bool doneWhite2d = false; switch (TabEnt.State) { case S_TermW: SETVALUE(TabEnt.Param); doneWhite2d = true; break; case S_MakeUpW: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "WhiteTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (doneWhite2d || decodingDone) break; } if (decodingDone) break; for (; ; ) { /* then black */ if (!LOOKUP16(out TabEnt, 13, true)) { handlePrematureEOFinExpand2D(module); return false; } bool doneBlack2d = false; switch (TabEnt.State) { case S_TermB: SETVALUE(TabEnt.Param); doneBlack2d = true; break; case S_MakeUpB: case S_MakeUp: m_a0 += TabEnt.Param; m_RunLength += TabEnt.Param; break; default: /* "BlackTable" */ Fax3Unexpected(module); decodingDone = true; break; } if (doneBlack2d || decodingDone) break; } } if (decodingDone) break; CHECK_b1(ref b1); break; case S_V0: CHECK_b1(ref b1); SETVALUE(b1 - m_a0); b1 += m_runs[m_pb]; m_pb++; break; case S_VR: CHECK_b1(ref b1); SETVALUE(b1 - m_a0 + TabEnt.Param); b1 += m_runs[m_pb]; m_pb++; break; case S_VL: CHECK_b1(ref b1); SETVALUE(b1 - m_a0 - TabEnt.Param); m_pb--; b1 -= m_runs[m_pb]; break; case S_Ext: m_runs[m_pa] = m_rowpixels - m_a0; m_pa++; Fax3Extension(module); decodingDone = true; break; case S_EOL: m_runs[m_pa] = m_rowpixels - m_a0; m_pa++; if (!NeedBits8(4)) { handlePrematureEOFinExpand2D(module); return false; } if (GetBits(4) != 0) { /* "EOL" */ Fax3Unexpected(module); } ClrBits(4); m_EOLcnt = 1; decodingDone = true; break; default: Fax3Unexpected(module); decodingDone = true; break; } } if (!decodingDone && m_RunLength != 0) { if (m_RunLength + m_a0 < m_rowpixels) { /* expect a final V0 */ if (!NeedBits8(1)) { handlePrematureEOFinExpand2D(module); return false; } if (GetBits(1) == 0) { /* "MainTable" */ Fax3Unexpected(module); decodingDone = true; } if (!decodingDone) ClrBits(1); } if (!decodingDone) SETVALUE(0); } CLEANUP_RUNS(module); return true; } /* * CCITT Group 3 1-D Modified Huffman RLE Compression Support. * (Compression algorithms 2 and 32771) */ private bool TIFFInitCCITTRLE() { /* reuse G3 support */ InitCCITTFax3(); m_decoder = Decoder.useFax3RLEDecoder; /* * Suppress RTC+EOLs when encoding and byte-align data. */ return m_tif.SetField(TiffTag.FAXMODE, FaxMode.NoRTC | FaxMode.NoEOL | FaxMode.ByteAlign); } private bool TIFFInitCCITTRLEW() { /* reuse G3 support */ InitCCITTFax3(); m_decoder = Decoder.useFax3RLEDecoder; /* * Suppress RTC+EOLs when encoding and word-align data. */ return m_tif.SetField(TiffTag.FAXMODE, FaxMode.NoRTC | FaxMode.NoEOL | FaxMode.WordAlign); } /// /// Decode the requested amount of RLE-encoded data. /// private bool Fax3DecodeRLE(byte[] buffer, int offset, int count) { const string module = "Fax3DecodeRLE"; int thisrun = m_curruns; // current row's run array while (count > 0) { m_a0 = 0; m_RunLength = 0; m_pa = thisrun; bool expandSucceeded = EXPAND1D(module); if (expandSucceeded) { fill(buffer, offset, m_runs, thisrun, m_pa, m_rowpixels); // Cleanup at the end of the row. if ((m_mode & FaxMode.ByteAlign) != 0) { int n = m_bit - (m_bit & ~7); ClrBits(n); } else if ((m_mode & FaxMode.WordAlign) != 0) { int n = m_bit - (m_bit & ~15); ClrBits(n); if (m_bit == 0 && !isShortAligned(m_tif.m_rawcp)) m_tif.m_rawcp++; } offset += m_rowbytes; count -= m_rowbytes; m_line++; continue; } // premature EOF fill(buffer, offset, m_runs, thisrun, m_pa, m_rowpixels); return false; } return true; } /* * CCITT Group 4 (T.6) Facsimile-compatible * Compression Scheme Support. */ private bool TIFFInitCCITTFax4() { /* reuse G3 support */ InitCCITTFax3(); m_tif.MergeFieldInfo(m_fax4FieldInfo, m_fax4FieldInfo.Length); m_decoder = Decoder.useFax4Decoder; m_encodingFax4 = true; /* * Suppress RTC at the end of each strip. */ return m_tif.SetField(TiffTag.FAXMODE, FaxMode.NoRTC); } /// /// Decode the requested amount of G4-encoded data. /// private bool Fax4Decode(byte[] buffer, int offset, int count) { const string module = "Fax4Decode"; while (count > 0) { m_a0 = 0; m_RunLength = 0; m_thisrun = m_curruns; m_pa = m_curruns; m_pb = m_refruns; int b1 = m_runs[m_pb]; m_pb++; // next change on prev line bool expandSucceeded = EXPAND2D(module, b1); if (expandSucceeded && m_EOLcnt != 0) expandSucceeded = false; if (expandSucceeded) { fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); SETVALUE(0); // imaginary change for reference SWAP(ref m_curruns, ref m_refruns); offset += m_rowbytes; count -= m_rowbytes; m_line++; continue; } NeedBits16(13); ClrBits(13); fill(buffer, offset, m_runs, m_thisrun, m_pa, m_rowpixels); return false; } return true; } /// /// Encode the requested amount of data. /// private bool Fax4Encode(byte[] buffer, int offset, int count) { m_buffer = buffer; m_offset = offset; while (count > 0) { if (!Fax3Encode2DRow()) return false; Buffer.BlockCopy(m_buffer, m_offset, m_refline, 0, m_rowbytes); m_offset += m_rowbytes; count -= m_rowbytes; } return true; } private bool Fax4PostEncode() { // terminate strip w/ EOFB putBits(EOL_CODE, 12); putBits(EOL_CODE, 12); if (m_bit != 8) flushBits(); return true; } } #endregion #region CCITTCodecTagMethods class CCITTCodecTagMethods : TiffTagMethods { public override bool SetField(Tiff tif, TiffTag tag, FieldValue[] ap) { CCITTCodec sp = tif.m_currentCodec as CCITTCodec; Debug.Assert(sp != null); switch (tag) { case TiffTag.FAXMODE: sp.m_mode = (FaxMode)ap[0].ToShort(); return true; /* NB: pseudo tag */ case TiffTag.FAXFILLFUNC: sp.fill = ap[0].Value as Tiff.FaxFillFunc; return true; /* NB: pseudo tag */ case TiffTag.Group3Options: /* XXX: avoid reading options if compression mismatches. */ if (tif.m_dir.td_compression == Compression.CCITTFAX3) sp.m_groupoptions = (Group3Option)ap[0].ToShort(); break; case TiffTag.Group4Options: /* XXX: avoid reading options if compression mismatches. */ if (tif.m_dir.td_compression == Compression.CCITTFAX4) sp.m_groupoptions = (Group3Option)ap[0].ToShort(); break; case TiffTag.BadFaxLines: sp.m_badfaxlines = ap[0].ToInt(); break; case TiffTag.CleanFaxData: sp.m_cleanfaxdata = (CleanFaxData)ap[0].ToByte(); break; case TiffTag.ConsecutiveBadFaxLines: sp.m_badfaxrun = ap[0].ToInt(); break; case TiffTag.FAXRECVPARAMS: sp.m_recvparams = ap[0].ToInt(); break; case TiffTag.FAXSUBADDRESS: Tiff.setString(out sp.m_subaddress, ap[0].ToString()); break; case TiffTag.FAXRECVTIME: sp.m_recvtime = ap[0].ToInt(); break; case TiffTag.FAXDCS: Tiff.setString(out sp.m_faxdcs, ap[0].ToString()); break; default: return base.SetField(tif, tag, ap); } TiffFieldInfo fip = tif.FieldWithTag(tag); if (fip != null) tif.setFieldBit(fip.Bit); else return false; tif.m_flags |= TiffFlags.DirtyDirect; return true; } public override FieldValue[] GetField(Tiff tif, TiffTag tag) { CCITTCodec sp = tif.m_currentCodec as CCITTCodec; Debug.Assert(sp != null); FieldValue[] result = new FieldValue[1]; switch (tag) { case TiffTag.FAXMODE: result[0].Set(sp.m_mode); break; case TiffTag.FAXFILLFUNC: result[0].Set(sp.fill); break; case TiffTag.Group3Options: case TiffTag.Group4Options: result[0].Set(sp.m_groupoptions); break; case TiffTag.BadFaxLines: result[0].Set(sp.m_badfaxlines); break; case TiffTag.CleanFaxData: result[0].Set(sp.m_cleanfaxdata); break; case TiffTag.ConsecutiveBadFaxLines: result[0].Set(sp.m_badfaxrun); break; case TiffTag.FAXRECVPARAMS: result[0].Set(sp.m_recvparams); break; case TiffTag.FAXSUBADDRESS: result[0].Set(sp.m_subaddress); break; case TiffTag.FAXRECVTIME: result[0].Set(sp.m_recvtime); break; case TiffTag.FAXDCS: result[0].Set(sp.m_faxdcs); break; default: return base.GetField(tif, tag); } return result; } public override void PrintDir(Tiff tif, Stream fd, TiffPrintFlags flags) { CCITTCodec sp = tif.m_currentCodec as CCITTCodec; Debug.Assert(sp != null); if (tif.fieldSet(CCITTCodec.FIELD_OPTIONS)) { string sep = " "; if (tif.m_dir.td_compression == Compression.CCITTFAX4) { Tiff.fprintf(fd, " Group 4 Options:"); if ((sp.m_groupoptions & Group3Option.UnCompressed) != 0) Tiff.fprintf(fd, "{0}uncompressed data", sep); } else { Tiff.fprintf(fd, " Group 3 Options:"); if ((sp.m_groupoptions & Group3Option.Encoding2D) != 0) { Tiff.fprintf(fd, "{0}2-d encoding", sep); sep = "+"; } if ((sp.m_groupoptions & Group3Option.FillBits) != 0) { Tiff.fprintf(fd, "{0}EOL padding", sep); sep = "+"; } if ((sp.m_groupoptions & Group3Option.UnCompressed) != 0) Tiff.fprintf(fd, "{0}uncompressed data", sep); } Tiff.fprintf(fd, " ({0} = 0x{1:x})\n", sp.m_groupoptions, sp.m_groupoptions); } if (tif.fieldSet(CCITTCodec.FIELD_CLEANFAXDATA)) { Tiff.fprintf(fd, " Fax Data:"); switch (sp.m_cleanfaxdata) { case CleanFaxData.Clean: Tiff.fprintf(fd, " clean"); break; case CleanFaxData.Regenerated: Tiff.fprintf(fd, " receiver regenerated"); break; case CleanFaxData.UnClean: Tiff.fprintf(fd, " uncorrected errors"); break; } Tiff.fprintf(fd, " ({0} = 0x{1:x})\n", sp.m_cleanfaxdata, sp.m_cleanfaxdata); } if (tif.fieldSet(CCITTCodec.FIELD_BADFAXLINES)) Tiff.fprintf(fd, " Bad Fax Lines: {0}\n", sp.m_badfaxlines); if (tif.fieldSet(CCITTCodec.FIELD_BADFAXRUN)) Tiff.fprintf(fd, " Consecutive Bad Fax Lines: {0}\n", sp.m_badfaxrun); if (tif.fieldSet(CCITTCodec.FIELD_RECVPARAMS)) Tiff.fprintf(fd, " Fax Receive Parameters: {0,8:x}\n", sp.m_recvparams); if (tif.fieldSet(CCITTCodec.FIELD_SUBADDRESS)) Tiff.fprintf(fd, " Fax SubAddress: {0}\n", sp.m_subaddress); if (tif.fieldSet(CCITTCodec.FIELD_RECVTIME)) Tiff.fprintf(fd, " Fax Receive Time: {0} secs\n", sp.m_recvtime); if (tif.fieldSet(CCITTCodec.FIELD_FAXDCS)) Tiff.fprintf(fd, " Fax DCS: {0}\n", sp.m_faxdcs); } } #endregion #region CodecWithPredictor /// /// Codecs that want to support the PredictionScheme tag should inherit from /// this class instead of TiffCodec. /// /// Such codecs should not override default TiffCodec's methods for /// decode|encode setup and encoding|decoding of row|tile|strip. /// Codecs with predictor support should override equivalent methods /// provided by this class. /// /// If codec wants to provide custom tag get|set|print methods, then /// it should pass pointer to a object derived from TiffTagMethods /// as parameter to TIFFPredictorInit /// class CodecWithPredictor : TiffCodec { public const int FIELD_PREDICTOR = (FieldBit.Codec + 0); private enum PredictorType { ptNone, ptHorAcc8, ptHorAcc16, ptHorAcc32, ptSwabHorAcc16, ptSwabHorAcc32, ptHorDiff8, ptHorDiff16, ptHorDiff32, ptFpAcc, ptFpDiff, }; private static TiffFieldInfo[] m_predictFieldInfo = { new TiffFieldInfo(TiffTag.Predictor, 1, 1, TiffType.Short, CodecWithPredictor.FIELD_PREDICTOR, false, false, "PredictionScheme"), }; /// /// predictor tag value /// private PredictionScheme m_predictor; /// /// sample stride over data /// private int m_stride; /// /// tile/strip row size /// private int m_rowSize; private TiffTagMethods m_parentTagMethods; private TiffTagMethods m_tagMethods; private TiffTagMethods m_childTagMethods; // could be null private bool m_passThruDecode; private bool m_passThruEncode; /// /// horizontal differencer/accumulator /// private PredictorType m_predictorType; public CodecWithPredictor(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { m_tagMethods = new CodecWithPredictorTagMethods(); } // tagMethods can be null public void TIFFPredictorInit(TiffTagMethods tagMethods) { // Merge codec-specific tag information and override parent get/set field methods. m_tif.MergeFieldInfo(m_predictFieldInfo, m_predictFieldInfo.Length); m_childTagMethods = tagMethods; m_parentTagMethods = m_tif.m_tagmethods; m_tif.m_tagmethods = m_tagMethods; m_predictor = PredictionScheme.None; // default value m_predictorType = PredictorType.ptNone; // no predictor method } public void TIFFPredictorCleanup() { m_tif.m_tagmethods = m_parentTagMethods; } ////////////////////////////////////////////////////////////////////////// // WARNING: do not override this methods! // please override their equivalents listed below /// /// Setups the decoder part of the codec. /// /// /// true if this codec successfully setup its decoder part and can decode data; /// otherwise, false. /// /// /// SetupDecode is called once before /// . public override bool SetupDecode() { return PredictorSetupDecode(); } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { if (!m_passThruDecode) return PredictorDecodeRow(buffer, offset, count, plane); return predictor_decoderow(buffer, offset, count, plane); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { if (!m_passThruDecode) return PredictorDecodeTile(buffer, offset, count, plane); return predictor_decodestrip(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { if (!m_passThruDecode) return PredictorDecodeTile(buffer, offset, count, plane); return predictor_decodetile(buffer, offset, count, plane); } /// /// Setups the encoder part of the codec. /// /// /// true if this codec successfully setup its encoder part and can encode data; /// otherwise, false. /// /// /// SetupEncode is called once before /// . public override bool SetupEncode() { return PredictorSetupEncode(); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { if (!m_passThruEncode) return PredictorEncodeRow(buffer, offset, count, plane); return predictor_encoderow(buffer, offset, count, plane); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { if (!m_passThruEncode) return PredictorEncodeTile(buffer, offset, count, plane); return predictor_encodestrip(buffer, offset, count, plane); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { if (!m_passThruEncode) return PredictorEncodeTile(buffer, offset, count, plane); return predictor_encodetile(buffer, offset, count, plane); } ////////////////////////////////////////////////////////////////////////// // derived class should override methods below instead of // TiffCodec's methods public virtual bool predictor_setupdecode() { return base.SetupDecode(); } public virtual bool predictor_decoderow(byte[] buffer, int offset, int count, short plane) { return base.DecodeRow(buffer, offset, count, plane); } public virtual bool predictor_decodestrip(byte[] buffer, int offset, int count, short plane) { return base.DecodeStrip(buffer, offset, count, plane); } public virtual bool predictor_decodetile(byte[] buffer, int offset, int count, short plane) { return base.DecodeTile(buffer, offset, count, plane); } public virtual bool predictor_setupencode() { return base.SetupEncode(); } public virtual bool predictor_encoderow(byte[] buffer, int offset, int count, short plane) { return base.EncodeRow(buffer, offset, count, plane); } public virtual bool predictor_encodestrip(byte[] buffer, int offset, int count, short plane) { return base.EncodeStrip(buffer, offset, count, plane); } public virtual bool predictor_encodetile(byte[] buffer, int offset, int count, short plane) { return base.EncodeTile(buffer, offset, count, plane); } public PredictionScheme GetPredictorValue() { return m_predictor; } public void SetPredictorValue(PredictionScheme value) { m_predictor = value; } // retrieves child object's tag methods (could be null) public TiffTagMethods GetChildTagMethods() { return m_childTagMethods; } private void predictorFunc(byte[] buffer, int offset, int count) { switch (m_predictorType) { case PredictorType.ptHorAcc8: horAcc8(buffer, offset, count); break; case PredictorType.ptHorAcc16: horAcc16(buffer, offset, count); break; case PredictorType.ptHorAcc32: horAcc32(buffer, offset, count); break; case PredictorType.ptSwabHorAcc16: swabHorAcc16(buffer, offset, count); break; case PredictorType.ptSwabHorAcc32: swabHorAcc32(buffer, offset, count); break; case PredictorType.ptHorDiff8: horDiff8(buffer, offset, count); break; case PredictorType.ptHorDiff16: horDiff16(buffer, offset, count); break; case PredictorType.ptHorDiff32: horDiff32(buffer, offset, count); break; case PredictorType.ptFpAcc: fpAcc(buffer, offset, count); break; case PredictorType.ptFpDiff: fpDiff(buffer, offset, count); break; } } private void horAcc8(byte[] buffer, int offset, int count) { int cp = offset; if (count > m_stride) { count -= m_stride; // Pipeline the most common cases. if (m_stride == 3) { int cr = buffer[cp]; int cg = buffer[cp + 1]; int cb = buffer[cp + 2]; do { count -= 3; cp += 3; cr += buffer[cp]; buffer[cp] = (byte)cr; cg += buffer[cp + 1]; buffer[cp + 1] = (byte)cg; cb += buffer[cp + 2]; buffer[cp + 2] = (byte)cb; } while (count > 0); } else if (m_stride == 4) { int cr = buffer[cp]; int cg = buffer[cp + 1]; int cb = buffer[cp + 2]; int ca = buffer[cp + 3]; do { count -= 4; cp += 4; cr += buffer[cp]; buffer[cp] = (byte)cr; cg += buffer[cp + 1]; buffer[cp + 1] = (byte)cg; cb += buffer[cp + 2]; buffer[cp + 2] = (byte)cb; ca += buffer[cp + 3]; buffer[cp + 3] = (byte)ca; } while (count > 0); } else { do { for (int i = m_stride; i > 0; i--) { buffer[cp + m_stride] = (byte)(buffer[cp + m_stride] + buffer[cp]); cp++; } count -= m_stride; } while (count > 0); } } } private void horAcc16(byte[] buffer, int offset, int count) { short[] wBuffer = Tiff.ByteArrayToShorts(buffer, offset, count); int wOffset = 0; int wCount = count / 2; if (wCount > m_stride) { wCount -= m_stride; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] += wBuffer[wOffset]; wOffset++; } wCount -= m_stride; } while (wCount > 0); } Tiff.ShortsToByteArray(wBuffer, 0, count / 2, buffer, offset); } private void horAcc32(byte[] buffer, int offset, int count) { int[] wBuffer = Tiff.ByteArrayToInts(buffer, offset, count); int wOffset = 0; int wCount = count / 4; if (wCount > m_stride) { wCount -= m_stride; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] += wBuffer[wOffset]; wOffset++; } wCount -= m_stride; } while (wCount > 0); } Tiff.IntsToByteArray(wBuffer, 0, count / 4, buffer, offset); } private void swabHorAcc16(byte[] buffer, int offset, int count) { short[] wBuffer = Tiff.ByteArrayToShorts(buffer, offset, count); int wOffset = 0; int wCount = count / 2; if (wCount > m_stride) { Tiff.SwabArrayOfShort(wBuffer, wCount); wCount -= m_stride; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] += wBuffer[wOffset]; wOffset++; } wCount -= m_stride; } while (wCount > 0); } Tiff.ShortsToByteArray(wBuffer, 0, count / 2, buffer, offset); } private void swabHorAcc32(byte[] buffer, int offset, int count) { int[] wBuffer = Tiff.ByteArrayToInts(buffer, offset, count); int wOffset = 0; int wCount = count / 4; if (wCount > m_stride) { Tiff.SwabArrayOfLong(wBuffer, wCount); wCount -= m_stride; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] += wBuffer[wOffset]; wOffset++; } wCount -= m_stride; } while (wCount > 0); } Tiff.IntsToByteArray(wBuffer, 0, count / 4, buffer, offset); } private void horDiff8(byte[] buffer, int offset, int count) { if (count > m_stride) { count -= m_stride; int cp = offset; // Pipeline the most common cases. if (m_stride == 3) { int r2 = buffer[cp]; int g2 = buffer[cp + 1]; int b2 = buffer[cp + 2]; do { int r1 = buffer[cp + 3]; buffer[cp + 3] = (byte)(r1 - r2); r2 = r1; int g1 = buffer[cp + 4]; buffer[cp + 4] = (byte)(g1 - g2); g2 = g1; int b1 = buffer[cp + 5]; buffer[cp + 5] = (byte)(b1 - b2); b2 = b1; cp += 3; } while ((count -= 3) > 0); } else if (m_stride == 4) { int r2 = buffer[cp]; int g2 = buffer[cp + 1]; int b2 = buffer[cp + 2]; int a2 = buffer[cp + 3]; do { int r1 = buffer[cp + 4]; buffer[cp + 4] = (byte)(r1 - r2); r2 = r1; int g1 = buffer[cp + 5]; buffer[cp + 5] = (byte)(g1 - g2); g2 = g1; int b1 = buffer[cp + 6]; buffer[cp + 6] = (byte)(b1 - b2); b2 = b1; int a1 = buffer[cp + 7]; buffer[cp + 7] = (byte)(a1 - a2); a2 = a1; cp += 4; } while ((count -= 4) > 0); } else { cp += count - 1; do { for (int i = m_stride; i > 0; i--) { buffer[cp + m_stride] -= buffer[cp]; cp--; } } while ((count -= m_stride) > 0); } } } private void horDiff16(byte[] buffer, int offset, int count) { short[] wBuffer = Tiff.ByteArrayToShorts(buffer, offset, count); int wOffset = 0; int wCount = count / 2; if (wCount > m_stride) { wCount -= m_stride; wOffset += wCount - 1; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] -= wBuffer[wOffset]; wOffset--; } wCount -= m_stride; } while (wCount > 0); } Tiff.ShortsToByteArray(wBuffer, 0, count / 2, buffer, offset); } private void horDiff32(byte[] buffer, int offset, int count) { int[] wBuffer = Tiff.ByteArrayToInts(buffer, offset, count); int wOffset = 0; int wCount = count / 4; if (wCount > m_stride) { wCount -= m_stride; wOffset += wCount - 1; do { for (int i = m_stride; i > 0; i--) { wBuffer[wOffset + m_stride] -= wBuffer[wOffset]; wOffset--; } wCount -= m_stride; } while (wCount > 0); } Tiff.IntsToByteArray(wBuffer, 0, count / 4, buffer, offset); } /// /// Floating point predictor accumulation routine. /// private void fpAcc(byte[] buffer, int offset, int count) { int bps = m_tif.m_dir.td_bitspersample / 8; int wCount = count / bps; int left = count; int cp = offset; while (left > m_stride) { for (int i = m_stride; i > 0; i--) { buffer[cp + m_stride] += buffer[cp]; cp++; } left -= m_stride; } byte[] tmp = new byte[count]; Buffer.BlockCopy(buffer, offset, tmp, 0, count); for (int i = 0; i < wCount; i++) { for (int b = 0; b < bps; b++) buffer[offset + bps * i + b] = tmp[(bps - b - 1) * wCount + i]; } } /// /// Floating point predictor differencing routine. /// private void fpDiff(byte[] buffer, int offset, int count) { byte[] tmp = new byte[count]; Buffer.BlockCopy(buffer, offset, tmp, 0, count); int bps = m_tif.m_dir.td_bitspersample / 8; int wCount = count / bps; for (int c = 0; c < wCount; c++) { for (int b = 0; b < bps; b++) buffer[offset + (bps - b - 1) * wCount + c] = tmp[bps * c + b]; } int cp = offset + count - m_stride - 1; for (int c = count; c > m_stride; c -= m_stride) { for (int i = m_stride; i > 0; i--) { buffer[cp + m_stride] -= buffer[cp]; cp--; } } } /// /// Decode a scanline and apply the predictor routine. /// private bool PredictorDecodeRow(byte[] buffer, int offset, int count, short plane) { Debug.Assert(m_predictorType != PredictorType.ptNone); if (predictor_decoderow(buffer, offset, count, plane)) { predictorFunc(buffer, offset, count); return true; } return false; } /// /// Decode a tile/strip and apply the predictor routine. Note that horizontal differencing /// must be done on a row-by-row basis. The width of a "row" has already been calculated /// at pre-decode time according to the strip/tile dimensions. /// private bool PredictorDecodeTile(byte[] buffer, int offset, int count, short plane) { if (predictor_decodetile(buffer, offset, count, plane)) { Debug.Assert(m_rowSize > 0); Debug.Assert(m_predictorType != PredictorType.ptNone); while (count > 0) { predictorFunc(buffer, offset, m_rowSize); count -= m_rowSize; offset += m_rowSize; } return true; } return false; } private bool PredictorEncodeRow(byte[] buffer, int offset, int count, short plane) { Debug.Assert(m_predictorType != PredictorType.ptNone); // XXX horizontal differencing alters user's data XXX predictorFunc(buffer, offset, count); return predictor_encoderow(buffer, offset, count, plane); } private bool PredictorEncodeTile(byte[] buffer, int offset, int count, short plane) { Debug.Assert(m_predictorType != PredictorType.ptNone); // Do predictor manipulation in a working buffer to avoid altering // the callers buffer. http://trac.osgeo.org/gdal/ticket/1965 byte[] working_copy = new byte[count]; Buffer.BlockCopy(buffer, 0, working_copy, 0, count); Debug.Assert(m_rowSize > 0); Debug.Assert((count % m_rowSize) == 0); int cc = count; while (cc > 0) { predictorFunc(working_copy, offset, m_rowSize); cc -= m_rowSize; offset += m_rowSize; } return predictor_encodetile(working_copy, 0, count, plane); } private bool PredictorSetupDecode() { if (!predictor_setupdecode() || !PredictorSetup()) return false; m_passThruDecode = true; if (m_predictor == PredictionScheme.Horizontal) { switch (m_tif.m_dir.td_bitspersample) { case 8: m_predictorType = PredictorType.ptHorAcc8; break; case 16: m_predictorType = PredictorType.ptHorAcc16; break; case 32: m_predictorType = PredictorType.ptHorAcc32; break; } // Override default decoding method with one that does the predictor stuff. m_passThruDecode = false; // If the data is horizontally differenced 16-bit data that requires byte-swapping, // then it must be byte swapped before the accumulation step. We do this with a // special-purpose method and override the normal post decoding logic that the // library setup when the directory was read. if ((m_tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) { if (m_predictorType == PredictorType.ptHorAcc16) { m_predictorType = PredictorType.ptSwabHorAcc16; m_tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmNone; } else if (m_predictorType == PredictorType.ptHorAcc32) { m_predictorType = PredictorType.ptSwabHorAcc32; m_tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmNone; } } } else if (m_predictor == PredictionScheme.FloatingPoint) { m_predictorType = PredictorType.ptFpAcc; // Override default decoding method with one that does the predictor stuff. m_passThruDecode = false; // The data should not be swapped outside of the floating point predictor, the // accumulation method should return bytes in the native order. if ((m_tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) m_tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmNone; // Allocate buffer to keep the decoded bytes before rearranging in the right order } return true; } private bool PredictorSetupEncode() { if (!predictor_setupencode() || !PredictorSetup()) return false; m_passThruEncode = true; if (m_predictor == PredictionScheme.Horizontal) { switch (m_tif.m_dir.td_bitspersample) { case 8: m_predictorType = PredictorType.ptHorDiff8; break; case 16: m_predictorType = PredictorType.ptHorDiff16; break; case 32: m_predictorType = PredictorType.ptHorDiff32; break; } // Override default encoding method with one that does the predictor stuff. m_passThruEncode = false; } else if (m_predictor == PredictionScheme.FloatingPoint) { m_predictorType = PredictorType.ptFpDiff; // Override default encoding method with one that does the predictor stuff. m_passThruEncode = false; } return true; } private bool PredictorSetup() { const string module = "PredictorSetup"; TiffDirectory td = m_tif.m_dir; switch (m_predictor) { case PredictionScheme.None: // no differencing return true; case PredictionScheme.Horizontal: if (td.td_bitspersample != 8 && td.td_bitspersample != 16 && td.td_bitspersample != 32) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Horizontal differencing \"PredictionScheme\" not supported with {0}-bit samples", td.td_bitspersample); return false; } break; case PredictionScheme.FloatingPoint: if (td.td_sampleformat != SampleFormat.IEEEFloat) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Floating point \"PredictionScheme\" not supported with {0} data format", td.td_sampleformat); return false; } break; default: Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "\"PredictionScheme\" value {0} not supported", m_predictor); return false; } m_stride = (td.td_planarconfig == PlanarConfig.Contig ? (int)td.td_samplesperpixel : 1); // Calculate the scanline/tile-width size in bytes. if (m_tif.IsTiled()) m_rowSize = m_tif.TileRowSize(); else m_rowSize = m_tif.ScanlineSize(); return true; } } #endregion #region CodecWithPredictorTagMethods class CodecWithPredictorTagMethods : TiffTagMethods { public override bool SetField(Tiff tif, TiffTag tag, FieldValue[] ap) { CodecWithPredictor sp = tif.m_currentCodec as CodecWithPredictor; Debug.Assert(sp != null); switch (tag) { case TiffTag.Predictor: sp.SetPredictorValue((PredictionScheme)ap[0].ToByte()); tif.setFieldBit(CodecWithPredictor.FIELD_PREDICTOR); tif.m_flags |= TiffFlags.DirtyDirect; return true; } TiffTagMethods childMethods = sp.GetChildTagMethods(); if (childMethods != null) return childMethods.SetField(tif, tag, ap); return base.SetField(tif, tag, ap); } public override FieldValue[] GetField(Tiff tif, TiffTag tag) { CodecWithPredictor sp = tif.m_currentCodec as CodecWithPredictor; Debug.Assert(sp != null); switch (tag) { case TiffTag.Predictor: FieldValue[] result = new FieldValue[1]; result[0].Set(sp.GetPredictorValue()); return result; } TiffTagMethods childMethods = sp.GetChildTagMethods(); if (childMethods != null) return childMethods.GetField(tif, tag); return base.GetField(tif, tag); } public override void PrintDir(Tiff tif, Stream fd, TiffPrintFlags flags) { CodecWithPredictor sp = tif.m_currentCodec as CodecWithPredictor; Debug.Assert(sp != null); if (tif.fieldSet(CodecWithPredictor.FIELD_PREDICTOR)) { Tiff.fprintf(fd, " PredictionScheme: "); PredictionScheme predictor = sp.GetPredictorValue(); switch (predictor) { case PredictionScheme.None: Tiff.fprintf(fd, "none "); break; case PredictionScheme.Horizontal: Tiff.fprintf(fd, "horizontal differencing "); break; case PredictionScheme.FloatingPoint: Tiff.fprintf(fd, "floating point predictor "); break; } Tiff.fprintf(fd, "{0} (0x{1:x})\r\n", predictor, predictor); } TiffTagMethods childMethods = sp.GetChildTagMethods(); if (childMethods != null) childMethods.PrintDir(tif, fd, flags); else base.PrintDir(tif, fd, flags); } } #endregion #region DeflateCodec class DeflateCodec : CodecWithPredictor { public const int ZSTATE_INIT_DECODE = 0x01; public const int ZSTATE_INIT_ENCODE = 0x02; public ZStream m_stream = new ZStream(); public int m_zipquality; /* compression level */ public int m_state; /* state flags */ private static TiffFieldInfo[] zipFieldInfo = { new TiffFieldInfo(TiffTag.ZIPQUALITY, 0, 0, TiffType.Any, FieldBit.Pseudo, true, false, ""), }; private TiffTagMethods m_tagMethods; public DeflateCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { m_tagMethods = new DeflateCodecTagMethods(); } public override bool Init() { Debug.Assert((m_scheme == Compression.Deflate) || (m_scheme == Compression.AdobeDeflate)); /* * Merge codec-specific tag information and * override parent get/set field methods. */ m_tif.MergeFieldInfo(zipFieldInfo, zipFieldInfo.Length); /* Default values for codec-specific fields */ m_zipquality = zlibConst.Z_DEFAULT_COMPRESSION; /* default comp. level */ m_state = 0; /* * Setup predictor setup. */ TIFFPredictorInit(m_tagMethods); return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// /// PreDecode is called after and before decoding. /// public override bool PreDecode(short plane) { return ZIPPreDecode(plane); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { return ZIPPreEncode(plane); } /// /// Performs any actions after encoding required by the codec. /// /// /// true if all post-encode actions succeeded; otherwise, false /// /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public override bool PostEncode() { return ZIPPostEncode(); } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public override void Cleanup() { ZIPCleanup(); } // CodecWithPredictor overrides public override bool predictor_setupdecode() { return ZIPSetupDecode(); } public override bool predictor_decoderow(byte[] buffer, int offset, int count, short plane) { return ZIPDecode(buffer, offset, count, plane); } public override bool predictor_decodestrip(byte[] buffer, int offset, int count, short plane) { return ZIPDecode(buffer, offset, count, plane); } public override bool predictor_decodetile(byte[] buffer, int offset, int count, short plane) { return ZIPDecode(buffer, offset, count, plane); } public override bool predictor_setupencode() { return ZIPSetupEncode(); } public override bool predictor_encoderow(byte[] buffer, int offset, int count, short plane) { return ZIPEncode(buffer, offset, count, plane); } public override bool predictor_encodestrip(byte[] buffer, int offset, int count, short plane) { return ZIPEncode(buffer, offset, count, plane); } public override bool predictor_encodetile(byte[] buffer, int offset, int count, short plane) { return ZIPEncode(buffer, offset, count, plane); } private void ZIPCleanup() { base.TIFFPredictorCleanup(); if ((m_state & ZSTATE_INIT_ENCODE) != 0) { m_stream.deflateEnd(); m_state = 0; } else if ((m_state & ZSTATE_INIT_DECODE) != 0) { m_stream.inflateEnd(); m_state = 0; } } private bool ZIPDecode(byte[] buffer, int offset, int count, short plane) { const string module = "ZIPDecode"; Debug.Assert(m_state == ZSTATE_INIT_DECODE); m_stream.next_out = buffer; m_stream.next_out_index = offset; m_stream.avail_out = count; do { int state = m_stream.inflate(zlibConst.Z_PARTIAL_FLUSH); if (state == zlibConst.Z_STREAM_END) break; if (state == zlibConst.Z_DATA_ERROR) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: Decoding error at scanline {1}, {2}", m_tif.m_name, m_tif.m_row, m_stream.msg); if (m_stream.inflateSync() != zlibConst.Z_OK) return false; continue; } if (state != zlibConst.Z_OK) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: zlib error: {1}", m_tif.m_name, m_stream.msg); return false; } } while (m_stream.avail_out > 0); if (m_stream.avail_out != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: Not enough data at scanline {1} (short {2} bytes)", m_tif.m_name, m_tif.m_row, m_stream.avail_out); return false; } return true; } /// /// Encode a chunk of pixels. /// private bool ZIPEncode(byte[] buffer, int offset, int count, short plane) { const string module = "ZIPEncode"; Debug.Assert(m_state == ZSTATE_INIT_ENCODE); m_stream.next_in = buffer; m_stream.next_in_index = offset; m_stream.avail_in = count; do { if (m_stream.deflate(zlibConst.Z_NO_FLUSH) != zlibConst.Z_OK) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: Encoder error: {1}", m_tif.m_name, m_stream.msg); return false; } if (m_stream.avail_out == 0) { m_tif.m_rawcc = m_tif.m_rawdatasize; m_tif.flushData1(); m_stream.next_out = m_tif.m_rawdata; m_stream.next_out_index = 0; m_stream.avail_out = m_tif.m_rawdatasize; } } while (m_stream.avail_in > 0); return true; } /* * Finish off an encoded strip by flushing the last * string and tacking on an End Of Information code. */ private bool ZIPPostEncode() { const string module = "ZIPPostEncode"; int state; m_stream.avail_in = 0; do { state = m_stream.deflate(zlibConst.Z_FINISH); switch (state) { case zlibConst.Z_STREAM_END: case zlibConst.Z_OK: if (m_stream.avail_out != m_tif.m_rawdatasize) { m_tif.m_rawcc = m_tif.m_rawdatasize - m_stream.avail_out; m_tif.flushData1(); m_stream.next_out = m_tif.m_rawdata; m_stream.next_out_index = 0; m_stream.avail_out = m_tif.m_rawdatasize; } break; default: Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: zlib error: {1}", m_tif.m_name, m_stream.msg); return false; } } while (state != zlibConst.Z_STREAM_END); return true; } /* * Setup state for decoding a strip. */ private bool ZIPPreDecode(short s) { if ((m_state & ZSTATE_INIT_DECODE) == 0) SetupDecode(); m_stream.next_in = m_tif.m_rawdata; m_stream.next_in_index = 0; m_stream.avail_in = m_tif.m_rawcc; return (m_stream.inflateInit() == zlibConst.Z_OK); } /* * Reset encoding state at the start of a strip. */ private bool ZIPPreEncode(short s) { if (m_state != ZSTATE_INIT_ENCODE) SetupEncode(); m_stream.next_out = m_tif.m_rawdata; m_stream.next_out_index = 0; m_stream.avail_out = m_tif.m_rawdatasize; return (m_stream.deflateInit(m_zipquality) == zlibConst.Z_OK); } private bool ZIPSetupDecode() { const string module = "ZIPSetupDecode"; /* if we were last encoding, terminate this mode */ if ((m_state & ZSTATE_INIT_ENCODE) != 0) { m_stream.deflateEnd(); m_state = 0; } if (m_stream.inflateInit() != zlibConst.Z_OK) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: {1}", m_tif.m_name, m_stream.msg); return false; } m_state |= ZSTATE_INIT_DECODE; return true; } private bool ZIPSetupEncode() { const string module = "ZIPSetupEncode"; if ((m_state & ZSTATE_INIT_DECODE) != 0) { m_stream.inflateEnd(); m_state = 0; } if (m_stream.deflateInit(m_zipquality) != zlibConst.Z_OK) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "{0}: {1}", m_tif.m_name, m_stream.msg); return false; } m_state |= ZSTATE_INIT_ENCODE; return true; } } #endregion #region DeflateCodecTagMethods class DeflateCodecTagMethods : TiffTagMethods { public override bool SetField(Tiff tif, TiffTag tag, FieldValue[] ap) { DeflateCodec sp = tif.m_currentCodec as DeflateCodec; Debug.Assert(sp != null); const string module = "ZIPVSetField"; switch (tag) { case TiffTag.ZIPQUALITY: sp.m_zipquality = ap[0].ToInt(); if ((sp.m_state & DeflateCodec.ZSTATE_INIT_ENCODE) != 0) { if (sp.m_stream.deflateParams(sp.m_zipquality, zlibConst.Z_DEFAULT_STRATEGY) != zlibConst.Z_OK) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: zlib error: {0}", tif.m_name, sp.m_stream.msg); return false; } } return true; } return base.SetField(tif, tag, ap); } public override FieldValue[] GetField(Tiff tif, TiffTag tag) { DeflateCodec sp = tif.m_currentCodec as DeflateCodec; Debug.Assert(sp != null); switch (tag) { case TiffTag.ZIPQUALITY: FieldValue[] result = new FieldValue[1]; result[0].Set(sp.m_zipquality); return result; } return base.GetField(tif, tag); } } #endregion #region DumpModeCodec class DumpModeCodec : TiffCodec { public DumpModeCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { } public override bool Init() { return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { return DumpModeDecode(buffer, offset, count, plane); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { return DumpModeDecode(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { return DumpModeDecode(buffer, offset, count, plane); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { return DumpModeEncode(buffer, offset, count, plane); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { return DumpModeEncode(buffer, offset, count, plane); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { return DumpModeEncode(buffer, offset, count, plane); } /// /// Seeks the specified row in the strip being processed. /// /// The row to seek. /// /// true if specified row was successfully found; otherwise, false /// public override bool Seek(int row) { m_tif.m_rawcp += row * m_tif.m_scanlinesize; m_tif.m_rawcc -= row * m_tif.m_scanlinesize; return true; } /// /// Encode a hunk of pixels. /// private bool DumpModeEncode(byte[] buffer, int offset, int count, short plane) { while (count > 0) { int n = count; if (m_tif.m_rawcc + n > m_tif.m_rawdatasize) n = m_tif.m_rawdatasize - m_tif.m_rawcc; Debug.Assert(n > 0); Buffer.BlockCopy(buffer, offset, m_tif.m_rawdata, m_tif.m_rawcp, n); m_tif.m_rawcp += n; m_tif.m_rawcc += n; offset += n; count -= n; if (m_tif.m_rawcc >= m_tif.m_rawdatasize && !m_tif.flushData1()) return false; } return true; } /// /// Decode a hunk of pixels. /// private bool DumpModeDecode(byte[] buffer, int offset, int count, short plane) { if (m_tif.m_rawcc < count) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "DumpModeDecode: Not enough data for scanline {0}", m_tif.m_row); return false; } Buffer.BlockCopy(m_tif.m_rawdata, m_tif.m_rawcp, buffer, offset, count); m_tif.m_rawcp += count; m_tif.m_rawcc -= count; return true; } } #endregion #region FieldBit /// /// Field bits (flags) for tags. /// /// Field bits used to indicate fields that have been set in a directory, and to /// reference fields when manipulating a directory. public static class FieldBit { internal const int SetLongs = 4; ///////////////////////////////////////////////////////////////////////// // multi-item fields internal const short ImageDimensions = 1; internal const short TileDimensions = 2; internal const short Resolution = 3; internal const short Position = 4; ///////////////////////////////////////////////////////////////////////// // single-item fields internal const short SubFileType = 5; internal const short BitsPerSample = 6; internal const short Compression = 7; internal const short Photometric = 8; internal const short Thresholding = 9; internal const short FillOrder = 10; internal const short Orientation = 15; internal const short SamplesPerPixel = 16; internal const short RowsPerStrip = 17; internal const short MinSampleValue = 18; internal const short MaxSampleValue = 19; internal const short PlanarConfig = 20; internal const short ResolutionUnit = 22; internal const short PageNumber = 23; internal const short StripByteCounts = 24; internal const short StripOffsets = 25; internal const short ColorMap = 26; internal const short ExtraSamples = 31; internal const short SampleFormat = 32; internal const short SMinSampleValue = 33; internal const short SMaxSampleValue = 34; internal const short ImageDepth = 35; internal const short TileDepth = 36; internal const short HalftoneHints = 37; internal const short YCbCrSubsampling = 39; internal const short YCbCrPositioning = 40; internal const short RefBlackWhite = 41; internal const short TransferFunction = 44; internal const short InkNames = 46; internal const short SubIFD = 49; ///////////////////////////////////////////////////////////////////////// // end of support for well-known tags; codec-private tags follow /// /// This value is used to signify tags that are to be processed /// but otherwise ignored.
/// This permits antiquated tags to be quietly read and discarded. Note that /// a bit is allocated for ignored tags; this is understood by the /// directory reading logic which uses this fact to avoid special-case handling. ///
public const short Ignore = 0; /// /// This value is used to signify pseudo-tags.
/// Pseudo-tags don't normally need field bits since they are not /// written to an output file (by definition). The library also has /// express logic to always query a codec for a pseudo-tag so allocating /// a field bit for one is a waste. If codec wants to promote the notion /// of a pseudo-tag being set or unset then it can do using /// internal state flags without polluting the field bit space defined /// for real tags. ///
public const short Pseudo = 0; /// /// This value is used to signify custom tags. /// public const short Custom = 65; /// /// This value is used as a base (starting) value for codec-private tags. /// public const short Codec = 66; /// /// Last usable value for field bit. All tags values should be less than this value. /// public const short Last = (32 * SetLongs - 1); } #endregion #region FieldValue /// /// Holds a value of a Tiff tag. /// /// /// Simply put, it is a wrapper around System.Object, that helps to deal with /// unboxing and conversion of types a bit easier. /// /// Please take a look at: /// http://blogs.msdn.com/ericlippert/archive/2009/03/19/representation-and-identity.aspx /// public struct FieldValue { private object m_value; internal FieldValue(object o) { m_value = o; } static internal FieldValue[] FromParams(params object[] list) { FieldValue[] values = new FieldValue[list.Length]; for (int i = 0; i < list.Length; i++) { if (list[i] is FieldValue) values[i] = new FieldValue(((FieldValue)(list[i])).Value); else values[i] = new FieldValue(list[i]); } return values; } internal void Set(object o) { m_value = o; } /// /// Gets the value. /// /// The value. public object Value { get { return m_value; } } /// /// Retrieves value converted to byte. /// /// The value converted to byte. public byte ToByte() { return Convert.ToByte(m_value); } /// /// Retrieves value converted to short. /// /// The value converted to short. public short ToShort() { return Convert.ToInt16(m_value); } /// /// Retrieves value converted to ushort. /// /// The value converted to ushort. public ushort ToUShort() { return Convert.ToUInt16(m_value); } /// /// Retrieves value converted to int. /// /// The value converted to int. public int ToInt() { return Convert.ToInt32(m_value); } /// /// Retrieves value converted to uint. /// /// The value converted to uint. public uint ToUInt() { return Convert.ToUInt32(m_value); } /// /// Retrieves value converted to float. /// /// The value converted to float. public float ToFloat() { return Convert.ToSingle(m_value); } /// /// Retrieves value converted to double. /// /// The value converted to double. public double ToDouble() { return Convert.ToDouble(m_value); } /// /// Retrieves value converted to string. /// /// /// A that represents this instance. /// /// If value is a byte array, then it gets converted to string using /// Latin1 encoding encoder. public override string ToString() { if (m_value is byte[]) return Tiff.Latin1Encoding.GetString(m_value as byte[]); return Convert.ToString(m_value); } /// /// Retrieves value converted to byte array. /// /// Value converted to byte array. /// /// If value is byte array then it retrieved unaltered. /// If value is array of short, ushort, int, uint, float or double values then this /// array is converted to byte array /// If value is a string then it gets converted to byte array using Latin1 encoding /// encoder. /// If value is of any other type then null is returned. /// public byte[] GetBytes() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is byte[]) return m_value as byte[]; else if (m_value is short[]) { short[] temp = m_value as short[]; byte[] result = new byte[temp.Length * sizeof(short)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } else if (m_value is ushort[]) { ushort[] temp = m_value as ushort[]; byte[] result = new byte[temp.Length * sizeof(ushort)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } else if (m_value is int[]) { int[] temp = m_value as int[]; byte[] result = new byte[temp.Length * sizeof(int)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } else if (m_value is uint[]) { uint[] temp = m_value as uint[]; byte[] result = new byte[temp.Length * sizeof(uint)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } else if (m_value is float[]) { float[] temp = m_value as float[]; byte[] result = new byte[temp.Length * sizeof(float)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } else if (m_value is double[]) { double[] temp = m_value as double[]; byte[] result = new byte[temp.Length * sizeof(double)]; Buffer.BlockCopy(temp, 0, result, 0, result.Length); return result; } } else if (m_value is string) { return Tiff.Latin1Encoding.GetBytes(m_value as string); } return null; } /// /// Retrieves value converted to array of bytes. /// /// Value converted to array of bytes. /// If value is array of bytes then it retrieved unaltered. /// If value is array of short, ushort, int or uint values then each element of /// field value gets converted to byte and added to resulting array. /// If value is string then it gets converted to byte[] using Latin1 encoding /// encoder. /// If value is of any other type then null is returned. public byte[] ToByteArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is byte[]) return m_value as byte[]; else if (m_value is short[]) { short[] temp = m_value as short[]; byte[] result = new byte[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (byte)temp[i]; return result; } else if (m_value is ushort[]) { ushort[] temp = m_value as ushort[]; byte[] result = new byte[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (byte)temp[i]; return result; } else if (m_value is int[]) { int[] temp = m_value as int[]; byte[] result = new byte[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (byte)temp[i]; return result; } else if (m_value is uint[]) { uint[] temp = m_value as uint[]; byte[] result = new byte[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (byte)temp[i]; return result; } } else if (m_value is string) return Tiff.Latin1Encoding.GetBytes(m_value as string); return null; } /// /// Retrieves value converted to array of short values. /// /// Value converted to array of short values. /// If value is array of short values then it retrieved unaltered. /// If value is array of bytes then each pair of bytes is converted to short and /// added to resulting array. If value contains odd amount of bytes, then null is /// returned. /// If value is array of ushort, int or uint values then each element of field value gets /// converted to short and added to resulting array. /// If value is of any other type then null is returned. public short[] ToShortArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is short[]) return m_value as short[]; else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(short) != 0) return null; int totalShorts = temp.Length / sizeof(short); short[] result = new short[totalShorts]; int byteOffset = 0; for (int i = 0; i < totalShorts; i++) { short s = BitConverter.ToInt16(temp, byteOffset); result[i] = s; byteOffset += sizeof(short); } return result; } else if (m_value is ushort[]) { ushort[] temp = m_value as ushort[]; short[] result = new short[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (short)temp[i]; return result; } else if (m_value is int[]) { int[] temp = m_value as int[]; short[] result = new short[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (short)temp[i]; return result; } else if (m_value is uint[]) { uint[] temp = m_value as uint[]; short[] result = new short[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (short)temp[i]; return result; } } return null; } /// /// Retrieves value converted to array of ushort values. /// /// Value converted to array of ushort values. /// If value is array of ushort values then it retrieved unaltered. /// If value is array of bytes then each pair of bytes is converted to ushort and /// added to resulting array. If value contains odd amount of bytes, then null is /// returned. /// If value is array of short, int or uint values then each element of field value gets /// converted to ushort and added to resulting array. /// If value is of any other type then null is returned. public ushort[] ToUShortArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is ushort[]) return m_value as ushort[]; else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(ushort) != 0) return null; int totalUShorts = temp.Length / sizeof(ushort); ushort[] result = new ushort[totalUShorts]; int byteOffset = 0; for (int i = 0; i < totalUShorts; i++) { ushort s = BitConverter.ToUInt16(temp, byteOffset); result[i] = s; byteOffset += sizeof(ushort); } return result; } else if (m_value is short[]) { short[] temp = m_value as short[]; ushort[] result = new ushort[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (ushort)temp[i]; return result; } else if (m_value is int[]) { int[] temp = m_value as int[]; ushort[] result = new ushort[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (ushort)temp[i]; return result; } else if (m_value is uint[]) { uint[] temp = m_value as uint[]; ushort[] result = new ushort[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (ushort)temp[i]; return result; } } return null; } /// /// Retrieves value converted to array of int values. /// /// Value converted to array of int values. /// If value is array of int values then it retrieved unaltered. /// If value is array of bytes then each 4 bytes are converted to int and added to /// resulting array. If value contains amount of bytes that can't be divided by 4 without /// remainder, then null is returned. /// If value is array of short, ushort or uint values then each element of /// field value gets converted to int and added to resulting array. /// If value is of any other type then null is returned. public int[] ToIntArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is int[]) return m_value as int[]; else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(int) != 0) return null; int totalInts = temp.Length / sizeof(int); int[] result = new int[totalInts]; int byteOffset = 0; for (int i = 0; i < totalInts; i++) { int s = BitConverter.ToInt32(temp, byteOffset); result[i] = s; byteOffset += sizeof(int); } return result; } else if (m_value is short[]) { short[] temp = m_value as short[]; int[] result = new int[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (int)temp[i]; return result; } else if (m_value is ushort[]) { ushort[] temp = m_value as ushort[]; int[] result = new int[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (int)temp[i]; return result; } else if (m_value is uint[]) { uint[] temp = m_value as uint[]; int[] result = new int[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (int)temp[i]; return result; } } return null; } /// /// Retrieves value converted to array of uint values. /// /// Value converted to array of uint values. /// If value is array of uint values then it retrieved unaltered. /// If value is array of bytes then each 4 bytes are converted to uint and added to /// resulting array. If value contains amount of bytes that can't be divided by 4 without /// remainder, then null is returned. /// If value is array of short, ushort or int values then each element of /// field value gets converted to uint and added to resulting array. /// If value is of any other type then null is returned. public uint[] ToUIntArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is uint[]) return m_value as uint[]; else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(uint) != 0) return null; int totalUInts = temp.Length / sizeof(uint); uint[] result = new uint[totalUInts]; int byteOffset = 0; for (int i = 0; i < totalUInts; i++) { uint s = BitConverter.ToUInt32(temp, byteOffset); result[i] = s; byteOffset += sizeof(uint); } return result; } else if (m_value is short[]) { short[] temp = m_value as short[]; uint[] result = new uint[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (uint)temp[i]; return result; } else if (m_value is ushort[]) { ushort[] temp = m_value as ushort[]; uint[] result = new uint[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (uint)temp[i]; return result; } else if (m_value is int[]) { int[] temp = m_value as int[]; uint[] result = new uint[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (uint)temp[i]; return result; } } return null; } /// /// Retrieves value converted to array of float values. /// /// Value converted to array of float values. /// If value is array of float values then it retrieved unaltered. /// If value is array of bytes then each 4 bytes are converted to float and added to /// resulting array. If value contains amount of bytes that can't be divided by 4 without /// remainder, then null is returned. /// If value is array of double values then each element of field value gets /// converted to float and added to resulting array. /// If value is of any other type then null is returned. public float[] ToFloatArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is float[]) return m_value as float[]; else if (m_value is double[]) { double[] temp = m_value as double[]; float[] result = new float[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (float)temp[i]; return result; } else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(float) != 0) return null; int tempPos = 0; int floatCount = temp.Length / sizeof(float); float[] result = new float[floatCount]; for (int i = 0; i < floatCount; i++) { float f = BitConverter.ToSingle(temp, tempPos); result[i] = f; tempPos += sizeof(float); } return result; } } return null; } /// /// Retrieves value converted to array of double values. /// /// Value converted to array of double values. /// If value is array of double values then it retrieved unaltered. /// If value is array of bytes then each 8 bytes are converted to double and added to /// resulting array. If value contains amount of bytes that can't be divided by 8 without /// remainder, then null is returned. /// If value is array of float values then each element of field value gets /// converted to double and added to resulting array. /// If value is of any other type then null is returned. public double[] ToDoubleArray() { if (m_value == null) return null; Type t = m_value.GetType(); if (t.IsArray) { if (m_value is double[]) return m_value as double[]; else if (m_value is float[]) { float[] temp = m_value as float[]; double[] result = new double[temp.Length]; for (int i = 0; i < temp.Length; i++) result[i] = (double)temp[i]; return result; } else if (m_value is byte[]) { byte[] temp = m_value as byte[]; if (temp.Length % sizeof(double) != 0) return null; int tempPos = 0; int floatCount = temp.Length / sizeof(double); double[] result = new double[floatCount]; for (int i = 0; i < floatCount; i++) { double d = BitConverter.ToDouble(temp, tempPos); result[i] = d; tempPos += sizeof(double); } return result; } } return null; } } #endregion #region JpegCodec class JpegCodec : TiffCodec { public const int FIELD_JPEGTABLES = (FieldBit.Codec + 0); public const int FIELD_RECVPARAMS = (FieldBit.Codec + 1); public const int FIELD_SUBADDRESS = (FieldBit.Codec + 2); public const int FIELD_RECVTIME = (FieldBit.Codec + 3); public const int FIELD_FAXDCS = (FieldBit.Codec + 4); internal JpegCompressor m_compression; internal JpegDecompressor m_decompression; internal JpegCommonBase m_common; internal int m_h_sampling; /* luminance sampling factors */ internal int m_v_sampling; /* pseudo-tag fields */ internal byte[] m_jpegtables; /* JPEGTables tag value, or null */ internal int m_jpegtables_length; /* number of bytes in same */ internal int m_jpegquality; /* Compression quality level */ internal JpegColorMode m_jpegcolormode; /* Auto RGB<=>YCbCr convert? */ internal JpegTablesMode m_jpegtablesmode; /* What to put in JPEGTables */ internal bool m_ycbcrsampling_fetched; internal int m_recvparams; /* encoded Class 2 session params */ internal string m_subaddress; /* subaddress string */ internal int m_recvtime; /* time spent receiving (secs) */ internal string m_faxdcs; /* encoded fax parameters (DCS, Table 2/T.30) */ private static TiffFieldInfo[] jpegFieldInfo = { new TiffFieldInfo(TiffTag.JpegTables, -3, -3, TiffType.Undefined, FIELD_JPEGTABLES, false, true, "JPEGTables"), new TiffFieldInfo(TiffTag.JPEGQUALITY, 0, 0, TiffType.Any, FieldBit.Pseudo, true, false, ""), new TiffFieldInfo(TiffTag.JPEGCOLORMODE, 0, 0, TiffType.Any, FieldBit.Pseudo, false, false, ""), new TiffFieldInfo(TiffTag.JPEGTABLESMODE, 0, 0, TiffType.Any, FieldBit.Pseudo, false, false, ""), /* Specific for JPEG in faxes */ new TiffFieldInfo(TiffTag.FAXRECVPARAMS, 1, 1, TiffType.Long, FIELD_RECVPARAMS, true, false, "FaxRecvParams"), new TiffFieldInfo(TiffTag.FAXSUBADDRESS, -1, -1, TiffType.ASCII, FIELD_SUBADDRESS, true, false, "FaxSubAddress"), new TiffFieldInfo(TiffTag.FAXRECVTIME, 1, 1, TiffType.Long, FIELD_RECVTIME, true, false, "FaxRecvTime"), new TiffFieldInfo(TiffTag.FAXDCS, -1, -1, TiffType.ASCII, FIELD_FAXDCS, true, false, "FaxDcs"), }; private bool m_rawDecode; private bool m_rawEncode; private TiffTagMethods m_tagMethods; private TiffTagMethods m_parentTagMethods; private bool m_cinfo_initialized; private Photometric m_photometric; /* copy of PhotometricInterpretation */ private int m_bytesperline; /* decompressed bytes per scanline */ /* pointers to intermediate buffers when processing downsampled data */ private byte[][][] m_ds_buffer = new byte[JpegConstants.MaxComponents][][]; private int m_scancount; /* number of "scanlines" accumulated */ private int m_samplesperclump; public JpegCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { m_tagMethods = new JpegCodecTagMethods(); } public override bool Init() { Debug.Assert(m_scheme == Compression.JPEG); /* * Merge codec-specific tag information and override parent get/set * field methods. */ m_tif.MergeFieldInfo(jpegFieldInfo, jpegFieldInfo.Length); /* * Allocate state block so tag methods have storage to record values. */ m_compression = null; m_decompression = null; m_photometric = 0; m_h_sampling = 0; m_v_sampling = 0; m_bytesperline = 0; m_scancount = 0; m_samplesperclump = 0; m_recvtime = 0; m_parentTagMethods = m_tif.m_tagmethods; m_tif.m_tagmethods = m_tagMethods; /* Default values for codec-specific fields */ m_jpegtables = null; m_jpegtables_length = 0; m_jpegquality = 75; /* Default IJG quality */ m_jpegcolormode = JpegColorMode.RGB; m_jpegtablesmode = JpegTablesMode.Quant | JpegTablesMode.Huff; m_recvparams = 0; m_subaddress = null; m_faxdcs = null; m_ycbcrsampling_fetched = false; m_rawDecode = false; m_rawEncode = false; m_tif.m_flags |= TiffFlags.NoBitRev; // no bit reversal, please m_cinfo_initialized = false; /* ** Create a JPEGTables field if no directory has yet been created. ** We do this just to ensure that sufficient space is reserved for ** the JPEGTables field. It will be properly created the right ** size later. */ if (m_tif.m_diroff == 0) { const int SIZE_OF_JPEGTABLES = 2000; // The following line assumes incorrectly that all JPEG-in-TIFF // files will have a JpegTables tag generated and causes // null-filled JpegTables tags to be written when the JPEG data // is placed with WriteRawStrip. The field bit should be // set, anyway, later when actual JpegTables header is // generated, so removing it here hopefully is harmless. // // m_tif.setFieldBit(FIELD_JPEGTABLES); // m_jpegtables_length = SIZE_OF_JPEGTABLES; m_jpegtables = new byte[m_jpegtables_length]; } /* * Mark the YCBCRSAMPLES as present even if it is not * see: JPEGFixupTestSubsampling(). */ m_tif.setFieldBit(FieldBit.YCbCrSubsampling); return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } /// /// Setups the decoder part of the codec. /// /// /// true if this codec successfully setup its decoder part and can decode data; /// otherwise, false. /// /// /// SetupDecode is called once before /// . public override bool SetupDecode() { return JPEGSetupDecode(); } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// /// PreDecode is called after and before decoding. /// public override bool PreDecode(short plane) { return JPEGPreDecode(plane); } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { if (m_rawDecode) return JPEGDecodeRaw(buffer, offset, count, plane); return JPEGDecode(buffer, offset, count, plane); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { if (m_rawDecode) return JPEGDecodeRaw(buffer, offset, count, plane); return JPEGDecode(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { if (m_rawDecode) return JPEGDecodeRaw(buffer, offset, count, plane); return JPEGDecode(buffer, offset, count, plane); } /// /// Setups the encoder part of the codec. /// /// /// true if this codec successfully setup its encoder part and can encode data; /// otherwise, false. /// /// /// SetupEncode is called once before /// . public override bool SetupEncode() { return JPEGSetupEncode(); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { return JPEGPreEncode(plane); } /// /// Performs any actions after encoding required by the codec. /// /// /// true if all post-encode actions succeeded; otherwise, false /// /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public override bool PostEncode() { return JPEGPostEncode(); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { if (m_rawEncode) return JPEGEncodeRaw(buffer, offset, count, plane); return JPEGEncode(buffer, offset, count, plane); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { if (m_rawEncode) return JPEGEncodeRaw(buffer, offset, count, plane); return JPEGEncode(buffer, offset, count, plane); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { if (m_rawEncode) return JPEGEncodeRaw(buffer, offset, count, plane); return JPEGEncode(buffer, offset, count, plane); } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public override void Cleanup() { JPEGCleanup(); } /// /// Calculates and/or constrains a strip size. /// /// The proposed strip size (may be zero or negative). /// A strip size to use. public override int DefStripSize(int size) { return JPEGDefaultStripSize(size); } /// /// Calculate and/or constrains a tile size /// /// The proposed tile width upon the call / tile width to use after the call. /// The proposed tile height upon the call / tile height to use after the call. public override void DefTileSize(ref int width, ref int height) { JPEGDefaultTileSize(ref width, ref height); } /* * The JPEG library initialized used to be done in TIFFInitJPEG(), but * now that we allow a TIFF file to be opened in update mode it is necessary * to have some way of deciding whether compression or decompression is * desired other than looking at tif.tif_mode. We accomplish this by * examining {TILE/STRIP}BYTECOUNTS to see if there is a non-zero entry. * If so, we assume decompression is desired. * * This is tricky, because TIFFInitJPEG() is called while the directory is * being read, and generally speaking the BYTECOUNTS tag won't have been read * at that point. So we try to defer jpeg library initialization till we * do have that tag ... basically any access that might require the compressor * or decompressor that occurs after the reading of the directory. * * In an ideal world compressors or decompressors would be setup * at the point where a single tile or strip was accessed (for read or write) * so that stuff like update of missing tiles, or replacement of tiles could * be done. However, we aren't trying to crack that nut just yet ... * * NFW, Feb 3rd, 2003. */ public bool InitializeLibJPEG(bool force_encode, bool force_decode) { int[] byte_counts = null; bool data_is_empty = true; bool decompress; if (m_cinfo_initialized) { if (force_encode && m_common.IsDecompressor) TIFFjpeg_destroy(); else if (force_decode && !m_common.IsDecompressor) TIFFjpeg_destroy(); else return true; m_cinfo_initialized = false; } /* * Do we have tile data already? Make sure we initialize the * the state in decompressor mode if we have tile data, even if we * are not in read-only file access mode. */ FieldValue[] result = m_tif.GetField(TiffTag.TileByteCounts); if (m_tif.IsTiled() && result != null) { byte_counts = result[0].ToIntArray(); if (byte_counts != null) data_is_empty = byte_counts[0] == 0; } result = m_tif.GetField(TiffTag.StripByteCounts); if (!m_tif.IsTiled() && result != null) { byte_counts = result[0].ToIntArray(); if (byte_counts != null) data_is_empty = byte_counts[0] == 0; } if (force_decode) decompress = true; else if (force_encode) decompress = false; else if (m_tif.m_mode == Tiff.O_RDONLY) decompress = true; else if (data_is_empty) decompress = false; else decompress = true; // Initialize LibJpeg.Net if (decompress) { if (!TIFFjpeg_create_decompress()) return false; } else { if (!TIFFjpeg_create_compress()) return false; } m_cinfo_initialized = true; return true; } public Tiff GetTiff() { return m_tif; } public void JPEGResetUpsampled() { /* * Mark whether returned data is up-sampled or not so TIFFStripSize * and TIFFTileSize return values that reflect the true amount of * data. */ m_tif.m_flags &= ~TiffFlags.UpSampled; if (m_tif.m_dir.td_planarconfig == PlanarConfig.Contig) { if (m_tif.m_dir.td_photometric == Photometric.YCBCR && m_jpegcolormode == JpegColorMode.RGB) m_tif.m_flags |= TiffFlags.UpSampled; } /* * Must recalculate cached tile size in case sampling state changed. * Should we really be doing this now if image size isn't set? */ if (m_tif.m_tilesize > 0) m_tif.m_tilesize = m_tif.IsTiled() ? m_tif.TileSize() : -1; if (m_tif.m_scanlinesize > 0) m_tif.m_scanlinesize = m_tif.ScanlineSize(); } /// /// Set encoding state at the start of a strip or tile. /// private bool JPEGPreEncode(short s) { const string module = "JPEGPreEncode"; int segment_width; int segment_height; bool downsampled_input; Debug.Assert(!m_common.IsDecompressor); /* * Set encoding parameters for this strip/tile. */ if (m_tif.IsTiled()) { segment_width = m_tif.m_dir.td_tilewidth; segment_height = m_tif.m_dir.td_tilelength; m_bytesperline = m_tif.TileRowSize(); } else { segment_width = m_tif.m_dir.td_imagewidth; segment_height = m_tif.m_dir.td_imagelength - m_tif.m_row; if (segment_height > m_tif.m_dir.td_rowsperstrip) segment_height = m_tif.m_dir.td_rowsperstrip; m_bytesperline = m_tif.oldScanlineSize(); } if (m_tif.m_dir.td_planarconfig == PlanarConfig.Separate && s > 0) { /* for PC 2, scale down the strip/tile size * to match a downsampled component */ segment_width = Tiff.howMany(segment_width, m_h_sampling); segment_height = Tiff.howMany(segment_height, m_v_sampling); } if (segment_width > 65535 || segment_height > 65535) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Strip/tile too large for JPEG"); return false; } m_compression.Image_width = segment_width; m_compression.Image_height = segment_height; downsampled_input = false; if (m_tif.m_dir.td_planarconfig == PlanarConfig.Contig) { m_compression.Input_components = m_tif.m_dir.td_samplesperpixel; if (m_photometric == Photometric.YCBCR) { if (m_jpegcolormode == JpegColorMode.RGB) { m_compression.In_color_space = ColorSpace.RGB; } else { m_compression.In_color_space = ColorSpace.YCbCr; if (m_h_sampling != 1 || m_v_sampling != 1) downsampled_input = true; } if (!TIFFjpeg_set_colorspace(ColorSpace.YCbCr)) return false; /* * Set Y sampling factors; * we assume jpeg_set_colorspace() set the rest to 1 */ m_compression.Component_info[0].H_samp_factor = m_h_sampling; m_compression.Component_info[0].V_samp_factor = m_v_sampling; } else { m_compression.In_color_space = ColorSpace.Unknown; if (!TIFFjpeg_set_colorspace(ColorSpace.Unknown)) return false; /* jpeg_set_colorspace set all sampling factors to 1 */ } } else { m_compression.Input_components = 1; m_compression.In_color_space = ColorSpace.Unknown; if (!TIFFjpeg_set_colorspace(ColorSpace.Unknown)) return false; m_compression.Component_info[0].Component_id = s; /* jpeg_set_colorspace() set sampling factors to 1 */ if (m_photometric == Photometric.YCBCR && s > 0) { m_compression.Component_info[0].Quant_tbl_no = 1; m_compression.Component_info[0].Dc_tbl_no = 1; m_compression.Component_info[0].Ac_tbl_no = 1; } } // ensure LibJpeg.Net won't write any extraneous markers m_compression.Write_JFIF_header = false; m_compression.Write_Adobe_marker = false; /* set up table handling correctly */ if (!TIFFjpeg_set_quality(m_jpegquality, false)) return false; if ((m_jpegtablesmode & JpegTablesMode.Quant) == 0) { unsuppress_quant_table(0); unsuppress_quant_table(1); } if ((m_jpegtablesmode & JpegTablesMode.Huff) != 0) m_compression.Optimize_coding = false; else m_compression.Optimize_coding = true; if (downsampled_input) { // Need to use raw-data interface to LibJpeg.Net m_compression.Raw_data_in = true; m_rawEncode = true; } else { // Use normal interface to LibJpeg.Net m_compression.Raw_data_in = false; m_rawEncode = false; } /* Start JPEG compressor */ if (!TIFFjpeg_start_compress(false)) return false; /* Allocate downsampled-data buffers if needed */ if (downsampled_input) { if (!alloc_downsampled_buffers(m_compression.Component_info, m_compression.Num_components)) return false; } m_scancount = 0; return true; } private bool JPEGSetupEncode() { const string module = "JPEGSetupEncode"; InitializeLibJPEG(true, false); Debug.Assert(!m_common.IsDecompressor); /* * Initialize all JPEG parameters to default values. * Note that jpeg_set_defaults needs legal values for * in_color_space and input_components. */ m_compression.In_color_space = ColorSpace.Unknown; m_compression.Input_components = 1; if (!TIFFjpeg_set_defaults()) return false; /* Set per-file parameters */ m_photometric = m_tif.m_dir.td_photometric; switch (m_photometric) { case Photometric.YCBCR: m_h_sampling = m_tif.m_dir.td_ycbcrsubsampling[0]; m_v_sampling = m_tif.m_dir.td_ycbcrsubsampling[1]; /* * A ReferenceBlackWhite field *must* be present since the * default value is inappropriate for YCbCr. Fill in the * proper value if application didn't set it. */ FieldValue[] result = m_tif.GetField(TiffTag.REFERENCEBLACKWHITE); if (result == null) { float[] refbw = new float[6]; int top = 1 << m_tif.m_dir.td_bitspersample; refbw[0] = 0; refbw[1] = (float)(top - 1L); refbw[2] = (float)(top >> 1); refbw[3] = refbw[1]; refbw[4] = refbw[2]; refbw[5] = refbw[1]; m_tif.SetField(TiffTag.REFERENCEBLACKWHITE, refbw); } break; /* disallowed by Tech Note */ case Photometric.Palette: case Photometric.Mask: Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "PhotometricInterpretation {0} not allowed for JPEG", m_photometric); return false; default: /* TIFF 6.0 forbids subsampling of all other color spaces */ m_h_sampling = 1; m_v_sampling = 1; break; } /* Verify miscellaneous parameters */ // This would need work if LibTiff.Net ever supports different // depths for different components, or if LibJpeg.Net ever supports // run-time selection of depth. Neither is imminent. if (m_tif.m_dir.td_bitspersample != JpegConstants.BitsInSample) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "BitsPerSample {0} not allowed for JPEG", m_tif.m_dir.td_bitspersample); return false; } m_compression.Data_precision = m_tif.m_dir.td_bitspersample; if (m_tif.IsTiled()) { if ((m_tif.m_dir.td_tilelength % (m_v_sampling * JpegConstants.DCTSize)) != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG tile height must be multiple of {0}", m_v_sampling * JpegConstants.DCTSize); return false; } if ((m_tif.m_dir.td_tilewidth % (m_h_sampling * JpegConstants.DCTSize)) != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG tile width must be multiple of {0}", m_h_sampling * JpegConstants.DCTSize); return false; } } else { if (m_tif.m_dir.td_rowsperstrip < m_tif.m_dir.td_imagelength && (m_tif.m_dir.td_rowsperstrip % (m_v_sampling * JpegConstants.DCTSize)) != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "RowsPerStrip must be multiple of {0} for JPEG", m_v_sampling * JpegConstants.DCTSize); return false; } } /* Create a JPEGTables field if appropriate */ if ((m_jpegtablesmode & (JpegTablesMode.Quant | JpegTablesMode.Huff)) != 0) { bool startsWithZeroes = true; if (m_jpegtables != null) { for (int i = 0; i < 8; i++) { if (m_jpegtables[i] != 0) { startsWithZeroes = false; break; } } } else { startsWithZeroes = false; } if (m_jpegtables == null || startsWithZeroes) { if (!prepare_JPEGTables()) return false; /* Mark the field present */ /* Can't use TIFFSetField since BeenWriting is already set! */ m_tif.m_flags |= TiffFlags.DirtyDirect; m_tif.setFieldBit(FIELD_JPEGTABLES); } } else { /* We do not support application-supplied JPEGTables, */ /* so mark the field not present */ m_tif.clearFieldBit(FIELD_JPEGTABLES); } /* Direct LibJpeg.Net output to LibTiff.Net's output buffer */ TIFFjpeg_data_dest(); return true; } /// /// Finish up at the end of a strip or tile. /// /// private bool JPEGPostEncode() { if (m_scancount > 0) { // Need to emit a partial bufferload of downsampled data. Pad the data vertically. for (int ci = 0; ci < m_compression.Num_components; ci++) { int vsamp = m_compression.Component_info[ci].V_samp_factor; int row_width = m_compression.Component_info[ci].Width_in_blocks * JpegConstants.DCTSize * sizeof(byte); for (int ypos = m_scancount * vsamp; ypos < JpegConstants.DCTSize * vsamp; ypos++) Buffer.BlockCopy(m_ds_buffer[ci][ypos - 1], 0, m_ds_buffer[ci][ypos], 0, row_width); } int n = m_compression.Max_v_samp_factor * JpegConstants.DCTSize; if (TIFFjpeg_write_raw_data(m_ds_buffer, n) != n) return false; } return TIFFjpeg_finish_compress(); } private void JPEGCleanup() { m_tif.m_tagmethods = m_parentTagMethods; if (m_cinfo_initialized) { // release LibJpeg.Net resources TIFFjpeg_destroy(); } } /* * JPEG Decoding. */ /* * Set up for decoding a strip or tile. */ private bool JPEGPreDecode(short s) { TiffDirectory td = m_tif.m_dir; const string module = "JPEGPreDecode"; int segment_width; int segment_height; int ci; Debug.Assert(m_common.IsDecompressor); /* * Reset decoder state from any previous strip/tile, * in case application didn't read the whole strip. */ if (!TIFFjpeg_abort()) return false; /* * Read the header for this strip/tile. */ if (TIFFjpeg_read_header(true) != ReadResult.Header_Ok) return false; /* * Check image parameters and set decompression parameters. */ segment_width = td.td_imagewidth; segment_height = td.td_imagelength - m_tif.m_row; if (m_tif.IsTiled()) { segment_width = td.td_tilewidth; segment_height = td.td_tilelength; m_bytesperline = m_tif.TileRowSize(); } else { if (segment_height > td.td_rowsperstrip) segment_height = td.td_rowsperstrip; m_bytesperline = m_tif.oldScanlineSize(); } if (td.td_planarconfig == PlanarConfig.Separate && s > 0) { /* * For PC 2, scale down the expected strip/tile size * to match a downsampled component */ segment_width = Tiff.howMany(segment_width, m_h_sampling); segment_height = Tiff.howMany(segment_height, m_v_sampling); } if (m_decompression.Image_width < segment_width || m_decompression.Image_height < segment_height) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG strip/tile size, expected {0}x{1}, got {2}x{3}", segment_width, segment_height, m_decompression.Image_width, m_decompression.Image_height); } if (m_decompression.Image_width > segment_width || m_decompression.Image_height > segment_height) { /* * This case could be dangerous, if the strip or tile size has * been reported as less than the amount of data jpeg will * return, some potential security issues arise. Catch this * case and error out. */ Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG strip/tile size exceeds expected dimensions, expected {0}x{1}, got {2}x{3}", segment_width, segment_height, m_decompression.Image_width, m_decompression.Image_height); return false; } if (m_decompression.Num_components != (td.td_planarconfig == PlanarConfig.Contig ? (int)td.td_samplesperpixel : 1)) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG component count"); return false; } if (m_decompression.Data_precision != td.td_bitspersample) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG data precision"); return false; } if (td.td_planarconfig == PlanarConfig.Contig) { /* Component 0 should have expected sampling factors */ if (m_decompression.Comp_info[0].H_samp_factor != m_h_sampling || m_decompression.Comp_info[0].V_samp_factor != m_v_sampling) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG sampling factors {0},{1}\nApparently should be {2},{3}.", m_decompression.Comp_info[0].H_samp_factor, m_decompression.Comp_info[0].V_samp_factor, m_h_sampling, m_v_sampling); /* * There are potential security issues here * for decoders that have already allocated * buffers based on the expected sampling * factors. Lets check the sampling factors * dont exceed what we were expecting. */ if (m_decompression.Comp_info[0].H_samp_factor > m_h_sampling || m_decompression.Comp_info[0].V_samp_factor > m_v_sampling) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Cannot honour JPEG sampling factors that exceed those specified."); return false; } /* * XXX: Files written by the Intergraph software * has different sampling factors stored in the * TIFF tags and in the JPEG structures. We will * try to deduce Intergraph files by the presense * of the tag 33918. */ if (m_tif.FindFieldInfo((TiffTag)33918, TiffType.Any) == null) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Decompressor will try reading with sampling {0},{1}.", m_decompression.Comp_info[0].H_samp_factor, m_decompression.Comp_info[0].V_samp_factor); m_h_sampling = m_decompression.Comp_info[0].H_samp_factor; m_v_sampling = m_decompression.Comp_info[0].V_samp_factor; } } /* Rest should have sampling factors 1,1 */ for (ci = 1; ci < m_decompression.Num_components; ci++) { if (m_decompression.Comp_info[ci].H_samp_factor != 1 || m_decompression.Comp_info[ci].V_samp_factor != 1) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG sampling factors"); return false; } } } else { /* PC 2's single component should have sampling factors 1,1 */ if (m_decompression.Comp_info[0].H_samp_factor != 1 || m_decompression.Comp_info[0].V_samp_factor != 1) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Improper JPEG sampling factors"); return false; } } bool downsampled_output = false; if (td.td_planarconfig == PlanarConfig.Contig && m_photometric == Photometric.YCBCR && m_jpegcolormode == JpegColorMode.RGB) { /* Convert YCbCr to RGB */ m_decompression.Jpeg_color_space = ColorSpace.YCbCr; m_decompression.Out_color_space = ColorSpace.RGB; } else { /* Suppress colorspace handling */ m_decompression.Jpeg_color_space = ColorSpace.Unknown; m_decompression.Out_color_space = ColorSpace.Unknown; if (td.td_planarconfig == PlanarConfig.Contig && (m_h_sampling != 1 || m_v_sampling != 1)) { downsampled_output = true; } /* XXX what about up-sampling? */ } if (downsampled_output) { // Need to use raw-data interface to LibJpeg.Net m_decompression.Raw_data_out = true; m_rawDecode = true; } else { // Use normal interface to LibJpeg.Net m_decompression.Raw_data_out = false; m_rawDecode = false; } /* Start JPEG decompressor */ if (!TIFFjpeg_start_decompress()) return false; /* Allocate downsampled-data buffers if needed */ if (downsampled_output) { if (!alloc_downsampled_buffers(m_decompression.Comp_info, m_decompression.Num_components)) return false; m_scancount = JpegConstants.DCTSize; /* mark buffer empty */ } return true; } private bool prepare_JPEGTables() { InitializeLibJPEG(false, false); /* Initialize quant tables for current quality setting */ if (!TIFFjpeg_set_quality(m_jpegquality, false)) return false; /* Mark only the tables we want for output */ /* NB: chrominance tables are currently used only with YCbCr */ if (!TIFFjpeg_suppress_tables(true)) return false; if ((m_jpegtablesmode & JpegTablesMode.Quant) != 0) { unsuppress_quant_table(0); if (m_photometric == Photometric.YCBCR) unsuppress_quant_table(1); } if ((m_jpegtablesmode & JpegTablesMode.Huff) != 0) { unsuppress_huff_table(0); if (m_photometric == Photometric.YCBCR) unsuppress_huff_table(1); } // Direct LibJpeg.Net output into jpegtables if (!TIFFjpeg_tables_dest()) return false; /* Emit tables-only datastream */ if (!TIFFjpeg_write_tables()) return false; return true; } private bool JPEGSetupDecode() { TiffDirectory td = m_tif.m_dir; InitializeLibJPEG(false, true); Debug.Assert(m_common.IsDecompressor); /* Read JPEGTables if it is present */ if (m_tif.fieldSet(FIELD_JPEGTABLES)) { m_decompression.Src = new JpegTablesSource(this); if (TIFFjpeg_read_header(false) != ReadResult.Header_Tables_Only) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, "JPEGSetupDecode", "Bogus JPEGTables field"); return false; } } /* Grab parameters that are same for all strips/tiles */ m_photometric = td.td_photometric; switch (m_photometric) { case Photometric.YCBCR: m_h_sampling = td.td_ycbcrsubsampling[0]; m_v_sampling = td.td_ycbcrsubsampling[1]; break; default: /* TIFF 6.0 forbids subsampling of all other color spaces */ m_h_sampling = 1; m_v_sampling = 1; break; } /* Set up for reading normal data */ m_decompression.Src = new JpegStdSource(this); m_tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmNone; /* override byte swapping */ return true; } private int TIFFjpeg_read_scanlines(byte[][] scanlines, int max_lines) { int n = 0; try { n = m_decompression.jpeg_read_scanlines(scanlines, max_lines); } catch (Exception) { return -1; } return n; } /// /// Decode a chunk of pixels. /// "Standard" case: returned data is not downsampled. /// private bool JPEGDecode(byte[] buffer, int offset, int count, short plane) { int nrows = count / m_bytesperline; if ((count % m_bytesperline) != 0) Tiff.WarningExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "fractional scanline not read"); if (nrows > (int)m_decompression.Image_height) nrows = m_decompression.Image_height; // data is expected to be read in multiples of a scanline if (nrows != 0) { byte[][] bufptr = new byte[1][]; bufptr[0] = new byte[m_bytesperline]; do { // In the 8bit case. We read directly into the TIFF buffer. Array.Clear(bufptr[0], 0, m_bytesperline); if (TIFFjpeg_read_scanlines(bufptr, 1) != 1) return false; ++m_tif.m_row; Buffer.BlockCopy(bufptr[0], 0, buffer, offset, m_bytesperline); offset += m_bytesperline; count -= m_bytesperline; } while (--nrows > 0); } // Close down the decompressor if we've finished the strip or tile. return m_decompression.Output_scanline < m_decompression.Output_height || TIFFjpeg_finish_decompress(); } /// /// Decode a chunk of pixels. /// Returned data is downsampled per sampling factors. /// private bool JPEGDecodeRaw(byte[] buffer, int offset, int count, short plane) { // data is expected to be read in multiples of a scanline int nrows = m_decompression.Image_height; if (nrows != 0) { // Cb,Cr both have sampling factors 1, so this is correct int clumps_per_line = m_decompression.Comp_info[1].Downsampled_width; do { // Reload downsampled-data buffer if needed if (m_scancount >= JpegConstants.DCTSize) { int n = m_decompression.Max_v_samp_factor * JpegConstants.DCTSize; if (TIFFjpeg_read_raw_data(m_ds_buffer, n) != n) return false; m_scancount = 0; } // Fastest way to unseparate data is to make one pass over the scanline for // each row of each component. int clumpoffset = 0; // first sample in clump for (int ci = 0; ci < m_decompression.Num_components; ci++) { int hsamp = m_decompression.Comp_info[ci].H_samp_factor; int vsamp = m_decompression.Comp_info[ci].V_samp_factor; for (int ypos = 0; ypos < vsamp; ypos++) { byte[] inBuf = m_ds_buffer[ci][m_scancount * vsamp + ypos]; int inptr = 0; int outptr = offset + clumpoffset; if (outptr >= buffer.Length) break; if (hsamp == 1) { // fast path for at least Cb and Cr for (int nclump = clumps_per_line; nclump-- > 0; ) { buffer[outptr] = inBuf[inptr]; inptr++; outptr += m_samplesperclump; } } else { // general case for (int nclump = clumps_per_line; nclump-- > 0; ) { for (int xpos = 0; xpos < hsamp; xpos++) { buffer[outptr + xpos] = inBuf[inptr]; inptr++; } outptr += m_samplesperclump; } } clumpoffset += hsamp; } } ++m_scancount; m_tif.m_row += m_v_sampling; // increment/decrement of buffer and count is still incorrect, but should not matter // TODO: resolve this offset += m_bytesperline; count -= m_bytesperline; nrows -= m_v_sampling; } while (nrows > 0); } // Close down the decompressor if done. return m_decompression.Output_scanline < m_decompression.Output_height || TIFFjpeg_finish_decompress(); } /// /// Encode a chunk of pixels. /// "Standard" case: incoming data is not downsampled. /// private bool JPEGEncode(byte[] buffer, int offset, int count, short plane) { // data is expected to be supplied in multiples of a scanline int nrows = count / m_bytesperline; if ((count % m_bytesperline) != 0) Tiff.WarningExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "fractional scanline discarded"); // The last strip will be limited to image size if (!m_tif.IsTiled() && m_tif.m_row + nrows > m_tif.m_dir.td_imagelength) nrows = m_tif.m_dir.td_imagelength - m_tif.m_row; byte[][] bufptr = new byte[1][]; bufptr[0] = new byte[m_bytesperline]; while (nrows-- > 0) { Buffer.BlockCopy(buffer, offset, bufptr[0], 0, m_bytesperline); if (TIFFjpeg_write_scanlines(bufptr, 1) != 1) return false; if (nrows > 0) m_tif.m_row++; offset += m_bytesperline; } return true; } /// /// Encode a chunk of pixels. /// Incoming data is expected to be downsampled per sampling factors. /// private bool JPEGEncodeRaw(byte[] buffer, int offset, int count, short plane) { // data is expected to be supplied in multiples of a clumpline // a clumpline is equivalent to v_sampling desubsampled scanlines // TODO: the following calculation of bytesperclumpline, should substitute // calculation of bytesperline, except that it is per v_sampling lines int bytesperclumpline = (((m_compression.Image_width + m_h_sampling - 1) / m_h_sampling) * (m_h_sampling * m_v_sampling + 2) * m_compression.Data_precision + 7) / 8; int nrows = (count / bytesperclumpline) * m_v_sampling; if ((count % bytesperclumpline) != 0) Tiff.WarningExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "fractional scanline discarded"); // Cb,Cr both have sampling factors 1, so this is correct int clumps_per_line = m_compression.Component_info[1].Downsampled_width; while (nrows > 0) { // Fastest way to separate the data is to make one pass over the scanline for // each row of each component. int clumpoffset = 0; // first sample in clump for (int ci = 0; ci < m_compression.Num_components; ci++) { JpegComponent compptr = m_compression.Component_info[ci]; int hsamp = compptr.H_samp_factor; int vsamp = compptr.V_samp_factor; int padding = compptr.Width_in_blocks * JpegConstants.DCTSize - clumps_per_line * hsamp; for (int ypos = 0; ypos < vsamp; ypos++) { int inptr = offset + clumpoffset; byte[] outbuf = m_ds_buffer[ci][m_scancount * vsamp + ypos]; int outptr = 0; if (hsamp == 1) { // fast path for at least Cb and Cr for (int nclump = clumps_per_line; nclump-- > 0; ) { outbuf[outptr] = buffer[inptr]; outptr++; inptr += m_samplesperclump; } } else { // general case for (int nclump = clumps_per_line; nclump-- > 0; ) { for (int xpos = 0; xpos < hsamp; xpos++) { outbuf[outptr] = buffer[inptr + xpos]; outptr++; } inptr += m_samplesperclump; } } // pad each scanline as needed for (int xpos = 0; xpos < padding; xpos++) { outbuf[outptr] = outbuf[outptr - 1]; outptr++; } clumpoffset += hsamp; } } m_scancount++; if (m_scancount >= JpegConstants.DCTSize) { int n = m_compression.Max_v_samp_factor * JpegConstants.DCTSize; if (TIFFjpeg_write_raw_data(m_ds_buffer, n) != n) return false; m_scancount = 0; } m_tif.m_row += m_v_sampling; offset += m_bytesperline; nrows -= m_v_sampling; } return true; } private int JPEGDefaultStripSize(int s) { s = base.DefStripSize(s); if (s < m_tif.m_dir.td_imagelength) s = Tiff.roundUp(s, m_tif.m_dir.td_ycbcrsubsampling[1] * JpegConstants.DCTSize); return s; } private void JPEGDefaultTileSize(ref int tw, ref int th) { base.DefTileSize(ref tw, ref th); tw = Tiff.roundUp(tw, m_tif.m_dir.td_ycbcrsubsampling[0] * JpegConstants.DCTSize); th = Tiff.roundUp(th, m_tif.m_dir.td_ycbcrsubsampling[1] * JpegConstants.DCTSize); } /* * Interface routines. This layer of routines exists * primarily to limit side-effects from LibJpeg.Net exceptions. * Also, normal/error returns are converted into return * values per LibTiff.Net practice. */ private bool TIFFjpeg_create_compress() { /* initialize JPEG error handling */ try { m_compression = new JpegCompressor(); m_common = m_compression; } catch (Exception) { return false; } return true; } private bool TIFFjpeg_create_decompress() { /* initialize JPEG error handling */ try { m_decompression = new JpegDecompressor(); m_common = m_decompression; } catch (Exception) { return false; } return true; } private bool TIFFjpeg_set_defaults() { try { m_compression.jpeg_set_defaults(); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_set_colorspace(ColorSpace colorspace) { try { m_compression.jpeg_set_colorspace(colorspace); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_set_quality(int quality, bool force_baseline) { try { m_compression.jpeg_set_quality(quality, force_baseline); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_suppress_tables(bool suppress) { try { m_compression.jpeg_suppress_tables(suppress); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_start_compress(bool write_all_tables) { try { m_compression.jpeg_start_compress(write_all_tables); } catch (Exception) { return false; } return true; } private int TIFFjpeg_write_scanlines(byte[][] scanlines, int num_lines) { int n = 0; try { n = m_compression.jpeg_write_scanlines(scanlines, num_lines); } catch (Exception) { return -1; } return n; } private int TIFFjpeg_write_raw_data(byte[][][] data, int num_lines) { int n = 0; try { n = m_compression.jpeg_write_raw_data(data, num_lines); } catch (Exception) { return -1; } return n; } private bool TIFFjpeg_finish_compress() { try { m_compression.jpeg_finish_compress(); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_write_tables() { try { m_compression.jpeg_write_tables(); } catch (Exception) { return false; } return true; } private ReadResult TIFFjpeg_read_header(bool require_image) { ReadResult res = ReadResult.Suspended; try { res = m_decompression.jpeg_read_header(require_image); } catch (Exception) { return ReadResult.Suspended; } return res; } private bool TIFFjpeg_start_decompress() { try { m_decompression.jpeg_start_decompress(); } catch (Exception) { return false; } return true; } private int TIFFjpeg_read_raw_data(byte[][][] data, int max_lines) { int n = 0; try { n = m_decompression.jpeg_read_raw_data(data, max_lines); } catch (Exception) { return -1; } return n; } private bool TIFFjpeg_finish_decompress() { bool res = true; try { res = m_decompression.jpeg_finish_decompress(); } catch (Exception) { return false; } return res; } private bool TIFFjpeg_abort() { try { m_common.jpeg_abort(); } catch (Exception) { return false; } return true; } private bool TIFFjpeg_destroy() { try { m_common.jpeg_destroy(); } catch (Exception) { return false; } return true; } private static byte[][] TIFFjpeg_alloc_sarray(int samplesperrow, int numrows) { byte[][] result = new byte[numrows][]; for (int i = 0; i < numrows; i++) result[i] = new byte[samplesperrow]; return result; } /* * Allocate downsampled-data buffers needed for downsampled I/O. * We use values computed in jpeg_start_compress or jpeg_start_decompress. * We use LibJpeg.Net's allocator so that buffers will be released automatically * when done with strip/tile. * This is also a handy place to compute samplesperclump, bytesperline. */ private bool alloc_downsampled_buffers(JpegComponent[] comp_info, int num_components) { int samples_per_clump = 0; for (int ci = 0; ci < num_components; ci++) { JpegComponent compptr = comp_info[ci]; samples_per_clump += compptr.H_samp_factor * compptr.V_samp_factor; byte[][] buf = TIFFjpeg_alloc_sarray( compptr.Width_in_blocks * JpegConstants.DCTSize, compptr.V_samp_factor * JpegConstants.DCTSize); m_ds_buffer[ci] = buf; } m_samplesperclump = samples_per_clump; return true; } private void unsuppress_quant_table(int tblno) { JpegQuantizationTable qtbl = m_compression.Quant_tbl_ptrs[tblno]; if (qtbl != null) qtbl.Sent_table = false; } private void unsuppress_huff_table(int tblno) { JpegHuffmanTable htbl = m_compression.Dc_huff_tbl_ptrs[tblno]; if (htbl != null) htbl.Sent_table = false; htbl = m_compression.Ac_huff_tbl_ptrs[tblno]; if (htbl != null) htbl.Sent_table = false; } private void TIFFjpeg_data_dest() { m_compression.Dest = new JpegStdDestination(m_tif); } private bool TIFFjpeg_tables_dest() { /* * Allocate a working buffer for building tables. * Initial size is 1000 bytes, which is usually adequate. */ m_jpegtables_length = 1000; m_jpegtables = new byte[m_jpegtables_length]; m_compression.Dest = new JpegTablesDestination(this); return true; } } #endregion #region JpegCodecTagMethods class JpegCodecTagMethods : TiffTagMethods { public override bool SetField(Tiff tif, TiffTag tag, FieldValue[] ap) { JpegCodec sp = tif.m_currentCodec as JpegCodec; Debug.Assert(sp != null); switch (tag) { case TiffTag.JpegTables: int v32 = ap[0].ToInt(); if (v32 == 0) { // XXX return false; } sp.m_jpegtables = new byte[v32]; Buffer.BlockCopy(ap[1].ToByteArray(), 0, sp.m_jpegtables, 0, v32); sp.m_jpegtables_length = v32; tif.setFieldBit(JpegCodec.FIELD_JPEGTABLES); break; case TiffTag.JPEGQUALITY: sp.m_jpegquality = ap[0].ToInt(); return true; // pseudo tag case TiffTag.JPEGCOLORMODE: sp.m_jpegcolormode = (JpegColorMode)ap[0].ToShort(); sp.JPEGResetUpsampled(); return true; // pseudo tag case TiffTag.Photometric: bool ret_value = base.SetField(tif, tag, ap); sp.JPEGResetUpsampled(); return ret_value; case TiffTag.JPEGTABLESMODE: sp.m_jpegtablesmode = (JpegTablesMode)ap[0].ToShort(); return true; // pseudo tag case TiffTag.YCBCRSUBSAMPLING: // mark the fact that we have a real ycbcrsubsampling! sp.m_ycbcrsampling_fetched = true; // should we be recomputing upsampling info here? return base.SetField(tif, tag, ap); case TiffTag.FAXRECVPARAMS: sp.m_recvparams = ap[0].ToInt(); break; case TiffTag.FAXSUBADDRESS: Tiff.setString(out sp.m_subaddress, ap[0].ToString()); break; case TiffTag.FAXRECVTIME: sp.m_recvtime = ap[0].ToInt(); break; case TiffTag.FAXDCS: Tiff.setString(out sp.m_faxdcs, ap[0].ToString()); break; default: return base.SetField(tif, tag, ap); } TiffFieldInfo fip = tif.FieldWithTag(tag); if (fip != null) tif.setFieldBit(fip.Bit); else return false; tif.m_flags |= TiffFlags.DirtyDirect; return true; } public override FieldValue[] GetField(Tiff tif, TiffTag tag) { JpegCodec sp = tif.m_currentCodec as JpegCodec; Debug.Assert(sp != null); FieldValue[] result = null; switch (tag) { case TiffTag.JpegTables: result = new FieldValue[2]; result[0].Set(sp.m_jpegtables_length); result[1].Set(sp.m_jpegtables); break; case TiffTag.JPEGQUALITY: result = new FieldValue[1]; result[0].Set(sp.m_jpegquality); break; case TiffTag.JPEGCOLORMODE: result = new FieldValue[1]; result[0].Set(sp.m_jpegcolormode); break; case TiffTag.JPEGTABLESMODE: result = new FieldValue[1]; result[0].Set(sp.m_jpegtablesmode); break; case TiffTag.YCBCRSUBSAMPLING: JPEGFixupTestSubsampling(tif); return base.GetField(tif, tag); case TiffTag.FAXRECVPARAMS: result = new FieldValue[1]; result[0].Set(sp.m_recvparams); break; case TiffTag.FAXSUBADDRESS: result = new FieldValue[1]; result[0].Set(sp.m_subaddress); break; case TiffTag.FAXRECVTIME: result = new FieldValue[1]; result[0].Set(sp.m_recvtime); break; case TiffTag.FAXDCS: result = new FieldValue[1]; result[0].Set(sp.m_faxdcs); break; default: return base.GetField(tif, tag); } return result; } public override void PrintDir(Tiff tif, Stream fd, TiffPrintFlags flags) { JpegCodec sp = tif.m_currentCodec as JpegCodec; Debug.Assert(sp != null); if (tif.fieldSet(JpegCodec.FIELD_JPEGTABLES)) Tiff.fprintf(fd, " JPEG Tables: ({0} bytes)\n", sp.m_jpegtables_length); if (tif.fieldSet(JpegCodec.FIELD_RECVPARAMS)) Tiff.fprintf(fd, " Fax Receive Parameters: {0,8:x}\n", sp.m_recvparams); if (tif.fieldSet(JpegCodec.FIELD_SUBADDRESS)) Tiff.fprintf(fd, " Fax SubAddress: {0}\n", sp.m_subaddress); if (tif.fieldSet(JpegCodec.FIELD_RECVTIME)) Tiff.fprintf(fd, " Fax Receive Time: {0} secs\n", sp.m_recvtime); if (tif.fieldSet(JpegCodec.FIELD_FAXDCS)) Tiff.fprintf(fd, " Fax DCS: {0}\n", sp.m_faxdcs); } /* * Some JPEG-in-TIFF produces do not emit the YCBCRSUBSAMPLING values in * the TIFF tags, but still use non-default (2,2) values within the jpeg * data stream itself. In order for TIFF applications to work properly * - for instance to get the strip buffer size right - it is imperative * that the subsampling be available before we start reading the image * data normally. This function will attempt to load the first strip in * order to get the sampling values from the jpeg data stream. Various * hacks are various places are done to ensure this function gets called * before the td_ycbcrsubsampling values are used from the directory structure, * including calling TIFFGetField() for the YCBCRSUBSAMPLING field from * TIFFStripSize(), and the printing code in tif_print.c. * * Note that JPEGPreDeocode() will produce a fairly loud warning when the * discovered sampling does not match the default sampling (2,2) or whatever * was actually in the tiff tags. * * Problems: * o This code will cause one whole strip/tile of compressed data to be * loaded just to get the tags right, even if the imagery is never read. * It would be more efficient to just load a bit of the header, and * initialize things from that. * * See the bug in bugzilla for details: * * http://bugzilla.remotesensing.org/show_bug.cgi?id=168 * * Frank Warmerdam, July 2002 */ private static void JPEGFixupTestSubsampling(Tiff tif) { if (Tiff.CHECK_JPEG_YCBCR_SUBSAMPLING) { JpegCodec sp = tif.m_currentCodec as JpegCodec; Debug.Assert(sp != null); sp.InitializeLibJPEG(false, false); /* * Some JPEG-in-TIFF files don't provide the ycbcrsampling tags, * and use a sampling schema other than the default 2,2. To handle * this we actually have to scan the header of a strip or tile of * jpeg data to get the sampling. */ if (!sp.m_common.IsDecompressor || sp.m_ycbcrsampling_fetched || tif.m_dir.td_photometric != Photometric.YCBCR) { return; } sp.m_ycbcrsampling_fetched = true; if (tif.IsTiled()) { if (!tif.fillTile(0)) return; } else { if (!tif.fillStrip(0)) return; } tif.SetField(TiffTag.YCBCRSUBSAMPLING, sp.m_h_sampling, sp.m_v_sampling); // We want to clear the loaded strip so the application has time // to set JPEGCOLORMODE or other behavior modifiers. This essentially // undoes the JPEGPreDecode triggers by FileStrip(). tif.m_curstrip = -1; } } } #endregion #region JpegStdDestination /// /// JPEG library destination data manager. /// These routines direct compressed data from LibJpeg.Net into the /// LibTiff.Net output buffer. /// class JpegStdDestination : DestinationManager { private Tiff m_tif; public JpegStdDestination(Tiff tif) { m_tif = tif; } public override void init_destination() { initInternalBuffer(m_tif.m_rawdata, 0); } public override bool empty_output_buffer() { /* the entire buffer has been filled */ m_tif.m_rawcc = m_tif.m_rawdatasize; m_tif.flushData1(); initInternalBuffer(m_tif.m_rawdata, 0); return true; } public override void term_destination() { m_tif.m_rawcp = m_tif.m_rawdatasize - freeInBuffer; m_tif.m_rawcc = m_tif.m_rawdatasize - freeInBuffer; /* NB: LibTiff.Net does the final buffer flush */ } } #endregion #region JpegStdSource /// /// JPEG library source data manager. /// These routines supply compressed data to LibJpeg.Net /// class JpegStdSource : Jpeg_Source { static byte[] dummy_EOI = { 0xFF, (byte)JpegMarkerType.EOI }; protected JpegCodec m_sp; public JpegStdSource(JpegCodec sp) { initInternalBuffer(null, 0); m_sp = sp; } public override void init_source() { Tiff tif = m_sp.GetTiff(); initInternalBuffer(tif.m_rawdata, tif.m_rawcc); } public override bool fill_input_buffer() { /* * Should never get here since entire strip/tile is * read into memory before the decompressor is called, * and thus was supplied by init_source. */ /* insert a fake EOI marker */ initInternalBuffer(dummy_EOI, 2); return true; } } #endregion #region JpegTablesDestination /// /// Alternate destination manager for outputting to JPEGTables field. /// class JpegTablesDestination : DestinationManager { private JpegCodec m_sp; public JpegTablesDestination(JpegCodec sp) { m_sp = sp; } public override void init_destination() { /* while building, jpegtables_length is allocated buffer size */ initInternalBuffer(m_sp.m_jpegtables, 0); } public override bool empty_output_buffer() { /* the entire buffer has been filled; enlarge it by 1000 bytes */ byte[] newbuf = Tiff.Realloc(m_sp.m_jpegtables, m_sp.m_jpegtables_length + 1000); initInternalBuffer(newbuf, m_sp.m_jpegtables_length); m_sp.m_jpegtables = newbuf; m_sp.m_jpegtables_length += 1000; return true; } public override void term_destination() { /* set tables length to number of bytes actually emitted */ m_sp.m_jpegtables_length -= freeInBuffer; } } #endregion #region JpegTablesSource /// /// Alternate source manager for reading from JPEGTables. /// We can share all the code except for the init routine. /// class JpegTablesSource : JpegStdSource { public JpegTablesSource(JpegCodec sp) : base(sp) { } public override void init_source() { initInternalBuffer(m_sp.m_jpegtables, m_sp.m_jpegtables_length); } } #endregion #region LZWCodec class LZWCodec : CodecWithPredictor { /* * Each strip of data is supposed to be terminated by a CODE_EOI. * If the following #define is included, the decoder will also * check for end-of-strip w/o seeing this code. This makes the * library more robust, but also slower. */ private bool LZW_CHECKEOS = true; /* include checks for strips w/o EOI code */ /* * The TIFF spec specifies that encoded bit * strings range from 9 to 12 bits. */ private const short BITS_MIN = 9; /* start with 9 bits */ private const short BITS_MAX = 12; /* max of 12 bit strings */ /* predefined codes */ private const short CODE_CLEAR = 256; /* code to clear string table */ private const short CODE_EOI = 257; /* end-of-information code */ private const short CODE_FIRST = 258; /* first free code entry */ private const short CODE_MAX = ((1 << BITS_MAX) - 1); private const short CODE_MIN = ((1 << BITS_MIN) - 1); private const int HSIZE = 9001; /* 91% occupancy */ private const int HSHIFT = (13 - 8); /* NB: +1024 is for compatibility with old files */ private const int CSIZE = (((1 << BITS_MAX) - 1) + 1024); private const int CHECK_GAP = 10000; /* enc_ratio check interval */ /* * Decoding-specific state. */ private struct code_t { public int next; public short length; /* string len, including this token */ public byte value; /* data value */ public byte firstchar; /* first token of string */ }; /* * Encoding-specific state. */ private struct hash_t { public int hash; public short code; }; private bool m_compatDecode; private short m_nbits; /* # of bits/code */ private short m_maxcode; /* maximum code for base.nbits */ private short m_free_ent; /* next free entry in hash table */ private int m_nextdata; /* next bits of i/o */ private int m_nextbits; /* # of valid bits in base.nextdata */ private int m_rw_mode; /* preserve rw_mode from init */ /* Decoding specific data */ private int m_dec_nbitsmask; /* lzw_nbits 1 bits, right adjusted */ private int m_dec_restart; /* restart count */ private int m_dec_bitsleft; /* available bits in raw data */ private bool m_oldStyleCodeFound; /* if true, old style LZW code found*/ private int m_dec_codep; /* current recognized code */ private int m_dec_oldcodep; /* previously recognized code */ private int m_dec_free_entp; /* next free entry */ private int m_dec_maxcodep; /* max available entry */ private code_t[] m_dec_codetab; /* kept separate for small machines */ /* Encoding specific data */ private int m_enc_oldcode; /* last code encountered */ private int m_enc_checkpoint; /* point at which to clear table */ private int m_enc_ratio; /* current compression ratio */ private int m_enc_incount; /* (input) data bytes encoded */ private int m_enc_outcount; /* encoded (output) bytes */ private int m_enc_rawlimit; /* bound on tif_rawdata buffer */ private hash_t[] m_enc_hashtab; /* kept separate for small machines */ public LZWCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { } public override bool Init() { Debug.Assert(m_scheme == Compression.LZW); m_dec_codetab = null; m_oldStyleCodeFound = false; m_enc_hashtab = null; m_rw_mode = m_tif.m_mode; m_compatDecode = false; /* * Setup predictor setup. */ TIFFPredictorInit(null); return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// /// PreDecode is called after and before decoding. /// public override bool PreDecode(short plane) { return LZWPreDecode(plane); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { return LZWPreEncode(plane); } /// /// Performs any actions after encoding required by the codec. /// /// /// true if all post-encode actions succeeded; otherwise, false /// /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public override bool PostEncode() { return LZWPostEncode(); } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public override void Cleanup() { LZWCleanup(); m_tif.m_mode = m_rw_mode; } // CodecWithPredictor overrides public override bool predictor_setupdecode() { return LZWSetupDecode(); } public override bool predictor_decoderow(byte[] buffer, int offset, int count, short plane) { if (m_compatDecode) return LZWDecodeCompat(buffer, offset, count, plane); return LZWDecode(buffer, offset, count, plane); } public override bool predictor_decodestrip(byte[] buffer, int offset, int count, short plane) { if (m_compatDecode) return LZWDecodeCompat(buffer, offset, count, plane); return LZWDecode(buffer, offset, count, plane); } public override bool predictor_decodetile(byte[] buffer, int offset, int count, short plane) { if (m_compatDecode) return LZWDecodeCompat(buffer, offset, count, plane); return LZWDecode(buffer, offset, count, plane); } public override bool predictor_setupencode() { return LZWSetupEncode(); } public override bool predictor_encoderow(byte[] buffer, int offset, int count, short plane) { return LZWEncode(buffer, offset, count, plane); } public override bool predictor_encodestrip(byte[] buffer, int offset, int count, short plane) { return LZWEncode(buffer, offset, count, plane); } public override bool predictor_encodetile(byte[] buffer, int offset, int count, short plane) { return LZWEncode(buffer, offset, count, plane); } private bool LZWSetupDecode() { if (m_dec_codetab == null) { m_dec_codetab = new code_t[CSIZE]; /* * Pre-load the table. */ int code = 255; do { m_dec_codetab[code].value = (byte)code; m_dec_codetab[code].firstchar = (byte)code; m_dec_codetab[code].length = 1; m_dec_codetab[code].next = -1; } while (code-- != 0); /* * Zero-out the unused entries */ Array.Clear(m_dec_codetab, CODE_CLEAR, CODE_FIRST - CODE_CLEAR); } return true; } /* * Setup state for decoding a strip. */ private bool LZWPreDecode(short s) { if (m_dec_codetab == null) SetupDecode(); /* * Check for old bit-reversed codes. */ if (m_tif.m_rawdata[0] == 0 && (m_tif.m_rawdata[1] & 0x1) != 0) { if (!m_oldStyleCodeFound) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Old-style LZW codes, convert file"); m_compatDecode = true; /* * If doing horizontal differencing, must * re-setup the predictor logic since we * switched the basic decoder methods... */ SetupDecode(); m_oldStyleCodeFound = true; } m_maxcode = CODE_MIN; } else { m_maxcode = CODE_MIN - 1; m_oldStyleCodeFound = false; } m_nbits = BITS_MIN; m_nextbits = 0; m_nextdata = 0; m_dec_restart = 0; m_dec_nbitsmask = CODE_MIN; m_dec_bitsleft = m_tif.m_rawcc << 3; m_dec_free_entp = CODE_FIRST; /* * Zero entries that are not yet filled in. We do * this to guard against bogus input data that causes * us to index into undefined entries. If you can * come up with a way to safely bounds-check input codes * while decoding then you can remove this operation. */ Array.Clear(m_dec_codetab, m_dec_free_entp, CSIZE - CODE_FIRST); m_dec_oldcodep = -1; m_dec_maxcodep = m_dec_nbitsmask - 1; return true; } private bool LZWDecode(byte[] buffer, int offset, int count, short plane) { Debug.Assert(m_dec_codetab != null); // Restart interrupted output operation. if (m_dec_restart != 0) { int codep = m_dec_codep; int residue = m_dec_codetab[codep].length - m_dec_restart; if (residue > count) { // Residue from previous decode is sufficient to satisfy decode request. Skip // to the start of the decoded string, place decoded values in the output // buffer, and return. m_dec_restart += count; do { codep = m_dec_codetab[codep].next; } while (--residue > count && codep != -1); if (codep != -1) { int tp = count; do { tp--; buffer[offset + tp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (--count != 0 && codep != -1); } return true; } // Residue satisfies only part of the decode request. offset += residue; count -= residue; int ttp = 0; do { --ttp; int t = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; buffer[offset + ttp] = (byte)t; } while (--residue != 0 && codep != -1); m_dec_restart = 0; } while (count > 0) { short code; NextCode(out code, false); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { m_dec_free_entp = CODE_FIRST; Array.Clear(m_dec_codetab, m_dec_free_entp, CSIZE - CODE_FIRST); m_nbits = BITS_MIN; m_dec_nbitsmask = CODE_MIN; m_dec_maxcodep = m_dec_nbitsmask - 1; NextCode(out code, false); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } buffer[offset] = (byte)code; offset++; count--; m_dec_oldcodep = code; continue; } int codep = code; // Add the new entry to the code table. if (m_dec_free_entp < 0 || m_dec_free_entp >= CSIZE) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } m_dec_codetab[m_dec_free_entp].next = m_dec_oldcodep; if (m_dec_codetab[m_dec_free_entp].next < 0 || m_dec_codetab[m_dec_free_entp].next >= CSIZE) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } m_dec_codetab[m_dec_free_entp].firstchar = m_dec_codetab[m_dec_codetab[m_dec_free_entp].next].firstchar; m_dec_codetab[m_dec_free_entp].length = (short)(m_dec_codetab[m_dec_codetab[m_dec_free_entp].next].length + 1); m_dec_codetab[m_dec_free_entp].value = (codep < m_dec_free_entp) ? m_dec_codetab[codep].firstchar : m_dec_codetab[m_dec_free_entp].firstchar; if (++m_dec_free_entp > m_dec_maxcodep) { if (++m_nbits > BITS_MAX) { // should not happen m_nbits = BITS_MAX; } m_dec_nbitsmask = MAXCODE(m_nbits); m_dec_maxcodep = m_dec_nbitsmask - 1; } m_dec_oldcodep = code; if (code >= 256) { // Code maps to a string, copy string value to output (written in reverse). if (m_dec_codetab[codep].length == 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Wrong length of decoded string: data probably corrupted at scanline {0}", m_tif.m_row); return false; } if (m_dec_codetab[codep].length > count) { // String is too long for decode buffer, locate portion that will fit, // copy to the decode buffer, and setup restart logic for the next // decoding call. m_dec_codep = code; do { codep = m_dec_codetab[codep].next; } while (codep != -1 && m_dec_codetab[codep].length > count); if (codep != -1) { m_dec_restart = count; int tp = count; do { tp--; buffer[offset + tp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (--count != 0 && codep != -1); if (codep != -1) codeLoop(); } break; } int len = m_dec_codetab[codep].length; int ttp = len; do { --ttp; int t = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; buffer[offset + ttp] = (byte)t; } while (codep != -1 && ttp > 0); if (codep != -1) { codeLoop(); break; } offset += len; count -= len; } else { buffer[offset] = (byte)code; offset++; count--; } } if (count > 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Not enough data at scanline {0} (short {1} bytes)", m_tif.m_row, count); return false; } return true; } private bool LZWDecodeCompat(byte[] buffer, int offset, int count, short plane) { // Restart interrupted output operation. if (m_dec_restart != 0) { int residue; int codep = m_dec_codep; residue = m_dec_codetab[codep].length - m_dec_restart; if (residue > count) { // Residue from previous decode is sufficient to satisfy decode request. // Skip to the start of the decoded string, place decoded values in the output // buffer, and return. m_dec_restart += count; do { codep = m_dec_codetab[codep].next; } while (--residue > count); int tp = count; do { --tp; buffer[offset + tp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (--count != 0); return true; } // Residue satisfies only part of the decode request. offset += residue; count -= residue; int ttp = 0; do { --ttp; buffer[offset + ttp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (--residue != 0); m_dec_restart = 0; } while (count > 0) { short code; NextCode(out code, true); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { m_dec_free_entp = CODE_FIRST; Array.Clear(m_dec_codetab, m_dec_free_entp, CSIZE - CODE_FIRST); m_nbits = BITS_MIN; m_dec_nbitsmask = CODE_MIN; m_dec_maxcodep = m_dec_nbitsmask; NextCode(out code, true); if (code == CODE_EOI) break; if (code == CODE_CLEAR) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } buffer[offset] = (byte)code; offset++; count--; m_dec_oldcodep = code; continue; } int codep = code; // Add the new entry to the code table. if (m_dec_free_entp < 0 || m_dec_free_entp >= CSIZE) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecodeCompat: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } m_dec_codetab[m_dec_free_entp].next = m_dec_oldcodep; if (m_dec_codetab[m_dec_free_entp].next < 0 || m_dec_codetab[m_dec_free_entp].next >= CSIZE) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecodeCompat: Corrupted LZW table at scanline {0}", m_tif.m_row); return false; } m_dec_codetab[m_dec_free_entp].firstchar = m_dec_codetab[m_dec_codetab[m_dec_free_entp].next].firstchar; m_dec_codetab[m_dec_free_entp].length = (short)(m_dec_codetab[m_dec_codetab[m_dec_free_entp].next].length + 1); m_dec_codetab[m_dec_free_entp].value = (codep < m_dec_free_entp) ? m_dec_codetab[codep].firstchar : m_dec_codetab[m_dec_free_entp].firstchar; if (++m_dec_free_entp > m_dec_maxcodep) { if (++m_nbits > BITS_MAX) { // should not happen m_nbits = BITS_MAX; } m_dec_nbitsmask = MAXCODE(m_nbits); m_dec_maxcodep = m_dec_nbitsmask; } m_dec_oldcodep = code; if (code >= 256) { int op_orig = offset; // Code maps to a string, copy string value to output (written in reverse). if (m_dec_codetab[codep].length == 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecodeCompat: Wrong length of decoded string: data probably corrupted at scanline {0}", m_tif.m_row); return false; } if (m_dec_codetab[codep].length > count) { // String is too long for decode buffer, locate portion that will fit, // copy to the decode buffer, and setup restart logic for the next // decoding call. m_dec_codep = code; do { codep = m_dec_codetab[codep].next; } while (m_dec_codetab[codep].length > count); m_dec_restart = count; int tp = count; do { --tp; buffer[offset + tp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (--count != 0); break; } offset += m_dec_codetab[codep].length; count -= m_dec_codetab[codep].length; int ttp = offset; do { --ttp; buffer[ttp] = m_dec_codetab[codep].value; codep = m_dec_codetab[codep].next; } while (codep != -1 && ttp > op_orig); } else { buffer[offset] = (byte)code; offset++; count--; } } if (count > 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecodeCompat: Not enough data at scanline {0} (short {1} bytes)", m_tif.m_row, count); return false; } return true; } private bool LZWSetupEncode() { m_enc_hashtab = new hash_t[HSIZE]; return true; } /* * Reset encoding state at the start of a strip. */ private bool LZWPreEncode(short s) { if (m_enc_hashtab == null) SetupEncode(); m_nbits = BITS_MIN; m_maxcode = CODE_MIN; m_free_ent = CODE_FIRST; m_nextbits = 0; m_nextdata = 0; m_enc_checkpoint = CHECK_GAP; m_enc_ratio = 0; m_enc_incount = 0; m_enc_outcount = 0; /* * The 4 here insures there is space for 2 max-sized * codes in LZWEncode and LZWPostDecode. */ m_enc_rawlimit = m_tif.m_rawdatasize - 1 - 4; cl_hash(); /* clear hash table */ m_enc_oldcode = -1; /* generates CODE_CLEAR in LZWEncode */ return true; } /* * Finish off an encoded strip by flushing the last * string and tacking on an End Of Information code. */ private bool LZWPostEncode() { if (m_tif.m_rawcp > m_enc_rawlimit) { m_tif.m_rawcc = m_tif.m_rawcp; m_tif.flushData1(); m_tif.m_rawcp = 0; } if (m_enc_oldcode != -1) { PutNextCode(m_enc_oldcode); m_enc_oldcode = -1; } PutNextCode(CODE_EOI); if (m_nextbits > 0) { m_tif.m_rawdata[m_tif.m_rawcp] = (byte)(m_nextdata << (8 - m_nextbits)); m_tif.m_rawcp++; } m_tif.m_rawcc = m_tif.m_rawcp; return true; } /// /// Encode a chunk of pixels. /// /// /// Uses an open addressing double hashing (no chaining) on the prefix code/next character /// combination. We do a variant of Knuth's algorithm D (vol. 3, sec. 6.4) along with /// G. Knott's relatively-prime secondary probe. Here, the modular division first probe is /// gives way to a faster exclusive-or manipulation. Also do block compression with an /// adaptive reset, whereby the code table is cleared when the compression ratio /// decreases, but after the table fills. The variable-length output codes are re-sized at /// this point, and a CODE_CLEAR is generated for the decoder. /// private bool LZWEncode(byte[] buffer, int offset, int count, short plane) { Debug.Assert(m_enc_hashtab != null); if (m_enc_oldcode == -1 && count > 0) { // NB: This is safe because it can only happen at the start of a strip where we // know there is space in the data buffer. PutNextCode(CODE_CLEAR); m_enc_oldcode = buffer[offset]; offset++; count--; m_enc_incount++; } while (count > 0) { int c = buffer[offset]; offset++; count--; m_enc_incount++; int fcode = (c << BITS_MAX) + m_enc_oldcode; int h = (c << HSHIFT) ^ m_enc_oldcode; // xor hashing // Check hash index for an overflow. if (h >= HSIZE) h -= HSIZE; if (m_enc_hashtab[h].hash == fcode) { m_enc_oldcode = m_enc_hashtab[h].code; continue; } bool hit = false; if (m_enc_hashtab[h].hash >= 0) { // Primary hash failed, check secondary hash. int disp = HSIZE - h; if (h == 0) disp = 1; do { h -= disp; if (h < 0) h += HSIZE; if (m_enc_hashtab[h].hash == fcode) { m_enc_oldcode = m_enc_hashtab[h].code; hit = true; break; } } while (m_enc_hashtab[h].hash >= 0); } if (!hit) { // New entry, emit code and add to table. // Verify there is space in the buffer for the code and any potential Clear // code that might be emitted below. The value of limit is setup so that there // are at least 4 bytes free - room for 2 codes. if (m_tif.m_rawcp > m_enc_rawlimit) { m_tif.m_rawcc = m_tif.m_rawcp; m_tif.flushData1(); m_tif.m_rawcp = 0; } PutNextCode(m_enc_oldcode); m_enc_oldcode = c; m_enc_hashtab[h].code = m_free_ent; m_free_ent++; m_enc_hashtab[h].hash = fcode; if (m_free_ent == CODE_MAX - 1) { // table is full, emit clear code and reset cl_hash(); m_enc_ratio = 0; m_enc_incount = 0; m_enc_outcount = 0; m_free_ent = CODE_FIRST; PutNextCode(CODE_CLEAR); m_nbits = BITS_MIN; m_maxcode = CODE_MIN; } else { // If the next entry is going to be too big for the code size, then // increase it, if possible. if (m_free_ent > m_maxcode) { m_nbits++; Debug.Assert(m_nbits <= BITS_MAX); m_maxcode = (short)MAXCODE(m_nbits); } else if (m_enc_incount >= m_enc_checkpoint) { // Check compression ratio and, if things seem to be slipping, clear // the hash table and reset state. The compression ratio is // a 24 + 8-bit fractional number. m_enc_checkpoint = m_enc_incount + CHECK_GAP; int rat; if (m_enc_incount > 0x007fffff) { // NB: shift will overflow rat = m_enc_outcount >> 8; rat = (rat == 0 ? 0x7fffffff : m_enc_incount / rat); } else rat = (m_enc_incount << 8) / m_enc_outcount; if (rat <= m_enc_ratio) { cl_hash(); m_enc_ratio = 0; m_enc_incount = 0; m_enc_outcount = 0; m_free_ent = CODE_FIRST; PutNextCode(CODE_CLEAR); m_nbits = BITS_MIN; m_maxcode = CODE_MIN; } else m_enc_ratio = rat; } } } } return true; } private void LZWCleanup() { m_dec_codetab = null; m_enc_hashtab = null; } private static int MAXCODE(int n) { return ((1 << n) - 1); } private void PutNextCode(int c) { m_nextdata = (m_nextdata << m_nbits) | c; m_nextbits += m_nbits; m_tif.m_rawdata[m_tif.m_rawcp] = (byte)(m_nextdata >> (m_nextbits - 8)); m_tif.m_rawcp++; m_nextbits -= 8; if (m_nextbits >= 8) { m_tif.m_rawdata[m_tif.m_rawcp] = (byte)(m_nextdata >> (m_nextbits - 8)); m_tif.m_rawcp++; m_nextbits -= 8; } m_enc_outcount += m_nbits; } /* * Reset encoding hash table. */ private void cl_hash() { int hp = HSIZE - 1; int i = HSIZE - 8; do { i -= 8; m_enc_hashtab[hp - 7].hash = -1; m_enc_hashtab[hp - 6].hash = -1; m_enc_hashtab[hp - 5].hash = -1; m_enc_hashtab[hp - 4].hash = -1; m_enc_hashtab[hp - 3].hash = -1; m_enc_hashtab[hp - 2].hash = -1; m_enc_hashtab[hp - 1].hash = -1; m_enc_hashtab[hp].hash = -1; hp -= 8; } while (i >= 0); for (i += 8; i > 0; i--, hp--) m_enc_hashtab[hp].hash = -1; } private void NextCode(out short _code, bool compat) { if (LZW_CHECKEOS) { if (m_dec_bitsleft < m_nbits) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Strip {0} not terminated with EOI code", m_tif.m_curstrip); _code = CODE_EOI; } else { if (compat) GetNextCodeCompat(out _code); else GetNextCode(out _code); m_dec_bitsleft -= m_nbits; } } else { if (compat) GetNextCodeCompat(out _code); else GetNextCode(out _code); } } private void GetNextCode(out short code) { m_nextdata = (m_nextdata << 8) | m_tif.m_rawdata[m_tif.m_rawcp]; m_tif.m_rawcp++; m_nextbits += 8; if (m_nextbits < m_nbits) { m_nextdata = (m_nextdata << 8) | m_tif.m_rawdata[m_tif.m_rawcp]; m_tif.m_rawcp++; m_nextbits += 8; } code = (short)((m_nextdata >> (m_nextbits - m_nbits)) & m_dec_nbitsmask); m_nextbits -= m_nbits; } private void GetNextCodeCompat(out short code) { m_nextdata |= m_tif.m_rawdata[m_tif.m_rawcp] << m_nextbits; m_tif.m_rawcp++; m_nextbits += 8; if (m_nextbits < m_nbits) { m_nextdata |= m_tif.m_rawdata[m_tif.m_rawcp] << m_nextbits; m_tif.m_rawcp++; m_nextbits += 8; } code = (short)(m_nextdata & m_dec_nbitsmask); m_nextdata >>= m_nbits; m_nextbits -= m_nbits; } private void codeLoop() { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "LZWDecode: Bogus encoding, loop in the code table; scanline {0}", m_tif.m_row); } } #endregion #region OJpegCodec class OJpegCodec : TiffCodec { internal const int FIELD_OJPEG_JPEGINTERCHANGEFORMAT = (FieldBit.Codec + 0); internal const int FIELD_OJPEG_JPEGINTERCHANGEFORMATLENGTH = (FieldBit.Codec + 1); internal const int FIELD_OJPEG_JPEGQTABLES = (FieldBit.Codec + 2); internal const int FIELD_OJPEG_JPEGDCTABLES = (FieldBit.Codec + 3); internal const int FIELD_OJPEG_JPEGACTABLES = (FieldBit.Codec + 4); internal const int FIELD_OJPEG_JPEGPROC = (FieldBit.Codec + 5); internal const int FIELD_OJPEG_JPEGRESTARTINTERVAL = (FieldBit.Codec + 6); internal const int FIELD_OJPEG_COUNT = 7; private const int OJPEG_BUFFER = 2048; private enum OJPEGStateInBufferSource { osibsNotSetYet, osibsJpegInterchangeFormat, osibsStrile, osibsEof } private enum OJPEGStateOutState { ososSoi, ososQTable0, ososQTable1, ososQTable2, ososQTable3, ososDcTable0, ososDcTable1, ososDcTable2, ososDcTable3, ososAcTable0, ososAcTable1, ososAcTable2, ososAcTable3, ososDri, ososSof, ososSos, ososCompressed, ososRst, ososEoi } private static TiffFieldInfo[] ojpeg_field_info = { new TiffFieldInfo(TiffTag.JPEGIFOffset, 1, 1, TiffType.Long, FIELD_OJPEG_JPEGINTERCHANGEFORMAT, true, false, "JpegInterchangeFormat"), new TiffFieldInfo(TiffTag.JPEGIFByteCount, 1, 1, TiffType.Long, FIELD_OJPEG_JPEGINTERCHANGEFORMATLENGTH, true, false, "JpegInterchangeFormatLength"), new TiffFieldInfo(TiffTag.JPEGQTables, -1, -1, TiffType.Long, FIELD_OJPEG_JPEGQTABLES, false, true, "JpegQTables"), new TiffFieldInfo(TiffTag.JPEGDCTables, -1, -1, TiffType.Long, FIELD_OJPEG_JPEGDCTABLES, false, true, "JpegDcTables"), new TiffFieldInfo(TiffTag.JPEGACTABLES, -1, -1, TiffType.Long, FIELD_OJPEG_JPEGACTABLES, false, true, "JpegAcTables"), new TiffFieldInfo(TiffTag.JPEGProc, 1, 1, TiffType.Short, FIELD_OJPEG_JPEGPROC, false, false, "JpegProc"), new TiffFieldInfo(TiffTag.JPEGRestartInterval, 1, 1, TiffType.Short, FIELD_OJPEG_JPEGRESTARTINTERVAL, false, false, "JpegRestartInterval"), }; private struct SosEnd { public bool m_log; public OJPEGStateInBufferSource m_in_buffer_source; public uint m_in_buffer_next_strile; public uint m_in_buffer_file_pos; public uint m_in_buffer_file_togo; } internal uint m_jpeg_interchange_format; internal uint m_jpeg_interchange_format_length; internal byte m_jpeg_proc; internal bool m_subsamplingcorrect_done; internal bool m_subsampling_tag; internal byte m_subsampling_hor; internal byte m_subsampling_ver; internal byte m_qtable_offset_count; internal byte m_dctable_offset_count; internal byte m_actable_offset_count; internal uint[] m_qtable_offset = new uint[3]; internal uint[] m_dctable_offset = new uint[3]; internal uint[] m_actable_offset = new uint[3]; internal ushort m_restart_interval; internal JpegDecompressor m_libjpeg_jpeg_decompress_struct; private TiffTagMethods m_tagMethods; private TiffTagMethods m_parentTagMethods; private uint m_file_size; private uint m_image_width; private uint m_image_length; private uint m_strile_width; private uint m_strile_length; private uint m_strile_length_total; private byte m_samples_per_pixel; private byte m_plane_sample_offset; private byte m_samples_per_pixel_per_plane; private bool m_subsamplingcorrect; private bool m_subsampling_force_desubsampling_inside_decompression; private byte[][] m_qtable = new byte[4][]; private byte[][] m_dctable = new byte[4][]; private byte[][] m_actable = new byte[4][]; private byte m_restart_index; private bool m_sof_log; private byte m_sof_marker_id; private uint m_sof_x; private uint m_sof_y; private byte[] m_sof_c = new byte[3]; private byte[] m_sof_hv = new byte[3]; private byte[] m_sof_tq = new byte[3]; private byte[] m_sos_cs = new byte[3]; private byte[] m_sos_tda = new byte[3]; private SosEnd[] m_sos_end = new SosEnd[3]; private bool m_readheader_done; private bool m_writeheader_done; private short m_write_cursample; private uint m_write_curstrile; private bool m_libjpeg_session_active; private byte m_libjpeg_jpeg_query_style; private Jpeg_Source m_libjpeg_jpeg_source_mgr; private bool m_subsampling_convert_log; private uint m_subsampling_convert_ylinelen; private uint m_subsampling_convert_ylines; private uint m_subsampling_convert_clinelen; private uint m_subsampling_convert_clines; private byte[][] m_subsampling_convert_ybuf; private byte[][] m_subsampling_convert_cbbuf; private byte[][] m_subsampling_convert_crbuf; private byte[][][] m_subsampling_convert_ycbcrimage; private uint m_subsampling_convert_clinelenout; private uint m_subsampling_convert_state; private uint m_bytes_per_line; /* if the codec outputs subsampled data, a 'line' in bytes_per_line */ private uint m_lines_per_strile; /* and lines_per_strile means subsampling_ver desubsampled rows */ private OJPEGStateInBufferSource m_in_buffer_source; private uint m_in_buffer_next_strile; private uint m_in_buffer_strile_count; private uint m_in_buffer_file_pos; private bool m_in_buffer_file_pos_log; private uint m_in_buffer_file_togo; private ushort m_in_buffer_togo; private int m_in_buffer_cur; // index into m_in_buffer private byte[] m_in_buffer = new byte[OJPEG_BUFFER]; private OJPEGStateOutState m_out_state; private byte[] m_out_buffer = new byte[OJPEG_BUFFER]; private byte[] m_skip_buffer; public OJpegCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { m_tagMethods = new OJpegCodecTagMethods(); } public override bool Init() { Debug.Assert(m_scheme == Compression.OJPEG); /* * Merge codec-specific tag information. */ m_tif.MergeFieldInfo(ojpeg_field_info, ojpeg_field_info.Length); m_jpeg_proc = 1; m_subsampling_hor = 2; m_subsampling_ver = 2; m_tif.SetField(TiffTag.YCBCRSUBSAMPLING, 2, 2); /* tif tag methods */ m_parentTagMethods = m_tif.m_tagmethods; m_tif.m_tagmethods = m_tagMethods; /* Some OJPEG files don't have strip or tile offsets or bytecounts * tags. Some others do, but have totally meaningless or corrupt * values in these tags. In these cases, the JpegInterchangeFormat * stream is reliable. In any case, this decoder reads the * compressed data itself, from the most reliable locations, and * we need to notify encapsulating LibTiff not to read raw strips * or tiles for us. */ m_tif.m_flags |= TiffFlags.NoReadRaw; return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return false; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } public Tiff GetTiff() { return m_tif; } /// /// Setups the decoder part of the codec. /// /// /// true if this codec successfully setup its decoder part and can decode data; /// otherwise, false. /// /// /// SetupDecode is called once before /// . public override bool SetupDecode() { return OJPEGSetupDecode(); } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// /// PreDecode is called after and before decoding. /// public override bool PreDecode(short plane) { return OJPEGPreDecode(plane); } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { return OJPEGDecode(buffer, offset, count, plane); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { return OJPEGDecode(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { return OJPEGDecode(buffer, offset, count, plane); } /// /// Setups the encoder part of the codec. /// /// /// true if this codec successfully setup its encoder part and can encode data; /// otherwise, false. /// /// /// SetupEncode is called once before /// . public override bool SetupEncode() { return OJpegEncodeIsUnsupported(); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { return OJpegEncodeIsUnsupported(); } /// /// Performs any actions after encoding required by the codec. /// /// /// true if all post-encode actions succeeded; otherwise, false /// /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public override bool PostEncode() { return OJpegEncodeIsUnsupported(); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { return OJpegEncodeIsUnsupported(); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { return OJpegEncodeIsUnsupported(); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { return OJpegEncodeIsUnsupported(); } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public override void Cleanup() { OJPEGCleanup(); } private bool OJPEGSetupDecode() { Tiff.WarningExt(m_tif.m_clientdata, "OJPEGSetupDecode", "Depreciated and troublesome old-style JPEG compression mode, please convert to new-style JPEG compression and notify vendor of writing software"); return true; } private bool OJPEGPreDecode(short s) { uint m; if (!m_subsamplingcorrect_done) OJPEGSubsamplingCorrect(); if (!m_readheader_done) { if (!OJPEGReadHeaderInfo()) return false; } if (!m_sos_end[s].m_log) { if (!OJPEGReadSecondarySos(s)) return false; } if (m_tif.IsTiled()) m = (uint)m_tif.m_curtile; else m = (uint)m_tif.m_curstrip; if (m_writeheader_done && ((m_write_cursample != s) || (m_write_curstrile > m))) { if (m_libjpeg_session_active) OJPEGLibjpegSessionAbort(); m_writeheader_done = false; } if (!m_writeheader_done) { m_plane_sample_offset = (byte)s; m_write_cursample = s; m_write_curstrile = (uint)(s * m_tif.m_dir.td_stripsperimage); if (!m_in_buffer_file_pos_log || (m_in_buffer_file_pos - m_in_buffer_togo != m_sos_end[s].m_in_buffer_file_pos)) { m_in_buffer_source = m_sos_end[s].m_in_buffer_source; m_in_buffer_next_strile = m_sos_end[s].m_in_buffer_next_strile; m_in_buffer_file_pos = m_sos_end[s].m_in_buffer_file_pos; m_in_buffer_file_pos_log = false; m_in_buffer_file_togo = m_sos_end[s].m_in_buffer_file_togo; m_in_buffer_togo = 0; m_in_buffer_cur = 0; } if (!OJPEGWriteHeaderInfo()) return false; } while (m_write_curstrile < m) { if (m_libjpeg_jpeg_query_style == 0) { if (!OJPEGPreDecodeSkipRaw()) return false; } else { if (!OJPEGPreDecodeSkipScanlines()) return false; } m_write_curstrile++; } return true; } private bool OJPEGDecode(byte[] buf, int offset, int cc, short s) { if (m_libjpeg_jpeg_query_style == 0) { if (!OJPEGDecodeRaw(buf, offset, cc)) return false; } else { if (!OJPEGDecodeScanlines(buf, offset, cc)) return false; } return true; } private bool OJpegEncodeIsUnsupported() { Tiff.ErrorExt(m_tif.m_clientdata, "OJPEGSetupEncode", "OJPEG encoding not supported; use new-style JPEG compression instead"); return false; } private void OJPEGCleanup() { m_tif.m_tagmethods = m_parentTagMethods; if (m_libjpeg_session_active) OJPEGLibjpegSessionAbort(); } private bool OJPEGPreDecodeSkipRaw() { uint m; m = m_lines_per_strile; if (m_subsampling_convert_state != 0) { if (m_subsampling_convert_clines - m_subsampling_convert_state >= m) { m_subsampling_convert_state += m; if (m_subsampling_convert_state == m_subsampling_convert_clines) m_subsampling_convert_state = 0; return true; } m -= m_subsampling_convert_clines - m_subsampling_convert_state; m_subsampling_convert_state = 0; } while (m >= m_subsampling_convert_clines) { if (jpeg_read_raw_data_encap(m_subsampling_ver * 8) == 0) return false; m -= m_subsampling_convert_clines; } if (m > 0) { if (jpeg_read_raw_data_encap(m_subsampling_ver * 8) == 0) return false; m_subsampling_convert_state = m; } return true; } private bool OJPEGPreDecodeSkipScanlines() { uint m; if (m_skip_buffer == null) m_skip_buffer = new byte[m_bytes_per_line]; for (m = 0; m < m_lines_per_strile; m++) { if (jpeg_read_scanlines_encap(m_skip_buffer, 1) == 0) return false; } return true; } private bool OJPEGDecodeRaw(byte[] buf, int offset, int cc) { const string module = "OJPEGDecodeRaw"; if (cc % m_bytes_per_line != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Fractional scanline not read"); return false; } Debug.Assert(cc > 0); int m = offset; int n = cc; do { if (m_subsampling_convert_state == 0) { if (jpeg_read_raw_data_encap(m_subsampling_ver * 8) == 0) return false; } uint oy = m_subsampling_convert_state * m_subsampling_ver * m_subsampling_convert_ylinelen; uint ocb = m_subsampling_convert_state * m_subsampling_convert_clinelen; uint ocr = m_subsampling_convert_state * m_subsampling_convert_clinelen; int i = 0; int ii = 0; int p = m; for (uint q = 0; q < m_subsampling_convert_clinelenout; q++) { uint r = oy; for (byte sy = 0; sy < m_subsampling_ver; sy++) { for (byte sx = 0; sx < m_subsampling_hor; sx++) { i = (int)(r / m_subsampling_convert_ylinelen); ii = (int)(r % m_subsampling_convert_ylinelen); r++; buf[p++] = m_subsampling_convert_ybuf[i][ii]; } r += m_subsampling_convert_ylinelen - m_subsampling_hor; } oy += m_subsampling_hor; i = (int)(ocb / m_subsampling_convert_clinelen); ii = (int)(ocb % m_subsampling_convert_clinelen); ocb++; buf[p++] = m_subsampling_convert_cbbuf[i][ii]; i = (int)(ocr / m_subsampling_convert_clinelen); ii = (int)(ocr % m_subsampling_convert_clinelen); ocr++; buf[p++] = m_subsampling_convert_crbuf[i][ii]; } m_subsampling_convert_state++; if (m_subsampling_convert_state == m_subsampling_convert_clines) m_subsampling_convert_state = 0; m += (int)m_bytes_per_line; n -= (int)m_bytes_per_line; } while (n > 0); return true; } private bool OJPEGDecodeScanlines(byte[] buf, int offset, int cc) { const string module = "OJPEGDecodeScanlines"; if (cc % m_bytes_per_line != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Fractional scanline not read"); return false; } Debug.Assert(cc > 0); int m = offset; byte[] temp = new byte[m_bytes_per_line]; int n = cc; do { if (jpeg_read_scanlines_encap(temp, 1) == 0) return false; Buffer.BlockCopy(temp, 0, buf, m, temp.Length); m += (int)m_bytes_per_line; n -= (int)m_bytes_per_line; } while (n > 0); return true; } public void OJPEGSubsamplingCorrect() { const string module = "OJPEGSubsamplingCorrect"; byte mh; byte mv; Debug.Assert(!m_subsamplingcorrect_done); if ((m_tif.m_dir.td_samplesperpixel != 3) || ((m_tif.m_dir.td_photometric != Photometric.YCBCR) && (m_tif.m_dir.td_photometric != Photometric.ITULAB))) { if (m_subsampling_tag) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling tag not appropriate for this Photometric and/or SamplesPerPixel"); } m_subsampling_hor = 1; m_subsampling_ver = 1; m_subsampling_force_desubsampling_inside_decompression = false; } else { m_subsamplingcorrect_done = true; mh = m_subsampling_hor; mv = m_subsampling_ver; m_subsamplingcorrect = true; OJPEGReadHeaderInfoSec(); if (m_subsampling_force_desubsampling_inside_decompression) { m_subsampling_hor = 1; m_subsampling_ver = 1; } m_subsamplingcorrect = false; if (((m_subsampling_hor != mh) || (m_subsampling_ver != mv)) && !m_subsampling_force_desubsampling_inside_decompression) { if (!m_subsampling_tag) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling tag is not set, yet subsampling inside JPEG data [{0},{1}] does not match default values [2,2]; assuming subsampling inside JPEG data is correct", m_subsampling_hor, m_subsampling_ver); } else { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling inside JPEG data [{0},{1}] does not match subsampling tag values [{2},{3}]; assuming subsampling inside JPEG data is correct", m_subsampling_hor, m_subsampling_ver, mh, mv); } } if (m_subsampling_force_desubsampling_inside_decompression) { if (!m_subsampling_tag) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling tag is not set, yet subsampling inside JPEG data does not match default values [2,2] (nor any other values allowed in TIFF); assuming subsampling inside JPEG data is correct and desubsampling inside JPEG decompression"); } else { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling inside JPEG data does not match subsampling tag values [{0},{1}] (nor any other values allowed in TIFF); assuming subsampling inside JPEG data is correct and desubsampling inside JPEG decompression", mh, mv); } } if (!m_subsampling_force_desubsampling_inside_decompression) { if (m_subsampling_hor < m_subsampling_ver) { Tiff.WarningExt(m_tif, m_tif.m_clientdata, module, "Subsampling values [{0},{1}] are not allowed in TIFF", m_subsampling_hor, m_subsampling_ver); } } } m_subsamplingcorrect_done = true; } private bool OJPEGReadHeaderInfo() { const string module = "OJPEGReadHeaderInfo"; Debug.Assert(!m_readheader_done); m_image_width = (uint)m_tif.m_dir.td_imagewidth; m_image_length = (uint)m_tif.m_dir.td_imagelength; if (m_tif.IsTiled()) { m_strile_width = (uint)m_tif.m_dir.td_tilewidth; m_strile_length = (uint)m_tif.m_dir.td_tilelength; m_strile_length_total = ((m_image_length + m_strile_length - 1) / m_strile_length) * m_strile_length; } else { m_strile_width = m_image_width; m_strile_length = (uint)m_tif.m_dir.td_rowsperstrip; m_strile_length_total = m_image_length; } m_samples_per_pixel = (byte)m_tif.m_dir.td_samplesperpixel; if (m_samples_per_pixel == 1) { m_plane_sample_offset = 0; m_samples_per_pixel_per_plane = m_samples_per_pixel; m_subsampling_hor = 1; m_subsampling_ver = 1; } else { if (m_samples_per_pixel != 3) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "SamplesPerPixel {0} not supported for this compression scheme", m_samples_per_pixel); return false; } m_plane_sample_offset = 0; if (m_tif.m_dir.td_planarconfig == PlanarConfig.Contig) m_samples_per_pixel_per_plane = 3; else m_samples_per_pixel_per_plane = 1; } if (m_strile_length < m_image_length) { if (m_strile_length % (m_subsampling_ver * 8) != 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Incompatible vertical subsampling and image strip/tile length"); return false; } m_restart_interval = (ushort)(((m_strile_width + m_subsampling_hor * 8 - 1) / (m_subsampling_hor * 8)) * (m_strile_length / (m_subsampling_ver * 8))); } if (!OJPEGReadHeaderInfoSec()) return false; m_sos_end[0].m_log = true; m_sos_end[0].m_in_buffer_source = m_in_buffer_source; m_sos_end[0].m_in_buffer_next_strile = m_in_buffer_next_strile; m_sos_end[0].m_in_buffer_file_pos = m_in_buffer_file_pos - m_in_buffer_togo; m_sos_end[0].m_in_buffer_file_togo = m_in_buffer_file_togo + m_in_buffer_togo; m_readheader_done = true; return true; } private bool OJPEGReadSecondarySos(short s) { Debug.Assert(s > 0); Debug.Assert(s < 3); Debug.Assert(m_sos_end[0].m_log); Debug.Assert(!m_sos_end[s].m_log); m_plane_sample_offset = (byte)(s - 1); while (!m_sos_end[m_plane_sample_offset].m_log) m_plane_sample_offset--; m_in_buffer_source = m_sos_end[m_plane_sample_offset].m_in_buffer_source; m_in_buffer_next_strile = m_sos_end[m_plane_sample_offset].m_in_buffer_next_strile; m_in_buffer_file_pos = m_sos_end[m_plane_sample_offset].m_in_buffer_file_pos; m_in_buffer_file_pos_log = false; m_in_buffer_file_togo = m_sos_end[m_plane_sample_offset].m_in_buffer_file_togo; m_in_buffer_togo = 0; m_in_buffer_cur = 0; while (m_plane_sample_offset < s) { do { byte m; if (!OJPEGReadByte(out m)) return false; if (m == 255) { do { if (!OJPEGReadByte(out m)) return false; if (m != 255) break; } while (true); if (m == (byte)JpegMarkerType.SOS) break; } } while (true); m_plane_sample_offset++; if (!OJPEGReadHeaderInfoSecStreamSos()) return false; m_sos_end[m_plane_sample_offset].m_log = true; m_sos_end[m_plane_sample_offset].m_in_buffer_source = m_in_buffer_source; m_sos_end[m_plane_sample_offset].m_in_buffer_next_strile = m_in_buffer_next_strile; m_sos_end[m_plane_sample_offset].m_in_buffer_file_pos = m_in_buffer_file_pos - m_in_buffer_togo; m_sos_end[m_plane_sample_offset].m_in_buffer_file_togo = m_in_buffer_file_togo + m_in_buffer_togo; } return true; } private bool OJPEGWriteHeaderInfo() { Debug.Assert(!m_libjpeg_session_active); m_out_state = OJPEGStateOutState.ososSoi; m_restart_index = 0; if (!jpeg_create_decompress_encap()) return false; m_libjpeg_session_active = true; m_libjpeg_jpeg_source_mgr = new OJpegSrcManager(this); m_libjpeg_jpeg_decompress_struct.Src = m_libjpeg_jpeg_source_mgr; if (jpeg_read_header_encap(true) == ReadResult.Suspended) return false; if (!m_subsampling_force_desubsampling_inside_decompression && (m_samples_per_pixel_per_plane > 1)) { m_libjpeg_jpeg_decompress_struct.Raw_data_out = true; //#if JPEG_LIB_VERSION >= 70 // libjpeg_jpeg_decompress_struct.do_fancy_upsampling=FALSE; //#endif m_libjpeg_jpeg_query_style = 0; if (!m_subsampling_convert_log) { Debug.Assert(m_subsampling_convert_ybuf == null); Debug.Assert(m_subsampling_convert_cbbuf == null); Debug.Assert(m_subsampling_convert_crbuf == null); Debug.Assert(m_subsampling_convert_ycbcrimage == null); m_subsampling_convert_ylinelen = (uint)((m_strile_width + m_subsampling_hor * 8 - 1) / (m_subsampling_hor * 8) * m_subsampling_hor * 8); m_subsampling_convert_ylines = (uint)(m_subsampling_ver * 8); m_subsampling_convert_clinelen = m_subsampling_convert_ylinelen / m_subsampling_hor; m_subsampling_convert_clines = 8; m_subsampling_convert_ybuf = new byte[m_subsampling_convert_ylines][]; for (int i = 0; i < m_subsampling_convert_ylines; i++) m_subsampling_convert_ybuf[i] = new byte[m_subsampling_convert_ylinelen]; m_subsampling_convert_cbbuf = new byte[m_subsampling_convert_clines][]; m_subsampling_convert_crbuf = new byte[m_subsampling_convert_clines][]; for (int i = 0; i < m_subsampling_convert_clines; i++) { m_subsampling_convert_cbbuf[i] = new byte[m_subsampling_convert_clinelen]; m_subsampling_convert_crbuf[i] = new byte[m_subsampling_convert_clinelen]; } m_subsampling_convert_ycbcrimage = new byte[3][][]; m_subsampling_convert_ycbcrimage[0] = new byte[m_subsampling_convert_ylines][]; for (uint n = 0; n < m_subsampling_convert_ylines; n++) m_subsampling_convert_ycbcrimage[0][n] = m_subsampling_convert_ybuf[n]; m_subsampling_convert_ycbcrimage[1] = new byte[m_subsampling_convert_clines][]; for (uint n = 0; n < m_subsampling_convert_clines; n++) m_subsampling_convert_ycbcrimage[1][n] = m_subsampling_convert_cbbuf[n]; m_subsampling_convert_ycbcrimage[2] = new byte[m_subsampling_convert_clines][]; for (uint n = 0; n < m_subsampling_convert_clines; n++) m_subsampling_convert_ycbcrimage[2][n] = m_subsampling_convert_crbuf[n]; m_subsampling_convert_clinelenout = ((m_strile_width + m_subsampling_hor - 1) / m_subsampling_hor); m_subsampling_convert_state = 0; m_bytes_per_line = (uint)(m_subsampling_convert_clinelenout * (m_subsampling_ver * m_subsampling_hor + 2)); m_lines_per_strile = ((m_strile_length + m_subsampling_ver - 1) / m_subsampling_ver); m_subsampling_convert_log = true; } } else { m_libjpeg_jpeg_decompress_struct.Jpeg_color_space = ColorSpace.Unknown; m_libjpeg_jpeg_decompress_struct.Out_color_space = ColorSpace.Unknown; m_libjpeg_jpeg_query_style = 1; m_bytes_per_line = m_samples_per_pixel_per_plane * m_strile_width; m_lines_per_strile = m_strile_length; } if (!jpeg_start_decompress_encap()) return false; m_writeheader_done = true; return true; } private void OJPEGLibjpegSessionAbort() { Debug.Assert(m_libjpeg_session_active); m_libjpeg_jpeg_decompress_struct.jpeg_destroy(); m_libjpeg_session_active = false; } private bool OJPEGReadHeaderInfoSec() { const string module = "OJPEGReadHeaderInfoSec"; byte m; ushort n; byte o; if (m_file_size == 0) m_file_size = (uint)m_tif.GetStream().Size(m_tif.m_clientdata); if (m_jpeg_interchange_format != 0) { if (m_jpeg_interchange_format >= m_file_size) { m_jpeg_interchange_format = 0; m_jpeg_interchange_format_length = 0; } else { if ((m_jpeg_interchange_format_length == 0) || (m_jpeg_interchange_format + m_jpeg_interchange_format_length > m_file_size)) m_jpeg_interchange_format_length = m_file_size - m_jpeg_interchange_format; } } m_in_buffer_source = OJPEGStateInBufferSource.osibsNotSetYet; m_in_buffer_next_strile = 0; m_in_buffer_strile_count = (uint)m_tif.m_dir.td_nstrips; m_in_buffer_file_togo = 0; m_in_buffer_togo = 0; do { if (!OJPEGReadBytePeek(out m)) return false; if (m != 255) break; OJPEGReadByteAdvance(); do { if (!OJPEGReadByte(out m)) return false; } while (m == 255); switch ((JpegMarkerType)m) { case JpegMarkerType.SOI: /* this type of marker has no data, and should be skipped */ break; case JpegMarkerType.COM: case JpegMarkerType.APP0: case JpegMarkerType.APP1: case JpegMarkerType.APP2: case JpegMarkerType.APP3: case JpegMarkerType.APP4: case JpegMarkerType.APP5: case JpegMarkerType.APP6: case JpegMarkerType.APP7: case JpegMarkerType.APP8: case JpegMarkerType.APP9: case JpegMarkerType.APP10: case JpegMarkerType.APP11: case JpegMarkerType.APP12: case JpegMarkerType.APP13: case JpegMarkerType.APP14: case JpegMarkerType.APP15: /* this type of marker has data, but it has no use to us (and no place here) and should be skipped */ if (!OJPEGReadWord(out n)) return false; if (n < 2) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt JPEG data"); return false; } if (n > 2) OJPEGReadSkip((ushort)(n - 2)); break; case JpegMarkerType.DRI: if (!OJPEGReadHeaderInfoSecStreamDri()) return false; break; case JpegMarkerType.DQT: if (!OJPEGReadHeaderInfoSecStreamDqt()) return false; break; case JpegMarkerType.DHT: if (!OJPEGReadHeaderInfoSecStreamDht()) return false; break; case JpegMarkerType.SOF0: case JpegMarkerType.SOF1: case JpegMarkerType.SOF3: if (!OJPEGReadHeaderInfoSecStreamSof(m)) return false; if (m_subsamplingcorrect) return true; break; case JpegMarkerType.SOS: if (m_subsamplingcorrect) return true; Debug.Assert(m_plane_sample_offset == 0); if (!OJPEGReadHeaderInfoSecStreamSos()) return false; break; default: Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Unknown marker type {0} in JPEG data", m); return false; } } while (m != (byte)JpegMarkerType.SOS); if (m_subsamplingcorrect) return true; if (!m_sof_log) { if (!OJPEGReadHeaderInfoSecTablesQTable()) return false; m_sof_marker_id = (byte)JpegMarkerType.SOF0; for (o = 0; o < m_samples_per_pixel; o++) m_sof_c[o] = o; m_sof_hv[0] = (byte)((m_subsampling_hor << 4) | m_subsampling_ver); for (o = 1; o < m_samples_per_pixel; o++) m_sof_hv[o] = 17; m_sof_x = m_strile_width; m_sof_y = m_strile_length_total; m_sof_log = true; if (!OJPEGReadHeaderInfoSecTablesDcTable()) return false; if (!OJPEGReadHeaderInfoSecTablesAcTable()) return false; for (o = 1; o < m_samples_per_pixel; o++) m_sos_cs[o] = o; } return true; } private bool OJPEGReadHeaderInfoSecStreamDri() { // this could easilly cause trouble in some cases... // but no such cases have occured so far const string module = "OJPEGReadHeaderInfoSecStreamDri"; ushort m; if (!OJPEGReadWord(out m)) return false; if (m != 4) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DRI marker in JPEG data"); return false; } if (!OJPEGReadWord(out m)) return false; m_restart_interval = m; return true; } private bool OJPEGReadHeaderInfoSecStreamDqt() { // this is a table marker, and it is to be saved as a whole for // exact pushing on the jpeg stream later on const string module = "OJPEGReadHeaderInfoSecStreamDqt"; ushort m; uint na; byte[] nb; byte o; if (!OJPEGReadWord(out m)) return false; if (m <= 2) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DQT marker in JPEG data"); return false; } if (m_subsamplingcorrect) { OJPEGReadSkip((ushort)(m - 2)); } else { m -= 2; do { if (m < 65) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DQT marker in JPEG data"); return false; } na = 69; nb = new byte[na]; nb[0] = 255; nb[1] = (byte)JpegMarkerType.DQT; nb[2] = 0; nb[3] = 67; if (!OJPEGReadBlock(65, nb, 4)) return false; o = (byte)(nb[4] & 15); if (3 < o) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DQT marker in JPEG data"); return false; } m_qtable[o] = nb; m -= 65; } while (m > 0); } return true; } private bool OJPEGReadHeaderInfoSecStreamDht() { // this is a table marker, and it is to be saved as a whole for // exact pushing on the jpeg stream later on // TODO: the following assumes there is only one table in // this marker... but i'm not quite sure that assumption is // guaranteed correct const string module = "OJPEGReadHeaderInfoSecStreamDht"; ushort m; uint na; byte[] nb; byte o; if (!OJPEGReadWord(out m)) return false; if (m <= 2) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DHT marker in JPEG data"); return false; } if (m_subsamplingcorrect) { OJPEGReadSkip((ushort)(m - 2)); } else { na = (uint)(2 + m); nb = new byte[na]; nb[0] = 255; nb[1] = (byte)JpegMarkerType.DHT; nb[2] = (byte)(m >> 8); nb[3] = (byte)(m & 255); if (!OJPEGReadBlock((ushort)(m - 2), nb, 4)) return false; o = nb[4]; if ((o & 240) == 0) { if (3 < o) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DHT marker in JPEG data"); return false; } m_dctable[o] = nb; } else { if ((o & 240) != 16) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DHT marker in JPEG data"); return false; } o &= 15; if (3 < o) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt DHT marker in JPEG data"); return false; } m_actable[o] = nb; } } return true; } private bool OJPEGReadHeaderInfoSecStreamSof(byte marker_id) { /* this marker needs to be checked, and part of its data needs to be saved for regeneration later on */ const string module = "OJPEGReadHeaderInfoSecStreamSof"; ushort m; ushort n; byte o; ushort p; ushort q; if (m_sof_log) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt JPEG data"); return false; } if (!m_subsamplingcorrect) m_sof_marker_id = marker_id; /* Lf: data length */ if (!OJPEGReadWord(out m)) return false; if (m < 11) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOF marker in JPEG data"); return false; } m -= 8; if (m % 3 != 0) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOF marker in JPEG data"); return false; } n = (ushort)(m / 3); if (!m_subsamplingcorrect) { if (n != m_samples_per_pixel) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected number of samples"); return false; } } /* P: Sample precision */ if (!OJPEGReadByte(out o)) return false; if (o != 8) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected number of bits per sample"); return false; } /* Y: Number of lines, X: Number of samples per line */ if (m_subsamplingcorrect) OJPEGReadSkip(4); else { /* TODO: probably best to also add check on allowed upper bound, especially x, may cause buffer overflow otherwise i think */ /* Y: Number of lines */ if (!OJPEGReadWord(out p)) return false; if ((p < m_image_length) && (p < m_strile_length_total)) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected height"); return false; } m_sof_y = p; /* X: Number of samples per line */ if (!OJPEGReadWord(out p)) return false; if ((p < m_image_width) && (p < m_strile_width)) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected width"); return false; } m_sof_x = p; } /* Nf: Number of image components in frame */ if (!OJPEGReadByte(out o)) return false; if (o != n) { if (!m_subsamplingcorrect) Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOF marker in JPEG data"); return false; } /* per component stuff */ /* TODO: double-check that flow implies that n cannot be as big as to make us overflow sof_c, sof_hv and sof_tq arrays */ for (q = 0; q < n; q++) { /* C: Component identifier */ if (!OJPEGReadByte(out o)) return false; if (!m_subsamplingcorrect) m_sof_c[q] = o; /* H: Horizontal sampling factor, and V: Vertical sampling factor */ if (!OJPEGReadByte(out o)) return false; if (m_subsamplingcorrect) { if (q == 0) { m_subsampling_hor = (byte)(o >> 4); m_subsampling_ver = (byte)(o & 15); if (((m_subsampling_hor != 1) && (m_subsampling_hor != 2) && (m_subsampling_hor != 4)) || ((m_subsampling_ver != 1) && (m_subsampling_ver != 2) && (m_subsampling_ver != 4))) m_subsampling_force_desubsampling_inside_decompression = true; } else { if (o != 17) m_subsampling_force_desubsampling_inside_decompression = true; } } else { m_sof_hv[q] = o; if (!m_subsampling_force_desubsampling_inside_decompression) { if (q == 0) { if (o != ((m_subsampling_hor << 4) | m_subsampling_ver)) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected subsampling values"); return false; } } else { if (o != 17) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "JPEG compressed data indicates unexpected subsampling values"); return false; } } } } /* Tq: Quantization table destination selector */ if (!OJPEGReadByte(out o)) return false; if (!m_subsamplingcorrect) m_sof_tq[q] = o; } if (!m_subsamplingcorrect) m_sof_log = true; return true; } private bool OJPEGReadHeaderInfoSecStreamSos() { /* this marker needs to be checked, and part of its data needs to be saved for regeneration later on */ const string module = "OJPEGReadHeaderInfoSecStreamSos"; ushort m; byte n; byte o; Debug.Assert(!m_subsamplingcorrect); if (!m_sof_log) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOS marker in JPEG data"); return false; } /* Ls */ if (!OJPEGReadWord(out m)) return false; if (m != 6 + m_samples_per_pixel_per_plane * 2) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOS marker in JPEG data"); return false; } /* Ns */ if (!OJPEGReadByte(out n)) return false; if (n != m_samples_per_pixel_per_plane) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt SOS marker in JPEG data"); return false; } /* Cs, Td, and Ta */ for (o = 0; o < m_samples_per_pixel_per_plane; o++) { /* Cs */ if (!OJPEGReadByte(out n)) return false; m_sos_cs[m_plane_sample_offset + o] = n; /* Td and Ta */ if (!OJPEGReadByte(out n)) return false; m_sos_tda[m_plane_sample_offset + o] = n; } /* skip Ss, Se, Ah, en Al -> no check, as per Tom Lane recommendation, as per LibJpeg source */ OJPEGReadSkip(3); return true; } private bool OJPEGReadHeaderInfoSecTablesQTable() { const string module = "OJPEGReadHeaderInfoSecTablesQTable"; byte m; byte n; uint oa; byte[] ob; uint p; if (m_qtable_offset[0] == 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Missing JPEG tables"); return false; } m_in_buffer_file_pos_log = false; for (m = 0; m < m_samples_per_pixel; m++) { if ((m_qtable_offset[m] != 0) && ((m == 0) || (m_qtable_offset[m] != m_qtable_offset[m - 1]))) { for (n = 0; n < m - 1; n++) { if (m_qtable_offset[m] == m_qtable_offset[n]) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt JpegQTables tag value"); return false; } } oa = 69; ob = new byte[oa]; ob[0] = 255; ob[1] = (byte)JpegMarkerType.DQT; ob[2] = 0; ob[3] = 67; ob[4] = m; TiffStream stream = m_tif.GetStream(); stream.Seek(m_tif.m_clientdata, m_qtable_offset[m], SeekOrigin.Begin); p = (uint)stream.Read(m_tif.m_clientdata, ob, 5, 64); if (p != 64) return false; m_qtable[m] = ob; m_sof_tq[m] = m; } else m_sof_tq[m] = m_sof_tq[m - 1]; } return true; } private bool OJPEGReadHeaderInfoSecTablesDcTable() { const string module = "OJPEGReadHeaderInfoSecTablesDcTable"; byte m; byte n; byte[] o = new byte[16]; uint p; uint q; uint ra; byte[] rb; if (m_dctable_offset[0] == 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Missing JPEG tables"); return false; } m_in_buffer_file_pos_log = false; for (m = 0; m < m_samples_per_pixel; m++) { if ((m_dctable_offset[m] != 0) && ((m == 0) || (m_dctable_offset[m] != m_dctable_offset[m - 1]))) { for (n = 0; n < m - 1; n++) { if (m_dctable_offset[m] == m_dctable_offset[n]) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt JpegDcTables tag value"); return false; } } TiffStream stream = m_tif.GetStream(); stream.Seek(m_tif.m_clientdata, m_dctable_offset[m], SeekOrigin.Begin); p = (uint)stream.Read(m_tif.m_clientdata, o, 0, 16); if (p != 16) return false; q = 0; for (n = 0; n < 16; n++) q += o[n]; ra = 21 + q; rb = new byte[ra]; rb[0] = 255; rb[1] = (byte)JpegMarkerType.DHT; rb[2] = (byte)((19 + q) >> 8); rb[3] = (byte)((19 + q) & 255); rb[4] = m; for (n = 0; n < 16; n++) rb[5 + n] = o[n]; p = (uint)stream.Read(m_tif.m_clientdata, rb, 21, (int)q); if (p != q) return false; m_dctable[m] = rb; m_sos_tda[m] = (byte)(m << 4); } else m_sos_tda[m] = m_sos_tda[m - 1]; } return true; } private bool OJPEGReadHeaderInfoSecTablesAcTable() { const string module = "OJPEGReadHeaderInfoSecTablesAcTable"; byte m; byte n; byte[] o = new byte[16]; uint p; uint q; uint ra; byte[] rb; if (m_actable_offset[0] == 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Missing JPEG tables"); return false; } m_in_buffer_file_pos_log = false; for (m = 0; m < m_samples_per_pixel; m++) { if ((m_actable_offset[m] != 0) && ((m == 0) || (m_actable_offset[m] != m_actable_offset[m - 1]))) { for (n = 0; n < m - 1; n++) { if (m_actable_offset[m] == m_actable_offset[n]) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, module, "Corrupt JpegAcTables tag value"); return false; } } TiffStream stream = m_tif.GetStream(); stream.Seek(m_tif.m_clientdata, m_actable_offset[m], SeekOrigin.Begin); p = (uint)stream.Read(m_tif.m_clientdata, o, 0, 16); if (p != 16) return false; q = 0; for (n = 0; n < 16; n++) q += o[n]; ra = 21 + q; rb = new byte[ra]; rb[0] = 255; rb[1] = (byte)JpegMarkerType.DHT; rb[2] = (byte)((19 + q) >> 8); rb[3] = (byte)((19 + q) & 255); rb[4] = (byte)(16 | m); for (n = 0; n < 16; n++) rb[5 + n] = o[n]; p = (uint)stream.Read(m_tif.m_clientdata, rb, 21, (int)q); if (p != q) return false; m_actable[m] = rb; m_sos_tda[m] = (byte)(m_sos_tda[m] | m); } else m_sos_tda[m] = (byte)(m_sos_tda[m] | (m_sos_tda[m - 1] & 15)); } return true; } private bool OJPEGReadBufferFill() { ushort m; int n; /* TODO: double-check: when subsamplingcorrect is set, no call to TIFFErrorExt or TIFFWarningExt should be made * in any other case, seek or read errors should be passed through */ do { if (m_in_buffer_file_togo != 0) { TiffStream stream = m_tif.GetStream(); if (!m_in_buffer_file_pos_log) { stream.Seek(m_tif.m_clientdata, m_in_buffer_file_pos, SeekOrigin.Begin); m_in_buffer_file_pos_log = true; } m = OJPEG_BUFFER; if (m > m_in_buffer_file_togo) m = (ushort)m_in_buffer_file_togo; n = stream.Read(m_tif.m_clientdata, m_in_buffer, 0, (int)m); if (n == 0) return false; Debug.Assert(n > 0); Debug.Assert(n <= OJPEG_BUFFER); Debug.Assert(n < 65536); Debug.Assert((ushort)n <= m_in_buffer_file_togo); m = (ushort)n; m_in_buffer_togo = m; m_in_buffer_cur = 0; m_in_buffer_file_togo -= m; m_in_buffer_file_pos += m; break; } m_in_buffer_file_pos_log = false; switch (m_in_buffer_source) { case OJPEGStateInBufferSource.osibsNotSetYet: if (m_jpeg_interchange_format != 0) { m_in_buffer_file_pos = m_jpeg_interchange_format; m_in_buffer_file_togo = m_jpeg_interchange_format_length; } m_in_buffer_source = OJPEGStateInBufferSource.osibsJpegInterchangeFormat; break; case OJPEGStateInBufferSource.osibsJpegInterchangeFormat: m_in_buffer_source = OJPEGStateInBufferSource.osibsStrile; goto case OJPEGStateInBufferSource.osibsStrile; case OJPEGStateInBufferSource.osibsStrile: if (m_in_buffer_next_strile == m_in_buffer_strile_count) m_in_buffer_source = OJPEGStateInBufferSource.osibsEof; else { if (m_tif.m_dir.td_stripoffset == null) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Strip offsets are missing"); return false; } m_in_buffer_file_pos = m_tif.m_dir.td_stripoffset[m_in_buffer_next_strile]; if (m_in_buffer_file_pos != 0) { if (m_in_buffer_file_pos >= m_file_size) m_in_buffer_file_pos = 0; else { m_in_buffer_file_togo = m_tif.m_dir.td_stripbytecount[m_in_buffer_next_strile]; if (m_in_buffer_file_togo == 0) m_in_buffer_file_pos = 0; else if (m_in_buffer_file_pos + m_in_buffer_file_togo > m_file_size) m_in_buffer_file_togo = m_file_size - m_in_buffer_file_pos; } } m_in_buffer_next_strile++; } break; default: return false; } } while (true); return true; } private bool OJPEGReadByte(out byte b) { if (m_in_buffer_togo == 0) { if (!OJPEGReadBufferFill()) { b = 0; return false; } Debug.Assert(m_in_buffer_togo > 0); } b = m_in_buffer[m_in_buffer_cur]; m_in_buffer_cur++; m_in_buffer_togo--; return true; } public bool OJPEGReadBytePeek(out byte b) { if (m_in_buffer_togo == 0) { if (!OJPEGReadBufferFill()) { b = 0; return false; } Debug.Assert(m_in_buffer_togo > 0); } b = m_in_buffer[m_in_buffer_cur]; return true; } private void OJPEGReadByteAdvance() { Debug.Assert(m_in_buffer_togo > 0); m_in_buffer_cur++; m_in_buffer_togo--; } private bool OJPEGReadWord(out ushort word) { word = 0; byte m; if (!OJPEGReadByte(out m)) return false; word = (ushort)(m << 8); if (!OJPEGReadByte(out m)) return false; word |= m; return true; } public bool OJPEGReadBlock(ushort len, byte[] mem, int offset) { ushort mlen; ushort n; Debug.Assert(len > 0); mlen = len; int mmem = offset; do { if (m_in_buffer_togo == 0) { if (!OJPEGReadBufferFill()) return false; Debug.Assert(m_in_buffer_togo > 0); } n = mlen; if (n > m_in_buffer_togo) n = m_in_buffer_togo; Buffer.BlockCopy(m_in_buffer, m_in_buffer_cur, mem, mmem, n); m_in_buffer_cur += n; m_in_buffer_togo -= n; mlen -= n; mmem += n; } while (mlen > 0); return true; } private void OJPEGReadSkip(ushort len) { ushort m; ushort n; m = len; n = m; if (n > m_in_buffer_togo) n = m_in_buffer_togo; m_in_buffer_cur += n; m_in_buffer_togo -= n; m -= n; if (m > 0) { Debug.Assert(m_in_buffer_togo == 0); n = m; if (n > m_in_buffer_file_togo) n = (ushort)m_in_buffer_file_togo; m_in_buffer_file_pos += n; m_in_buffer_file_togo -= n; m_in_buffer_file_pos_log = false; /* we don't skip past jpeginterchangeformat/strile block... * if that is asked from us, we're dealing with totally bazurk * data anyway, and we've not seen this happening on any * testfile, so we might as well likely cause some other * meaningless error to be passed at some later time */ } } internal bool OJPEGWriteStream(out byte[] mem, out uint len) { mem = null; len = 0; do { Debug.Assert(m_out_state <= OJPEGStateOutState.ososEoi); switch (m_out_state) { case OJPEGStateOutState.ososSoi: OJPEGWriteStreamSoi(out mem, out len); break; case OJPEGStateOutState.ososQTable0: OJPEGWriteStreamQTable(0, out mem, out len); break; case OJPEGStateOutState.ososQTable1: OJPEGWriteStreamQTable(1, out mem, out len); break; case OJPEGStateOutState.ososQTable2: OJPEGWriteStreamQTable(2, out mem, out len); break; case OJPEGStateOutState.ososQTable3: OJPEGWriteStreamQTable(3, out mem, out len); break; case OJPEGStateOutState.ososDcTable0: OJPEGWriteStreamDcTable(0, out mem, out len); break; case OJPEGStateOutState.ososDcTable1: OJPEGWriteStreamDcTable(1, out mem, out len); break; case OJPEGStateOutState.ososDcTable2: OJPEGWriteStreamDcTable(2, out mem, out len); break; case OJPEGStateOutState.ososDcTable3: OJPEGWriteStreamDcTable(3, out mem, out len); break; case OJPEGStateOutState.ososAcTable0: OJPEGWriteStreamAcTable(0, out mem, out len); break; case OJPEGStateOutState.ososAcTable1: OJPEGWriteStreamAcTable(1, out mem, out len); break; case OJPEGStateOutState.ososAcTable2: OJPEGWriteStreamAcTable(2, out mem, out len); break; case OJPEGStateOutState.ososAcTable3: OJPEGWriteStreamAcTable(3, out mem, out len); break; case OJPEGStateOutState.ososDri: OJPEGWriteStreamDri(out mem, out len); break; case OJPEGStateOutState.ososSof: OJPEGWriteStreamSof(out mem, out len); break; case OJPEGStateOutState.ososSos: OJPEGWriteStreamSos(out mem, out len); break; case OJPEGStateOutState.ososCompressed: if (!OJPEGWriteStreamCompressed(out mem, out len)) return false; break; case OJPEGStateOutState.ososRst: OJPEGWriteStreamRst(out mem, out len); break; case OJPEGStateOutState.ososEoi: OJPEGWriteStreamEoi(out mem, out len); break; } } while (len == 0); return true; } private void OJPEGWriteStreamSoi(out byte[] mem, out uint len) { Debug.Assert(OJPEG_BUFFER >= 2); m_out_buffer[0] = 255; m_out_buffer[1] = (byte)JpegMarkerType.SOI; len = 2; mem = m_out_buffer; m_out_state++; } private void OJPEGWriteStreamQTable(byte table_index, out byte[] mem, out uint len) { mem = null; len = 0; if (m_qtable[table_index] != null) { mem = m_qtable[table_index]; len = (uint)m_qtable[table_index].Length; } m_out_state++; } private void OJPEGWriteStreamDcTable(byte table_index, out byte[] mem, out uint len) { mem = null; len = 0; if (m_dctable[table_index] != null) { mem = m_dctable[table_index]; len = (uint)m_dctable[table_index].Length; } m_out_state++; } private void OJPEGWriteStreamAcTable(byte table_index, out byte[] mem, out uint len) { mem = null; len = 0; if (m_actable[table_index] != null) { mem = m_actable[table_index]; len = (uint)m_actable[table_index].Length; } m_out_state++; } private void OJPEGWriteStreamDri(out byte[] mem, out uint len) { Debug.Assert(OJPEG_BUFFER >= 6); mem = null; len = 0; if (m_restart_interval != 0) { m_out_buffer[0] = 255; m_out_buffer[1] = (byte)JpegMarkerType.DRI; m_out_buffer[2] = 0; m_out_buffer[3] = 4; m_out_buffer[4] = (byte)(m_restart_interval >> 8); m_out_buffer[5] = (byte)(m_restart_interval & 255); len = 6; mem = m_out_buffer; } m_out_state++; } private void OJPEGWriteStreamSof(out byte[] mem, out uint len) { byte m; Debug.Assert(OJPEG_BUFFER >= 2 + 8 + m_samples_per_pixel_per_plane * 3); Debug.Assert(255 >= 8 + m_samples_per_pixel_per_plane * 3); m_out_buffer[0] = 255; m_out_buffer[1] = m_sof_marker_id; /* Lf */ m_out_buffer[2] = 0; m_out_buffer[3] = (byte)(8 + m_samples_per_pixel_per_plane * 3); /* P */ m_out_buffer[4] = 8; /* Y */ m_out_buffer[5] = (byte)(m_sof_y >> 8); m_out_buffer[6] = (byte)(m_sof_y & 255); /* X */ m_out_buffer[7] = (byte)(m_sof_x >> 8); m_out_buffer[8] = (byte)(m_sof_x & 255); /* Nf */ m_out_buffer[9] = m_samples_per_pixel_per_plane; for (m = 0; m < m_samples_per_pixel_per_plane; m++) { /* C */ m_out_buffer[10 + m * 3] = m_sof_c[m_plane_sample_offset + m]; /* H and V */ m_out_buffer[10 + m * 3 + 1] = m_sof_hv[m_plane_sample_offset + m]; /* Tq */ m_out_buffer[10 + m * 3 + 2] = m_sof_tq[m_plane_sample_offset + m]; } len = (uint)(10 + m_samples_per_pixel_per_plane * 3); mem = m_out_buffer; m_out_state++; } private void OJPEGWriteStreamSos(out byte[] mem, out uint len) { byte m; Debug.Assert(OJPEG_BUFFER >= 2 + 6 + m_samples_per_pixel_per_plane * 2); Debug.Assert(255 >= 6 + m_samples_per_pixel_per_plane * 2); m_out_buffer[0] = 255; m_out_buffer[1] = (byte)JpegMarkerType.SOS; /* Ls */ m_out_buffer[2] = 0; m_out_buffer[3] = (byte)(6 + m_samples_per_pixel_per_plane * 2); /* Ns */ m_out_buffer[4] = m_samples_per_pixel_per_plane; for (m = 0; m < m_samples_per_pixel_per_plane; m++) { /* Cs */ m_out_buffer[5 + m * 2] = m_sos_cs[m_plane_sample_offset + m]; /* Td and Ta */ m_out_buffer[5 + m * 2 + 1] = m_sos_tda[m_plane_sample_offset + m]; } /* Ss */ m_out_buffer[5 + m_samples_per_pixel_per_plane * 2] = 0; /* Se */ m_out_buffer[5 + m_samples_per_pixel_per_plane * 2 + 1] = 63; /* Ah and Al */ m_out_buffer[5 + m_samples_per_pixel_per_plane * 2 + 2] = 0; len = (uint)(8 + m_samples_per_pixel_per_plane * 2); mem = m_out_buffer; m_out_state++; } private bool OJPEGWriteStreamCompressed(out byte[] mem, out uint len) { mem = null; len = 0; if (m_in_buffer_togo == 0) { if (!OJPEGReadBufferFill()) return false; Debug.Assert(m_in_buffer_togo > 0); } len = m_in_buffer_togo; if (m_in_buffer_cur == 0) { mem = m_in_buffer; } else { mem = new byte[len]; Buffer.BlockCopy(m_in_buffer, m_in_buffer_cur, mem, 0, (int)len); } m_in_buffer_togo = 0; if (m_in_buffer_file_togo == 0) { switch (m_in_buffer_source) { case OJPEGStateInBufferSource.osibsStrile: if (m_in_buffer_next_strile < m_in_buffer_strile_count) m_out_state = OJPEGStateOutState.ososRst; else m_out_state = OJPEGStateOutState.ososEoi; break; case OJPEGStateInBufferSource.osibsEof: m_out_state = OJPEGStateOutState.ososEoi; break; default: break; } } return true; } private void OJPEGWriteStreamRst(out byte[] mem, out uint len) { Debug.Assert(OJPEG_BUFFER >= 2); m_out_buffer[0] = 255; m_out_buffer[1] = (byte)((byte)JpegMarkerType.RST0 + m_restart_index); m_restart_index++; if (m_restart_index == 8) m_restart_index = 0; len = 2; mem = m_out_buffer; m_out_state = OJPEGStateOutState.ososCompressed; } private void OJPEGWriteStreamEoi(out byte[] mem, out uint len) { Debug.Assert(OJPEG_BUFFER >= 2); m_out_buffer[0] = 255; m_out_buffer[1] = (byte)JpegMarkerType.EOI; len = 2; mem = m_out_buffer; } private bool jpeg_create_decompress_encap() { try { m_libjpeg_jpeg_decompress_struct = new JpegDecompressor(); } catch (Exception) { return false; } return true; } private ReadResult jpeg_read_header_encap(bool require_image) { ReadResult res = ReadResult.Suspended; try { res = m_libjpeg_jpeg_decompress_struct.jpeg_read_header(require_image); } catch (Exception) { return ReadResult.Suspended; } return res; } private bool jpeg_start_decompress_encap() { try { m_libjpeg_jpeg_decompress_struct.jpeg_start_decompress(); } catch (Exception) { return false; } return true; } private int jpeg_read_scanlines_encap(byte[] scanlines, int max_lines) { int n = 0; try { byte[][] temp = new byte[1][]; temp[0] = scanlines; n = m_libjpeg_jpeg_decompress_struct.jpeg_read_scanlines(temp, max_lines); } catch (Exception) { return 0; } return n; } private int jpeg_read_raw_data_encap(int max_lines) { int n = 0; try { n = m_libjpeg_jpeg_decompress_struct.jpeg_read_raw_data(m_subsampling_convert_ycbcrimage, max_lines); } catch (Exception) { return 0; } return n; } } #endregion #region OJpegCodecTagMethod class OJpegCodecTagMethods : TiffTagMethods { public override bool SetField(Tiff tif, TiffTag tag, FieldValue[] ap) { const string module = "OJPEGVSetField"; OJpegCodec sp = tif.m_currentCodec as OJpegCodec; Debug.Assert(sp != null); uint ma; uint[] mb; uint n; switch (tag) { case TiffTag.JPEGIFOffset: sp.m_jpeg_interchange_format = ap[0].ToUInt(); break; case TiffTag.JPEGIFByteCount: sp.m_jpeg_interchange_format_length = ap[0].ToUInt(); break; case TiffTag.YCBCRSUBSAMPLING: sp.m_subsampling_tag = true; sp.m_subsampling_hor = ap[0].ToByte(); sp.m_subsampling_ver = ap[1].ToByte(); tif.m_dir.td_ycbcrsubsampling[0] = sp.m_subsampling_hor; tif.m_dir.td_ycbcrsubsampling[1] = sp.m_subsampling_ver; break; case TiffTag.JPEGQTables: ma = ap[0].ToUInt(); if (ma != 0) { if (ma > 3) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "JpegQTables tag has incorrect count"); return false; } sp.m_qtable_offset_count = (byte)ma; mb = ap[1].ToUIntArray(); for (n = 0; n < ma; n++) sp.m_qtable_offset[n] = mb[n]; } break; case TiffTag.JPEGDCTables: ma = ap[0].ToUInt(); if (ma != 0) { if (ma > 3) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "JpegDcTables tag has incorrect count"); return false; } sp.m_dctable_offset_count = (byte)ma; mb = ap[1].ToUIntArray(); for (n = 0; n < ma; n++) sp.m_dctable_offset[n] = mb[n]; } break; case TiffTag.JPEGACTABLES: ma = ap[0].ToUInt(); if (ma != 0) { if (ma > 3) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "JpegAcTables tag has incorrect count"); return false; } sp.m_actable_offset_count = (byte)ma; mb = ap[1].ToUIntArray(); for (n = 0; n < ma; n++) sp.m_actable_offset[n] = mb[n]; } break; case TiffTag.JPEGProc: sp.m_jpeg_proc = ap[0].ToByte(); break; case TiffTag.JPEGRestartInterval: sp.m_restart_interval = ap[0].ToUShort(); break; default: return base.SetField(tif, tag, ap); } TiffFieldInfo fip = tif.FieldWithTag(tag); if (fip != null) tif.setFieldBit(fip.Bit); else return false; tif.m_flags |= TiffFlags.DirtyDirect; return true; } public override FieldValue[] GetField(Tiff tif, TiffTag tag) { OJpegCodec sp = tif.m_currentCodec as OJpegCodec; Debug.Assert(sp != null); FieldValue[] result = null; switch (tag) { case TiffTag.JPEGIFOffset: result = new FieldValue[1]; result[0].Set(sp.m_jpeg_interchange_format); break; case TiffTag.JPEGIFByteCount: result = new FieldValue[1]; result[0].Set(sp.m_jpeg_interchange_format_length); break; case TiffTag.YCBCRSUBSAMPLING: if (!sp.m_subsamplingcorrect_done) sp.OJPEGSubsamplingCorrect(); result = new FieldValue[2]; result[0].Set(sp.m_subsampling_hor); result[1].Set(sp.m_subsampling_ver); break; case TiffTag.JPEGQTables: result = new FieldValue[2]; result[0].Set(sp.m_qtable_offset_count); result[1].Set(sp.m_qtable_offset); break; case TiffTag.JPEGDCTables: result = new FieldValue[2]; result[0].Set(sp.m_dctable_offset_count); result[1].Set(sp.m_dctable_offset); break; case TiffTag.JPEGACTABLES: result = new FieldValue[2]; result[0].Set(sp.m_actable_offset_count); result[1].Set(sp.m_actable_offset); break; case TiffTag.JPEGProc: result = new FieldValue[1]; result[0].Set(sp.m_jpeg_proc); break; case TiffTag.JPEGRestartInterval: result = new FieldValue[1]; result[0].Set(sp.m_restart_interval); break; default: return base.GetField(tif, tag); } return result; } public override void PrintDir(Tiff tif, Stream fd, TiffPrintFlags flags) { OJpegCodec sp = tif.m_currentCodec as OJpegCodec; Debug.Assert(sp != null); if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGINTERCHANGEFORMAT)) Tiff.fprintf(fd, " JpegInterchangeFormat: {0}\n", sp.m_jpeg_interchange_format); if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGINTERCHANGEFORMATLENGTH)) Tiff.fprintf(fd, " JpegInterchangeFormatLength: {0}\n", sp.m_jpeg_interchange_format_length); if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGQTABLES)) { Tiff.fprintf(fd, " JpegQTables:"); for (byte m = 0; m < sp.m_qtable_offset_count; m++) Tiff.fprintf(fd, " {0}", sp.m_qtable_offset[m]); Tiff.fprintf(fd, "\n"); } if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGDCTABLES)) { Tiff.fprintf(fd, " JpegDcTables:"); for (byte m = 0; m < sp.m_dctable_offset_count; m++) Tiff.fprintf(fd, " {0}", sp.m_dctable_offset[m]); Tiff.fprintf(fd, "\n"); } if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGACTABLES)) { Tiff.fprintf(fd, " JpegAcTables:"); for (byte m = 0; m < sp.m_actable_offset_count; m++) Tiff.fprintf(fd, " {0}", sp.m_actable_offset[m]); Tiff.fprintf(fd, "\n"); } if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGPROC)) Tiff.fprintf(fd, " JpegProc: {0}\n", sp.m_jpeg_proc); if (tif.fieldSet(OJpegCodec.FIELD_OJPEG_JPEGRESTARTINTERVAL)) Tiff.fprintf(fd, " JpegRestartInterval: {0}\n", sp.m_restart_interval); } } #endregion #region OJpegSourceManager class OJpegSrcManager : Jpeg_Source { protected OJpegCodec m_sp; public OJpegSrcManager(OJpegCodec sp) { initInternalBuffer(null, 0); m_sp = sp; } /// /// Initializes this instance. /// public override void init_source() { } /// /// Fills input buffer /// /// /// true if operation succeed; otherwise, false /// public override bool fill_input_buffer() { Tiff tif = m_sp.GetTiff(); byte[] mem = null; uint len = 0; if (!m_sp.OJPEGWriteStream(out mem, out len)) Tiff.ErrorExt(tif, tif.m_clientdata, "LibJpeg", "Premature end of JPEG data"); initInternalBuffer(mem, (int)len); return true; } /// /// Skip data - used to skip over a potentially large amount of /// uninteresting data (such as an APPn marker). /// /// The number of bytes to skip. /// Writers of suspendable-input applications must note that skip_input_data /// is not granted the right to give a suspension return. If the skip extends /// beyond the data currently in the buffer, the buffer can be marked empty so /// that the next read will cause a fill_input_buffer call that can suspend. /// Arranging for additional bytes to be discarded before reloading the input /// buffer is the application writer's problem. public override void skip_input_data(int num_bytes) { Tiff tif = m_sp.GetTiff(); Tiff.ErrorExt(tif, tif.m_clientdata, "LibJpeg", "Unexpected error"); } /// /// This is the default resync_to_restart method for data source /// managers to use if they don't have any better approach. /// /// An instance of /// The desired /// false if suspension is required. /// That method assumes that no backtracking is possible. /// Some data source managers may be able to back up, or may have /// additional knowledge about the data which permits a more /// intelligent recovery strategy; such managers would /// presumably supply their own resync method.

/// read_restart_marker calls resync_to_restart if it finds a marker other than /// the restart marker it was expecting. (This code is *not* used unless /// a nonzero restart interval has been declared.) cinfo.unread_marker is /// the marker code actually found (might be anything, except 0 or FF). /// The desired restart marker number (0..7) is passed as a parameter.

/// This routine is supposed to apply whatever error recovery strategy seems /// appropriate in order to position the input stream to the next data segment. /// Note that cinfo.unread_marker is treated as a marker appearing before /// the current data-source input point; usually it should be reset to zero /// before returning.

/// This implementation is substantially constrained by wanting to treat the /// input as a data stream; this means we can't back up. Therefore, we have /// only the following actions to work with:
/// 1. Simply discard the marker and let the entropy decoder resume at next /// byte of file.
/// 2. Read forward until we find another marker, discarding intervening /// data. (In theory we could look ahead within the current bufferload, /// without having to discard data if we don't find the desired marker. /// This idea is not implemented here, in part because it makes behavior /// dependent on buffer size and chance buffer-boundary positions.)
/// 3. Leave the marker unread (by failing to zero cinfo.unread_marker). /// This will cause the entropy decoder to process an empty data segment, /// inserting dummy zeroes, and then we will reprocess the marker.
/// #2 is appropriate if we think the desired marker lies ahead, while #3 is /// appropriate if the found marker is a future restart marker (indicating /// that we have missed the desired restart marker, probably because it got /// corrupted).
/// We apply #2 or #3 if the found marker is a restart marker no more than /// two counts behind or ahead of the expected one. We also apply #2 if the /// found marker is not a legal JPEG marker code (it's certainly bogus data). /// If the found marker is a restart marker more than 2 counts away, we do #1 /// (too much risk that the marker is erroneous; with luck we will be able to /// resync at some future point).
/// For any valid non-restart JPEG marker, we apply #3. This keeps us from /// overrunning the end of a scan. An implementation limited to single-scan /// files might find it better to apply #2 for markers other than EOI, since /// any other marker would have to be bogus data in that case.
public override bool resync_to_restart(JpegDecompressor cinfo, int desired) { Tiff tif = m_sp.GetTiff(); Tiff.ErrorExt(tif, tif.m_clientdata, "LibJpeg", "Unexpected error"); return false; } /// /// Terminate source - called by jpeg_finish_decompress /// after all data has been read. Often a no-op. /// /// NB: not called by jpeg_abort or jpeg_destroy; surrounding /// application must deal with any cleanup that should happen even /// for error exit. public override void term_source() { } } #endregion #region PackBitsCodec class PackBitsCodec : TiffCodec { private enum EncodingState { BASE, LITERAL, RUN, LITERAL_RUN }; private int m_rowsize; public PackBitsCodec(Tiff tif, Compression scheme, string name) : base(tif, scheme, name) { } public override bool Init() { return true; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public override bool CanEncode { get { return true; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public override bool CanDecode { get { return true; } } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeRow(byte[] buffer, int offset, int count, short plane) { return PackBitsDecode(buffer, offset, count, plane); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { return PackBitsDecode(buffer, offset, count, plane); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public override bool DecodeTile(byte[] buffer, int offset, int count, short plane) { return PackBitsDecode(buffer, offset, count, plane); } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// /// PreEncode is called after and before encoding. /// public override bool PreEncode(short plane) { return PackBitsPreEncode(plane); } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeRow(byte[] buffer, int offset, int count, short plane) { return PackBitsEncode(buffer, offset, count, plane); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { return PackBitsEncodeChunk(buffer, offset, count, plane); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public override bool EncodeTile(byte[] buffer, int offset, int count, short plane) { return PackBitsEncodeChunk(buffer, offset, count, plane); } private bool PackBitsPreEncode(short s) { /* * Calculate the scanline/tile-width size in bytes. */ if (m_tif.IsTiled()) m_rowsize = m_tif.TileRowSize(); else m_rowsize = m_tif.ScanlineSize(); return true; } /* * Encode a run of pixels. */ private bool PackBitsEncode(byte[] buf, int offset, int cc, short s) { int op = m_tif.m_rawcp; EncodingState state = EncodingState.BASE; int lastliteral = 0; int bp = offset; while (cc > 0) { /* * Find the longest string of identical bytes. */ int b = buf[bp]; bp++; cc--; int n = 1; for (; cc > 0 && b == buf[bp]; cc--, bp++) n++; bool stop = false; while (!stop) { if (op + 2 >= m_tif.m_rawdatasize) { /* insure space for new data */ /* * Be careful about writing the last * literal. Must write up to that point * and then copy the remainder to the * front of the buffer. */ if (state == EncodingState.LITERAL || state == EncodingState.LITERAL_RUN) { int slop = op - lastliteral; m_tif.m_rawcc += lastliteral - m_tif.m_rawcp; if (!m_tif.flushData1()) return false; op = m_tif.m_rawcp; while (slop-- > 0) { m_tif.m_rawdata[op] = m_tif.m_rawdata[lastliteral]; lastliteral++; op++; } lastliteral = m_tif.m_rawcp; } else { m_tif.m_rawcc += op - m_tif.m_rawcp; if (!m_tif.flushData1()) return false; op = m_tif.m_rawcp; } } switch (state) { case EncodingState.BASE: /* initial state, set run/literal */ if (n > 1) { state = EncodingState.RUN; if (n > 128) { int temp = -127; m_tif.m_rawdata[op] = (byte)temp; op++; m_tif.m_rawdata[op] = (byte)b; op++; n -= 128; continue; } m_tif.m_rawdata[op] = (byte)(-n + 1); op++; m_tif.m_rawdata[op] = (byte)b; op++; } else { lastliteral = op; m_tif.m_rawdata[op] = 0; op++; m_tif.m_rawdata[op] = (byte)b; op++; state = EncodingState.LITERAL; } stop = true; break; case EncodingState.LITERAL: /* last object was literal string */ if (n > 1) { state = EncodingState.LITERAL_RUN; if (n > 128) { int temp = -127; m_tif.m_rawdata[op] = (byte)temp; op++; m_tif.m_rawdata[op] = (byte)b; op++; n -= 128; continue; } m_tif.m_rawdata[op] = (byte)(-n + 1); /* encode run */ op++; m_tif.m_rawdata[op] = (byte)b; op++; } else { /* extend literal */ m_tif.m_rawdata[lastliteral]++; if (m_tif.m_rawdata[lastliteral] == 127) state = EncodingState.BASE; m_tif.m_rawdata[op] = (byte)b; op++; } stop = true; break; case EncodingState.RUN: /* last object was run */ if (n > 1) { if (n > 128) { int temp = -127; m_tif.m_rawdata[op] = (byte)temp; op++; m_tif.m_rawdata[op] = (byte)b; op++; n -= 128; continue; } m_tif.m_rawdata[op] = (byte)(-n + 1); op++; m_tif.m_rawdata[op] = (byte)b; op++; } else { lastliteral = op; m_tif.m_rawdata[op] = 0; op++; m_tif.m_rawdata[op] = (byte)b; op++; state = EncodingState.LITERAL; } stop = true; break; case EncodingState.LITERAL_RUN: /* literal followed by a run */ /* * Check to see if previous run should * be converted to a literal, in which * case we convert literal-run-literal * to a single literal. */ int atemp = -1; if (n == 1 && m_tif.m_rawdata[op - 2] == (byte)atemp && m_tif.m_rawdata[lastliteral] < 126) { m_tif.m_rawdata[lastliteral] += 2; state = (m_tif.m_rawdata[lastliteral] == 127 ? EncodingState.BASE : EncodingState.LITERAL); m_tif.m_rawdata[op - 2] = m_tif.m_rawdata[op - 1]; /* replicate */ } else state = EncodingState.RUN; continue; } } } m_tif.m_rawcc += op - m_tif.m_rawcp; m_tif.m_rawcp = op; return true; } /// /// Encode a rectangular chunk of pixels. We break it up into row-sized pieces to insure /// that encoded runs do not span rows. Otherwise, there can be problems with the decoder /// if data is read, for example, by scanlines when it was encoded by strips. /// private bool PackBitsEncodeChunk(byte[] buffer, int offset, int count, short plane) { while (count > 0) { int chunk = m_rowsize; if (count < chunk) chunk = count; if (!PackBitsEncode(buffer, offset, chunk, plane)) return false; offset += chunk; count -= chunk; } return true; } private bool PackBitsDecode(byte[] buffer, int offset, int count, short plane) { int bp = m_tif.m_rawcp; int cc = m_tif.m_rawcc; while (cc > 0 && count > 0) { int n = m_tif.m_rawdata[bp]; bp++; cc--; // Watch out for compilers that don't sign extend chars... if (n >= 128) n -= 256; if (n < 0) { // replicate next byte (-n + 1) times if (n == -128) { // nop continue; } n = -n + 1; if (count < n) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "PackBitsDecode: discarding {0} bytes to avoid buffer overrun", n - count); n = count; } count -= n; int b = m_tif.m_rawdata[bp]; bp++; cc--; while (n-- > 0) { buffer[offset] = (byte)b; offset++; } } else { // copy next (n + 1) bytes literally if (count < n + 1) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "PackBitsDecode: discarding {0} bytes to avoid buffer overrun", n - count + 1); n = count - 1; } Buffer.BlockCopy(m_tif.m_rawdata, bp, buffer, offset, ++n); offset += n; count -= n; bp += n; cc -= n; } } m_tif.m_rawcp = bp; m_tif.m_rawcc = cc; if (count > 0) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "PackBitsDecode: Not enough data for scanline {0}", m_tif.m_row); return false; } return true; } } #endregion #region TagCompare internal class TagCompare : IComparer { int IComparer.Compare(object x, object y) { TiffFieldInfo ta = x as TiffFieldInfo; TiffFieldInfo tb = y as TiffFieldInfo; Debug.Assert(ta != null); Debug.Assert(tb != null); if (ta.Tag != tb.Tag) return ((int)ta.Tag - (int)tb.Tag); return (ta.Type == TiffType.Any) ? 0 : ((int)tb.Type - (int)ta.Type); } } #endregion #region TiffCIELabToRGB /// /// CIE Lab 1976->RGB support /// class TiffCIELabToRGB { public const int CIELABTORGB_TABLE_RANGE = 1500; /// /// Size of conversion table /// private int range; private float rstep; private float gstep; private float bstep; // Reference white point private float X0; private float Y0; private float Z0; private TiffDisplay display; /// /// Conversion of Yr to r /// private float[] Yr2r = new float[CIELABTORGB_TABLE_RANGE + 1]; /// /// Conversion of Yg to g /// private float[] Yg2g = new float[CIELABTORGB_TABLE_RANGE + 1]; /// /// Conversion of Yb to b /// private float[] Yb2b = new float[CIELABTORGB_TABLE_RANGE + 1]; /* * Allocate conversion state structures and make look_up tables for * the Yr,Yb,Yg <=> r,g,b conversions. */ public void Init(TiffDisplay refDisplay, float[] refWhite) { range = CIELABTORGB_TABLE_RANGE; display = refDisplay; /* Red */ double gamma = 1.0 / display.d_gammaR; rstep = (display.d_YCR - display.d_Y0R) / range; for (int i = 0; i <= range; i++) { Yr2r[i] = display.d_Vrwr * ((float)Math.Pow((double)i / range, gamma)); } /* Green */ gamma = 1.0 / display.d_gammaG; gstep = (display.d_YCR - display.d_Y0R) / range; for (int i = 0; i <= range; i++) { Yg2g[i] = display.d_Vrwg * ((float)Math.Pow((double)i / range, gamma)); } /* Blue */ gamma = 1.0 / display.d_gammaB; bstep = (display.d_YCR - display.d_Y0R) / range; for (int i = 0; i <= range; i++) { Yb2b[i] = display.d_Vrwb * ((float)Math.Pow((double)i / range, gamma)); } /* Init reference white point */ X0 = refWhite[0]; Y0 = refWhite[1]; Z0 = refWhite[2]; } /* * Convert color value from the CIE L*a*b* 1976 space to CIE XYZ. */ public void CIELabToXYZ(int l, int a, int b, out float X, out float Y, out float Z) { float L = (float)l * 100.0F / 255.0F; float cby; if (L < 8.856F) { Y = (L * Y0) / 903.292F; cby = 7.787F * (Y / Y0) + 16.0F / 116.0F; } else { cby = (L + 16.0F) / 116.0F; Y = Y0 * cby * cby * cby; } float tmp = (float)a / 500.0F + cby; if (tmp < 0.2069F) X = X0 * (tmp - 0.13793F) / 7.787F; else X = X0 * tmp * tmp * tmp; tmp = cby - (float)b / 200.0F; if (tmp < 0.2069F) Z = Z0 * (tmp - 0.13793F) / 7.787F; else Z = Z0 * tmp * tmp * tmp; } /* * Convert color value from the XYZ space to RGB. */ public void XYZToRGB(float X, float Y, float Z, out int r, out int g, out int b) { /* Multiply through the matrix to get luminosity values. */ float Yr = display.d_mat[0][0] * X + display.d_mat[0][1] * Y + display.d_mat[0][2] * Z; float Yg = display.d_mat[1][0] * X + display.d_mat[1][1] * Y + display.d_mat[1][2] * Z; float Yb = display.d_mat[2][0] * X + display.d_mat[2][1] * Y + display.d_mat[2][2] * Z; /* Clip input */ Yr = Math.Max(Yr, display.d_Y0R); Yg = Math.Max(Yg, display.d_Y0G); Yb = Math.Max(Yb, display.d_Y0B); /* Avoid overflow in case of wrong input values */ Yr = Math.Min(Yr, display.d_YCR); Yg = Math.Min(Yg, display.d_YCG); Yb = Math.Min(Yb, display.d_YCB); /* Turn luminosity to color value. */ int i = (int)((Yr - display.d_Y0R) / rstep); i = Math.Min(range, i); r = rInt(Yr2r[i]); i = (int)((Yg - display.d_Y0G) / gstep); i = Math.Min(range, i); g = rInt(Yg2g[i]); i = (int)((Yb - display.d_Y0B) / bstep); i = Math.Min(range, i); b = rInt(Yb2b[i]); /* Clip output. */ r = Math.Min(r, display.d_Vrwr); g = Math.Min(g, display.d_Vrwg); b = Math.Min(b, display.d_Vrwb); } private static int rInt(float R) { return (int)(R > 0 ? (R + 0.5) : (R - 0.5)); } } #endregion #region TiffCodec /// /// Base class for all codecs within the library. /// /// /// A codec is a class that implements decoding, encoding, or decoding and encoding of a /// compression algorithm. /// /// The library provides a collection of builtin codecs. More codecs may be registered /// through calls to the library and/or the builtin implementations may be overridden. /// public class TiffCodec { /// /// An instance of . /// protected Tiff m_tif; /// /// Compression scheme this codec impelements. /// protected internal Compression m_scheme; /// /// Codec name. /// protected internal string m_name; /// /// Initializes a new instance of the class. /// /// An instance of class. /// The compression scheme for the codec. /// The name of the codec. public TiffCodec(Tiff tif, Compression scheme, string name) { m_scheme = scheme; m_tif = tif; m_name = name; } /// /// Gets a value indicating whether this codec can encode data. /// /// /// true if this codec can encode data; otherwise, false. /// public virtual bool CanEncode { get { return false; } } /// /// Gets a value indicating whether this codec can decode data. /// /// /// true if this codec can decode data; otherwise, false. /// public virtual bool CanDecode { get { return false; } } /// /// Initializes this instance. /// /// true if initialized successfully public virtual bool Init() { return true; } /// /// Setups the decoder part of the codec. /// /// /// true if this codec successfully setup its decoder part and can decode data; /// otherwise, false. /// /// /// SetupDecode is called once before /// . public virtual bool SetupDecode() { return true; } /// /// Prepares the decoder part of the codec for a decoding. /// /// The zero-based sample plane index. /// true if this codec successfully prepared its decoder part and ready /// to decode data; otherwise, false. /// /// PreDecode is called after and before decoding. /// public virtual bool PreDecode(short plane) { return true; } /// /// Decodes one row of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to . /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public virtual bool DecodeRow(byte[] buffer, int offset, int count, short plane) { return noDecode("scanline"); } /// /// Decodes one strip of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to . /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public virtual bool DecodeStrip(byte[] buffer, int offset, int count, short plane) { return noDecode("strip"); } /// /// Decodes one tile of image data. /// /// The buffer to place decoded image data to. /// The zero-based byte offset in at /// which to begin storing decoded bytes. /// The number of decoded bytes that should be placed /// to . /// The zero-based sample plane index. /// /// true if image data was decoded successfully; otherwise, false. /// public virtual bool DecodeTile(byte[] buffer, int offset, int count, short plane) { return noDecode("tile"); } /// /// Setups the encoder part of the codec. /// /// /// true if this codec successfully setup its encoder part and can encode data; /// otherwise, false. /// /// /// SetupEncode is called once before /// . public virtual bool SetupEncode() { return true; } /// /// Prepares the encoder part of the codec for a encoding. /// /// The zero-based sample plane index. /// true if this codec successfully prepared its encoder part and ready /// to encode data; otherwise, false. /// /// PreEncode is called after and before encoding. /// public virtual bool PreEncode(short plane) { return true; } /// /// Performs any actions after encoding required by the codec. /// /// true if all post-encode actions succeeded; otherwise, false /// /// PostEncode is called after encoding and can be used to release any external /// resources needed during encoding. /// public virtual bool PostEncode() { return true; } /// /// Encodes one row of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to . /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public virtual bool EncodeRow(byte[] buffer, int offset, int count, short plane) { return noEncode("scanline"); } /// /// Encodes one strip of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to . /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public virtual bool EncodeStrip(byte[] buffer, int offset, int count, short plane) { return noEncode("strip"); } /// /// Encodes one tile of image data. /// /// The buffer with image data to be encoded. /// The zero-based byte offset in at /// which to begin read image data. /// The maximum number of encoded bytes that can be placed /// to . /// The zero-based sample plane index. /// /// true if image data was encoded successfully; otherwise, false. /// public virtual bool EncodeTile(byte[] buffer, int offset, int count, short plane) { return noEncode("tile"); } /// /// Flushes any internal data buffers and terminates current operation. /// public virtual void Close() { } /// /// Seeks the specified row in the strip being processed. /// /// The row to seek. /// true if specified row was successfully found; otherwise, false public virtual bool Seek(int row) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Compression algorithm does not support random access"); return false; } /// /// Cleanups the state of the codec. /// /// /// Cleanup is called when codec is no longer needed (won't be used) and can be /// used for example to restore tag methods that were substituted. public virtual void Cleanup() { } /// /// Calculates and/or constrains a strip size. /// /// The proposed strip size (may be zero or negative). /// A strip size to use. public virtual int DefStripSize(int size) { if (size < 1) { // If RowsPerStrip is unspecified, try to break the image up into strips that are // approximately STRIP_SIZE_DEFAULT bytes long. int scanline = m_tif.ScanlineSize(); size = Tiff.STRIP_SIZE_DEFAULT / (scanline == 0 ? 1 : scanline); if (size == 0) { // very wide images size = 1; } } return size; } /// /// Calculate and/or constrains a tile size /// /// The proposed tile width upon the call / tile width to use after the call. /// The proposed tile height upon the call / tile height to use after the call. public virtual void DefTileSize(ref int width, ref int height) { if (width < 1) width = 256; if (height < 1) height = 256; // roundup to a multiple of 16 per the spec if ((width & 0xf) != 0) width = Tiff.roundUp(width, 16); if ((height & 0xf) != 0) height = Tiff.roundUp(height, 16); } private bool noEncode(string method) { TiffCodec c = m_tif.FindCodec(m_tif.m_dir.td_compression); if (c != null) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "{0} {1} encoding is not implemented", c.m_name, method); } else { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Compression scheme {0} {1} encoding is not implemented", m_tif.m_dir.td_compression, method); } return false; } private bool noDecode(string method) { TiffCodec c = m_tif.FindCodec(m_tif.m_dir.td_compression); if (c != null) { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "{0} {1} decoding is not implemented", c.m_name, method); } else { Tiff.ErrorExt(m_tif, m_tif.m_clientdata, m_tif.m_name, "Compression scheme {0} {1} decoding is not implemented", m_tif.m_dir.td_compression, method); } return false; } } #endregion #region TiffDirectory /// /// Internal format of a TIFF directory entry. /// class TiffDirectory { /// /// bit vector of fields that are set /// public int[] td_fieldsset = new int[FieldBit.SetLongs]; public int td_imagewidth; public int td_imagelength; public int td_imagedepth; public int td_tilewidth; public int td_tilelength; public int td_tiledepth; public FileType td_subfiletype; public short td_bitspersample; public SampleFormat td_sampleformat; public Compression td_compression; public Photometric td_photometric; public Threshold td_threshholding; public BitOrder td_fillorder; public Orientation td_orientation; public short td_samplesperpixel; public int td_rowsperstrip; public short td_minsamplevalue; public short td_maxsamplevalue; public double td_sminsamplevalue; public double td_smaxsamplevalue; public float td_xresolution; public float td_yresolution; public ResolutionUnit td_resolutionunit; public PlanarConfig td_planarconfig; public float td_xposition; public float td_yposition; public short[] td_pagenumber = new short[2]; public short[][] td_colormap = { null, null, null }; public short[] td_halftonehints = new short[2]; public short td_extrasamples; public ExtraSample[] td_sampleinfo; public int td_stripsperimage; /// /// size of offset and bytecount arrays /// public int td_nstrips; public uint[] td_stripoffset; public uint[] td_stripbytecount; /// /// is the bytecount array sorted ascending? /// public bool td_stripbytecountsorted; public short td_nsubifd; public int[] td_subifd; // YCbCr parameters public short[] td_ycbcrsubsampling = new short[2]; public YCbCrPosition td_ycbcrpositioning; // Colorimetry parameters public float[] td_refblackwhite; public short[][] td_transferfunction = { null, null, null }; // CMYK parameters public int td_inknameslen; public string td_inknames; public int td_customValueCount; public TiffTagValue[] td_customValues; public TiffDirectory() { td_subfiletype = 0; td_compression = 0; td_photometric = 0; td_planarconfig = 0; td_fillorder = BitOrder.BigEndian; td_bitspersample = 1; td_threshholding = Threshold.BILevel; td_orientation = Orientation.TopLeft; td_samplesperpixel = 1; td_rowsperstrip = -1; td_tiledepth = 1; td_stripbytecountsorted = true; // Our own arrays always sorted. td_resolutionunit = ResolutionUnit.Inch; td_sampleformat = SampleFormat.UInt; td_imagedepth = 1; td_ycbcrsubsampling[0] = 2; td_ycbcrsubsampling[1] = 2; td_ycbcrpositioning = YCbCrPosition.Centered; } } #endregion #region TiffDirEntry /// /// TIFF Image File Directories are comprised of a table of field /// descriptors of the form shown below. The table is sorted in /// ascending order by tag. The values associated with each entry are /// disjoint and may appear anywhere in the file (so long as they are /// placed on a word boundary). /// /// If the value is 4 bytes or less, then it is placed in the offset /// field to save space. If the value is less than 4 bytes, it is /// left-justified in the offset field. /// class TiffDirEntry { public const int SizeInBytes = 12; public TiffTag tdir_tag; public TiffType tdir_type; /// /// number of items; length in spec /// public int tdir_count; /// /// byte offset to field data /// public uint tdir_offset; public new string ToString() { return tdir_tag.ToString() + ", " + tdir_type.ToString() + " " + tdir_offset.ToString(CultureInfo.InvariantCulture); } } #endregion #region TiffDisplay /// /// Structure for holding information about a display device. /// class TiffDisplay { /// /// XYZ -> luminance matrix /// internal float[][] d_mat; // Light o/p for reference white internal float d_YCR; internal float d_YCG; internal float d_YCB; // Pixel values for ref. white internal int d_Vrwr; internal int d_Vrwg; internal int d_Vrwb; // Residual light for black pixel internal float d_Y0R; internal float d_Y0G; internal float d_Y0B; // Gamma values for the three guns internal float d_gammaR; internal float d_gammaG; internal float d_gammaB; public TiffDisplay() { } public TiffDisplay(float[] mat0, float[] mat1, float[] mat2, float YCR, float YCG, float YCB, int Vrwr, int Vrwg, int Vrwb, float Y0R, float Y0G, float Y0B, float gammaR, float gammaG, float gammaB) { d_mat = new float[3][] { mat0, mat1, mat2 }; d_YCR = YCR; d_YCG = YCG; d_YCB = YCB; d_Vrwr = Vrwr; d_Vrwg = Vrwg; d_Vrwb = Vrwb; d_Y0R = Y0R; d_Y0G = Y0G; d_Y0B = Y0B; d_gammaR = gammaR; d_gammaG = gammaG; d_gammaB = gammaB; } } #endregion #region TiffErrorHandler /// /// Default error handler implementation. /// /// /// TiffErrorHandler provides error and warning handling methods that write an /// error or a warning messages to the . /// /// Applications that desire to capture control in the event of an error or a warning should /// set their custom error and warning handler using method. /// /// public class TiffErrorHandler { /// /// Handles an error by writing it text to the . /// /// An instance of the class. Can be null. /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// public virtual void ErrorHandler(Tiff tif, string method, string format, params object[] args) { using (TextWriter stderr = Console.Error) { if (method != null) stderr.Write("{0}: ", method); stderr.Write(format, args); stderr.Write("\n"); } } /// /// Handles an error by writing it text to the . /// /// An instance of the class. Can be null. /// A client data. /// The method where an error is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which an error is detected. /// /// The parameter can be anything. Its value and meaning is /// defined by an application and not the library. /// public virtual void ErrorHandlerExt(Tiff tif, object clientData, string method, string format, params object[] args) { } /// /// Handles a warning by writing it text to the . /// /// An instance of the class. Can be null. /// The method where a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// public virtual void WarningHandler(Tiff tif, string method, string format, params object[] args) { using (TextWriter stderr = Console.Error) { if (method != null) stderr.Write("{0}: ", method); stderr.Write("Warning, "); stderr.Write(format, args); stderr.Write("\n"); } } /// /// Handles a warning by writing it text to the . /// /// An instance of the class. Can be null. /// A client data. /// The method where a warning is detected. /// A composite format string (see Remarks). /// An object array that contains zero or more objects to format. /// /// The is a composite format string that uses the same format as /// method. The parameter, if /// not null, is printed before the message; it typically is used to identify the /// method in which a warning is detected. /// /// The parameter can be anything. Its value and meaning is /// defined by an application and not the library. /// public virtual void WarningHandlerExt(Tiff tif, object clientData, string method, string format, params object[] args) { } } #endregion #region TiffFieldInfo /// /// Represents a TIFF field information. /// /// /// TiffFieldInfo describes a field. It provides information about field name, type, /// number of values etc. /// public class TiffFieldInfo { private TiffTag m_tag; private short m_readCount; private short m_writeCount; private TiffType m_type; private short m_bit; private bool m_okToChange; private bool m_passCount; private string m_name; /// /// marker for variable length tags /// public const short Variable = -1; /// /// marker for SamplesPerPixel-bound tags /// public const short Spp = -2; /// /// marker for integer variable length tags /// public const short Variable2 = -3; /// /// Initializes a new instance of the class. /// /// The tag to describe. /// The number of values to read when reading field information or /// one of , and . /// The number of values to write when writing field information /// or one of , and . /// The type of the field value. /// Index of the bit to use in "Set Fields Vector" when this instance /// is merged into field info collection. Take a look at class. /// If true, then it is permissible to set the tag's value even /// after writing has commenced. /// If true, then number of value elements should be passed to /// method as second parameter (right after tag type AND /// before value itself). /// The name (description) of the tag this instance describes. public TiffFieldInfo(TiffTag tag, short readCount, short writeCount, TiffType type, short bit, bool okToChange, bool passCount, string name) { m_tag = tag; m_readCount = readCount; m_writeCount = writeCount; m_type = type; m_bit = bit; m_okToChange = okToChange; m_passCount = passCount; m_name = name; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { if (m_bit != FieldBit.Custom || m_name.Length == 0) return m_tag.ToString(); return m_name; } /// /// The tag described by this instance. /// public TiffTag Tag { get { return m_tag; } } /// /// Number of values to read when reading field information or /// one of , and . /// public short ReadCount { get { return m_readCount; } } /// /// Number of values to write when writing field information or /// one of , and . /// public short WriteCount { get { return m_writeCount; } } /// /// Type of the field values. /// public TiffType Type { get { return m_type; } } /// /// Index of the bit to use in "Set Fields Vector" when this instance /// is merged into field info collection. Take a look at class. /// public short Bit { get { return m_bit; } } /// /// If true, then it is permissible to set the tag's value even after writing has commenced. /// public bool OkToChange { get { return m_okToChange; } } /// /// If true, then number of value elements should be passed to /// method as second parameter (right after tag type AND before values itself). /// public bool PassCount { get { return m_passCount; } } /// /// The name (or description) of the tag this instance describes. /// public string Name { get { return m_name; } internal set { m_name = value; } } } #endregion #region TiffFlags [Flags] enum TiffFlags { /// /// Use BigEndian (most significant -> least) fill order /// BigEndian = 1, /// /// Use LittleEndian (least significant -> most) fill order /// LittleEndian = 2, /// /// natural bit fill order for machine /// FillOrder = 0x0003, /// /// current directory must be written /// DirtyDirect = 0x0008, /// /// data buffers setup /// BufferSetup = 0x0010, /// /// encoder/decoder setup done /// CoderSetup = 0x0020, /// /// written 1+ scanlines to file /// BeenWriting = 0x0040, /// /// byte swap file information /// Swab = 0x0080, /// /// inhibit bit reversal logic /// NoBitRev = 0x0100, /// /// my raw data buffer; free on close /// MyBuffer = 0x0200, /// /// file is tile, not strip- based /// IsTiled = 0x0400, /// /// need call to post-encode routine /// PostEncode = 0x1000, /// /// currently writing a subifd /// InSubIFD = 0x2000, /// /// library is doing data up-sampling /// UpSampled = 0x4000, /// /// enable strip chopping support /// StripChop = 0x8000, /// /// read header only, do not process the first directory /// HeaderOnly = 0x10000, /// /// skip reading of raw uncompressed image data /// NoReadRaw = 0x20000, } #endregion #region TiffHeader struct TiffHeader { public const int TIFF_MAGIC_SIZE = 2; public const int TIFF_VERSION_SIZE = 2; public const int TIFF_DIROFFSET_SIZE = 4; public const int SizeInBytes = TIFF_MAGIC_SIZE + TIFF_VERSION_SIZE + TIFF_DIROFFSET_SIZE; /// /// magic number (defines byte order) /// public short tiff_magic; /// /// TIFF version number /// public short tiff_version; /// /// byte offset to first directory /// public uint tiff_diroff; } #endregion #region TiffStream /// /// A stream used by the library for TIFF reading and writing. /// public class TiffStream { /// /// Reads a sequence of bytes from the stream and advances the position within the stream /// by the number of bytes read. /// /// A client data (by default, an underlying stream). /// An array of bytes. When this method returns, the /// contains the specified byte array with the values between /// and ( + - 1) /// replaced by the bytes read from the current source. /// The zero-based byte offset in at which /// to begin storing the data read from the current stream. /// The maximum number of bytes to be read from the current stream. /// The total number of bytes read into the . This can /// be less than the number of bytes requested if that many bytes are not currently /// available, or zero (0) if the end of the stream has been reached. public virtual int Read(object clientData, byte[] buffer, int offset, int count) { Stream stream = clientData as Stream; if (stream == null) throw new ArgumentException("Can't get underlying stream to read from"); return stream.Read(buffer, offset, count); } /// /// Writes a sequence of bytes to the current stream and advances the current position /// within this stream by the number of bytes written. /// /// A client data (by default, an underlying stream). /// An array of bytes. This method copies /// bytes from to the current stream. /// The zero-based byte offset in at which /// to begin copying bytes to the current stream. /// The number of bytes to be written to the current stream. public virtual void Write(object clientData, byte[] buffer, int offset, int count) { Stream stream = clientData as Stream; if (stream == null) throw new ArgumentException("Can't get underlying stream to write to"); stream.Write(buffer, offset, count); } /// /// Sets the position within the current stream. /// /// A client data (by default, an underlying stream). /// A byte offset relative to the parameter. /// A value of type indicating the /// reference point used to obtain the new position. /// The new position within the current stream. public virtual long Seek(object clientData, long offset, SeekOrigin origin) { // we use this as a special code, so avoid accepting it if (offset == -1) return -1; // was 0xFFFFFFFF Stream stream = clientData as Stream; if (stream == null) throw new ArgumentException("Can't get underlying stream to seek in"); return stream.Seek(offset, origin); } /// /// Closes the current stream. /// /// A client data (by default, an underlying stream). public virtual void Close(object clientData) { Stream stream = clientData as Stream; if (stream == null) throw new ArgumentException("Can't get underlying stream to close"); stream.Close(); } /// /// Gets the length in bytes of the stream. /// /// A client data (by default, an underlying stream). /// The length of the stream in bytes. public virtual long Size(object clientData) { Stream stream = clientData as Stream; if (stream == null) throw new ArgumentException("Can't get underlying stream to retrieve size from"); return stream.Length; } } #endregion #region TiffTagMethods /// /// Tiff tag methods. /// public class TiffTagMethods { // // These are used in the backwards compatibility code... // /// /// untyped data /// private const short DATATYPE_VOID = 0; /// /// signed integer data /// private const short DATATYPE_INT = 1; /// /// unsigned integer data /// private const short DATATYPE_UINT = 2; /// /// IEEE floating point data /// private const short DATATYPE_IEEEFP = 3; /// /// Sets the value(s) of a tag in a TIFF file/stream open for writing. /// /// An instance of the class. /// The tag. /// The tag value(s). /// /// true if tag value(s) were set successfully; otherwise, false. /// /// public virtual bool SetField(Tiff tif, TiffTag tag, FieldValue[] value) { const string module = "vsetfield"; TiffDirectory td = tif.m_dir; bool status = true; int v32 = 0; int v = 0; bool end = false; bool badvalue = false; bool badvalue32 = false; switch (tag) { case TiffTag.SubFileType: td.td_subfiletype = (FileType)value[0].ToByte(); break; case TiffTag.ImageWidth: td.td_imagewidth = value[0].ToInt(); break; case TiffTag.ImageLength: td.td_imagelength = value[0].ToInt(); break; case TiffTag.BitsPerSample: td.td_bitspersample = value[0].ToShort(); // If the data require post-decoding processing to byte-swap samples, set it // up here. Note that since tags are required to be ordered, compression code // can override this behavior in the setup method if it wants to roll the post // decoding work in with its normal work. if ((tif.m_flags & TiffFlags.Swab) == TiffFlags.Swab) { if (td.td_bitspersample == 16) tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab16Bit; else if (td.td_bitspersample == 24) tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab24Bit; else if (td.td_bitspersample == 32) tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab32Bit; else if (td.td_bitspersample == 64) tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab64Bit; else if (td.td_bitspersample == 128) { // two 64's tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab64Bit; } } break; case TiffTag.Compression: v = value[0].ToInt() & 0xffff; Compression comp = (Compression)v; // If we're changing the compression scheme, then notify the previous module // so that it can cleanup any state it's setup. if (tif.fieldSet(FieldBit.Compression)) { if (td.td_compression == comp) break; tif.m_currentCodec.Cleanup(); tif.m_flags &= ~TiffFlags.CoderSetup; } // Setup new compression scheme. status = tif.setCompressionScheme(comp); if (status) td.td_compression = comp; else status = false; break; case TiffTag.Photometric: td.td_photometric = (Photometric)value[0].ToInt(); break; case TiffTag.Threshholding: td.td_threshholding = (Threshold)value[0].ToByte(); break; case TiffTag.FillOrder: v = value[0].ToInt(); BitOrder fo = (BitOrder)v; if (fo != BitOrder.LittleEndian && fo != BitOrder.BigEndian) { badvalue = true; break; } td.td_fillorder = fo; break; case TiffTag.Orientation: v = value[0].ToInt(); Orientation or = (Orientation)v; if (or < Orientation.TopLeft || Orientation.LeftBottom < or) { badvalue = true; break; } else td.td_orientation = or; break; case TiffTag.SamplesPerPixel: // XXX should cross check - e.g. if pallette, then 1 v = value[0].ToInt(); if (v == 0) { badvalue = true; break; } td.td_samplesperpixel = (short)v; break; case TiffTag.RowsPerStrip: v32 = value[0].ToInt(); if (v32 == 0) { badvalue32 = true; break; } td.td_rowsperstrip = v32; if (!tif.fieldSet(FieldBit.TileDimensions)) { td.td_tilelength = v32; td.td_tilewidth = td.td_imagewidth; } break; case TiffTag.MinSampleValue: td.td_minsamplevalue = value[0].ToShort(); break; case TiffTag.MaxSampleValue: td.td_maxsamplevalue = value[0].ToShort(); break; case TiffTag.SMinSampleValue: td.td_sminsamplevalue = value[0].ToDouble(); break; case TiffTag.SMaxSampleValue: td.td_smaxsamplevalue = value[0].ToDouble(); break; case TiffTag.XResolution: td.td_xresolution = value[0].ToFloat(); break; case TiffTag.YResolution: td.td_yresolution = value[0].ToFloat(); break; case TiffTag.PlanarConfig: v = value[0].ToInt(); PlanarConfig pc = (PlanarConfig)v; if (pc != PlanarConfig.Contig && pc != PlanarConfig.Separate) { badvalue = true; break; } td.td_planarconfig = pc; break; case TiffTag.XPosition: td.td_xposition = value[0].ToFloat(); break; case TiffTag.YPosition: td.td_yposition = value[0].ToFloat(); break; case TiffTag.ResolutionUnit: v = value[0].ToInt(); ResolutionUnit ru = (ResolutionUnit)v; if (ru < ResolutionUnit.None || ResolutionUnit.Centimeter < ru) { badvalue = true; break; } td.td_resolutionunit = ru; break; case TiffTag.PageNumber: td.td_pagenumber[0] = value[0].ToShort(); td.td_pagenumber[1] = value[1].ToShort(); break; case TiffTag.HalfToneHints: td.td_halftonehints[0] = value[0].ToShort(); td.td_halftonehints[1] = value[1].ToShort(); break; case TiffTag.Colormap: v32 = 1 << td.td_bitspersample; Tiff.setShortArray(out td.td_colormap[0], value[0].ToShortArray(), v32); Tiff.setShortArray(out td.td_colormap[1], value[1].ToShortArray(), v32); Tiff.setShortArray(out td.td_colormap[2], value[2].ToShortArray(), v32); break; case TiffTag.ExtraSamples: if (!setExtraSamples(td, ref v, value)) { badvalue = true; break; } break; case TiffTag.MATTEING: if (value[0].ToShort() != 0) td.td_extrasamples = 1; else td.td_extrasamples = 0; if (td.td_extrasamples != 0) { td.td_sampleinfo = new ExtraSample[1]; td.td_sampleinfo[0] = ExtraSample.AssociatedAlpha; } break; case TiffTag.TileWidth: v32 = value[0].ToInt(); if ((v32 % 16) != 0) { if (tif.m_mode != Tiff.O_RDONLY) { badvalue32 = true; break; } Tiff.WarningExt(tif, tif.m_clientdata, tif.m_name, "Nonstandard tile width {0}, convert file", v32); } td.td_tilewidth = v32; tif.m_flags |= TiffFlags.IsTiled; break; case TiffTag.TileLength: v32 = value[0].ToInt(); if ((v32 % 16) != 0) { if (tif.m_mode != Tiff.O_RDONLY) { badvalue32 = true; break; } Tiff.WarningExt(tif, tif.m_clientdata, tif.m_name, "Nonstandard tile length {0}, convert file", v32); } td.td_tilelength = v32; tif.m_flags |= TiffFlags.IsTiled; break; case TiffTag.TILEDEPTH: v32 = value[0].ToInt(); if (v32 == 0) { badvalue32 = true; break; } td.td_tiledepth = v32; break; case TiffTag.DATATYPE: v = value[0].ToInt(); SampleFormat sf = SampleFormat.UnTyped; switch (v) { case DATATYPE_VOID: sf = SampleFormat.UnTyped; break; case DATATYPE_INT: sf = SampleFormat.Int; break; case DATATYPE_UINT: sf = SampleFormat.UInt; break; case DATATYPE_IEEEFP: sf = SampleFormat.IEEEFloat; break; default: badvalue = true; break; } if (!badvalue) td.td_sampleformat = sf; break; case TiffTag.SampleFormat: v = value[0].ToInt(); sf = (SampleFormat)v; if (sf < SampleFormat.UInt || SampleFormat.ComplexIEEEFloat < sf) { badvalue = true; break; } td.td_sampleformat = sf; // Try to fix up the Swab function for complex data. if (td.td_sampleformat == SampleFormat.COMPLEXINT && td.td_bitspersample == 32 && tif.m_postDecodeMethod == Tiff.PostDecodeMethodType.pdmSwab32Bit) { tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab16Bit; } else if ((td.td_sampleformat == SampleFormat.COMPLEXINT || td.td_sampleformat == SampleFormat.ComplexIEEEFloat) && td.td_bitspersample == 64 && tif.m_postDecodeMethod == Tiff.PostDecodeMethodType.pdmSwab64Bit) { tif.m_postDecodeMethod = Tiff.PostDecodeMethodType.pdmSwab32Bit; } break; case TiffTag.IMAGEDEPTH: td.td_imagedepth = value[0].ToInt(); break; case TiffTag.SubImageDescriptor: if ((tif.m_flags & TiffFlags.InSubIFD) != TiffFlags.InSubIFD) { td.td_nsubifd = value[0].ToShort(); Tiff.setLongArray(out td.td_subifd, value[1].ToIntArray(), td.td_nsubifd); } else { Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: Sorry, cannot nest SubIFDs", tif.m_name); status = false; } break; case TiffTag.YCBCRPOSITIONING: td.td_ycbcrpositioning = (YCbCrPosition)value[0].ToByte(); break; case TiffTag.YCBCRSUBSAMPLING: td.td_ycbcrsubsampling[0] = value[0].ToShort(); td.td_ycbcrsubsampling[1] = value[1].ToShort(); break; case TiffTag.TransferFunction: v = ((td.td_samplesperpixel - td.td_extrasamples) > 1 ? 3 : 1); for (int i = 0; i < v; i++) { Tiff.setShortArray(out td.td_transferfunction[i], value[0].ToShortArray(), 1 << td.td_bitspersample); } break; case TiffTag.REFERENCEBLACKWHITE: // XXX should check for null range Tiff.setFloatArray(out td.td_refblackwhite, value[0].ToFloatArray(), 6); break; case TiffTag.InkNames: v = value[0].ToInt(); string s = value[1].ToString(); v = checkInkNamesString(tif, v, s); status = v > 0; if (v > 0) { setNString(out td.td_inknames, s, v); td.td_inknameslen = v; } break; default: // This can happen if multiple images are open with // different codecs which have private tags. The global tag // information table may then have tags that are valid for // one file but not the other. If the client tries to set a // tag that is not valid for the image's codec then we'll // arrive here. This happens, for example, when tiffcp is // used to convert between compression schemes and // codec-specific tags are blindly copied. TiffFieldInfo fip = tif.FindFieldInfo(tag, TiffType.Any); if (fip == null || fip.Bit != FieldBit.Custom) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: Invalid {1}tag \"{2}\" (not supported by codec)", tif.m_name, Tiff.isPseudoTag(tag) ? "pseudo-" : "", fip != null ? fip.Name : "Unknown"); status = false; break; } // Find the existing entry for this custom value. int tvIndex = -1; for (int iCustom = 0; iCustom < td.td_customValueCount; iCustom++) { if (td.td_customValues[iCustom].info.Tag == tag) { td.td_customValues[iCustom].value = null; break; } } // Grow the custom list if the entry was not found. if (tvIndex == -1) { td.td_customValueCount++; TiffTagValue[] new_customValues = Tiff.Realloc( td.td_customValues, td.td_customValueCount - 1, td.td_customValueCount); td.td_customValues = new_customValues; tvIndex = td.td_customValueCount - 1; td.td_customValues[tvIndex].info = fip; td.td_customValues[tvIndex].value = null; td.td_customValues[tvIndex].count = 0; } // Set custom value ... save a copy of the custom tag value. int tv_size = Tiff.dataSize(fip.Type); if (tv_size == 0) { status = false; Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: Bad field type {1} for \"{2}\"", tif.m_name, fip.Type, fip.Name); end = true; break; } int paramIndex = 0; if (fip.PassCount) { if (fip.WriteCount == TiffFieldInfo.Variable2) td.td_customValues[tvIndex].count = value[paramIndex++].ToInt(); else td.td_customValues[tvIndex].count = value[paramIndex++].ToInt(); } else if (fip.WriteCount == TiffFieldInfo.Variable || fip.WriteCount == TiffFieldInfo.Variable2) { td.td_customValues[tvIndex].count = 1; } else if (fip.WriteCount == TiffFieldInfo.Spp) { td.td_customValues[tvIndex].count = td.td_samplesperpixel; } else { td.td_customValues[tvIndex].count = fip.WriteCount; } if (fip.Type == TiffType.ASCII) { string ascii; Tiff.setString(out ascii, value[paramIndex++].ToString()); td.td_customValues[tvIndex].value = Tiff.Latin1Encoding.GetBytes(ascii); } else { td.td_customValues[tvIndex].value = new byte[tv_size * td.td_customValues[tvIndex].count]; if ((fip.PassCount || fip.WriteCount == TiffFieldInfo.Variable || fip.WriteCount == TiffFieldInfo.Variable2 || fip.WriteCount == TiffFieldInfo.Spp || td.td_customValues[tvIndex].count > 1) && fip.Tag != TiffTag.PageNumber && fip.Tag != TiffTag.HalfToneHints && fip.Tag != TiffTag.YCBCRSUBSAMPLING && fip.Tag != TiffTag.DotRange) { byte[] apBytes = value[paramIndex++].GetBytes(); //Buffer.BlockCopy(apBytes, 0, td.td_customValues[tvIndex].value, 0, apBytes.Length); Buffer.BlockCopy(apBytes, 0, td.td_customValues[tvIndex].value, 0, td.td_customValues[tvIndex].value.Length); } else { // XXX: The following loop required to handle // PageNumber, HalfToneHints, // YCBCRSUBSAMPLING and DotRange tags. // These tags are actually arrays and should be // passed as arrays to SetField() function, but // actually passed as a list of separate values. // This behavior must be changed in the future! // Upd: This loop also processes some EXIF tags with // Undefined type (like EXIF_FILESOURCE or EXIF_SCENETYPE) // In this case input value is string-based, so // in TiffType.Undefined case we use FieldValue.GetBytes()[0] // construction instead of direct call of FieldValue.ToByte() method. byte[] val = td.td_customValues[tvIndex].value; int valPos = 0; for (int i = 0; i < td.td_customValues[tvIndex].count; i++, valPos += tv_size) { switch (fip.Type) { case TiffType.Byte: case TiffType.Undefined: val[valPos] = value[paramIndex + i].GetBytes()[0]; break; case TiffType.SByte: val[valPos] = value[paramIndex + i].ToByte(); break; case TiffType.Short: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToShort()), 0, val, valPos, tv_size); break; case TiffType.SShort: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToShort()), 0, val, valPos, tv_size); break; case TiffType.Long: case TiffType.IFD: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToInt()), 0, val, valPos, tv_size); break; case TiffType.SLong: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToInt()), 0, val, valPos, tv_size); break; case TiffType.Rational: case TiffType.SRational: case TiffType.Float: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToFloat()), 0, val, valPos, tv_size); break; case TiffType.Double: Buffer.BlockCopy(BitConverter.GetBytes(value[paramIndex + i].ToDouble()), 0, val, valPos, tv_size); break; default: Array.Clear(val, valPos, tv_size); status = false; break; } } } } break; } if (!end && !badvalue && !badvalue32) { if (status) { tif.setFieldBit(tif.FieldWithTag(tag).Bit); tif.m_flags |= TiffFlags.DirtyDirect; } } if (badvalue) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: Bad value {1} for \"{2}\" tag", tif.m_name, v, tif.FieldWithTag(tag).Name); return false; } if (badvalue32) { Tiff.ErrorExt(tif, tif.m_clientdata, module, "{0}: Bad value {1} for \"{2}\" tag", tif.m_name, v32, tif.FieldWithTag(tag).Name); return false; } return status; } /// /// Gets the value(s) of a tag in an open TIFF file. /// /// An instance of the class. /// The tag. /// The value(s) of a tag in an open TIFF file/stream as array of /// objects or null if there is no such tag set. /// public virtual FieldValue[] GetField(Tiff tif, TiffTag tag) { TiffDirectory td = tif.m_dir; FieldValue[] result = null; switch (tag) { case TiffTag.SubFileType: result = new FieldValue[1]; result[0].Set(td.td_subfiletype); break; case TiffTag.ImageWidth: result = new FieldValue[1]; result[0].Set(td.td_imagewidth); break; case TiffTag.ImageLength: result = new FieldValue[1]; result[0].Set(td.td_imagelength); break; case TiffTag.BitsPerSample: result = new FieldValue[1]; result[0].Set(td.td_bitspersample); break; case TiffTag.Compression: result = new FieldValue[1]; result[0].Set(td.td_compression); break; case TiffTag.Photometric: result = new FieldValue[1]; result[0].Set(td.td_photometric); break; case TiffTag.Threshholding: result = new FieldValue[1]; result[0].Set(td.td_threshholding); break; case TiffTag.FillOrder: result = new FieldValue[1]; result[0].Set(td.td_fillorder); break; case TiffTag.Orientation: result = new FieldValue[1]; result[0].Set(td.td_orientation); break; case TiffTag.SamplesPerPixel: result = new FieldValue[1]; result[0].Set(td.td_samplesperpixel); break; case TiffTag.RowsPerStrip: result = new FieldValue[1]; result[0].Set(td.td_rowsperstrip); break; case TiffTag.MinSampleValue: result = new FieldValue[1]; result[0].Set(td.td_minsamplevalue); break; case TiffTag.MaxSampleValue: result = new FieldValue[1]; result[0].Set(td.td_maxsamplevalue); break; case TiffTag.SMinSampleValue: result = new FieldValue[1]; result[0].Set(td.td_sminsamplevalue); break; case TiffTag.SMaxSampleValue: result = new FieldValue[1]; result[0].Set(td.td_smaxsamplevalue); break; case TiffTag.XResolution: result = new FieldValue[1]; result[0].Set(td.td_xresolution); break; case TiffTag.YResolution: result = new FieldValue[1]; result[0].Set(td.td_yresolution); break; case TiffTag.PlanarConfig: result = new FieldValue[1]; result[0].Set(td.td_planarconfig); break; case TiffTag.XPosition: result = new FieldValue[1]; result[0].Set(td.td_xposition); break; case TiffTag.YPosition: result = new FieldValue[1]; result[0].Set(td.td_yposition); break; case TiffTag.ResolutionUnit: result = new FieldValue[1]; result[0].Set(td.td_resolutionunit); break; case TiffTag.PageNumber: result = new FieldValue[2]; result[0].Set(td.td_pagenumber[0]); result[1].Set(td.td_pagenumber[1]); break; case TiffTag.HalfToneHints: result = new FieldValue[2]; result[0].Set(td.td_halftonehints[0]); result[1].Set(td.td_halftonehints[1]); break; case TiffTag.Colormap: result = new FieldValue[3]; result[0].Set(td.td_colormap[0]); result[1].Set(td.td_colormap[1]); result[2].Set(td.td_colormap[2]); break; case TiffTag.StripOffsets: case TiffTag.TileOffsets: result = new FieldValue[1]; result[0].Set(td.td_stripoffset); break; case TiffTag.StripByteCounts: case TiffTag.TileByteCounts: result = new FieldValue[1]; result[0].Set(td.td_stripbytecount); break; case TiffTag.MATTEING: result = new FieldValue[1]; result[0].Set((td.td_extrasamples == 1 && td.td_sampleinfo[0] == ExtraSample.AssociatedAlpha)); break; case TiffTag.ExtraSamples: result = new FieldValue[2]; result[0].Set(td.td_extrasamples); result[1].Set(td.td_sampleinfo); break; case TiffTag.TileWidth: result = new FieldValue[1]; result[0].Set(td.td_tilewidth); break; case TiffTag.TileLength: result = new FieldValue[1]; result[0].Set(td.td_tilelength); break; case TiffTag.TILEDEPTH: result = new FieldValue[1]; result[0].Set(td.td_tiledepth); break; case TiffTag.DATATYPE: switch (td.td_sampleformat) { case SampleFormat.UInt: result = new FieldValue[1]; result[0].Set(DATATYPE_UINT); break; case SampleFormat.Int: result = new FieldValue[1]; result[0].Set(DATATYPE_INT); break; case SampleFormat.IEEEFloat: result = new FieldValue[1]; result[0].Set(DATATYPE_IEEEFP); break; case SampleFormat.UnTyped: result = new FieldValue[1]; result[0].Set(DATATYPE_VOID); break; } break; case TiffTag.SampleFormat: result = new FieldValue[1]; result[0].Set(td.td_sampleformat); break; case TiffTag.IMAGEDEPTH: result = new FieldValue[1]; result[0].Set(td.td_imagedepth); break; case TiffTag.SubImageDescriptor: result = new FieldValue[2]; result[0].Set(td.td_nsubifd); result[1].Set(td.td_subifd); break; case TiffTag.YCBCRPOSITIONING: result = new FieldValue[1]; result[0].Set(td.td_ycbcrpositioning); break; case TiffTag.YCBCRSUBSAMPLING: result = new FieldValue[2]; result[0].Set(td.td_ycbcrsubsampling[0]); result[1].Set(td.td_ycbcrsubsampling[1]); break; case TiffTag.TransferFunction: result = new FieldValue[3]; result[0].Set(td.td_transferfunction[0]); if (td.td_samplesperpixel - td.td_extrasamples > 1) { result[1].Set(td.td_transferfunction[1]); result[2].Set(td.td_transferfunction[2]); } break; case TiffTag.REFERENCEBLACKWHITE: if (td.td_refblackwhite != null) { result = new FieldValue[1]; result[0].Set(td.td_refblackwhite); } break; case TiffTag.InkNames: result = new FieldValue[1]; result[0].Set(td.td_inknames); break; default: // This can happen if multiple images are open with // different codecs which have private tags. The global tag // information table may then have tags that are valid for // one file but not the other. If the client tries to get a // tag that is not valid for the image's codec then we'll // arrive here. TiffFieldInfo fip = tif.FindFieldInfo(tag, TiffType.Any); if (fip == null || fip.Bit != FieldBit.Custom) { Tiff.ErrorExt(tif, tif.m_clientdata, "_TIFFVGetField", "{0}: Invalid {1}tag \"{2}\" (not supported by codec)", tif.m_name, Tiff.isPseudoTag(tag) ? "pseudo-" : "", fip != null ? fip.Name : "Unknown"); result = null; break; } // Do we have a custom value? result = null; for (int i = 0; i < td.td_customValueCount; i++) { TiffTagValue tv = td.td_customValues[i]; if (tv.info.Tag != tag) continue; if (fip.PassCount) { result = new FieldValue[2]; if (fip.ReadCount == TiffFieldInfo.Variable2) { result[0].Set(tv.count); } else { // Assume TiffFieldInfo.Variable result[0].Set(tv.count); } result[1].Set(tv.value); } else { if ((fip.Type == TiffType.ASCII || fip.ReadCount == TiffFieldInfo.Variable || fip.ReadCount == TiffFieldInfo.Variable2 || fip.ReadCount == TiffFieldInfo.Spp || tv.count > 1) && fip.Tag != TiffTag.PageNumber && fip.Tag != TiffTag.HalfToneHints && fip.Tag != TiffTag.YCBCRSUBSAMPLING && fip.Tag != TiffTag.DotRange) { result = new FieldValue[1]; byte[] value = tv.value; if (fip.Type == TiffType.ASCII && tv.value.Length > 0 && tv.value[tv.value.Length - 1] == 0) { // cut unwanted zero at the end value = new byte[Math.Max(tv.value.Length - 1, 0)]; Buffer.BlockCopy(tv.value, 0, value, 0, value.Length); } result[0].Set(value); } else { result = new FieldValue[tv.count]; byte[] val = tv.value; int valPos = 0; for (int j = 0; j < tv.count; j++, valPos += Tiff.dataSize(tv.info.Type)) { switch (fip.Type) { case TiffType.Byte: case TiffType.Undefined: case TiffType.SByte: result[j].Set(val[valPos]); break; case TiffType.Short: case TiffType.SShort: result[j].Set(BitConverter.ToInt16(val, valPos)); break; case TiffType.Long: case TiffType.IFD: case TiffType.SLong: result[j].Set(BitConverter.ToInt32(val, valPos)); break; case TiffType.Rational: case TiffType.SRational: case TiffType.Float: result[j].Set(BitConverter.ToSingle(val, valPos)); break; case TiffType.Double: result[j].Set(BitConverter.ToDouble(val, valPos)); break; default: result = null; break; } } } } break; } break; } return result; } /// /// Prints formatted description of the contents of the current directory to the /// specified stream using specified print (formatting) options. /// /// An instance of the class. /// The stream to print to. /// The print (formatting) options. public virtual void PrintDir(Tiff tif, Stream stream, TiffPrintFlags flags) { } /// /// Install extra samples information. /// private static bool setExtraSamples(TiffDirectory td, ref int v, FieldValue[] ap) { // XXX: Unassociated alpha data == 999 is a known Corel Draw bug, see below const short EXTRASAMPLE_COREL_UNASSALPHA = 999; v = ap[0].ToInt(); if (v > td.td_samplesperpixel) return false; byte[] va = ap[1].ToByteArray(); if (v > 0 && va == null) { // typically missing param return false; } for (int i = 0; i < v; i++) { if ((ExtraSample)va[i] > ExtraSample.UnAssociatedAlpha) { // XXX: Corel Draw is known to produce incorrect // ExtraSamples tags which must be patched here if we // want to be able to open some of the damaged TIFF files: if (i < v - 1) { short s = BitConverter.ToInt16(va, i); if (s == EXTRASAMPLE_COREL_UNASSALPHA) va[i] = (byte)ExtraSample.UnAssociatedAlpha; } else return false; } } td.td_extrasamples = (short)v; td.td_sampleinfo = new ExtraSample[td.td_extrasamples]; for (int i = 0; i < td.td_extrasamples; i++) td.td_sampleinfo[i] = (ExtraSample)va[i]; return true; } private static int checkInkNamesString(Tiff tif, int slen, string s) { bool failed = false; short i = tif.m_dir.td_samplesperpixel; if (slen > 0) { int endPos = slen; int pos = 0; for (; i > 0; i--) { for (; s[pos] != '\0'; pos++) { if (pos >= endPos) { failed = true; break; } } if (failed) break; pos++; // skip \0 } if (!failed) return pos; } Tiff.ErrorExt(tif, tif.m_clientdata, "TIFFSetField", "{0}: Invalid InkNames value; expecting {1} names, found {2}", tif.m_name, tif.m_dir.td_samplesperpixel, tif.m_dir.td_samplesperpixel - i); return 0; } private static void setNString(out string cpp, string cp, int n) { cpp = cp.Substring(0, n); } } #endregion #region TiffTagValue struct TiffTagValue { public TiffFieldInfo info; public int count; public byte[] value; } #endregion #region TiffYCbCrToRGB /// /// Convert color value from the YCbCr space to CIE XYZ. /// The colorspace conversion algorithm comes from the IJG v5a code; /// see below for more information on how it works. /// class TiffYCbCrToRGB { private const int clamptabOffset = 256; private const int SHIFT = 16; private const int ONE_HALF = 1 << (SHIFT - 1); /// /// range clamping table /// private byte[] clamptab; private int[] Cr_r_tab; private int[] Cb_b_tab; private int[] Cr_g_tab; private int[] Cb_g_tab; private int[] Y_tab; public TiffYCbCrToRGB() { clamptab = new byte[4 * 256]; Cr_r_tab = new int[256]; Cb_b_tab = new int[256]; Cr_g_tab = new int[256]; Cb_g_tab = new int[256]; Y_tab = new int[256]; } /* * Initialize the YCbCr->RGB conversion tables. The conversion * is done according to the 6.0 spec: * * R = Y + Cr * (2 - 2 * LumaRed) * B = Y + Cb * (2 - 2 * LumaBlue) * G = Y * - LumaBlue * Cb * (2 - 2 * LumaBlue) / LumaGreen * - LumaRed * Cr * (2 - 2 * LumaRed) / LumaGreen * * To avoid floating point arithmetic the fractional constants that * come out of the equations are represented as fixed point values * in the range 0...2^16. We also eliminate multiplications by * pre-calculating possible values indexed by Cb and Cr (this code * assumes conversion is being done for 8-bit samples). */ public void Init(float[] luma, float[] refBlackWhite) { Array.Clear(clamptab, 0, 256); /* v < 0 => 0 */ for (int i = 0; i < 256; i++) clamptab[clamptabOffset + i] = (byte)i; int start = clamptabOffset + 256; int stop = start + 2 * 256; for (int i = start; i < stop; i++) clamptab[i] = 255; /* v > 255 => 255 */ float LumaRed = luma[0]; float LumaGreen = luma[1]; float LumaBlue = luma[2]; float f1 = 2 - 2 * LumaRed; int D1 = fix(f1); float f2 = LumaRed * f1 / LumaGreen; int D2 = -fix(f2); float f3 = 2 - 2 * LumaBlue; int D3 = fix(f3); float f4 = LumaBlue * f3 / LumaGreen; int D4 = -fix(f4); /* * i is the actual input pixel value in the range 0..255 * Cb and Cr values are in the range -128..127 (actually * they are in a range defined by the ReferenceBlackWhite * tag) so there is some range shifting to do here when * constructing tables indexed by the raw pixel data. */ for (int i = 0, x = -128; i < 256; i++, x++) { int Cr = code2V(x, refBlackWhite[4] - 128.0F, refBlackWhite[5] - 128.0F, 127); int Cb = code2V(x, refBlackWhite[2] - 128.0F, refBlackWhite[3] - 128.0F, 127); Cr_r_tab[i] = (D1 * Cr + ONE_HALF) >> SHIFT; Cb_b_tab[i] = (D3 * Cb + ONE_HALF) >> SHIFT; Cr_g_tab[i] = D2 * Cr; Cb_g_tab[i] = D4 * Cb + ONE_HALF; Y_tab[i] = code2V(x + 128, refBlackWhite[0], refBlackWhite[1], 255); } } public void YCbCrtoRGB(int Y, int Cb, int Cr, out int r, out int g, out int b) { /* XXX: Only 8-bit YCbCr input supported for now */ Y = hiClamp(Y, 255); Cb = clamp(Cb, 0, 255); Cr = clamp(Cr, 0, 255); r = clamptab[clamptabOffset + Y_tab[Y] + Cr_r_tab[Cr]]; g = clamptab[clamptabOffset + Y_tab[Y] + ((Cb_g_tab[Cb] + Cr_g_tab[Cr]) >> SHIFT)]; b = clamptab[clamptabOffset + Y_tab[Y] + Cb_b_tab[Cb]]; } private static int fix(float x) { return (int)(x * (1L << SHIFT) + 0.5); } private static int code2V(int c, float RB, float RW, float CR) { return (int)(((c - (int)RB) * CR) / ((int)(RW - RB) != 0 ? (RW - RB) : 1.0f)); } private static int clamp(int f, int min, int max) { return (f < min ? min : f > max ? max : f); } private static int hiClamp(int f, int max) { return (f > max ? max : f); } } #endregion #endregion }