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()); }
/// <summary> /// Encodes and writes a scanline of data to an open TIFF file/stream. /// </summary> /// <param name="buffer">The buffer with image data to be encoded and written.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which /// to begin reading bytes.</param> /// <param name="row">The zero-based index of scanline (row) to place encoded data at.</param> /// <param name="plane">The zero-based index of the sample plane.</param> /// <returns> /// <c>true</c> if image data were encoded and written successfully; otherwise, <c>false</c> /// </returns> /// <remarks> /// <para> /// <b>WriteScanline</b> encodes and writes to a file at the specified /// <paramref name="row"/> and specified sample plane <paramref name="plane"/>. /// Applications may use <see cref="WriteScanline(byte[], int)"/> or specify 0 for /// <paramref name="plane"/> parameter if image data in a file/stream is contiguous (i.e /// not organized in separate planes, /// <see cref="TiffTag.PlanarConfig"/> = <see cref="PlanarConfig"/>.Contig). /// </para><para> /// 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 <see cref="TiffTag.ImageLength"/> /// tag is automatically increased to include the scanline (except for /// <see cref="TiffTag.PlanarConfig"/> = <see cref="PlanarConfig"/>.Contig, where the /// <see cref="TiffTag.ImageLength"/> tag cannot be changed once the first data are /// written). If the <see cref="TiffTag.ImageLength"/> is increased, the values of /// <see cref="TiffTag.StripOffsets"/> and <see cref="TiffTag.StripByteCounts"/> tags are /// similarly enlarged to reflect data written past the previous end of image. /// </para><para> /// 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 <see cref="TiffTag.BitsPerSample"/> 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. /// </para><para> /// Once data are written to a file/stream for the current directory, the values of /// certain tags may not be altered; see /// <a href = "54cbd23d-dc55-44b9-921f-3a06efc2f6ce.htm">"Well-known tags and their /// value(s) data types"</a> for more information. /// </para><para> /// It is not possible to write scanlines to a file/stream that uses a tiled organization. /// The <see cref="IsTiled"/> can be used to determine if the file/stream is organized as /// tiles or strips. /// </para></remarks> 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; }
/// <summary> /// Unlinks the specified directory from the directory chain. /// </summary> /// <param name="number">The zero-based number of the directory to unlink.</param> /// <returns><c>true</c> if directory was unlinked successfully; otherwise, <c>false</c>.</returns> /// <remarks><b>UnlinkDirectory</b> does not removes directory bytes from the file/stream. /// It only makes them unused.</remarks> 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; }
/// <summary> /// Verifies that file/stream is writable and that the directory information is /// setup properly. /// </summary> /// <param name="tiles">If set to <c>true</c> then ability to write tiles will be verified; /// otherwise, ability to write strips will be verified.</param> /// <param name="method">The name of the calling method.</param> /// <returns><c>true</c> if file/stream is writeable and the directory information is /// setup properly; otherwise, <c>false</c></returns> 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; }
/// <summary> /// Sets up the data buffer used to write raw (encoded) data to a file. /// </summary> /// <param name="buffer">The data buffer.</param> /// <param name="size">The buffer size.</param> /// <remarks> /// <para> /// 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. /// </para> /// <para> /// If the <paramref name="size"/> 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 /// <paramref name="buffer"/> is <c>null</c>, then a buffer of appropriate size is /// allocated by the library. /// </para> /// </remarks> 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; }
/// <summary> /// Sets up the data buffer used to read raw (encoded) data from a file. /// </summary> /// <param name="buffer">The data buffer.</param> /// <param name="size">The buffer size.</param> /// <remarks> /// <para> /// 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. /// </para> /// <para> /// If the <paramref name="buffer"/> is <c>null</c>, 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. /// </para> /// </remarks> 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; } }
/// <summary> /// Reads the contents of the next TIFF directory in an open TIFF file/stream and makes /// it the current directory. /// </summary> /// <returns><c>true</c> if directory was successfully read; otherwise, <c>false</c> if an /// error was encountered, or if there are no more directories to be read.</returns> /// <remarks><para>Directories are read sequentially.</para> /// <para>Applications only need to call <see cref="ReadDirectory"/> to read multiple /// subfiles in a single TIFF file/stream - the first directory in a file/stream is /// automatically read when <see cref="Open"/> or <see cref="ClientOpen"/> is called. /// </para><para> /// 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 <see cref="NumberOfStrips"/> /// or the number of tiles returned by <see cref="NumberOfTiles"/>.</para> /// </remarks> 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; }
/// <summary> /// Flushes any pending image data for the specified file to be written out. /// </summary> /// <returns><c>true</c> if succeeded; otherwise, <c>false</c></returns> /// <remarks><see cref="FlushData"/> 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. /// </remarks> /// <seealso cref="Flush"/> 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(); }
/* * 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; }
/// <summary> /// Link the current directory into the directory chain for the file. /// </summary> 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; }
/// <summary> /// Writes the contents of the current directory to the specified file. /// </summary> /// <remarks>This routine doesn't handle overwriting a directory with /// auxiliary storage that's been changed.</remarks> 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 <n> 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; }
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(); }
/// <summary> /// Encodes and writes a tile of data to an open TIFF file/stream. /// </summary> /// <param name="tile">The zero-based index of the tile to write.</param> /// <param name="buffer">The buffer with image data to be encoded and written.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which /// to begin reading bytes to be encoded and written.</param> /// <param name="count">The maximum number of tile bytes to be read from /// <paramref name="buffer"/>.</param> /// <returns>The number of encoded and written bytes or <c>-1</c> if an error occurred.</returns> /// <remarks> /// <para> /// <b>WriteEncodedTile</b> encodes <paramref name="count"/> bytes of raw data from /// <paramref name="buffer"/> and append the result to the end of the specified tile. Note /// that the value of <paramref name="tile"/> is a "raw tile number". That is, the caller /// must take into account whether or not the data are organized in separate planes /// (<see cref="TiffTag.PlanarConfig"/> = <see cref="PlanarConfig"/>.Separate). /// <see cref="ComputeTile"/> automatically does this when converting an (x, y, z, plane) /// coordinate quadruple to a tile number. /// </para><para> /// 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. /// </para><para> /// A correct value for the <see cref="TiffTag.ImageLength"/> tag must be setup before /// writing; <b>WriteEncodedTile</b> does not support automatically growing the image on /// each write (as <see cref="O:BitMiracle.LibTiff.Classic.Tiff.WriteScanline"/> does). /// </para><para> /// 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 <see cref="TiffTag.BitsPerSample"/> tag greater /// than 8. /// </para></remarks> 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; }
/// <summary> /// Encodes and writes a strip of data to an open TIFF file/stream. /// </summary> /// <param name="strip">The zero-based index of the strip to write.</param> /// <param name="buffer">The buffer with image data to be encoded and written.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which /// to begin reading bytes to be encoded and written.</param> /// <param name="count">The maximum number of strip bytes to be read from /// <paramref name="buffer"/>.</param> /// <returns>The number of encoded and written bytes or <c>-1</c> if an error occurred.</returns> /// <remarks> /// <para> /// <b>WriteEncodedStrip</b> encodes <paramref name="count"/> bytes of raw data from /// <paramref name="buffer"/> and append the result to the specified strip; replacing any /// previously written data. Note that the value of <paramref name="strip"/> is a "raw /// strip number". That is, the caller must take into account whether or not the data are /// organized in separate planes /// (<see cref="TiffTag.PlanarConfig"/> = <see cref="PlanarConfig"/>.Separate). /// <see cref="ComputeStrip"/> automatically does this when converting an (row, plane) to /// a strip index. /// </para><para> /// If there is no space for the strip, the value of <see cref="TiffTag.ImageLength"/> /// tag is automatically increased to include the strip (except for /// <see cref="TiffTag.PlanarConfig"/> = <see cref="PlanarConfig"/>.Separate, where the /// <see cref="TiffTag.ImageLength"/> tag cannot be changed once the first data are /// written). If the <see cref="TiffTag.ImageLength"/> is increased, the values of /// <see cref="TiffTag.StripOffsets"/> and <see cref="TiffTag.StripByteCounts"/> tags are /// similarly enlarged to reflect data written past the previous end of image. /// </para><para> /// 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 <see cref="TiffTag.BitsPerSample"/> tag greater /// than 8. /// </para></remarks> 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; }
private bool isFillOrder(FillOrder o) { TiffFlags order = (TiffFlags)o; return((m_flags & order) == order); }