@ -8,6 +8,7 @@ using System.Threading;
using SixLabors.ImageSharp.Advanced ;
using SixLabors.ImageSharp.Advanced ;
using SixLabors.ImageSharp.Compression.Zlib ;
using SixLabors.ImageSharp.Compression.Zlib ;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression ;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants ;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants ;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers ;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers ;
using SixLabors.ImageSharp.Memory ;
using SixLabors.ImageSharp.Memory ;
@ -30,6 +31,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
? TiffConstants . ByteOrderLittleEndianShort
? TiffConstants . ByteOrderLittleEndianShort
: TiffConstants . ByteOrderBigEndianShort ;
: TiffConstants . ByteOrderBigEndianShort ;
private const int DefaultStripSize = 8 * 1 0 2 4 ;
/// <summary>
/// <summary>
/// Used for allocating memory during processing operations.
/// Used for allocating memory during processing operations.
/// </summary>
/// </summary>
@ -43,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// <summary>
/// The color depth, in number of bits per pixel.
/// The color depth, in number of bits per pixel.
/// </summary>
/// </summary>
private TiffBitsPerPixel ? bitsPerPixel ;
private TiffBitsPerPixel bitsPerPixel ;
/// <summary>
/// <summary>
/// The quantizer for creating color palette image.
/// The quantizer for creating color palette image.
@ -55,6 +58,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
/// </summary>
private readonly DeflateCompressionLevel compressionLevel ;
private readonly DeflateCompressionLevel compressionLevel ;
private readonly TiffEncoderPixelStorageMethod storageMode ;
private readonly int maxStripBytes ;
/// <summary>
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
/// </summary>
@ -68,6 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this . quantizer = options . Quantizer ? ? KnownQuantizers . Octree ;
this . quantizer = options . Quantizer ? ? KnownQuantizers . Octree ;
this . UseHorizontalPredictor = options . UseHorizontalPredictor ;
this . UseHorizontalPredictor = options . UseHorizontalPredictor ;
this . compressionLevel = options . CompressionLevel ;
this . compressionLevel = options . CompressionLevel ;
this . storageMode = options . PixelStorageMethod ;
this . maxStripBytes = options . MaxStripBytes ;
}
}
/// <summary>
/// <summary>
@ -105,26 +114,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Guard . NotNull ( stream , nameof ( stream ) ) ;
Guard . NotNull ( stream , nameof ( stream ) ) ;
this . configuration = image . GetConfiguration ( ) ;
this . configuration = image . GetConfiguration ( ) ;
ImageMetadata metadata = image . Metadata ;
TiffMetadata tiffMetadata = metadata . GetTiffMetadata ( ) ;
this . bitsPerPixel ? ? = tiffMetadata . BitsPerPixel ;
if ( this . Mode = = TiffEncodingMode . Default )
{
// Preserve input bits per pixel, if no mode was specified.
if ( this . bitsPerPixel = = TiffBitsPerPixel . Pixel8 )
{
this . Mode = TiffEncodingMode . Gray ;
}
else if ( this . bitsPerPixel = = TiffBitsPerPixel . Pixel1 )
{
this . Mode = TiffEncodingMode . BiColor ;
}
else
{
this . Mode = TiffEncodingMode . Rgb ;
}
}
this . SetMode ( image ) ;
this . SetPhotometricInterpretation ( ) ;
this . SetPhotometricInterpretation ( ) ;
using ( var writer = new TiffStreamWriter ( stream ) )
using ( var writer = new TiffStreamWriter ( stream ) )
@ -132,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
long firstIfdMarker = this . WriteHeader ( writer ) ;
long firstIfdMarker = this . WriteHeader ( writer ) ;
// TODO: multiframing is not support
// TODO: multiframing is not support
long nextIfdMarker = this . WriteImage ( writer , image , firstIfdMarker ) ;
this . WriteImage ( writer , image , firstIfdMarker ) ;
}
}
}
}
@ -159,8 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
private void WriteImage < TPixel > ( TiffStreamWriter writer , Image < TPixel > image , long ifdOffset )
public long WriteImage < TPixel > ( TiffStreamWriter writer , Image < TPixel > image , long ifdOffset )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
var entriesCollector = new TiffEncoderEntriesCollector ( ) ;
var entriesCollector = new TiffEncoderEntriesCollector ( ) ;
@ -168,18 +158,43 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
// Write the image bytes to the steam.
// Write the image bytes to the steam.
var imageDataStart = ( uint ) writer . Position ;
var imageDataStart = ( uint ) writer . Position ;
TiffBaseColorWriter colorWriter = TiffColorWriterFactory . Create ( this . Mode , writer , this . memoryAllocator , this . configuration , entriesCollector ) ;
using TiffBaseCompressor compressor = TiffCompressorFactory . Create (
this . CompressionType ,
writer . BaseStream ,
this . memoryAllocator ,
image . Width ,
( int ) this . bitsPerPixel ,
this . compressionLevel ,
this . UseHorizontalPredictor ? TiffPredictor . Horizontal : TiffPredictor . None ) ;
using TiffBaseColorWriter < TPixel > colorWriter = TiffColorWriterFactory . Create ( this . Mode , image . Frames . RootFrame , this . quantizer , this . memoryAllocator , this . configuration , entriesCollector ) ;
int rowsPerStrip = this . CalcRowsPerStrip ( image . Frames . RootFrame , colorWriter . BytesPerRow ) ;
int imageDataBytes = colorWriter . Write ( image , this . quantizer , this . CompressionType , this . compressionLevel , this . UseHorizontalPredictor ) ;
colorWriter . Write ( compressor , rowsPerStrip ) ;
this . AddStripTags ( image , entriesCollector , imageDataStart , imageDataBytes ) ;
entriesCollector . ProcessImageFormat ( this ) ;
entriesCollector . ProcessImageFormat ( this ) ;
entriesCollector . ProcessGeneral ( image ) ;
entriesCollector . ProcessGeneral ( image ) ;
writer . WriteMarker ( ifdOffset , ( uint ) writer . Position ) ;
writer . WriteMarker ( ifdOffset , ( uint ) writer . Position ) ;
long nextIfdMarker = this . WriteIfd ( writer , entriesCollector . Entries ) ;
long nextIfdMarker = this . WriteIfd ( writer , entriesCollector . Entries ) ;
}
private int CalcRowsPerStrip ( ImageFrame image , int bytesPerRow )
{
switch ( this . storageMode )
{
default :
case TiffEncoderPixelStorageMethod . Auto :
case TiffEncoderPixelStorageMethod . MultiStrip :
int sz = this . maxStripBytes > 0 ? this . maxStripBytes : DefaultStripSize ;
int height = sz / bytesPerRow ;
return height > 0 ? ( height < image . Height ? height : image . Height ) : 1 ;
return nextIfdMarker + imageDataBytes ;
case TiffEncoderPixelStorageMethod . SingleStrip :
return image . Height ;
}
}
}
/// <summary>
/// <summary>
@ -188,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="entries">The IFD entries to write to the file.</param>
/// <param name="entries">The IFD entries to write to the file.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteIfd ( TiffStreamWriter writer , List < IExifValue > entries )
private long WriteIfd ( TiffStreamWriter writer , List < IExifValue > entries )
{
{
if ( entries . Count = = 0 )
if ( entries . Count = = 0 )
{
{
@ -239,37 +254,51 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return nextIfdMarker ;
return nextIfdMarker ;
}
}
/// <summary>
private void SetMode ( Image image )
/// Adds image format information to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}" /> to encode from.</param>
/// <param name="entriesCollector">The entries collector.</param>
/// <param name="imageDataStartOffset">The start of the image data in the stream.</param>
/// <param name="imageDataBytes">The image data in bytes to write.</param>
public void AddStripTags < TPixel > ( Image < TPixel > image , TiffEncoderEntriesCollector entriesCollector , uint imageDataStartOffset , int imageDataBytes )
where TPixel : unmanaged , IPixel < TPixel >
{
{
var stripOffsets = new ExifLongArray ( ExifTagValue . StripOffsets )
if ( this . CompressionType = = TiffEncoderCompression . CcittGroup3Fax | | this . CompressionType = = TiffEncoderCompression . ModifiedHuffman )
{
{
// TODO: we only write one image strip for the start.
this . Mode = TiffEncodingMode . BiColor ;
Value = new [ ] { imageDataStartOffset }
this . bitsPerPixel = TiffBitsPerPixel . Pixel1 ;
} ;
return ;
}
var rowsPerStrip = new ExifLong ( ExifTagValue . RowsPerStrip )
if ( this . Mode = = TiffEncodingMode . Default )
{
{
// All rows in one strip.
// Preserve input bits per pixel, if no mode was specified.
Value = ( uint ) image . Height
TiffMetadata tiffMetadata = image . Metadata . GetTiffMetadata ( ) ;
} ;
switch ( tiffMetadata . BitsPerPixel )
{
case TiffBitsPerPixel . Pixel1 :
this . Mode = TiffEncodingMode . BiColor ;
break ;
case TiffBitsPerPixel . Pixel8 :
// todo: can gray or palette
this . Mode = TiffEncodingMode . Gray ;
break ;
default :
this . Mode = TiffEncodingMode . Rgb ;
break ;
}
}
var stripByteCounts = new ExifLongArray ( ExifTagValue . StripByteCounts )
switch ( this . Mode )
{
{
Value = new [ ] { ( uint ) imageDataBytes }
case TiffEncodingMode . BiColor :
} ;
this . bitsPerPixel = TiffBitsPerPixel . Pixel1 ;
break ;
entriesCollector . Add ( stripOffsets ) ;
case TiffEncodingMode . ColorPalette :
entriesCollector . Add ( rowsPerStrip ) ;
case TiffEncodingMode . Gray :
entriesCollector . Add ( stripByteCounts ) ;
this . bitsPerPixel = TiffBitsPerPixel . Pixel8 ;
break ;
case TiffEncodingMode . Rgb :
this . bitsPerPixel = TiffBitsPerPixel . Pixel24 ;
break ;
default :
this . Mode = TiffEncodingMode . Rgb ;
this . bitsPerPixel = TiffBitsPerPixel . Pixel24 ;
break ;
}
}
}
private void SetPhotometricInterpretation ( )
private void SetPhotometricInterpretation ( )