mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
169 changed files with 12673 additions and 89 deletions
@ -0,0 +1,56 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed using Deflate compression.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
|
|||
/// </remarks>
|
|||
internal class DeflateTiffCompression : TiffBaseCompression |
|||
{ |
|||
public DeflateTiffCompression(MemoryAllocator allocator) |
|||
: base(allocator) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer) |
|||
{ |
|||
// Read the 'zlib' header information
|
|||
int cmf = stream.ReadByte(); |
|||
int flag = stream.ReadByte(); |
|||
|
|||
if ((cmf & 0x0f) != 8) |
|||
{ |
|||
throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); |
|||
} |
|||
|
|||
// If the 'fdict' flag is set then we should skip the next four bytes
|
|||
bool fdict = (flag & 32) != 0; |
|||
|
|||
if (fdict) |
|||
{ |
|||
stream.ReadByte(); |
|||
stream.ReadByte(); |
|||
stream.ReadByte(); |
|||
stream.ReadByte(); |
|||
} |
|||
|
|||
// The subsequent data is the Deflate compressed data (except for the last four bytes of checksum)
|
|||
int headerLength = fdict ? 10 : 6; |
|||
var subStream = new SubStream(stream, byteCount - headerLength); |
|||
using (var deflateStream = new DeflateStream(subStream, CompressionMode.Decompress, true)) |
|||
{ |
|||
deflateStream.ReadFull(buffer); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed using LZW compression.
|
|||
/// </summary>
|
|||
internal class LzwTiffCompression : TiffBaseCompression |
|||
{ |
|||
public LzwTiffCompression(MemoryAllocator allocator) |
|||
: base(allocator) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer) |
|||
{ |
|||
var subStream = new SubStream(stream, byteCount); |
|||
using (var decoder = new TiffLzwDecoder(subStream)) |
|||
{ |
|||
decoder.DecodePixels(buffer.Length, 8, buffer); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is not compressed.
|
|||
/// </summary>
|
|||
internal class NoneTiffCompression : TiffBaseCompression |
|||
{ |
|||
public NoneTiffCompression(MemoryAllocator allocator) |
|||
: base(allocator) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer) |
|||
{ |
|||
stream.ReadFull(buffer, byteCount); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
|
|||
/// </summary>
|
|||
internal class PackBitsTiffCompression : TiffBaseCompression |
|||
{ |
|||
public PackBitsTiffCompression(MemoryAllocator allocator) |
|||
: base(allocator) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer) |
|||
{ |
|||
using IMemoryOwner<byte> compressedDataMemory = this.Allocator.Allocate<byte>(byteCount); |
|||
|
|||
Span<byte> compressedData = compressedDataMemory.GetSpan(); |
|||
|
|||
stream.ReadFull(compressedData, byteCount); |
|||
int compressedOffset = 0; |
|||
int decompressedOffset = 0; |
|||
|
|||
while (compressedOffset < byteCount) |
|||
{ |
|||
byte headerByte = compressedData[compressedOffset]; |
|||
|
|||
if (headerByte <= (byte)127) |
|||
{ |
|||
int literalOffset = compressedOffset + 1; |
|||
int literalLength = compressedData[compressedOffset] + 1; |
|||
|
|||
compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset)); |
|||
|
|||
compressedOffset += literalLength + 1; |
|||
decompressedOffset += literalLength; |
|||
} |
|||
else if (headerByte == (byte)0x80) |
|||
{ |
|||
compressedOffset += 1; |
|||
} |
|||
else |
|||
{ |
|||
byte repeatData = compressedData[compressedOffset + 1]; |
|||
int repeatLength = 257 - headerByte; |
|||
|
|||
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); |
|||
|
|||
compressedOffset += 2; |
|||
decompressedOffset += repeatLength; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length) |
|||
{ |
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
destinationArray[i + destinationIndex] = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Base tiff decompressor class.
|
|||
/// </summary>
|
|||
internal abstract class TiffBaseCompression |
|||
{ |
|||
private readonly MemoryAllocator allocator; |
|||
|
|||
public TiffBaseCompression(MemoryAllocator allocator) => this.allocator = allocator; |
|||
|
|||
protected MemoryAllocator Allocator => this.allocator; |
|||
|
|||
/// <summary>
|
|||
/// Decompresses image data into the supplied buffer.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
|
|||
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
|
|||
/// <param name="buffer">The output buffer for uncompressed data.</param>
|
|||
public abstract void Decompress(Stream stream, int byteCount, Span<byte> buffer); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal static class TiffCompressionFactory |
|||
{ |
|||
public static TiffBaseCompression Create(TiffCompressionType compressionType, MemoryAllocator allocator) |
|||
{ |
|||
switch (compressionType) |
|||
{ |
|||
case TiffCompressionType.None: |
|||
return new NoneTiffCompression(allocator); |
|||
case TiffCompressionType.PackBits: |
|||
return new PackBitsTiffCompression(allocator); |
|||
case TiffCompressionType.Deflate: |
|||
return new DeflateTiffCompression(allocator); |
|||
case TiffCompressionType.Lzw: |
|||
return new LzwTiffCompression(allocator); |
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of the various TIFF compression types.
|
|||
/// </summary>
|
|||
internal enum TiffCompressionType |
|||
{ |
|||
/// <summary>
|
|||
/// Image data is stored uncompressed in the TIFF file.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Image data is compressed using PackBits compression.
|
|||
/// </summary>
|
|||
PackBits = 1, |
|||
|
|||
/// <summary>
|
|||
/// Image data is compressed using Deflate compression.
|
|||
/// </summary>
|
|||
Deflate = 2, |
|||
|
|||
/// <summary>
|
|||
/// Image data is compressed using LZW compression.
|
|||
/// </summary>
|
|||
Lzw = 3, |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The tiff data stream byte order enum.
|
|||
/// </summary>
|
|||
public enum TiffByteOrder |
|||
{ |
|||
/// <summary>
|
|||
/// The big-endian byte order (Motorola).
|
|||
/// </summary>
|
|||
BigEndian, |
|||
|
|||
/// <summary>
|
|||
/// The little-endian byte order (Intel).
|
|||
/// </summary>
|
|||
LittleEndian |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the compression formats defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
public enum TiffCompression : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// No compression.
|
|||
/// </summary>
|
|||
None = 1, |
|||
|
|||
/// <summary>
|
|||
/// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.
|
|||
/// </summary>
|
|||
Ccitt1D = 2, |
|||
|
|||
/// <summary>
|
|||
/// PackBits compression
|
|||
/// </summary>
|
|||
PackBits = 32773, |
|||
|
|||
/// <summary>
|
|||
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
CcittGroup3Fax = 3, |
|||
|
|||
/// <summary>
|
|||
/// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
CcittGroup4Fax = 4, |
|||
|
|||
/// <summary>
|
|||
/// LZW compression (see Section 13 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
Lzw = 5, |
|||
|
|||
/// <summary>
|
|||
/// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
OldJpeg = 6, |
|||
|
|||
/// <summary>
|
|||
/// JPEG compression (see TIFF Specification, supplement 2).
|
|||
/// </summary>
|
|||
Jpeg = 7, |
|||
|
|||
/// <summary>
|
|||
/// Deflate compression, using zlib data format (see TIFF Specification, supplement 2).
|
|||
/// </summary>
|
|||
Deflate = 8, |
|||
|
|||
/// <summary>
|
|||
/// Deflate compression - old.
|
|||
/// </summary>
|
|||
OldDeflate = 32946, |
|||
|
|||
/// <summary>
|
|||
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
|
|||
/// </summary>
|
|||
ItuTRecT82 = 9, |
|||
|
|||
/// <summary>
|
|||
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
|
|||
/// </summary>
|
|||
ItuTRecT43 = 10 |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Defines constants defined in the TIFF specification.
|
|||
/// </summary>
|
|||
internal static class TiffConstants |
|||
{ |
|||
/// <summary>
|
|||
/// Byte order markers for indicating little endian encoding.
|
|||
/// </summary>
|
|||
public const byte ByteOrderLittleEndian = 0x49; |
|||
|
|||
/// <summary>
|
|||
/// Byte order markers for indicating big endian encoding.
|
|||
/// </summary>
|
|||
public const byte ByteOrderBigEndian = 0x4D; |
|||
|
|||
/// <summary>
|
|||
/// Byte order markers for indicating little endian encoding.
|
|||
/// </summary>
|
|||
public const ushort ByteOrderLittleEndianShort = 0x4949; |
|||
|
|||
/// <summary>
|
|||
/// Byte order markers for indicating big endian encoding.
|
|||
/// </summary>
|
|||
public const ushort ByteOrderBigEndianShort = 0x4D4D; |
|||
|
|||
/// <summary>
|
|||
/// Magic number used within the image file header to identify a TIFF format file.
|
|||
/// </summary>
|
|||
public const ushort HeaderMagicNumber = 42; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the TIFF file header.
|
|||
/// </summary>
|
|||
public const int SizeOfTiffHeader = 8; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of each individual TIFF IFD entry
|
|||
/// </summary>
|
|||
public const int SizeOfIfdEntry = 12; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the Short and SShort data types
|
|||
/// </summary>
|
|||
public const int SizeOfShort = 2; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the Long and SLong data types
|
|||
/// </summary>
|
|||
public const int SizeOfLong = 4; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the Rational and SRational data types
|
|||
/// </summary>
|
|||
public const int SizeOfRational = 8; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the Float data type
|
|||
/// </summary>
|
|||
public const int SizeOfFloat = 4; |
|||
|
|||
/// <summary>
|
|||
/// Size (in bytes) of the Double data type
|
|||
/// </summary>
|
|||
public const int SizeOfDouble = 8; |
|||
|
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a tiff.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a tiff.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" }; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the possible uses of extra components in TIFF format files.
|
|||
/// </summary>
|
|||
internal enum TiffExtraSamples |
|||
{ |
|||
/// <summary>
|
|||
/// Unspecified data.
|
|||
/// </summary>
|
|||
Unspecified = 0, |
|||
|
|||
/// <summary>
|
|||
/// Associated alpha data (with pre-multiplied color).
|
|||
/// </summary>
|
|||
AssociatedAlpha = 1, |
|||
|
|||
/// <summary>
|
|||
/// Unassociated alpha data.
|
|||
/// </summary>
|
|||
UnassociatedAlpha = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the fill orders defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
internal enum TiffFillOrder : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// Pixels with lower column values are stored in the higher-order bits of the byte.
|
|||
/// </summary>
|
|||
MostSignificantBitFirst = 1, |
|||
|
|||
/// <summary>
|
|||
/// Pixels with lower column values are stored in the lower-order bits of the byte.
|
|||
/// </summary>
|
|||
LeastSignificantBitFirst = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the sub-file types defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum TiffNewSubfileType : uint |
|||
{ |
|||
/// <summary>
|
|||
/// A full-resolution image.
|
|||
/// </summary>
|
|||
FullImage = 0x0000, |
|||
|
|||
/// <summary>
|
|||
/// Reduced-resolution version of another image in this TIFF file.
|
|||
/// </summary>
|
|||
Preview = 0x0001, |
|||
|
|||
/// <summary>
|
|||
/// A single page of a multi-page image.
|
|||
/// </summary>
|
|||
SinglePage = 0x0002, |
|||
|
|||
/// <summary>
|
|||
/// A transparency mask for another image in this TIFF file.
|
|||
/// </summary>
|
|||
TransparencyMask = 0x0004, |
|||
|
|||
/// <summary>
|
|||
/// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification).
|
|||
/// </summary>
|
|||
AlternativePreview = 0x10000, |
|||
|
|||
/// <summary>
|
|||
/// Mixed raster content (see RFC2301).
|
|||
/// </summary>
|
|||
MixedRasterContent = 0x0008 |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the image orientations defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
internal enum TiffOrientation |
|||
{ |
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual top and left-hand side of the image respectively.
|
|||
/// </summary>
|
|||
TopLeft = 1, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual top and right-hand side of the image respectively.
|
|||
/// </summary>
|
|||
TopRight = 2, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively.
|
|||
/// </summary>
|
|||
BottomRight = 3, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively.
|
|||
/// </summary>
|
|||
BottomLeft = 4, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual left-hand side and top of the image respectively.
|
|||
/// </summary>
|
|||
LeftTop = 5, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual right-hand side and top of the image respectively.
|
|||
/// </summary>
|
|||
RightTop = 6, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively.
|
|||
/// </summary>
|
|||
RightBottom = 7, |
|||
|
|||
/// <summary>
|
|||
/// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively.
|
|||
/// </summary>
|
|||
LeftBottom = 8 |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
public enum TiffPhotometricInterpretation : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
|
|||
/// </summary>
|
|||
WhiteIsZero = 0, |
|||
|
|||
/// <summary>
|
|||
/// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white.
|
|||
/// </summary>
|
|||
BlackIsZero = 1, |
|||
|
|||
/// <summary>
|
|||
/// RGB
|
|||
/// </summary>
|
|||
Rgb = 2, |
|||
|
|||
/// <summary>
|
|||
/// Palette Color
|
|||
/// </summary>
|
|||
PaletteColor = 3, |
|||
|
|||
/// <summary>
|
|||
/// A transparency mask
|
|||
/// </summary>
|
|||
TransparencyMask = 4, |
|||
|
|||
/// <summary>
|
|||
/// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
Separated = 5, |
|||
|
|||
/// <summary>
|
|||
/// YCbCr (see Section 21 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
YCbCr = 6, |
|||
|
|||
/// <summary>
|
|||
/// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
|
|||
/// </summary>
|
|||
CieLab = 8, |
|||
|
|||
/// <summary>
|
|||
/// ICC L*a*b* (see TIFF Specification, supplement 1).
|
|||
/// </summary>
|
|||
IccLab = 9, |
|||
|
|||
/// <summary>
|
|||
/// ITU L*a*b* (see RFC2301).
|
|||
/// </summary>
|
|||
ItuLab = 10, |
|||
|
|||
/// <summary>
|
|||
/// Color Filter Array (see the DNG specification).
|
|||
/// </summary>
|
|||
ColorFilterArray = 32803, |
|||
|
|||
/// <summary>
|
|||
/// Linear Raw (see the DNG specification).
|
|||
/// </summary>
|
|||
LinearRaw = 34892 |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing how the components of each pixel are stored the Tiff file-format.
|
|||
/// </summary>
|
|||
public enum TiffPlanarConfiguration : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// Chunky format.
|
|||
/// </summary>
|
|||
Chunky = 1, |
|||
|
|||
/// <summary>
|
|||
/// Planar format.
|
|||
/// </summary>
|
|||
Planar = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// A mathematical operator that is applied to the image data before an encoding scheme is applied.
|
|||
/// </summary>
|
|||
public enum TiffPredictor : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// No prediction scheme used before coding
|
|||
/// </summary>
|
|||
None = 1, |
|||
|
|||
/// <summary>
|
|||
/// Horizontal differencing.
|
|||
/// </summary>
|
|||
Horizontal = 2, |
|||
|
|||
/// <summary>
|
|||
/// Floating point horizontal differencing.
|
|||
/// </summary>
|
|||
FloatingPoint = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the resolution units defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
public enum TiffResolutionUnit : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// No absolute unit of measurement.
|
|||
/// </summary>
|
|||
None = 1, |
|||
|
|||
/// <summary>
|
|||
/// Inch.
|
|||
/// </summary>
|
|||
Inch = 2, |
|||
|
|||
/// <summary>
|
|||
/// Centimeter.
|
|||
/// </summary>
|
|||
Centimeter = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Specifies how to interpret each data sample in a pixel.
|
|||
/// </summary>
|
|||
public enum TiffSampleFormat : ushort |
|||
{ |
|||
/// <summary>
|
|||
/// Unsigned integer data. Default value.
|
|||
/// </summary>
|
|||
UnsignedInteger = 1, |
|||
|
|||
/// <summary>
|
|||
/// Signed integer data.
|
|||
/// </summary>
|
|||
SignedInteger = 2, |
|||
|
|||
/// <summary>
|
|||
/// IEEE floating point data.
|
|||
/// </summary>
|
|||
Float = 3, |
|||
|
|||
/// <summary>
|
|||
/// Undefined data format.
|
|||
/// </summary>
|
|||
Undefined = 4, |
|||
|
|||
/// <summary>
|
|||
/// The complex int.
|
|||
/// </summary>
|
|||
ComplexInt = 5, |
|||
|
|||
/// <summary>
|
|||
/// The complex float.
|
|||
/// </summary>
|
|||
ComplexFloat = 6 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the sub-file types defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
public enum TiffSubfileType : uint |
|||
{ |
|||
/// <summary>
|
|||
/// Full-resolution image data.
|
|||
/// </summary>
|
|||
FullImage = 1, |
|||
|
|||
/// <summary>
|
|||
/// Reduced-resolution image data.
|
|||
/// </summary>
|
|||
Preview = 2, |
|||
|
|||
/// <summary>
|
|||
/// A single page of a multi-page image.
|
|||
/// </summary>
|
|||
SinglePage = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the threshholding applied to image data defined by the Tiff file-format.
|
|||
/// </summary>
|
|||
internal enum TiffThreshholding |
|||
{ |
|||
/// <summary>
|
|||
/// No dithering or halftoning.
|
|||
/// </summary>
|
|||
None = 1, |
|||
|
|||
/// <summary>
|
|||
/// An ordered dither or halftone technique.
|
|||
/// </summary>
|
|||
Ordered = 2, |
|||
|
|||
/// <summary>
|
|||
/// A randomized process such as error diffusion.
|
|||
/// </summary>
|
|||
Random = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
|
|||
/// </summary>
|
|||
public interface ITiffDecoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
bool IgnoreMetadata { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
|
|||
/// </summary>
|
|||
public interface ITiffEncoderOptions |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The TIFF IFD reader class.
|
|||
/// </summary>
|
|||
internal class DirectoryReader |
|||
{ |
|||
private readonly TiffStream stream; |
|||
|
|||
private readonly EntryReader tagReader; |
|||
|
|||
private uint nextIfdOffset; |
|||
|
|||
public DirectoryReader(TiffStream stream) |
|||
{ |
|||
this.stream = stream; |
|||
this.tagReader = new EntryReader(stream); |
|||
} |
|||
|
|||
public IEnumerable<IExifValue[]> Read() |
|||
{ |
|||
if (this.ReadHeader()) |
|||
{ |
|||
return this.ReadIfds(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private bool ReadHeader() |
|||
{ |
|||
ushort magic = this.stream.ReadUInt16(); |
|||
if (magic != TiffConstants.HeaderMagicNumber) |
|||
{ |
|||
throw new ImageFormatException("Invalid TIFF header magic number: " + magic); |
|||
} |
|||
|
|||
uint firstIfdOffset = this.stream.ReadUInt32(); |
|||
if (firstIfdOffset == 0) |
|||
{ |
|||
throw new ImageFormatException("Invalid TIFF file header."); |
|||
} |
|||
|
|||
this.nextIfdOffset = firstIfdOffset; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private IEnumerable<IExifValue[]> ReadIfds() |
|||
{ |
|||
var list = new List<IExifValue[]>(); |
|||
while (this.nextIfdOffset != 0) |
|||
{ |
|||
this.stream.Seek(this.nextIfdOffset); |
|||
IExifValue[] ifd = this.ReadIfd(); |
|||
list.Add(ifd); |
|||
} |
|||
|
|||
this.tagReader.LoadExtendedData(); |
|||
|
|||
return list; |
|||
} |
|||
|
|||
private IExifValue[] ReadIfd() |
|||
{ |
|||
long pos = this.stream.Position; |
|||
|
|||
ushort entryCount = this.stream.ReadUInt16(); |
|||
var entries = new List<IExifValue>(entryCount); |
|||
for (int i = 0; i < entryCount; i++) |
|||
{ |
|||
IExifValue tag = this.tagReader.ReadNext(); |
|||
if (tag != null) |
|||
{ |
|||
entries.Add(tag); |
|||
} |
|||
} |
|||
|
|||
this.nextIfdOffset = this.stream.ReadUInt32(); |
|||
|
|||
int ifdSize = 2 + (entryCount * TiffConstants.SizeOfIfdEntry) + 4; |
|||
int readedBytes = (int)(this.stream.Position - pos); |
|||
int leftBytes = ifdSize - readedBytes; |
|||
if (leftBytes > 0) |
|||
{ |
|||
this.stream.Skip(leftBytes); |
|||
} |
|||
else if (leftBytes < 0) |
|||
{ |
|||
throw new InvalidDataException("Out of range of IFD structure."); |
|||
} |
|||
|
|||
return entries.ToArray(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,311 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal class EntryReader |
|||
{ |
|||
private readonly TiffStream stream; |
|||
|
|||
private readonly SortedDictionary<uint, Action> extValueLoaders = new SortedDictionary<uint, Action>(); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EntryReader" /> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
public EntryReader(TiffStream stream) |
|||
{ |
|||
this.stream = stream; |
|||
} |
|||
|
|||
public IExifValue ReadNext() |
|||
{ |
|||
var tagId = (ExifTagValue)this.stream.ReadUInt16(); |
|||
var dataType = (ExifDataType)EnumUtils.Parse(this.stream.ReadUInt16(), ExifDataType.Unknown); |
|||
uint count = this.stream.ReadUInt32(); |
|||
|
|||
ExifDataType rawDataType = dataType; |
|||
dataType = LongOrShortFiltering(tagId, dataType); |
|||
bool isArray = GetIsArray(tagId, count); |
|||
|
|||
ExifValue entry = ExifValues.Create(tagId, dataType, isArray); |
|||
if (rawDataType == ExifDataType.Undefined && count == 0) |
|||
{ |
|||
// todo: investgate
|
|||
count = 4; |
|||
} |
|||
|
|||
if (this.ReadValueOrOffset(entry, rawDataType, count)) |
|||
{ |
|||
return entry; |
|||
} |
|||
|
|||
return null; // new UnkownExifTag(tagId);
|
|||
} |
|||
|
|||
public void LoadExtendedData() |
|||
{ |
|||
foreach (Action action in this.extValueLoaders.Values) |
|||
{ |
|||
action(); |
|||
} |
|||
} |
|||
|
|||
private static bool HasExtData(ExifValue tag, uint count) => ExifDataTypes.GetSize(tag.DataType) * count > 4; |
|||
|
|||
private static bool SetValue(ExifValue entry, object value) |
|||
{ |
|||
if (!entry.IsArray && entry.DataType != ExifDataType.Ascii) |
|||
{ |
|||
DebugGuard.IsTrue(((Array)value).Length == 1, "Expected a length is 1"); |
|||
|
|||
var single = ((Array)value).GetValue(0); |
|||
return entry.TrySetValue(single); |
|||
} |
|||
|
|||
return entry.TrySetValue(value); |
|||
} |
|||
|
|||
private static ExifDataType LongOrShortFiltering(ExifTagValue tagId, ExifDataType dataType) |
|||
{ |
|||
switch (tagId) |
|||
{ |
|||
case ExifTagValue.ImageWidth: |
|||
case ExifTagValue.ImageLength: |
|||
case ExifTagValue.StripOffsets: |
|||
case ExifTagValue.RowsPerStrip: |
|||
case ExifTagValue.StripByteCounts: |
|||
case ExifTagValue.TileWidth: |
|||
case ExifTagValue.TileLength: |
|||
case ExifTagValue.TileOffsets: |
|||
case ExifTagValue.TileByteCounts: |
|||
case ExifTagValue.OldSubfileType: // by spec SHORT, but can be LONG
|
|||
return ExifDataType.Long; |
|||
|
|||
default: |
|||
return dataType; |
|||
} |
|||
} |
|||
|
|||
private static bool GetIsArray(ExifTagValue tagId, uint count) |
|||
{ |
|||
switch (tagId) |
|||
{ |
|||
case ExifTagValue.BitsPerSample: |
|||
case ExifTagValue.StripOffsets: |
|||
case ExifTagValue.StripByteCounts: |
|||
case ExifTagValue.TileOffsets: |
|||
case ExifTagValue.TileByteCounts: |
|||
case ExifTagValue.ColorMap: |
|||
case ExifTagValue.ExtraSamples: |
|||
case ExifTagValue.SampleFormat: |
|||
return true; |
|||
|
|||
default: |
|||
return count > 1; |
|||
} |
|||
} |
|||
|
|||
private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count) |
|||
{ |
|||
if (HasExtData(entry, count)) |
|||
{ |
|||
uint offset = this.stream.ReadUInt32(); |
|||
this.extValueLoaders.Add(offset, () => |
|||
{ |
|||
this.ReadExtValue(entry, rawDataType, offset, count); |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
long pos = this.stream.Position; |
|||
object value = this.ReadData(entry.DataType, rawDataType, count); |
|||
if (value == null) |
|||
{ |
|||
// read unknown type value
|
|||
value = this.stream.ReadBytes(4); |
|||
} |
|||
else |
|||
{ |
|||
int leftBytes = 4 - (int)(this.stream.Position - pos); |
|||
if (leftBytes > 0) |
|||
{ |
|||
this.stream.Skip(leftBytes); |
|||
} |
|||
else if (leftBytes < 0) |
|||
{ |
|||
throw new InvalidDataException("Out of range of IFD entry structure."); |
|||
} |
|||
} |
|||
|
|||
return SetValue(entry, value); |
|||
} |
|||
|
|||
private void ReadExtValue(ExifValue entry, ExifDataType rawDataType, uint offset, uint count) |
|||
{ |
|||
DebugGuard.IsTrue(HasExtData(entry, count), "Excepted extended data"); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(offset, (uint)TiffConstants.SizeOfTiffHeader, nameof(offset)); |
|||
|
|||
this.stream.Seek(offset); |
|||
var value = this.ReadData(entry.DataType, rawDataType, count); |
|||
|
|||
SetValue(entry, value); |
|||
|
|||
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii || count > 1 ^ !entry.IsArray, "Invalid tag"); |
|||
DebugGuard.IsTrue(entry.GetValue() != null, "Invalid tag"); |
|||
} |
|||
|
|||
private object ReadData(ExifDataType entryDataType, ExifDataType rawDataType, uint count) |
|||
{ |
|||
switch (rawDataType) |
|||
{ |
|||
case ExifDataType.Byte: |
|||
case ExifDataType.Undefined: |
|||
{ |
|||
return this.stream.ReadBytes(count); |
|||
} |
|||
|
|||
case ExifDataType.SignedByte: |
|||
{ |
|||
sbyte[] res = new sbyte[count]; |
|||
byte[] buf = this.stream.ReadBytes(count); |
|||
Array.Copy(buf, res, buf.Length); |
|||
return res; |
|||
} |
|||
|
|||
case ExifDataType.Short: |
|||
{ |
|||
if (entryDataType == ExifDataType.Long) |
|||
{ |
|||
uint[] buf = new uint[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
else |
|||
{ |
|||
ushort[] buf = new ushort[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
} |
|||
|
|||
case ExifDataType.SignedShort: |
|||
{ |
|||
short[] buf = new short[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Long: |
|||
{ |
|||
uint[] buf = new uint[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt32(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.SignedLong: |
|||
{ |
|||
int[] buf = new int[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadInt32(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Ascii: |
|||
{ |
|||
byte[] buf = this.stream.ReadBytes(count); |
|||
|
|||
if (buf[buf.Length - 1] != 0) |
|||
{ |
|||
throw new ImageFormatException("The retrieved string is not null terminated."); |
|||
} |
|||
|
|||
return Encoding.UTF8.GetString(buf, 0, buf.Length - 1); |
|||
} |
|||
|
|||
case ExifDataType.SingleFloat: |
|||
{ |
|||
float[] buf = new float[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadSingle(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.DoubleFloat: |
|||
{ |
|||
double[] buf = new double[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadDouble(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Rational: |
|||
{ |
|||
var buf = new Rational[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
uint numerator = this.stream.ReadUInt32(); |
|||
uint denominator = this.stream.ReadUInt32(); |
|||
buf[i] = new Rational(numerator, denominator); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.SignedRational: |
|||
{ |
|||
var buf = new SignedRational[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
int numerator = this.stream.ReadInt32(); |
|||
int denominator = this.stream.ReadInt32(); |
|||
buf[i] = new SignedRational(numerator, denominator); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Ifd: |
|||
{ |
|||
return this.stream.ReadUInt32(); |
|||
} |
|||
|
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Saves the image to the given stream with the tiff format.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="stream">The stream to save the image to.</param>
|
|||
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}"/>.
|
|||
/// </returns>
|
|||
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
return SaveAsTiff(source, stream, null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Saves the image to the given stream with the tiff format.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="stream">The stream to save the image to.</param>
|
|||
/// <param name="encoder">The options for the encoder.</param>
|
|||
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}"/>.
|
|||
/// </returns>
|
|||
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream, TiffEncoder encoder) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
encoder = encoder ?? new TiffEncoder(); |
|||
encoder.Encode(source, stream); |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
|||
/// </summary>
|
|||
public static partial class MetadataExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the tiff format specific metadata for the image.
|
|||
/// </summary>
|
|||
/// <param name="metadata">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="TiffMetadata"/>.</returns>
|
|||
public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); |
|||
|
|||
/// <summary>
|
|||
/// Gets the tiff format specific metadata for the image frame.
|
|||
/// </summary>
|
|||
/// <param name="metadata">The metadata this method extends.</param>
|
|||
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
|
|||
public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public BlackIsZero1TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x += 8) |
|||
{ |
|||
byte b = data[offset++]; |
|||
int maxShift = Math.Min(left + width - x, 8); |
|||
|
|||
for (int shift = 0; shift < maxShift; shift++) |
|||
{ |
|||
int bit = (b >> (7 - shift)) & 1; |
|||
byte intensity = (bit == 1) ? (byte)255 : (byte)0; |
|||
|
|||
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255)); |
|||
pixels[x + shift, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
|
|||
/// </summary>
|
|||
internal class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public BlackIsZero4TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
bool isOddWidth = (width & 1) == 1; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width - 1; x += 2) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
|||
color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255)); |
|||
pixels[x, y] = color; |
|||
|
|||
byte intensity2 = (byte)((byteData & 0x0F) * 17); |
|||
color.FromRgba32(new Rgba32(intensity2, intensity2, intensity2, 255)); |
|||
pixels[x + 1, y] = color; |
|||
} |
|||
|
|||
if (isOddWidth) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
|||
color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255)); |
|||
pixels[left + width - 1, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
|
|||
/// </summary>
|
|||
internal class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public BlackIsZero8TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
byte intensity = data[offset++]; |
|||
|
|||
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255)); |
|||
pixels[x, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
|
|||
/// </summary>
|
|||
internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly ushort bitsPerSample0; |
|||
|
|||
private readonly float factor; |
|||
|
|||
public BlackIsZeroTiffColor(ushort[] bitsPerSample) |
|||
{ |
|||
this.bitsPerSample0 = bitsPerSample[0]; |
|||
this.factor = (float)(1 << this.bitsPerSample0) - 1.0f; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
var bitReader = new BitReader(data); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int value = bitReader.ReadBits(this.bitsPerSample0); |
|||
float intensity = ((float)value) / this.factor; |
|||
|
|||
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
|
|||
/// </summary>
|
|||
internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly ushort bitsPerSample0; |
|||
|
|||
private readonly TPixel[] palette; |
|||
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
|
|||
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
|
|||
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap) |
|||
{ |
|||
this.bitsPerSample0 = bitsPerSample[0]; |
|||
int colorCount = 1 << this.bitsPerSample0; |
|||
this.palette = GeneratePalette(colorMap, colorCount); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var bitReader = new BitReader(data); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int index = bitReader.ReadBits(this.bitsPerSample0); |
|||
pixels[x, y] = this.palette[index]; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
|
|||
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) |
|||
{ |
|||
var palette = new TPixel[colorCount]; |
|||
|
|||
int rOffset = 0; |
|||
int gOffset = colorCount; |
|||
int bOffset = colorCount * 2; |
|||
|
|||
for (int i = 0; i < palette.Length; i++) |
|||
{ |
|||
float r = colorMap[rOffset + i] / 65535F; |
|||
float g = colorMap[gOffset + i] / 65535F; |
|||
float b = colorMap[bOffset + i] / 65535F; |
|||
palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); |
|||
} |
|||
|
|||
return palette; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images).
|
|||
/// </summary>
|
|||
internal class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public Rgb888TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
Span<TPixel> pixelRow = pixels.GetRowSpan(y); |
|||
|
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
byte r = data[offset++]; |
|||
byte g = data[offset++]; |
|||
byte b = data[offset++]; |
|||
|
|||
color.FromRgba32(new Rgba32(r, g, b, 255)); |
|||
pixelRow[x] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
|
|||
/// </summary>
|
|||
internal class RgbPlanarTiffColor<TPixel> /* : TiffColorDecoder<TPixel> */ |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly float rFactor; |
|||
|
|||
private readonly float gFactor; |
|||
|
|||
private readonly float bFactor; |
|||
|
|||
private readonly ushort bitsPerSampleR; |
|||
|
|||
private readonly ushort bitsPerSampleG; |
|||
|
|||
private readonly ushort bitsPerSampleB; |
|||
|
|||
public RgbPlanarTiffColor(ushort[] bitsPerSample) |
|||
/* : base(bitsPerSample, null) */ |
|||
{ |
|||
this.bitsPerSampleR = bitsPerSample[0]; |
|||
this.bitsPerSampleG = bitsPerSample[1]; |
|||
this.bitsPerSampleB = bitsPerSample[2]; |
|||
|
|||
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f; |
|||
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f; |
|||
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <param name="data">The buffers to read image data from.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
public void Decode(IManagedByteBuffer[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
var rBitReader = new BitReader(data[0].GetSpan()); |
|||
var gBitReader = new BitReader(data[1].GetSpan()); |
|||
var bBitReader = new BitReader(data[2].GetSpan()); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
float r = ((float)rBitReader.ReadBits(this.bitsPerSampleR)) / this.rFactor; |
|||
float g = ((float)gBitReader.ReadBits(this.bitsPerSampleG)) / this.gFactor; |
|||
float b = ((float)bBitReader.ReadBits(this.bitsPerSampleB)) / this.bFactor; |
|||
|
|||
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
rBitReader.NextRow(); |
|||
gBitReader.NextRow(); |
|||
bBitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'RGB' photometric interpretation (for all bit depths).
|
|||
/// </summary>
|
|||
internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly float rFactor; |
|||
|
|||
private readonly float gFactor; |
|||
|
|||
private readonly float bFactor; |
|||
|
|||
private readonly ushort bitsPerSampleR; |
|||
|
|||
private readonly ushort bitsPerSampleG; |
|||
|
|||
private readonly ushort bitsPerSampleB; |
|||
|
|||
public RgbTiffColor(ushort[] bitsPerSample) |
|||
{ |
|||
this.bitsPerSampleR = bitsPerSample[0]; |
|||
this.bitsPerSampleG = bitsPerSample[1]; |
|||
this.bitsPerSampleB = bitsPerSample[2]; |
|||
|
|||
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f; |
|||
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f; |
|||
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
var bitReader = new BitReader(data); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
float r = ((float)bitReader.ReadBits(this.bitsPerSampleR)) / this.rFactor; |
|||
float g = ((float)bitReader.ReadBits(this.bitsPerSampleG)) / this.gFactor; |
|||
float b = ((float)bitReader.ReadBits(this.bitsPerSampleB)) / this.bFactor; |
|||
|
|||
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The base class for photometric interpretation decoders.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal abstract class TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
protected TiffBaseColorDecoder() |
|||
{ |
|||
} |
|||
|
|||
/* |
|||
/// <summary>
|
|||
/// Gets the photometric interpretation value.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The photometric interpretation value.
|
|||
/// </value>
|
|||
public TiffColorType ColorType { get; } |
|||
*/ |
|||
|
|||
/// <summary>
|
|||
/// Decodes source raw pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
public abstract void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height); |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal static class TiffColorDecoderFactory<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) |
|||
{ |
|||
switch (colorType) |
|||
{ |
|||
case TiffColorType.WhiteIsZero: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new WhiteIsZeroTiffColor<TPixel>(bitsPerSample); |
|||
|
|||
case TiffColorType.WhiteIsZero1: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new WhiteIsZero1TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.WhiteIsZero4: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new WhiteIsZero4TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.WhiteIsZero8: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new WhiteIsZero8TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.BlackIsZero: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new BlackIsZeroTiffColor<TPixel>(bitsPerSample); |
|||
|
|||
case TiffColorType.BlackIsZero1: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new BlackIsZero1TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.BlackIsZero4: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new BlackIsZero4TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.BlackIsZero8: |
|||
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new BlackIsZero8TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.Rgb: |
|||
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new RgbTiffColor<TPixel>(bitsPerSample); |
|||
|
|||
case TiffColorType.Rgb888: |
|||
DebugGuard.IsTrue( |
|||
bitsPerSample.Length == 3 |
|||
&& bitsPerSample[0] == 8 |
|||
&& bitsPerSample[1] == 8 |
|||
&& bitsPerSample[2] == 8, |
|||
"bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new Rgb888TiffColor<TPixel>(); |
|||
|
|||
case TiffColorType.PaletteColor: |
|||
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
|||
DebugGuard.NotNull(colorMap, "colorMap"); |
|||
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap); |
|||
|
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
} |
|||
|
|||
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) |
|||
{ |
|||
switch (colorType) |
|||
{ |
|||
case TiffColorType.RgbPlanar: |
|||
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
|||
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
|||
return new RgbPlanarTiffColor<TPixel>(bitsPerSample); |
|||
|
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Provides enumeration of the various TIFF photometric interpretation implementation types.
|
|||
/// </summary>
|
|||
internal enum TiffColorType |
|||
{ |
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white.
|
|||
/// </summary>
|
|||
BlackIsZero, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for bilevel images.
|
|||
/// </summary>
|
|||
BlackIsZero1, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 4-bit images.
|
|||
/// </summary>
|
|||
BlackIsZero4, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 8-bit images.
|
|||
/// </summary>
|
|||
BlackIsZero8, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
|
|||
/// </summary>
|
|||
WhiteIsZero, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images.
|
|||
/// </summary>
|
|||
WhiteIsZero1, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 4-bit images.
|
|||
/// </summary>
|
|||
WhiteIsZero4, |
|||
|
|||
/// <summary>
|
|||
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images.
|
|||
/// </summary>
|
|||
WhiteIsZero8, |
|||
|
|||
/// <summary>
|
|||
/// Palette-color.
|
|||
/// </summary>
|
|||
PaletteColor, |
|||
|
|||
/// <summary>
|
|||
/// RGB Full Color.
|
|||
/// </summary>
|
|||
Rgb, |
|||
|
|||
/// <summary>
|
|||
/// RGB Full Color. Optimised implementation for 8-bit images.
|
|||
/// </summary>
|
|||
Rgb888, |
|||
|
|||
/// <summary>
|
|||
/// RGB Full Color. Planar configuration of data.
|
|||
/// </summary>
|
|||
RgbPlanar, |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
|
|||
/// </summary>
|
|||
internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public WhiteIsZero1TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x += 8) |
|||
{ |
|||
byte b = data[offset++]; |
|||
int maxShift = Math.Min(left + width - x, 8); |
|||
|
|||
for (int shift = 0; shift < maxShift; shift++) |
|||
{ |
|||
int bit = (b >> (7 - shift)) & 1; |
|||
byte intensity = (bit == 1) ? (byte)0 : (byte)255; |
|||
|
|||
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255)); |
|||
pixels[x + shift, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
|
|||
/// </summary>
|
|||
internal class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public WhiteIsZero4TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
bool isOddWidth = (width & 1) == 1; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width - 1; x += 2) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); |
|||
color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255)); |
|||
pixels[x, y] = color; |
|||
|
|||
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); |
|||
color.FromRgba32(new Rgba32(intensity2, intensity2, intensity2, 255)); |
|||
pixels[x + 1, y] = color; |
|||
} |
|||
|
|||
if (isOddWidth) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); |
|||
color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255)); |
|||
pixels[left + width - 1, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
|
|||
/// </summary>
|
|||
internal class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
public WhiteIsZero8TiffColor() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
int offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
byte intensity = (byte)(255 - data[offset++]); |
|||
|
|||
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255)); |
|||
pixels[x, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
|
|||
/// </summary>
|
|||
internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly ushort bitsPerSample0; |
|||
|
|||
private readonly float factor; |
|||
|
|||
public WhiteIsZeroTiffColor(ushort[] bitsPerSample) |
|||
{ |
|||
this.bitsPerSample0 = bitsPerSample[0]; |
|||
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
|
|||
var bitReader = new BitReader(data); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int value = bitReader.ReadBits(this.bitsPerSample0); |
|||
float intensity = 1.0f - (((float)value) / this.factor); |
|||
|
|||
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,250 @@ |
|||
# ImageSharp TIFF codec |
|||
|
|||
## References |
|||
- TIFF |
|||
- [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf) |
|||
- [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf) |
|||
- [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf) |
|||
- [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf) |
|||
- [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt) |
|||
- [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP) |
|||
- [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html) |
|||
- [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html) |
|||
|
|||
- DNG |
|||
- [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html) |
|||
|
|||
- Metadata (EXIF) |
|||
- [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) |
|||
|
|||
- Metadata (XMP) |
|||
- [Adobe XMP Pages](http://www.adobe.com/products/xmp.html) |
|||
- [Adobe XMP Developer Center](http://www.adobe.com/devnet/xmp.html) |
|||
|
|||
## Implementation Status |
|||
|
|||
### Deviations from the TIFF spec (to be fixed) |
|||
|
|||
- Decoder |
|||
- A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels) |
|||
- NB: Need to handle this for both planar and chunky data |
|||
- If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this |
|||
- Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows) |
|||
- RowsPerStrip should default to 2^32-1 (effectively infinity) to store the image as a single strip |
|||
- Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB? |
|||
- Make sure we ignore any strips that are not needed for the image (if too many are present) |
|||
|
|||
### Compression Formats |
|||
|
|||
| |Encoder|Decoder|Comments | |
|||
|---------------------------|:-----:|:-----:|--------------------------| |
|||
|None | | Y | | |
|||
|Ccitt1D | | | | |
|||
|PackBits | | Y | | |
|||
|CcittGroup3Fax | | | | |
|||
|CcittGroup4Fax | | | | |
|||
|Lzw | | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |
|||
|Old Jpeg | | | | |
|||
|Jpeg (Technote 2) | | | | |
|||
|Deflate (Technote 2) | | Y | | |
|||
|Old Deflate (Technote 2) | | Y | | |
|||
|
|||
### Photometric Interpretation Formats |
|||
|
|||
| |Encoder|Decoder|Comments | |
|||
|---------------------------|:-----:|:-----:|--------------------------| |
|||
|WhiteIsZero | | Y | General + 1/4/8-bit optimised implementations | |
|||
|BlackIsZero | | Y | General + 1/4/8-bit optimised implementations | |
|||
|Rgb (Chunky) | | Y | General + Rgb888 optimised implementation | |
|||
|Rgb (Planar) | | Y | General implementation only | |
|||
|PaletteColor | | Y | General implementation only | |
|||
|TransparencyMask | | | | |
|||
|Separated (TIFF Extension) | | | | |
|||
|YCbCr (TIFF Extension) | | | | |
|||
|CieLab (TIFF Extension) | | | | |
|||
|IccLab (TechNote 1) | | | | |
|||
|
|||
### Baseline TIFF Tags |
|||
|
|||
| |Encoder|Decoder|Comments | |
|||
|---------------------------|:-----:|:-----:|--------------------------| |
|||
|NewSubfileType | | | | |
|||
|SubfileType | | | | |
|||
|ImageWidth | | Y | | |
|||
|ImageLength | | Y | | |
|||
|BitsPerSample | | Y | | |
|||
|Compression | | Y | | |
|||
|PhotometricInterpretation | | Y | | |
|||
|Threshholding | | | | |
|||
|CellWidth | | | | |
|||
|CellLength | | | | |
|||
|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | |
|||
|ImageDescription | | Y | | |
|||
|Make | | Y | | |
|||
|Model | | Y | | |
|||
|StripOffsets | | Y | | |
|||
|Orientation | | - | Ignore. Many readers ignore this tag. | |
|||
|SamplesPerPixel | | - | Currently ignored, as can be inferred from count of BitsPerSample | |
|||
|RowsPerStrip | | Y | | |
|||
|StripByteCounts | | Y | | |
|||
|MinSampleValue | | | | |
|||
|MaxSampleValue | | | | |
|||
|XResolution | | Y | | |
|||
|YResolution | | Y | | |
|||
|PlanarConfiguration | | Y | | |
|||
|FreeOffsets | | | | |
|||
|FreeByteCounts | | | | |
|||
|GrayResponseUnit | | | | |
|||
|GrayResponseCurve | | | | |
|||
|ResolutionUnit | | Y | | |
|||
|Software | | Y | | |
|||
|DateTime | | Y | | |
|||
|Artist | | Y | | |
|||
|HostComputer | | Y | | |
|||
|ColorMap | | Y | | |
|||
|ExtraSamples | | - | | |
|||
|Copyright | | Y | | |
|||
|
|||
### Extension TIFF Tags |
|||
|
|||
| |Encoder|Decoder|Comments | |
|||
|---------------------------|:-----:|:-----:|--------------------------| |
|||
|NewSubfileType | | | | |
|||
|DocumentName | | | | |
|||
|PageName | | | | |
|||
|XPosition | | | | |
|||
|YPosition | | | | |
|||
|T4Options | | | | |
|||
|T6Options | | | | |
|||
|PageNumber | | | | |
|||
|TransferFunction | | | | |
|||
|Predictor | | - | priority | |
|||
|WhitePoint | | | | |
|||
|PrimaryChromaticities | | | | |
|||
|HalftoneHints | | | | |
|||
|TileWidth | | - | | |
|||
|TileLength | | - | | |
|||
|TileOffsets | | - | | |
|||
|TileByteCounts | | - | | |
|||
|BadFaxLines | | | | |
|||
|CleanFaxData | | | | |
|||
|ConsecutiveBadFaxLines | | | | |
|||
|SubIFDs | | - | | |
|||
|InkSet | | | | |
|||
|InkNames | | | | |
|||
|NumberOfInks | | | | |
|||
|DotRange | | | | |
|||
|TargetPrinter | | | | |
|||
|SampleFormat | | - | | |
|||
|SMinSampleValue | | | | |
|||
|SMaxSampleValue | | | | |
|||
|TransferRange | | | | |
|||
|ClipPath | | | | |
|||
|XClipPathUnits | | | | |
|||
|YClipPathUnits | | | | |
|||
|Indexed | | | | |
|||
|JPEGTables | | | | |
|||
|OPIProxy | | | | |
|||
|GlobalParametersIFD | | | | |
|||
|ProfileType | | | | |
|||
|FaxProfile | | | | |
|||
|CodingMethods | | | | |
|||
|VersionYear | | | | |
|||
|ModeNumber | | | | |
|||
|Decode | | | | |
|||
|DefaultImageColor | | | | |
|||
|JPEGProc | | | | |
|||
|JPEGInterchangeFormat | | | | |
|||
|JPEGInterchangeFormatLength| | | | |
|||
|JPEGRestartInterval | | | | |
|||
|JPEGLosslessPredictors | | | | |
|||
|JPEGPointTransforms | | | | |
|||
|JPEGQTables | | | | |
|||
|JPEGDCTables | | | | |
|||
|JPEGACTables | | | | |
|||
|YCbCrCoefficients | | | | |
|||
|YCbCrSubSampling | | | | |
|||
|YCbCrPositioning | | | | |
|||
|ReferenceBlackWhite | | | | |
|||
|StripRowCounts | | - | | |
|||
|XMP | | Y | | |
|||
|ImageID | | | | |
|||
|ImageLayer | | | | |
|||
|
|||
### Private TIFF Tags |
|||
|
|||
| |Encoder|Decoder|Comments | |
|||
|---------------------------|:-----:|:-----:|--------------------------| |
|||
|Wang Annotation | | | | |
|||
|MD FileTag | | | | |
|||
|MD ScalePixel | | | | |
|||
|MD ColorTable | | | | |
|||
|MD LabName | | | | |
|||
|MD SampleInfo | | | | |
|||
|MD PrepDate | | | | |
|||
|MD PrepTime | | | | |
|||
|MD FileUnits | | | | |
|||
|ModelPixelScaleTag | | | | |
|||
|IPTC | | Y | | |
|||
|INGR Packet Data Tag | | | | |
|||
|INGR Flag Registers | | | | |
|||
|IrasB Transformation Matrix| | | | |
|||
|ModelTiepointTag | | | | |
|||
|ModelTransformationTag | | | | |
|||
|Photoshop | | | | |
|||
|Exif IFD | | - | 0x8769 SubExif | |
|||
|ICC Profile | | Y | | |
|||
|GeoKeyDirectoryTag | | | | |
|||
|GeoDoubleParamsTag | | | | |
|||
|GeoAsciiParamsTag | | | | |
|||
|GPS IFD | | | | |
|||
|HylaFAX FaxRecvParams | | | | |
|||
|HylaFAX FaxSubAddress | | | | |
|||
|HylaFAX FaxRecvTime | | | | |
|||
|ImageSourceData | | | | |
|||
|Interoperability IFD | | | | |
|||
|GDAL_METADATA | | | | |
|||
|GDAL_NODATA | | | | |
|||
|Oce Scanjob Description | | | | |
|||
|Oce Application Selector | | | | |
|||
|Oce Identification Number | | | | |
|||
|Oce ImageLogic Characteristics| | | | |
|||
|DNGVersion | | | | |
|||
|DNGBackwardVersion | | | | |
|||
|UniqueCameraModel | | | | |
|||
|LocalizedCameraModel | | | | |
|||
|CFAPlaneColor | | | | |
|||
|CFALayout | | | | |
|||
|LinearizationTable | | | | |
|||
|BlackLevelRepeatDim | | | | |
|||
|BlackLevel | | | | |
|||
|BlackLevelDeltaH | | | | |
|||
|BlackLevelDeltaV | | | | |
|||
|WhiteLevel | | | | |
|||
|DefaultScale | | | | |
|||
|DefaultCropOrigin | | | | |
|||
|DefaultCropSize | | | | |
|||
|ColorMatrix1 | | | | |
|||
|ColorMatrix2 | | | | |
|||
|CameraCalibration1 | | | | |
|||
|CameraCalibration2 | | | | |
|||
|ReductionMatrix1 | | | | |
|||
|ReductionMatrix2 | | | | |
|||
|AnalogBalance | | | | |
|||
|AsShotNeutral | | | | |
|||
|AsShotWhiteXY | | | | |
|||
|BaselineExposure | | | | |
|||
|BaselineNoise | | | | |
|||
|BaselineSharpness | | | | |
|||
|BayerGreenSplit | | | | |
|||
|LinearResponseLimit | | | | |
|||
|CameraSerialNumber | | | | |
|||
|LensInfo | | | | |
|||
|ChromaBlurRadius | | | | |
|||
|AntiAliasStrength | | | | |
|||
|DNGPrivateData | | | | |
|||
|MakerNoteSafety | | | | |
|||
|CalibrationIlluminant1 | | | | |
|||
|CalibrationIlluminant2 | | | | |
|||
|BestQualityScale | | | | |
|||
|Alias Layer Metadata | | | | |
|||
@ -0,0 +1,88 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal class TiffBigEndianStream : TiffStream |
|||
{ |
|||
public TiffBigEndianStream(Stream stream) |
|||
: base(stream) |
|||
{ |
|||
} |
|||
|
|||
public override TiffByteOrder ByteOrder => TiffByteOrder.BigEndian; |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override short ReadInt16() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(2); |
|||
return (short)((bytes[0] << 8) | bytes[1]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override int ReadInt32() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override uint ReadUInt32() |
|||
{ |
|||
return (uint)this.ReadInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override ushort ReadUInt16() |
|||
{ |
|||
return (ushort)this.ReadInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override float ReadSingle() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
|
|||
if (BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToSingle(bytes, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override double ReadDouble() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(8); |
|||
|
|||
if (BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToDouble(bytes, 0); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
internal class TiffLittleEndianStream : TiffStream |
|||
{ |
|||
public TiffLittleEndianStream(Stream stream) |
|||
: base(stream) |
|||
{ |
|||
} |
|||
|
|||
public override TiffByteOrder ByteOrder => TiffByteOrder.LittleEndian; |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override short ReadInt16() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(2); |
|||
return (short)(bytes[0] | (bytes[1] << 8)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override int ReadInt32() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override uint ReadUInt32() |
|||
{ |
|||
return (uint)this.ReadInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override ushort ReadUInt16() |
|||
{ |
|||
return (ushort)this.ReadInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override float ReadSingle() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
|
|||
if (!BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToSingle(bytes, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override double ReadDouble() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(8); |
|||
|
|||
if (!BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToDouble(bytes, 0); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The tiff data stream base class.
|
|||
/// </summary>
|
|||
internal abstract class TiffStream |
|||
{ |
|||
/// <summary>
|
|||
/// The input stream.
|
|||
/// </summary>
|
|||
private readonly Stream stream; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
protected TiffStream(Stream stream) |
|||
{ |
|||
this.stream = stream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the file is encoded in little-endian or big-endian format.
|
|||
/// </summary>
|
|||
public abstract TiffByteOrder ByteOrder { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public Stream InputStream => this.stream; |
|||
|
|||
/// <summary>
|
|||
/// Gets the stream position.
|
|||
/// </summary>
|
|||
public long Position => this.stream.Position; |
|||
|
|||
public void Seek(uint offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Begin); |
|||
} |
|||
|
|||
public void Skip(uint offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Current); |
|||
} |
|||
|
|||
public void Skip(int offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Current); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="byte"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public byte ReadByte() |
|||
{ |
|||
return (byte)this.stream.ReadByte(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="sbyte"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public sbyte ReadSByte() |
|||
{ |
|||
return (sbyte)this.stream.ReadByte(); |
|||
} |
|||
|
|||
public byte[] ReadBytes(uint count) |
|||
{ |
|||
byte[] buf = new byte[count]; |
|||
this.stream.Read(buf, 0, buf.Length); |
|||
return buf; |
|||
} |
|||
|
|||
public abstract short ReadInt16(); |
|||
|
|||
public abstract int ReadInt32(); |
|||
|
|||
public abstract uint ReadUInt32(); |
|||
|
|||
public abstract ushort ReadUInt16(); |
|||
|
|||
public abstract float ReadSingle(); |
|||
|
|||
public abstract double ReadDouble(); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The tiff data stream factory class.
|
|||
/// </summary>
|
|||
internal static class TiffStreamFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates the specified byte order.
|
|||
/// </summary>
|
|||
/// <param name="byteOrder">The byte order.</param>
|
|||
/// <param name="stream">The stream.</param>
|
|||
public static TiffStream Create(TiffByteOrder byteOrder, Stream stream) |
|||
{ |
|||
if (byteOrder == TiffByteOrder.BigEndian) |
|||
{ |
|||
return new TiffBigEndianStream(stream); |
|||
} |
|||
else if (byteOrder == TiffByteOrder.LittleEndian) |
|||
{ |
|||
return new TiffLittleEndianStream(stream); |
|||
} |
|||
|
|||
throw new ArgumentOutOfRangeException(nameof(byteOrder)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the byte order of stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
public static TiffByteOrder ReadByteOrder(Stream stream) |
|||
{ |
|||
byte[] headerBytes = new byte[2]; |
|||
stream.Read(headerBytes, 0, 2); |
|||
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) |
|||
{ |
|||
return TiffByteOrder.LittleEndian; |
|||
} |
|||
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) |
|||
{ |
|||
return TiffByteOrder.BigEndian; |
|||
} |
|||
|
|||
throw new ImageFormatException("Invalid TIFF file header."); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the TIFF format.
|
|||
/// </summary>
|
|||
public sealed class TiffConfigurationModule : IConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a TIFF stream.
|
|||
/// </summary>
|
|||
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, "stream"); |
|||
|
|||
using var decoder = new TiffDecoderCore(stream, configuration, this); |
|||
return decoder.Decode<TPixel>(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new TiffDecoderCore(stream, configuration, this); |
|||
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken) |
|||
.ConfigureAwait(false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new TiffDecoderCore(stream, configuration, this); |
|||
return decoder.Identify(configuration, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var decoder = new TiffDecoderCore(stream, configuration, this); |
|||
return decoder.IdentifyAsync(configuration, stream, cancellationToken); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,326 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Performs the tiff decoding operation.
|
|||
/// </summary>
|
|||
internal class TiffDecoderCore : IImageDecoderInternals, IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The global configuration
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Used for allocating memory during processing operations.
|
|||
/// </summary>
|
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
private readonly bool ignoreMetadata; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The decoder options.</param>
|
|||
private TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) |
|||
{ |
|||
options = options ?? new TiffDecoder(); |
|||
|
|||
this.configuration = configuration ?? Configuration.Default; |
|||
this.ignoreMetadata = options.IgnoreMetadata; |
|||
this.memoryAllocator = this.configuration.MemoryAllocator; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The decoder options.</param>
|
|||
public TiffDecoderCore(Stream stream, Configuration configuration, ITiffDecoderOptions options) |
|||
: this(configuration, options) |
|||
{ |
|||
this.ByteOrder = TiffStreamFactory.ReadByteOrder(stream); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the byte order.
|
|||
/// </summary>
|
|||
public TiffByteOrder ByteOrder { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public TiffStream Stream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
|
|||
/// </summary>
|
|||
public ushort[] BitsPerSample { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the lookup table for RGB palette colored images.
|
|||
/// </summary>
|
|||
public ushort[] ColorMap { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the photometric interpretation implementation to use when decoding the image.
|
|||
/// </summary>
|
|||
public TiffColorType ColorType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression implementation to use when decoding the image.
|
|||
/// </summary>
|
|||
public TiffCompressionType CompressionType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the planar configuration type to use when decoding the image.
|
|||
/// </summary>
|
|||
public TiffPlanarConfiguration PlanarConfiguration { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the photometric interpretation.
|
|||
/// </summary>
|
|||
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Configuration Configuration => this.configuration; |
|||
|
|||
/// <inheritdoc/>
|
|||
public Size Dimensions { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream); |
|||
var reader = new DirectoryReader(this.Stream); |
|||
|
|||
IEnumerable<IExifValue[]> directories = reader.Read(); |
|||
|
|||
var frames = new List<ImageFrame<TPixel>>(); |
|||
var framesMetadata = new List<TiffFrameMetadata>(); |
|||
foreach (IExifValue[] ifd in directories) |
|||
{ |
|||
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, out TiffFrameMetadata frameMetadata); |
|||
frames.Add(frame); |
|||
framesMetadata.Add(frameMetadata); |
|||
} |
|||
|
|||
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder); |
|||
|
|||
// todo: tiff frames can have different sizes
|
|||
{ |
|||
ImageFrame<TPixel> root = frames.First(); |
|||
this.Dimensions = root.Size(); |
|||
foreach (ImageFrame<TPixel> frame in frames) |
|||
{ |
|||
if (frame.Size() != root.Size()) |
|||
{ |
|||
throw new NotSupportedException("Images with different sizes are not supported"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
var image = new Image<TPixel>(this.configuration, metadata, frames); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|||
{ |
|||
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream); |
|||
var reader = new DirectoryReader(this.Stream); |
|||
|
|||
IEnumerable<IExifValue[]> directories = reader.Read(); |
|||
|
|||
var framesMetadata = new List<TiffFrameMetadata>(); |
|||
foreach (IExifValue[] ifd in directories) |
|||
{ |
|||
framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd }); |
|||
} |
|||
|
|||
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder); |
|||
|
|||
TiffFrameMetadata root = framesMetadata.First(); |
|||
int bitsPerPixel = 0; |
|||
foreach (var bits in root.BitsPerSample) |
|||
{ |
|||
bitsPerPixel += bits; |
|||
} |
|||
|
|||
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, metadata); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
// nothing
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image data from a specified IFD.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="tags">The IFD tags.</param>
|
|||
/// <param name="metadata">The frame metadata.</param>
|
|||
/// <returns>
|
|||
/// The tiff frame.
|
|||
/// </returns>
|
|||
private ImageFrame<TPixel> DecodeFrame<TPixel>(IExifValue[] tags, out TiffFrameMetadata metadata) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var coreMetadata = new ImageFrameMetadata(); |
|||
metadata = coreMetadata.GetTiffMetadata(); |
|||
metadata.Tags = tags; |
|||
|
|||
this.VerifyAndParseOptions(metadata); |
|||
|
|||
int width = (int)metadata.Width; |
|||
int height = (int)metadata.Height; |
|||
var frame = new ImageFrame<TPixel>(this.configuration, width, height, coreMetadata); |
|||
|
|||
int rowsPerStrip = (int)metadata.RowsPerStrip; |
|||
uint[] stripOffsets = metadata.StripOffsets; |
|||
uint[] stripByteCounts = metadata.StripByteCounts; |
|||
|
|||
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) |
|||
{ |
|||
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts); |
|||
} |
|||
else |
|||
{ |
|||
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts); |
|||
} |
|||
|
|||
return frame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the size (in bytes) for a pixel buffer using the determined color format.
|
|||
/// </summary>
|
|||
/// <param name="width">The width for the desired pixel buffer.</param>
|
|||
/// <param name="height">The height for the desired pixel buffer.</param>
|
|||
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
|
|||
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
|
|||
private int CalculateStripBufferSize(int width, int height, int plane = -1) |
|||
{ |
|||
uint bitsPerPixel = 0; |
|||
|
|||
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) |
|||
{ |
|||
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar."); |
|||
for (int i = 0; i < this.BitsPerSample.Length; i++) |
|||
{ |
|||
bitsPerPixel += this.BitsPerSample[i]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
bitsPerPixel = this.BitsPerSample[plane]; |
|||
} |
|||
|
|||
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8; |
|||
int stripBytes = bytesPerRow * height; |
|||
|
|||
return stripBytes; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image data for strip encoded data.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="frame">The image frame to decode data into.</param>
|
|||
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
|
|||
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
|
|||
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
|
|||
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int stripsPerPixel = this.BitsPerSample.Length; |
|||
int stripsPerPlane = stripOffsets.Length / stripsPerPixel; |
|||
|
|||
Buffer2D<TPixel> pixels = frame.PixelBuffer; |
|||
|
|||
var stripBuffers = new IManagedByteBuffer[stripsPerPixel]; |
|||
|
|||
try |
|||
{ |
|||
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) |
|||
{ |
|||
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); |
|||
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); |
|||
} |
|||
|
|||
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator); |
|||
|
|||
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); |
|||
|
|||
for (int i = 0; i < stripsPerPlane; i++) |
|||
{ |
|||
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; |
|||
|
|||
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) |
|||
{ |
|||
int stripIndex = (i * stripsPerPixel) + planeIndex; |
|||
|
|||
this.Stream.Seek(stripOffsets[stripIndex]); |
|||
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); |
|||
} |
|||
|
|||
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
foreach (IManagedByteBuffer buf in stripBuffers) |
|||
{ |
|||
buf?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); |
|||
|
|||
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); |
|||
|
|||
Buffer2D<TPixel> pixels = frame.PixelBuffer; |
|||
|
|||
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator); |
|||
|
|||
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap); |
|||
|
|||
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) |
|||
{ |
|||
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; |
|||
|
|||
this.Stream.Seek(stripOffsets[stripIndex]); |
|||
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan()); |
|||
|
|||
colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,354 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Iptc; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// The decoder helper methods.
|
|||
/// </summary>
|
|||
internal static class TiffDecoderHelpers |
|||
{ |
|||
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, TiffByteOrder byteOrder) |
|||
{ |
|||
var coreMetadata = new ImageMetadata(); |
|||
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata(); |
|||
tiffMetadata.ByteOrder = byteOrder; |
|||
|
|||
TiffFrameMetadata rootFrameMetadata = frames.First(); |
|||
switch (rootFrameMetadata.ResolutionUnit) |
|||
{ |
|||
case TiffResolutionUnit.None: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio; |
|||
break; |
|||
case TiffResolutionUnit.Inch: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch; |
|||
break; |
|||
case TiffResolutionUnit.Centimeter: |
|||
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter; |
|||
break; |
|||
} |
|||
|
|||
if (rootFrameMetadata.HorizontalResolution != null) |
|||
{ |
|||
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value; |
|||
} |
|||
|
|||
if (rootFrameMetadata.VerticalResolution != null) |
|||
{ |
|||
coreMetadata.VerticalResolution = rootFrameMetadata.VerticalResolution.Value; |
|||
} |
|||
|
|||
if (!ignoreMetadata) |
|||
{ |
|||
foreach (TiffFrameMetadata frame in frames) |
|||
{ |
|||
if (tiffMetadata.XmpProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true); |
|||
if (buf != null) |
|||
{ |
|||
tiffMetadata.XmpProfile = buf; |
|||
} |
|||
} |
|||
|
|||
if (coreMetadata.IptcProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true); |
|||
if (buf != null) |
|||
{ |
|||
coreMetadata.IptcProfile = new IptcProfile(buf); |
|||
} |
|||
} |
|||
|
|||
if (coreMetadata.IccProfile == null) |
|||
{ |
|||
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true); |
|||
if (buf != null) |
|||
{ |
|||
coreMetadata.IccProfile = new IccProfile(buf); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return coreMetadata; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines the TIFF compression and color types, and reads any associated parameters.
|
|||
/// </summary>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="entries">The IFD entries container to read the image format information for.</param>
|
|||
public static void VerifyAndParseOptions(this TiffDecoderCore options, TiffFrameMetadata entries) |
|||
{ |
|||
if (entries.ExtraSamples != null) |
|||
{ |
|||
throw new NotSupportedException("ExtraSamples is not supported."); |
|||
} |
|||
|
|||
if (entries.FillOrder != TiffFillOrder.MostSignificantBitFirst) |
|||
{ |
|||
throw new NotSupportedException("The lower-order bits of the byte FillOrder is not supported."); |
|||
} |
|||
|
|||
if (entries.GetArray<uint>(ExifTag.TileOffsets, true) != null) |
|||
{ |
|||
throw new NotSupportedException("The Tile images is not supported."); |
|||
} |
|||
|
|||
if (entries.Predictor != TiffPredictor.None) |
|||
{ |
|||
throw new NotSupportedException("At the moment support only None Predictor."); |
|||
} |
|||
|
|||
if (entries.SampleFormat != null) |
|||
{ |
|||
foreach (TiffSampleFormat format in entries.SampleFormat) |
|||
{ |
|||
if (format != TiffSampleFormat.UnsignedInteger) |
|||
{ |
|||
throw new NotSupportedException("At the moment support only UnsignedInteger SampleFormat."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ParseCompression(options, entries.Compression); |
|||
|
|||
options.PlanarConfiguration = entries.PlanarConfiguration; |
|||
|
|||
ParsePhotometric(options, entries); |
|||
|
|||
ParseBitsPerSample(options, entries); |
|||
|
|||
ParseColorType(options, entries); |
|||
} |
|||
|
|||
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries) |
|||
{ |
|||
switch (options.PhotometricInterpretation) |
|||
{ |
|||
case TiffPhotometricInterpretation.WhiteIsZero: |
|||
{ |
|||
if (options.BitsPerSample.Length == 1) |
|||
{ |
|||
switch (options.BitsPerSample[0]) |
|||
{ |
|||
case 8: |
|||
{ |
|||
options.ColorType = TiffColorType.WhiteIsZero8; |
|||
break; |
|||
} |
|||
|
|||
case 4: |
|||
{ |
|||
options.ColorType = TiffColorType.WhiteIsZero4; |
|||
break; |
|||
} |
|||
|
|||
case 1: |
|||
{ |
|||
options.ColorType = TiffColorType.WhiteIsZero1; |
|||
break; |
|||
} |
|||
|
|||
default: |
|||
{ |
|||
options.ColorType = TiffColorType.WhiteIsZero; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case TiffPhotometricInterpretation.BlackIsZero: |
|||
{ |
|||
if (options.BitsPerSample.Length == 1) |
|||
{ |
|||
switch (options.BitsPerSample[0]) |
|||
{ |
|||
case 8: |
|||
{ |
|||
options.ColorType = TiffColorType.BlackIsZero8; |
|||
break; |
|||
} |
|||
|
|||
case 4: |
|||
{ |
|||
options.ColorType = TiffColorType.BlackIsZero4; |
|||
break; |
|||
} |
|||
|
|||
case 1: |
|||
{ |
|||
options.ColorType = TiffColorType.BlackIsZero1; |
|||
break; |
|||
} |
|||
|
|||
default: |
|||
{ |
|||
options.ColorType = TiffColorType.BlackIsZero; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case TiffPhotometricInterpretation.Rgb: |
|||
{ |
|||
if (options.BitsPerSample.Length == 3) |
|||
{ |
|||
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) |
|||
{ |
|||
if (options.BitsPerSample[0] == 8 && options.BitsPerSample[1] == 8 && options.BitsPerSample[2] == 8) |
|||
{ |
|||
options.ColorType = TiffColorType.Rgb888; |
|||
} |
|||
else |
|||
{ |
|||
options.ColorType = TiffColorType.Rgb; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
options.ColorType = TiffColorType.RgbPlanar; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case TiffPhotometricInterpretation.PaletteColor: |
|||
{ |
|||
options.ColorMap = entries.ColorMap; |
|||
if (options.ColorMap != null) |
|||
{ |
|||
if (options.BitsPerSample.Length == 1) |
|||
{ |
|||
switch (options.BitsPerSample[0]) |
|||
{ |
|||
default: |
|||
{ |
|||
options.ColorType = TiffColorType.PaletteColor; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new ImageFormatException("The TIFF ColorMap entry is missing for a palette color image."); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
default: |
|||
throw new NotSupportedException("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation); |
|||
} |
|||
} |
|||
|
|||
private static void ParseBitsPerSample(this TiffDecoderCore options, TiffFrameMetadata entries) |
|||
{ |
|||
options.BitsPerSample = entries.BitsPerSample; |
|||
if (options.BitsPerSample == null) |
|||
{ |
|||
if (options.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero |
|||
|| options.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) |
|||
{ |
|||
options.BitsPerSample = new[] { (ushort)1 }; |
|||
} |
|||
else |
|||
{ |
|||
throw new ImageFormatException("The TIFF BitsPerSample entry is missing."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void ParsePhotometric(this TiffDecoderCore options, TiffFrameMetadata entries) |
|||
{ |
|||
/* |
|||
if (!entries.TryGetSingleNumber(ExifTag.PhotometricInterpretation, out uint photometricInterpretation)) |
|||
{ |
|||
if (entries.Compression == TiffCompression.Ccitt1D) |
|||
{ |
|||
photometricInterpretation = (uint)TiffPhotometricInterpretation.WhiteIsZero; |
|||
} |
|||
else |
|||
{ |
|||
throw new ImageFormatException("The TIFF photometric interpretation entry is missing."); |
|||
} |
|||
} |
|||
|
|||
options.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretation; |
|||
/* */ |
|||
|
|||
// There is no default for PhotometricInterpretation, and it is required.
|
|||
options.PhotometricInterpretation = entries.PhotometricInterpretation; |
|||
} |
|||
|
|||
private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression) |
|||
{ |
|||
switch (compression) |
|||
{ |
|||
case TiffCompression.None: |
|||
{ |
|||
options.CompressionType = TiffCompressionType.None; |
|||
break; |
|||
} |
|||
|
|||
case TiffCompression.PackBits: |
|||
{ |
|||
options.CompressionType = TiffCompressionType.PackBits; |
|||
break; |
|||
} |
|||
|
|||
case TiffCompression.Deflate: |
|||
case TiffCompression.OldDeflate: |
|||
{ |
|||
options.CompressionType = TiffCompressionType.Deflate; |
|||
break; |
|||
} |
|||
|
|||
case TiffCompression.Lzw: |
|||
{ |
|||
options.CompressionType = TiffCompressionType.Lzw; |
|||
break; |
|||
} |
|||
|
|||
default: |
|||
{ |
|||
throw new NotSupportedException("The specified TIFF compression format is not supported: " + compression); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Encoder for writing the data image to a stream in TIFF format.
|
|||
/// </summary>
|
|||
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var encode = new TiffEncoderCore(this); |
|||
encode.Encode(image, stream); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,165 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Performs the TIFF encoding operation.
|
|||
/// </summary>
|
|||
internal sealed class TiffEncoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The options for the encoder.</param>
|
|||
public TiffEncoderCore(ITiffEncoderOptions options) |
|||
{ |
|||
options = options ?? new TiffEncoder(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the photometric interpretation implementation to use when encoding the image.
|
|||
/// </summary>
|
|||
public TiffColorType ColorType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression implementation to use when encoding the image.
|
|||
/// </summary>
|
|||
public TiffCompressionType CompressionType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(image, nameof(image)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var writer = new TiffWriter(stream)) |
|||
{ |
|||
long firstIfdMarker = this.WriteHeader(writer); |
|||
//// todo: multiframing is not support
|
|||
long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the TIFF file header.
|
|||
/// </summary>
|
|||
/// <param name="writer">The <see cref="TiffWriter"/> to write data to.</param>
|
|||
/// <returns>The marker to write the first IFD offset.</returns>
|
|||
public long WriteHeader(TiffWriter writer) |
|||
{ |
|||
ushort byteOrderMarker = BitConverter.IsLittleEndian |
|||
? TiffConstants.ByteOrderLittleEndianShort |
|||
: TiffConstants.ByteOrderBigEndianShort; |
|||
|
|||
writer.Write(byteOrderMarker); |
|||
writer.Write((ushort)42); |
|||
long firstIfdMarker = writer.PlaceMarker(); |
|||
|
|||
return firstIfdMarker; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a TIFF IFD block.
|
|||
/// </summary>
|
|||
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</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>
|
|||
public long WriteIfd(TiffWriter writer, List<IExifValue> entries) |
|||
{ |
|||
if (entries.Count == 0) |
|||
{ |
|||
throw new ArgumentException("There must be at least one entry per IFD.", nameof(entries)); |
|||
} |
|||
|
|||
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); |
|||
var largeDataBlocks = new List<byte[]>(); |
|||
|
|||
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); |
|||
|
|||
writer.Write((ushort)entries.Count); |
|||
|
|||
foreach (ExifValue entry in entries) |
|||
{ |
|||
writer.Write((ushort)entry.Tag); |
|||
writer.Write((ushort)entry.DataType); |
|||
writer.Write(ExifWriter.GetNumberOfComponents(entry)); |
|||
|
|||
uint lenght = ExifWriter.GetLength(entry); |
|||
var raw = new byte[lenght]; |
|||
int sz = ExifWriter.WriteValue(entry, raw, 0); |
|||
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); |
|||
if (raw.Length <= 4) |
|||
{ |
|||
writer.WritePadded(raw); |
|||
} |
|||
else |
|||
{ |
|||
largeDataBlocks.Add(raw); |
|||
writer.Write(dataOffset); |
|||
dataOffset += (uint)(raw.Length + (raw.Length % 2)); |
|||
} |
|||
} |
|||
|
|||
long nextIfdMarker = writer.PlaceMarker(); |
|||
|
|||
foreach (byte[] dataBlock in largeDataBlocks) |
|||
{ |
|||
writer.Write(dataBlock); |
|||
|
|||
if (dataBlock.Length % 2 == 1) |
|||
{ |
|||
writer.Write((byte)0); |
|||
} |
|||
} |
|||
|
|||
return nextIfdMarker; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes all data required to define an image
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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="ifdOffset">The marker to write this IFD offset.</param>
|
|||
/// <returns>The marker to write the next IFD offset (if present).</returns>
|
|||
public long WriteImage<TPixel>(TiffWriter writer, Image<TPixel> image, long ifdOffset) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var ifdEntries = new List<IExifValue>(); |
|||
|
|||
this.AddImageFormat(image, ifdEntries); |
|||
|
|||
writer.WriteMarker(ifdOffset, (uint)writer.Position); |
|||
long nextIfdMarker = this.WriteIfd(writer, ifdEntries); |
|||
|
|||
return nextIfdMarker; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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="ifdEntries">The image format entries to add to the IFD.</param>
|
|||
public void AddImageFormat<TPixel>(Image<TPixel> image, List<IExifValue> ifdEntries) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the means to encode and decode Tiff images.
|
|||
/// </summary>
|
|||
public class TiffFormat : IImageFormat<TiffMetadata, TiffFrameMetadata> |
|||
{ |
|||
private TiffFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static TiffFormat Instance { get; } = new TiffFormat(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "TIFF"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/tiff"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => TiffConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => TiffConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata(); |
|||
} |
|||
} |
|||
@ -0,0 +1,331 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Provides Tiff specific metadata information for the frame.
|
|||
/// </summary>
|
|||
public class TiffFrameMetadata : IDeepCloneable |
|||
{ |
|||
private const TiffResolutionUnit DefaultResolutionUnit = TiffResolutionUnit.Inch; |
|||
|
|||
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; |
|||
|
|||
private const TiffPredictor DefaultPredictor = TiffPredictor.None; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
|
|||
/// </summary>
|
|||
public TiffFrameMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Tiff directory tags list.
|
|||
/// </summary>
|
|||
public IList<IExifValue> Tags { get; set; } |
|||
|
|||
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
|
|||
/// <value>A general indication of the kind of data contained in this subfile.</value>
|
|||
public TiffNewSubfileType NewSubfileType => this.GetSingleEnum<TiffNewSubfileType, uint>(ExifTag.SubfileType, TiffNewSubfileType.FullImage); |
|||
|
|||
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
|
|||
/// <value>A general indication of the kind of data contained in this subfile.</value>
|
|||
public TiffSubfileType? SubfileType => this.GetSingleEnumNullable<TiffSubfileType, uint>(ExifTag.OldSubfileType); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of columns in the image, i.e., the number of pixels per row.
|
|||
/// </summary>
|
|||
public uint Width => this.GetSingle<uint>(ExifTag.ImageWidth); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of rows of pixels in the image.
|
|||
/// </summary>
|
|||
public uint Height => this.GetSingle<uint>(ExifTag.ImageLength); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bits per component.
|
|||
/// </summary>
|
|||
public ushort[] BitsPerSample => this.GetArray<ushort>(ExifTag.BitsPerSample, true); |
|||
|
|||
/// <summary>Gets the compression scheme used on the image data.</summary>
|
|||
/// <value>The compression scheme used on the image data.</value>
|
|||
public TiffCompression Compression => this.GetSingleEnum<TiffCompression, ushort>(ExifTag.Compression); |
|||
|
|||
/// <summary>
|
|||
/// Gets the color space of the image data.
|
|||
/// </summary>
|
|||
public TiffPhotometricInterpretation PhotometricInterpretation => this.GetSingleEnum<TiffPhotometricInterpretation, ushort>(ExifTag.PhotometricInterpretation); |
|||
|
|||
/// <summary>
|
|||
/// Gets the logical order of bits within a byte.
|
|||
/// </summary>
|
|||
internal TiffFillOrder FillOrder => this.GetSingleEnum<TiffFillOrder, ushort>(ExifTag.FillOrder, TiffFillOrder.MostSignificantBitFirst); |
|||
|
|||
/// <summary>
|
|||
/// Gets the a string that describes the subject of the image.
|
|||
/// </summary>
|
|||
public string ImageDescription => this.GetString(ExifTag.ImageDescription); |
|||
|
|||
/// <summary>
|
|||
/// Gets the scanner manufacturer.
|
|||
/// </summary>
|
|||
public string Make => this.GetString(ExifTag.Make); |
|||
|
|||
/// <summary>
|
|||
/// Gets the scanner model name or number.
|
|||
/// </summary>
|
|||
public string Model => this.GetString(ExifTag.Model); |
|||
|
|||
/// <summary>Gets for each strip, the byte offset of that strip..</summary>
|
|||
public uint[] StripOffsets => this.GetArray<uint>(ExifTag.StripOffsets); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of components per pixel.
|
|||
/// </summary>
|
|||
public ushort SamplesPerPixel => this.GetSingle<ushort>(ExifTag.SamplesPerPixel); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of rows per strip.
|
|||
/// </summary>
|
|||
public uint RowsPerStrip => this.GetSingle<uint>(ExifTag.RowsPerStrip); |
|||
|
|||
/// <summary>
|
|||
/// Gets for each strip, the number of bytes in the strip after compression.
|
|||
/// </summary>
|
|||
public uint[] StripByteCounts => this.GetArray<uint>(ExifTag.StripByteCounts); |
|||
|
|||
/// <summary>Gets the resolution of the image in x- direction.</summary>
|
|||
/// <value>The density of the image in x- direction.</value>
|
|||
public double? HorizontalResolution |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ResolutionUnit != TiffResolutionUnit.None) |
|||
{ |
|||
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0; |
|||
|
|||
if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution)) |
|||
{ |
|||
return xResolution.ToDouble() * resolutionUnitFactor; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the resolution of the image in y- direction.
|
|||
/// </summary>
|
|||
/// <value>The density of the image in y- direction.</value>
|
|||
public double? VerticalResolution |
|||
{ |
|||
get |
|||
{ |
|||
if (this.ResolutionUnit != TiffResolutionUnit.None) |
|||
{ |
|||
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0; |
|||
|
|||
if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution)) |
|||
{ |
|||
return yResolution.ToDouble() * resolutionUnitFactor; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets how the components of each pixel are stored.
|
|||
/// </summary>
|
|||
public TiffPlanarConfiguration PlanarConfiguration => this.GetSingleEnum<TiffPlanarConfiguration, ushort>(ExifTag.PlanarConfiguration, DefaultPlanarConfiguration); |
|||
|
|||
/// <summary>
|
|||
/// Gets the unit of measurement for XResolution and YResolution.
|
|||
/// </summary>
|
|||
public TiffResolutionUnit ResolutionUnit => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit); |
|||
|
|||
/// <summary>
|
|||
/// Gets the name and version number of the software package(s) used to create the image.
|
|||
/// </summary>
|
|||
public string Software => this.GetString(ExifTag.Software); |
|||
|
|||
/// <summary>
|
|||
/// Gets the date and time of image creation.
|
|||
/// </summary>
|
|||
public string DateTime => this.GetString(ExifTag.DateTime); |
|||
|
|||
/// <summary>
|
|||
/// Gets the person who created the image.
|
|||
/// </summary>
|
|||
public string Artist => this.GetString(ExifTag.Artist); |
|||
|
|||
/// <summary>
|
|||
/// Gets the computer and/or operating system in use at the time of image creation.
|
|||
/// </summary>
|
|||
public string HostComputer => this.GetString(ExifTag.HostComputer); |
|||
|
|||
/// <summary>
|
|||
/// Gets a color map for palette color images.
|
|||
/// </summary>
|
|||
public ushort[] ColorMap => this.GetArray<ushort>(ExifTag.ColorMap, true); |
|||
|
|||
/// <summary>
|
|||
/// Gets the description of extra components.
|
|||
/// </summary>
|
|||
public ushort[] ExtraSamples => this.GetArray<ushort>(ExifTag.ExtraSamples, true); |
|||
|
|||
/// <summary>
|
|||
/// Gets the copyright notice.
|
|||
/// </summary>
|
|||
public string Copyright => this.GetString(ExifTag.Copyright); |
|||
|
|||
/// <summary>
|
|||
/// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied.
|
|||
/// </summary>
|
|||
public TiffPredictor Predictor => this.GetSingleEnum<TiffPredictor, ushort>(ExifTag.Predictor, DefaultPredictor); |
|||
|
|||
/// <summary>
|
|||
/// Gets the specifies how to interpret each data sample in a pixel.
|
|||
/// <see cref="SamplesPerPixel"/>
|
|||
/// </summary>
|
|||
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true); |
|||
|
|||
internal T[] GetArray<T>(ExifTag tag, bool optional = false) |
|||
where T : struct |
|||
{ |
|||
if (this.TryGetArray(tag, out T[] result)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
if (!optional) |
|||
{ |
|||
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private bool TryGetArray<T>(ExifTag tag, out T[] result) |
|||
where T : struct |
|||
{ |
|||
foreach (IExifValue entry in this.Tags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(entry.IsArray, "Expected array entry"); |
|||
|
|||
result = (T[])entry.GetValue(); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = null; |
|||
return false; |
|||
} |
|||
|
|||
private TEnum[] GetEnumArray<TEnum, TTagValue>(ExifTag tag, bool optional = false) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
{ |
|||
if (this.TryGetArray(tag, out TTagValue[] result)) |
|||
{ |
|||
// todo: improve
|
|||
return result.Select(a => (TEnum)(object)a).ToArray(); |
|||
} |
|||
|
|||
if (!optional) |
|||
{ |
|||
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private string GetString(ExifTag tag) |
|||
{ |
|||
foreach (IExifValue entry in this.Tags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry"); |
|||
object value = entry.GetValue(); |
|||
DebugGuard.IsTrue(value is string, "Expected string entry"); |
|||
|
|||
return (string)value; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private TEnum? GetSingleEnumNullable<TEnum, TTagValue>(ExifTag tag) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
{ |
|||
if (!this.TryGetSingle(tag, out TTagValue value)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return (TEnum)(object)value; |
|||
} |
|||
|
|||
private TEnum GetSingleEnum<TEnum, TTagValue>(ExifTag tag, TEnum? defaultValue = null) |
|||
where TEnum : struct |
|||
where TTagValue : struct |
|||
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag))); |
|||
|
|||
private T GetSingle<T>(ExifTag tag) |
|||
where T : struct |
|||
{ |
|||
if (this.TryGetSingle(tag, out T result)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)); |
|||
} |
|||
|
|||
private bool TryGetSingle<T>(ExifTag tag, out T result) |
|||
where T : struct |
|||
{ |
|||
foreach (IExifValue entry in this.Tags) |
|||
{ |
|||
if (entry.Tag == tag) |
|||
{ |
|||
DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry"); |
|||
|
|||
object value = entry.GetValue(); |
|||
|
|||
result = (T)value; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
result = default; |
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() |
|||
{ |
|||
var tags = new List<IExifValue>(); |
|||
foreach (IExifValue entry in this.Tags) |
|||
{ |
|||
tags.Add(entry.DeepClone()); |
|||
} |
|||
|
|||
return new TiffFrameMetadata() { Tags = tags }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Detects tiff file headers
|
|||
/// </summary>
|
|||
public sealed class TiffImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
if (this.IsSupportedFileFormat(header)) |
|||
{ |
|||
return TiffFormat.Instance; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
return header.Length >= this.HeaderSize && |
|||
((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian
|
|||
(header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Provides Tiff specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class TiffMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
|
|||
/// </summary>
|
|||
public TiffMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private TiffMetadata(TiffMetadata other) |
|||
{ |
|||
this.ByteOrder = other.ByteOrder; |
|||
this.XmpProfile = other.XmpProfile; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the byte order.
|
|||
/// </summary>
|
|||
public TiffByteOrder ByteOrder { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the XMP profile.
|
|||
/// </summary>
|
|||
public byte[] XmpProfile { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new TiffMetadata(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class to read a sequence of bits from an array
|
|||
/// </summary>
|
|||
internal ref struct BitReader |
|||
{ |
|||
private readonly ReadOnlySpan<byte> array; |
|||
private int offset; |
|||
private int bitOffset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BitReader" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to read data from.</param>
|
|||
public BitReader(ReadOnlySpan<byte> array) |
|||
{ |
|||
this.array = array; |
|||
this.offset = 0; |
|||
this.bitOffset = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the specified number of bits from the array.
|
|||
/// </summary>
|
|||
/// <param name="bits">The number of bits to read.</param>
|
|||
/// <returns>The value read from the array.</returns>
|
|||
public int ReadBits(uint bits) |
|||
{ |
|||
int value = 0; |
|||
|
|||
for (uint i = 0; i < bits; i++) |
|||
{ |
|||
int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01; |
|||
value = (value << 1) | bit; |
|||
|
|||
this.bitOffset++; |
|||
|
|||
if (this.bitOffset == 8) |
|||
{ |
|||
this.bitOffset = 0; |
|||
this.offset++; |
|||
} |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Moves the reader to the next row of byte-aligned data.
|
|||
/// </summary>
|
|||
public void NextRow() |
|||
{ |
|||
if (this.bitOffset > 0) |
|||
{ |
|||
this.bitOffset = 0; |
|||
this.offset++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class to encapsulate a sub-portion of another <see cref="Stream"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Note that disposing of the <see cref="SubStream"/> does not dispose the underlying
|
|||
/// <see cref="Stream"/>.
|
|||
/// </remarks>
|
|||
internal class SubStream : Stream |
|||
{ |
|||
private Stream innerStream; |
|||
private long offset; |
|||
private long endOffset; |
|||
private long length; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SubStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="innerStream">The underlying <see cref="Stream"/> to wrap.</param>
|
|||
/// <param name="length">The length of the sub-stream.</param>
|
|||
/// <remarks>
|
|||
/// Note that calling the sub-stream with start from the current offset of the
|
|||
/// underlying <see cref="Stream"/>
|
|||
/// </remarks>
|
|||
public SubStream(Stream innerStream, long length) |
|||
{ |
|||
this.innerStream = innerStream; |
|||
this.offset = this.innerStream.Position; |
|||
this.endOffset = this.offset + length; |
|||
this.length = length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SubStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="innerStream">The underlying <see cref="Stream"/> to wrap.</param>
|
|||
/// <param name="offset">The offset of the sub-stream within the underlying <see cref="Stream"/>.</param>
|
|||
/// <param name="length">The length of the sub-stream.</param>
|
|||
/// <remarks>
|
|||
/// Note that calling the constructor will immediately move the underlying
|
|||
/// <see cref="Stream"/> to the specified offset.
|
|||
/// </remarks>
|
|||
public SubStream(Stream innerStream, long offset, long length) |
|||
{ |
|||
this.innerStream = innerStream; |
|||
this.offset = offset; |
|||
this.endOffset = offset + length; |
|||
this.length = length; |
|||
|
|||
innerStream.Seek(offset, SeekOrigin.Begin); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanRead |
|||
{ |
|||
get |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanWrite |
|||
{ |
|||
get |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanSeek |
|||
{ |
|||
get |
|||
{ |
|||
return this.innerStream.CanSeek; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Length |
|||
{ |
|||
get |
|||
{ |
|||
return this.length; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Position |
|||
{ |
|||
get |
|||
{ |
|||
return this.innerStream.Position - this.offset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
this.Seek(value, SeekOrigin.Begin); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Flush() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
long bytesRemaining = this.endOffset - this.innerStream.Position; |
|||
|
|||
if (bytesRemaining < count) |
|||
{ |
|||
count = (int)bytesRemaining; |
|||
} |
|||
|
|||
return this.innerStream.Read(buffer, offset, count); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int ReadByte() |
|||
{ |
|||
if (this.innerStream.Position < this.endOffset) |
|||
{ |
|||
return this.innerStream.ReadByte(); |
|||
} |
|||
else |
|||
{ |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Write(byte[] array, int offset, int count) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void WriteByte(byte value) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Seek(long offset, SeekOrigin origin) |
|||
{ |
|||
switch (origin) |
|||
{ |
|||
case SeekOrigin.Current: |
|||
return this.innerStream.Seek(offset, SeekOrigin.Current) - this.offset; |
|||
case SeekOrigin.Begin: |
|||
return this.innerStream.Seek(this.offset + offset, SeekOrigin.Begin) - this.offset; |
|||
case SeekOrigin.End: |
|||
return this.innerStream.Seek(this.endOffset - offset, SeekOrigin.Begin) - this.offset; |
|||
default: |
|||
throw new ArgumentException("Invalid seek origin."); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void SetLength(long value) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Decompresses and decodes data using the dynamic LZW algorithms.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This code is based on the <see cref="LzwDecoder"/> used for GIF decoding. There is potential
|
|||
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
|||
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
|
|||
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
|
|||
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
|
|||
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
|
|||
/// </remarks>
|
|||
internal sealed class TiffLzwDecoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The max decoder pixel stack size.
|
|||
/// </summary>
|
|||
private const int MaxStackSize = 4096; |
|||
|
|||
/// <summary>
|
|||
/// The null code.
|
|||
/// </summary>
|
|||
private const int NullCode = -1; |
|||
|
|||
/// <summary>
|
|||
/// The stream to decode.
|
|||
/// </summary>
|
|||
private readonly Stream stream; |
|||
|
|||
/// <summary>
|
|||
/// The prefix buffer.
|
|||
/// </summary>
|
|||
private readonly int[] prefix; |
|||
|
|||
/// <summary>
|
|||
/// The suffix buffer.
|
|||
/// </summary>
|
|||
private readonly int[] suffix; |
|||
|
|||
/// <summary>
|
|||
/// The pixel stack buffer.
|
|||
/// </summary>
|
|||
private readonly int[] pixelStack; |
|||
|
|||
/// <summary>
|
|||
/// A value indicating whether this instance of the given entity has been disposed.
|
|||
/// </summary>
|
|||
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
|||
/// <remarks>
|
|||
/// If the entity is disposed, it must not be disposed a second
|
|||
/// time. The isDisposed field is set the first time the entity
|
|||
/// is disposed. If the isDisposed field is true, then the Dispose()
|
|||
/// method will not dispose again. This help not to prolong the entity's
|
|||
/// life in the Garbage Collector.
|
|||
/// </remarks>
|
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffLzwDecoder"/> class
|
|||
/// and sets the stream, where the compressed data should be read from.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
|
|||
public TiffLzwDecoder(Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
this.stream = stream; |
|||
|
|||
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize); |
|||
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize); |
|||
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1); |
|||
|
|||
Array.Clear(this.prefix, 0, MaxStackSize); |
|||
Array.Clear(this.suffix, 0, MaxStackSize); |
|||
Array.Clear(this.pixelStack, 0, MaxStackSize + 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes and decompresses all pixel indices from the stream.
|
|||
/// </summary>
|
|||
/// <param name="length">The length of the compressed data.</param>
|
|||
/// <param name="dataSize">Size of the data.</param>
|
|||
/// <param name="pixels">The pixel array to decode to.</param>
|
|||
public void DecodePixels(int length, int dataSize, Span<byte> pixels) |
|||
{ |
|||
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); |
|||
|
|||
// Calculate the clear code. The value of the clear code is 2 ^ dataSize
|
|||
int clearCode = 1 << dataSize; |
|||
|
|||
int codeSize = dataSize + 1; |
|||
|
|||
// Calculate the end code
|
|||
int endCode = clearCode + 1; |
|||
|
|||
// Calculate the available code.
|
|||
int availableCode = clearCode + 2; |
|||
|
|||
// Jillzhangs Code see: http://giflib.codeplex.com/
|
|||
// Adapted from John Cristy's ImageMagick.
|
|||
int code; |
|||
int oldCode = NullCode; |
|||
int codeMask = (1 << codeSize) - 1; |
|||
int bits = 0; |
|||
|
|||
int top = 0; |
|||
int count = 0; |
|||
int bi = 0; |
|||
int xyz = 0; |
|||
|
|||
int data = 0; |
|||
int first = 0; |
|||
|
|||
for (code = 0; code < clearCode; code++) |
|||
{ |
|||
this.prefix[code] = 0; |
|||
this.suffix[code] = (byte)code; |
|||
} |
|||
|
|||
byte[] buffer = new byte[255]; |
|||
while (xyz < length) |
|||
{ |
|||
if (top == 0) |
|||
{ |
|||
if (bits < codeSize) |
|||
{ |
|||
// Load bytes until there are enough bits for a code.
|
|||
if (count == 0) |
|||
{ |
|||
// Read a new data block.
|
|||
count = this.ReadBlock(buffer); |
|||
if (count == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
bi = 0; |
|||
} |
|||
|
|||
data += buffer[bi] << bits; |
|||
|
|||
bits += 8; |
|||
bi++; |
|||
count--; |
|||
continue; |
|||
} |
|||
|
|||
// Get the next code
|
|||
code = data & codeMask; |
|||
data >>= codeSize; |
|||
bits -= codeSize; |
|||
|
|||
// Interpret the code
|
|||
if (code > availableCode || code == endCode) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
if (code == clearCode) |
|||
{ |
|||
// Reset the decoder
|
|||
codeSize = dataSize + 1; |
|||
codeMask = (1 << codeSize) - 1; |
|||
availableCode = clearCode + 2; |
|||
oldCode = NullCode; |
|||
continue; |
|||
} |
|||
|
|||
if (oldCode == NullCode) |
|||
{ |
|||
this.pixelStack[top++] = this.suffix[code]; |
|||
oldCode = code; |
|||
first = code; |
|||
continue; |
|||
} |
|||
|
|||
int inCode = code; |
|||
if (code == availableCode) |
|||
{ |
|||
this.pixelStack[top++] = (byte)first; |
|||
|
|||
code = oldCode; |
|||
} |
|||
|
|||
while (code > clearCode) |
|||
{ |
|||
this.pixelStack[top++] = this.suffix[code]; |
|||
code = this.prefix[code]; |
|||
} |
|||
|
|||
first = this.suffix[code]; |
|||
|
|||
this.pixelStack[top++] = this.suffix[code]; |
|||
|
|||
// Fix for Gifs that have "deferred clear code" as per here :
|
|||
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
|
|||
if (availableCode < MaxStackSize) |
|||
{ |
|||
this.prefix[availableCode] = oldCode; |
|||
this.suffix[availableCode] = first; |
|||
availableCode++; |
|||
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) |
|||
{ |
|||
codeSize++; |
|||
codeMask = (1 << codeSize) - 1; |
|||
} |
|||
} |
|||
|
|||
oldCode = inCode; |
|||
} |
|||
|
|||
// Pop a pixel off the pixel stack.
|
|||
top--; |
|||
|
|||
// Clear missing pixels
|
|||
pixels[xyz++] = (byte)this.pixelStack[top]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|||
this.Dispose(true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the next data block from the stream. For consistency with the GIF decoder,
|
|||
/// the image is read in blocks - For TIFF this is always a maximum of 255
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to store the block in.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="T:byte[]"/>.
|
|||
/// </returns>
|
|||
private int ReadBlock(byte[] buffer) |
|||
{ |
|||
return this.stream.Read(buffer, 0, 255); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes the object and frees resources for the Garbage Collector.
|
|||
/// </summary>
|
|||
/// <param name="disposing">If true, the object gets disposed.</param>
|
|||
private void Dispose(bool disposing) |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (disposing) |
|||
{ |
|||
ArrayPool<int>.Shared.Return(this.prefix); |
|||
ArrayPool<int>.Shared.Return(this.suffix); |
|||
ArrayPool<int>.Shared.Return(this.pixelStack); |
|||
} |
|||
|
|||
this.isDisposed = true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,495 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
|
|||
/// <para>
|
|||
/// GIFCOMPR.C - GIF Image compression routines
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
|||
/// David Rowley (mgardi@watdcsu.waterloo.edu)
|
|||
/// </para>
|
|||
/// GIF Image compression - modified 'compress'
|
|||
/// <para>
|
|||
/// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
|||
/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
|||
/// Jim McKie (decvax!mcvax!jim)
|
|||
/// Steve Davies (decvax!vax135!petsd!peora!srd)
|
|||
/// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
|||
/// James A. Woods (decvax!ihnp4!ames!jaw)
|
|||
/// Joe Orost (decvax!vax135!petsd!joe)
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
|
|||
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
|||
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
|
|||
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
|
|||
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
|
|||
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
internal sealed class TiffLzwEncoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The end-of-file marker
|
|||
/// </summary>
|
|||
private const int Eof = -1; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of bits.
|
|||
/// </summary>
|
|||
private const int Bits = 12; |
|||
|
|||
/// <summary>
|
|||
/// 80% occupancy
|
|||
/// </summary>
|
|||
private const int HashSize = 5003; |
|||
|
|||
/// <summary>
|
|||
/// Mask used when shifting pixel values
|
|||
/// </summary>
|
|||
private static readonly int[] Masks = |
|||
{ |
|||
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, |
|||
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// The working pixel array
|
|||
/// </summary>
|
|||
private readonly byte[] pixelArray; |
|||
|
|||
/// <summary>
|
|||
/// The initial code size.
|
|||
/// </summary>
|
|||
private readonly int initialCodeSize; |
|||
|
|||
/// <summary>
|
|||
/// The hash table.
|
|||
/// </summary>
|
|||
private readonly int[] hashTable; |
|||
|
|||
/// <summary>
|
|||
/// The code table.
|
|||
/// </summary>
|
|||
private readonly int[] codeTable; |
|||
|
|||
/// <summary>
|
|||
/// Define the storage for the packet accumulator.
|
|||
/// </summary>
|
|||
private readonly byte[] accumulators = new byte[256]; |
|||
|
|||
/// <summary>
|
|||
/// A value indicating whether this instance of the given entity has been disposed.
|
|||
/// </summary>
|
|||
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
|||
/// <remarks>
|
|||
/// If the entity is disposed, it must not be disposed a second
|
|||
/// time. The isDisposed field is set the first time the entity
|
|||
/// is disposed. If the isDisposed field is true, then the Dispose()
|
|||
/// method will not dispose again. This help not to prolong the entity's
|
|||
/// life in the Garbage Collector.
|
|||
/// </remarks>
|
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// The current pixel
|
|||
/// </summary>
|
|||
private int currentPixel; |
|||
|
|||
/// <summary>
|
|||
/// Number of bits/code
|
|||
/// </summary>
|
|||
private int bitCount; |
|||
|
|||
/// <summary>
|
|||
/// User settable max # bits/code
|
|||
/// </summary>
|
|||
private int maxbits = Bits; |
|||
|
|||
/// <summary>
|
|||
/// maximum code, given bitCount
|
|||
/// </summary>
|
|||
private int maxcode; |
|||
|
|||
/// <summary>
|
|||
/// should NEVER generate this code
|
|||
/// </summary>
|
|||
private int maxmaxcode = 1 << Bits; |
|||
|
|||
/// <summary>
|
|||
/// For dynamic table sizing
|
|||
/// </summary>
|
|||
private int hsize = HashSize; |
|||
|
|||
/// <summary>
|
|||
/// First unused entry
|
|||
/// </summary>
|
|||
private int freeEntry; |
|||
|
|||
/// <summary>
|
|||
/// Block compression parameters -- after all codes are used up,
|
|||
/// and compression rate changes, start over.
|
|||
/// </summary>
|
|||
private bool clearFlag; |
|||
|
|||
/// <summary>
|
|||
/// Algorithm: use 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 special CLEAR code is generated
|
|||
/// for the decompressor. Late addition: construct the table according to
|
|||
/// file size for noticeable speed improvement on small files. Please direct
|
|||
/// questions about this implementation to ames!jaw.
|
|||
/// </summary>
|
|||
private int globalInitialBits; |
|||
|
|||
/// <summary>
|
|||
/// The clear code.
|
|||
/// </summary>
|
|||
private int clearCode; |
|||
|
|||
/// <summary>
|
|||
/// The end-of-file code.
|
|||
/// </summary>
|
|||
private int eofCode; |
|||
|
|||
/// <summary>
|
|||
/// Output the given code.
|
|||
/// Inputs:
|
|||
/// code: A bitCount-bit integer. If == -1, then EOF. This assumes
|
|||
/// that bitCount =< wordsize - 1.
|
|||
/// Outputs:
|
|||
/// Outputs code to the file.
|
|||
/// Assumptions:
|
|||
/// Chars are 8 bits long.
|
|||
/// Algorithm:
|
|||
/// Maintain a BITS character long buffer (so that 8 codes will
|
|||
/// fit in it exactly). Use the VAX insv instruction to insert each
|
|||
/// code in turn. When the buffer fills up empty it and start over.
|
|||
/// </summary>
|
|||
private int currentAccumulator; |
|||
|
|||
/// <summary>
|
|||
/// The current bits.
|
|||
/// </summary>
|
|||
private int currentBits; |
|||
|
|||
/// <summary>
|
|||
/// Number of characters so far in this 'packet'
|
|||
/// </summary>
|
|||
private int accumulatorCount; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="indexedPixels">The array of indexed pixels.</param>
|
|||
/// <param name="colorDepth">The color depth in bits.</param>
|
|||
public TiffLzwEncoder(byte[] indexedPixels, int colorDepth) |
|||
{ |
|||
this.pixelArray = indexedPixels; |
|||
this.initialCodeSize = Math.Max(2, colorDepth); |
|||
|
|||
this.hashTable = ArrayPool<int>.Shared.Rent(HashSize); |
|||
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize); |
|||
Array.Clear(this.hashTable, 0, HashSize); |
|||
Array.Clear(this.codeTable, 0, HashSize); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and compresses the indexed pixels to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
public void Encode(Stream stream) |
|||
{ |
|||
this.currentPixel = 0; |
|||
|
|||
// Compress and write the pixel data
|
|||
this.Compress(this.initialCodeSize + 1, stream); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|||
this.Dispose(true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum code value
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of bits</param>
|
|||
/// <returns>See <see cref="int"/></returns>
|
|||
private static int GetMaxcode(int bitCount) |
|||
{ |
|||
return (1 << bitCount) - 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add a character to the end of the current packet, and if it is 254 characters,
|
|||
/// flush the packet to disk.
|
|||
/// </summary>
|
|||
/// <param name="c">The character to add.</param>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
private void AddCharacter(byte c, Stream stream) |
|||
{ |
|||
this.accumulators[this.accumulatorCount++] = c; |
|||
if (this.accumulatorCount >= 254) |
|||
{ |
|||
this.FlushPacket(stream); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Table clear for block compress
|
|||
/// </summary>
|
|||
/// <param name="stream">The output stream.</param>
|
|||
private void ClearBlock(Stream stream) |
|||
{ |
|||
this.ResetCodeTable(this.hsize); |
|||
this.freeEntry = this.clearCode + 2; |
|||
this.clearFlag = true; |
|||
|
|||
this.Output(this.clearCode, stream); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset the code table.
|
|||
/// </summary>
|
|||
/// <param name="size">The hash size.</param>
|
|||
private void ResetCodeTable(int size) |
|||
{ |
|||
for (int i = 0; i < size; ++i) |
|||
{ |
|||
this.hashTable[i] = -1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compress the packets to the stream.
|
|||
/// </summary>
|
|||
/// <param name="intialBits">The initial bits.</param>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
private void Compress(int intialBits, Stream stream) |
|||
{ |
|||
int fcode; |
|||
int c; |
|||
int ent; |
|||
int hsizeReg; |
|||
int hshift; |
|||
|
|||
// Set up the globals: globalInitialBits - initial number of bits
|
|||
this.globalInitialBits = intialBits; |
|||
|
|||
// Set up the necessary values
|
|||
this.clearFlag = false; |
|||
this.bitCount = this.globalInitialBits; |
|||
this.maxcode = GetMaxcode(this.bitCount); |
|||
|
|||
this.clearCode = 1 << (intialBits - 1); |
|||
this.eofCode = this.clearCode + 1; |
|||
this.freeEntry = this.clearCode + 2; |
|||
|
|||
this.accumulatorCount = 0; // clear packet
|
|||
|
|||
ent = this.NextPixel(); |
|||
|
|||
hshift = 0; |
|||
for (fcode = this.hsize; fcode < 65536; fcode *= 2) |
|||
{ |
|||
++hshift; |
|||
} |
|||
|
|||
hshift = 8 - hshift; // set hash code range bound
|
|||
|
|||
hsizeReg = this.hsize; |
|||
|
|||
this.ResetCodeTable(hsizeReg); // clear hash table
|
|||
|
|||
this.Output(this.clearCode, stream); |
|||
|
|||
while ((c = this.NextPixel()) != Eof) |
|||
{ |
|||
fcode = (c << this.maxbits) + ent; |
|||
int i = (c << hshift) ^ ent /* = 0 */; |
|||
|
|||
if (this.hashTable[i] == fcode) |
|||
{ |
|||
ent = this.codeTable[i]; |
|||
continue; |
|||
} |
|||
|
|||
// Non-empty slot
|
|||
if (this.hashTable[i] >= 0) |
|||
{ |
|||
int disp = hsizeReg - i; |
|||
if (i == 0) |
|||
{ |
|||
disp = 1; |
|||
} |
|||
|
|||
do |
|||
{ |
|||
if ((i -= disp) < 0) |
|||
{ |
|||
i += hsizeReg; |
|||
} |
|||
|
|||
if (this.hashTable[i] == fcode) |
|||
{ |
|||
ent = this.codeTable[i]; |
|||
break; |
|||
} |
|||
} |
|||
while (this.hashTable[i] >= 0); |
|||
|
|||
if (this.hashTable[i] == fcode) |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
this.Output(ent, stream); |
|||
ent = c; |
|||
if (this.freeEntry < this.maxmaxcode) |
|||
{ |
|||
this.codeTable[i] = this.freeEntry++; // code -> hashtable
|
|||
this.hashTable[i] = fcode; |
|||
} |
|||
else |
|||
{ |
|||
this.ClearBlock(stream); |
|||
} |
|||
} |
|||
|
|||
// Put out the final code.
|
|||
this.Output(ent, stream); |
|||
|
|||
this.Output(this.eofCode, stream); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flush the packet to disk, and reset the accumulator.
|
|||
/// </summary>
|
|||
/// <param name="outStream">The output stream.</param>
|
|||
private void FlushPacket(Stream outStream) |
|||
{ |
|||
if (this.accumulatorCount > 0) |
|||
{ |
|||
outStream.Write(this.accumulators, 0, this.accumulatorCount); |
|||
this.accumulatorCount = 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the next pixel from the image
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The <see cref="int"/>
|
|||
/// </returns>
|
|||
private int NextPixel() |
|||
{ |
|||
if (this.currentPixel == this.pixelArray.Length) |
|||
{ |
|||
return Eof; |
|||
} |
|||
|
|||
this.currentPixel++; |
|||
return this.pixelArray[this.currentPixel - 1] & 0xff; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Output the current code to the stream.
|
|||
/// </summary>
|
|||
/// <param name="code">The code.</param>
|
|||
/// <param name="outs">The stream to write to.</param>
|
|||
private void Output(int code, Stream outs) |
|||
{ |
|||
this.currentAccumulator &= Masks[this.currentBits]; |
|||
|
|||
if (this.currentBits > 0) |
|||
{ |
|||
this.currentAccumulator |= code << this.currentBits; |
|||
} |
|||
else |
|||
{ |
|||
this.currentAccumulator = code; |
|||
} |
|||
|
|||
this.currentBits += this.bitCount; |
|||
|
|||
while (this.currentBits >= 8) |
|||
{ |
|||
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|||
this.currentAccumulator >>= 8; |
|||
this.currentBits -= 8; |
|||
} |
|||
|
|||
// If the next entry is going to be too big for the code size,
|
|||
// then increase it, if possible.
|
|||
if (this.freeEntry > this.maxcode || this.clearFlag) |
|||
{ |
|||
if (this.clearFlag) |
|||
{ |
|||
this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); |
|||
this.clearFlag = false; |
|||
} |
|||
else |
|||
{ |
|||
++this.bitCount; |
|||
this.maxcode = this.bitCount == this.maxbits |
|||
? this.maxmaxcode |
|||
: GetMaxcode(this.bitCount); |
|||
} |
|||
} |
|||
|
|||
if (code == this.eofCode) |
|||
{ |
|||
// At EOF, write the rest of the buffer.
|
|||
while (this.currentBits > 0) |
|||
{ |
|||
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|||
this.currentAccumulator >>= 8; |
|||
this.currentBits -= 8; |
|||
} |
|||
|
|||
this.FlushPacket(outs); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes the object and frees resources for the Garbage Collector.
|
|||
/// </summary>
|
|||
/// <param name="disposing">If true, the object gets disposed.</param>
|
|||
private void Dispose(bool disposing) |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (disposing) |
|||
{ |
|||
ArrayPool<int>.Shared.Return(this.hashTable); |
|||
ArrayPool<int>.Shared.Return(this.codeTable); |
|||
} |
|||
|
|||
this.isDisposed = true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// TIFF specific utilities and extension methods.
|
|||
/// </summary>
|
|||
internal static class TiffUtils |
|||
{ |
|||
/// <summary>
|
|||
/// Reads a sequence of bytes from the input stream into a buffer.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="buffer">A buffer to store the retrieved data.</param>
|
|||
/// <param name="count">The number of bytes to read.</param>
|
|||
public static void ReadFull(this Stream stream, Span<byte> buffer, int count) |
|||
{ |
|||
int offset = 0; |
|||
|
|||
while (count > 0) |
|||
{ |
|||
int bytesRead = stream.Read(buffer, offset, count); |
|||
|
|||
if (bytesRead == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
offset += bytesRead; |
|||
count -= bytesRead; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads all bytes from the input stream into a buffer until the end of stream or the buffer is full.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to read from.</param>
|
|||
/// <param name="buffer">A buffer to store the retrieved data.</param>
|
|||
public static void ReadFull(this Stream stream, Span<byte> buffer) |
|||
{ |
|||
ReadFull(stream, buffer, buffer.Length); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
|
|||
/// </summary>
|
|||
internal class TiffWriter : IDisposable |
|||
{ |
|||
private readonly Stream output; |
|||
|
|||
private readonly byte[] paddingBytes = new byte[4]; |
|||
|
|||
private readonly List<long> references = new List<long>(); |
|||
|
|||
/// <summary>Initializes a new instance of the <see cref="TiffWriter"/> class.</summary>
|
|||
/// <param name="output">The output stream.</param>
|
|||
public TiffWriter(Stream output) |
|||
{ |
|||
this.output = output; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the architecture is little-endian.
|
|||
/// </summary>
|
|||
public bool IsLittleEndian => BitConverter.IsLittleEndian; |
|||
|
|||
/// <summary>
|
|||
/// Gets the current position within the stream.
|
|||
/// </summary>
|
|||
public long Position => this.output.Position; |
|||
|
|||
/// <summary>Writes an empty four bytes to the stream, returning the offset to be written later.</summary>
|
|||
/// <returns>The offset to be written later</returns>
|
|||
public long PlaceMarker() |
|||
{ |
|||
long offset = this.output.Position; |
|||
this.Write(0u); |
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>Writes an array of bytes to the current stream.</summary>
|
|||
/// <param name="value">The bytes to write.</param>
|
|||
public void Write(byte[] value) |
|||
{ |
|||
this.output.Write(value, 0, value.Length); |
|||
} |
|||
|
|||
/// <summary>Writes a byte to the current stream.</summary>
|
|||
/// <param name="value">The byte to write.</param>
|
|||
public void Write(byte value) |
|||
{ |
|||
this.output.Write(new byte[] { value }, 0, 1); |
|||
} |
|||
|
|||
/// <summary>Writes a two-byte unsigned integer to the current stream.</summary>
|
|||
/// <param name="value">The two-byte unsigned integer to write.</param>
|
|||
public void Write(ushort value) |
|||
{ |
|||
byte[] bytes = BitConverter.GetBytes(value); |
|||
this.output.Write(bytes, 0, 2); |
|||
} |
|||
|
|||
/// <summary>Writes a four-byte unsigned integer to the current stream.</summary>
|
|||
/// <param name="value">The four-byte unsigned integer to write.</param>
|
|||
public void Write(uint value) |
|||
{ |
|||
byte[] bytes = BitConverter.GetBytes(value); |
|||
this.output.Write(bytes, 0, 4); |
|||
} |
|||
|
|||
/// <summary>Writes an array of bytes to the current stream, padded to four-bytes.</summary>
|
|||
/// <param name="value">The bytes to write.</param>
|
|||
public void WritePadded(byte[] value) |
|||
{ |
|||
this.output.Write(value, 0, value.Length); |
|||
|
|||
if (value.Length < 4) |
|||
{ |
|||
this.output.Write(this.paddingBytes, 0, 4 - value.Length); |
|||
} |
|||
} |
|||
|
|||
/// <summary>Writes a four-byte unsigned integer to the specified marker in the stream.</summary>
|
|||
/// <param name="offset">The offset returned when placing the marker</param>
|
|||
/// <param name="value">The four-byte unsigned integer to write.</param>
|
|||
public void WriteMarker(long offset, uint value) |
|||
{ |
|||
long currentOffset = this.output.Position; |
|||
this.output.Seek(offset, SeekOrigin.Begin); |
|||
this.Write(value); |
|||
this.output.Seek(currentOffset, SeekOrigin.Begin); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes <see cref="TiffWriter"/> instance, ensuring any unwritten data is flushed.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.output.Flush(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,352 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class for generating TIFF IFD entries.
|
|||
/// </summary>
|
|||
internal static class TiffIfdEntryCreator |
|||
{ |
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Byte' from a unsigned integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedByte(this List<TiffIfdEntry> entries, ushort tag, uint value) |
|||
{ |
|||
TiffIfdEntryCreator.AddUnsignedByte(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Byte' from an array of unsigned integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedByte(this List<TiffIfdEntry> entries, ushort tag, uint[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
bytes[i] = (byte)value[i]; |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Byte, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Short' from a unsigned integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedShort(this List<TiffIfdEntry> entries, ushort tag, uint value) |
|||
{ |
|||
TiffIfdEntryCreator.AddUnsignedShort(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Short' from an array of unsigned integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedShort(this List<TiffIfdEntry> entries, ushort tag, uint[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
ToBytes((ushort)value[i], bytes, i * TiffConstants.SizeOfShort); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Short, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Long' from a unsigned integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedLong(this List<TiffIfdEntry> entries, ushort tag, uint value) |
|||
{ |
|||
TiffIfdEntryCreator.AddUnsignedLong(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Long' from an array of unsigned integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedLong(this List<TiffIfdEntry> entries, ushort tag, uint[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Long, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SByte' from a signed integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedByte(this List<TiffIfdEntry> entries, ushort tag, int value) |
|||
{ |
|||
TiffIfdEntryCreator.AddSignedByte(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SByte' from an array of signed integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedByte(this List<TiffIfdEntry> entries, ushort tag, int[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
bytes[i] = (byte)((sbyte)value[i]); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.SByte, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SShort' from a signed integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedShort(this List<TiffIfdEntry> entries, ushort tag, int value) |
|||
{ |
|||
TiffIfdEntryCreator.AddSignedShort(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SShort' from an array of signed integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedShort(this List<TiffIfdEntry> entries, ushort tag, int[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
ToBytes((short)value[i], bytes, i * TiffConstants.SizeOfShort); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.SShort, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SLong' from a signed integer.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedLong(this List<TiffIfdEntry> entries, ushort tag, int value) |
|||
{ |
|||
TiffIfdEntryCreator.AddSignedLong(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SLong' from an array of signed integers.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedLong(this List<TiffIfdEntry> entries, ushort tag, int[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.SLong, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Ascii' from a string.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddAscii(this List<TiffIfdEntry> entries, ushort tag, string value) |
|||
{ |
|||
byte[] bytes = Encoding.UTF8.GetBytes(value + "\0"); |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Ascii, (uint)bytes.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Rational' from a <see cref="Rational"/>.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedRational(this List<TiffIfdEntry> entries, ushort tag, Rational value) |
|||
{ |
|||
TiffIfdEntryCreator.AddUnsignedRational(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Rational' from an array of <see cref="Rational"/> values.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddUnsignedRational(this List<TiffIfdEntry> entries, ushort tag, Rational[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
int offset = i * TiffConstants.SizeOfRational; |
|||
ToBytes(value[i].Numerator, bytes, offset); |
|||
ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Rational, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SRational' from a <see cref="SignedRational"/>.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedRational(this List<TiffIfdEntry> entries, ushort tag, SignedRational value) |
|||
{ |
|||
TiffIfdEntryCreator.AddSignedRational(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SRational' from an array of <see cref="SignedRational"/> values.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddSignedRational(this List<TiffIfdEntry> entries, ushort tag, SignedRational[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
int offset = i * TiffConstants.SizeOfRational; |
|||
ToBytes(value[i].Numerator, bytes, offset); |
|||
ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.SRational, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Float' from a floating-point value.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddFloat(this List<TiffIfdEntry> entries, ushort tag, float value) |
|||
{ |
|||
TiffIfdEntryCreator.AddFloat(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Float' from an array of floating-point values.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddFloat(this List<TiffIfdEntry> entries, ushort tag, float[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfFloat]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
byte[] itemBytes = BitConverter.GetBytes(value[i]); |
|||
Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfFloat, TiffConstants.SizeOfFloat); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Float, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Double' from a floating-point value.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddDouble(this List<TiffIfdEntry> entries, ushort tag, double value) |
|||
{ |
|||
TiffIfdEntryCreator.AddDouble(entries, tag, new[] { value }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Double' from an array of floating-point values.
|
|||
/// </summary>
|
|||
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
|
|||
/// <param name="tag">The tag for the resulting entry.</param>
|
|||
/// <param name="value">The value for the resulting entry.</param>
|
|||
public static void AddDouble(this List<TiffIfdEntry> entries, ushort tag, double[] value) |
|||
{ |
|||
byte[] bytes = new byte[value.Length * TiffConstants.SizeOfDouble]; |
|||
|
|||
for (int i = 0; i < value.Length; i++) |
|||
{ |
|||
byte[] itemBytes = BitConverter.GetBytes(value[i]); |
|||
Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfDouble, TiffConstants.SizeOfDouble); |
|||
} |
|||
|
|||
entries.Add(new TiffIfdEntry(tag, TiffType.Double, (uint)value.Length, bytes)); |
|||
} |
|||
|
|||
private static void ToBytes(ushort value, byte[] bytes, int offset) |
|||
{ |
|||
bytes[offset + 0] = (byte)value; |
|||
bytes[offset + 1] = (byte)(value >> 8); |
|||
} |
|||
|
|||
private static void ToBytes(uint value, byte[] bytes, int offset) |
|||
{ |
|||
bytes[offset + 0] = (byte)value; |
|||
bytes[offset + 1] = (byte)(value >> 8); |
|||
bytes[offset + 2] = (byte)(value >> 16); |
|||
bytes[offset + 3] = (byte)(value >> 24); |
|||
} |
|||
|
|||
private static void ToBytes(short value, byte[] bytes, int offset) |
|||
{ |
|||
bytes[offset + 0] = (byte)value; |
|||
bytes[offset + 1] = (byte)(value >> 8); |
|||
} |
|||
|
|||
private static void ToBytes(int value, byte[] bytes, int offset) |
|||
{ |
|||
bytes[offset + 0] = (byte)value; |
|||
bytes[offset + 1] = (byte)(value >> 8); |
|||
bytes[offset + 2] = (byte)(value >> 16); |
|||
bytes[offset + 3] = (byte)(value >> 24); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Defines constants for each of the supported TIFF metadata types.
|
|||
/// </summary>
|
|||
public static class TiffMetadataNames |
|||
{ |
|||
/// <summary>
|
|||
/// Person who created the image.
|
|||
/// </summary>
|
|||
public const string Artist = "Artist"; |
|||
|
|||
/// <summary>
|
|||
/// Copyright notice.
|
|||
/// </summary>
|
|||
public const string Copyright = "Copyright"; |
|||
|
|||
/// <summary>
|
|||
/// Date and time of image creation.
|
|||
/// </summary>
|
|||
public const string DateTime = "DateTime"; |
|||
|
|||
/// <summary>
|
|||
/// The computer and/or operating system in use at the time of image creation.
|
|||
/// </summary>
|
|||
public const string HostComputer = "HostComputer"; |
|||
|
|||
/// <summary>
|
|||
/// A string that describes the subject of the image.
|
|||
/// </summary>
|
|||
public const string ImageDescription = "ImageDescription"; |
|||
|
|||
/// <summary>
|
|||
/// The scanner/camera manufacturer.
|
|||
/// </summary>
|
|||
public const string Make = "Make"; |
|||
|
|||
/// <summary>
|
|||
/// The scanner/camera model name or number.
|
|||
/// </summary>
|
|||
public const string Model = "Model"; |
|||
|
|||
/// <summary>
|
|||
/// Name and version number of the software package(s) used to create the image.
|
|||
/// </summary>
|
|||
public const string Software = "Software"; |
|||
} |
|||
} |
|||
@ -0,0 +1,716 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Constants representing tag IDs in the Tiff file-format.
|
|||
/// </summary>
|
|||
internal class TiffTags |
|||
{ |
|||
/// <summary>
|
|||
/// Artist (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Artist = 315; |
|||
|
|||
/// <summary>
|
|||
/// BitsPerSample (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int BitsPerSample = 258; |
|||
|
|||
/// <summary>
|
|||
/// CellLength (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int CellLength = 265; |
|||
|
|||
/// <summary>
|
|||
/// CellWidth (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int CellWidth = 264; |
|||
|
|||
/// <summary>
|
|||
/// ColorMap (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ColorMap = 320; |
|||
|
|||
/// <summary>
|
|||
/// Compression (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Compression = 259; |
|||
|
|||
/// <summary>
|
|||
/// Copyright (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Copyright = 33432; |
|||
|
|||
/// <summary>
|
|||
/// DateTime (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int DateTime = 306; |
|||
|
|||
/// <summary>
|
|||
/// ExtraSamples (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ExtraSamples = 338; |
|||
|
|||
/// <summary>
|
|||
/// FillOrder (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int FillOrder = 266; |
|||
|
|||
/// <summary>
|
|||
/// FreeByteCounts (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int FreeByteCounts = 289; |
|||
|
|||
/// <summary>
|
|||
/// FreeOffsets (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int FreeOffsets = 288; |
|||
|
|||
/// <summary>
|
|||
/// GrayResponseCurve (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int GrayResponseCurve = 291; |
|||
|
|||
/// <summary>
|
|||
/// GrayResponseUnit (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int GrayResponseUnit = 290; |
|||
|
|||
/// <summary>
|
|||
/// HostComputer (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int HostComputer = 316; |
|||
|
|||
/// <summary>
|
|||
/// ImageDescription (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ImageDescription = 270; |
|||
|
|||
/// <summary>
|
|||
/// ImageLength (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ImageLength = 257; |
|||
|
|||
/// <summary>
|
|||
/// ImageWidth (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ImageWidth = 256; |
|||
|
|||
/// <summary>
|
|||
/// Make (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Make = 271; |
|||
|
|||
/// <summary>
|
|||
/// MaxSampleValue (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int MaxSampleValue = 281; |
|||
|
|||
/// <summary>
|
|||
/// MinSampleValue (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int MinSampleValue = 280; |
|||
|
|||
/// <summary>
|
|||
/// Model (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Model = 272; |
|||
|
|||
/// <summary>
|
|||
/// NewSubfileType (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int NewSubfileType = 254; |
|||
|
|||
/// <summary>
|
|||
/// Orientation (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Orientation = 274; |
|||
|
|||
/// <summary>
|
|||
/// PhotometricInterpretation (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int PhotometricInterpretation = 262; |
|||
|
|||
/// <summary>
|
|||
/// PlanarConfiguration (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int PlanarConfiguration = 284; |
|||
|
|||
/// <summary>
|
|||
/// ResolutionUnit (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int ResolutionUnit = 296; |
|||
|
|||
/// <summary>
|
|||
/// RowsPerStrip (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int RowsPerStrip = 278; |
|||
|
|||
/// <summary>
|
|||
/// SamplesPerPixel (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int SamplesPerPixel = 277; |
|||
|
|||
/// <summary>
|
|||
/// Software (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Software = 305; |
|||
|
|||
/// <summary>
|
|||
/// StripByteCounts (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int StripByteCounts = 279; |
|||
|
|||
/// <summary>
|
|||
/// StripOffsets (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int StripOffsets = 273; |
|||
|
|||
/// <summary>
|
|||
/// SubfileType (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int SubfileType = 255; |
|||
|
|||
/// <summary>
|
|||
/// Threshholding (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int Threshholding = 263; |
|||
|
|||
/// <summary>
|
|||
/// XResolution (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int XResolution = 282; |
|||
|
|||
/// <summary>
|
|||
/// YResolution (see Section 8: Baseline Fields).
|
|||
/// </summary>
|
|||
public const int YResolution = 283; |
|||
|
|||
/// <summary>
|
|||
/// T4Options (see Section 11: CCITT Bilevel Encodings).
|
|||
/// </summary>
|
|||
public const int T4Options = 292; |
|||
|
|||
/// <summary>
|
|||
/// T6Options (see Section 11: CCITT Bilevel Encodings).
|
|||
/// </summary>
|
|||
public const int T6Options = 293; |
|||
|
|||
/// <summary>
|
|||
/// DocumentName (see Section 12: Document Storage and Retrieval).
|
|||
/// </summary>
|
|||
public const int DocumentName = 269; |
|||
|
|||
/// <summary>
|
|||
/// PageName (see Section 12: Document Storage and Retrieval).
|
|||
/// </summary>
|
|||
public const int PageName = 285; |
|||
|
|||
/// <summary>
|
|||
/// PageNumber (see Section 12: Document Storage and Retrieval).
|
|||
/// </summary>
|
|||
public const int PageNumber = 297; |
|||
|
|||
/// <summary>
|
|||
/// XPosition (see Section 12: Document Storage and Retrieval).
|
|||
/// </summary>
|
|||
public const int XPosition = 286; |
|||
|
|||
/// <summary>
|
|||
/// YPosition (see Section 12: Document Storage and Retrieval).
|
|||
/// </summary>
|
|||
public const int YPosition = 287; |
|||
|
|||
/// <summary>
|
|||
/// Predictor (see Section 14: Differencing Predictor).
|
|||
/// </summary>
|
|||
public const int Predictor = 317; |
|||
|
|||
/// <summary>
|
|||
/// TileWidth (see Section 15: Tiled Images).
|
|||
/// </summary>
|
|||
public const int TileWidth = 322; |
|||
|
|||
/// <summary>
|
|||
/// TileLength (see Section 15: Tiled Images).
|
|||
/// </summary>
|
|||
public const int TileLength = 323; |
|||
|
|||
/// <summary>
|
|||
/// TileOffsets (see Section 15: Tiled Images).
|
|||
/// </summary>
|
|||
public const int TileOffsets = 324; |
|||
|
|||
/// <summary>
|
|||
/// TileByteCounts (see Section 15: Tiled Images).
|
|||
/// </summary>
|
|||
public const int TileByteCounts = 325; |
|||
|
|||
/// <summary>
|
|||
/// InkSet (see Section 16: CMYK Images).
|
|||
/// </summary>
|
|||
public const int InkSet = 332; |
|||
|
|||
/// <summary>
|
|||
/// NumberOfInks (see Section 16: CMYK Images).
|
|||
/// </summary>
|
|||
public const int NumberOfInks = 334; |
|||
|
|||
/// <summary>
|
|||
/// InkNames (see Section 16: CMYK Images).
|
|||
/// </summary>
|
|||
public const int InkNames = 333; |
|||
|
|||
/// <summary>
|
|||
/// DotRange (see Section 16: CMYK Images).
|
|||
/// </summary>
|
|||
public const int DotRange = 336; |
|||
|
|||
/// <summary>
|
|||
/// TargetPrinter (see Section 16: CMYK Images).
|
|||
/// </summary>
|
|||
public const int TargetPrinter = 337; |
|||
|
|||
/// <summary>
|
|||
/// HalftoneHints (see Section 17: Halftone Hints).
|
|||
/// </summary>
|
|||
public const int HalftoneHints = 321; |
|||
|
|||
/// <summary>
|
|||
/// SampleFormat (see Section 19: Data Sample Format).
|
|||
/// </summary>
|
|||
public const int SampleFormat = 339; |
|||
|
|||
/// <summary>
|
|||
/// SMinSampleValue (see Section 19: Data Sample Format).
|
|||
/// </summary>
|
|||
public const int SMinSampleValue = 340; |
|||
|
|||
/// <summary>
|
|||
/// SMaxSampleValue (see Section 19: Data Sample Format).
|
|||
/// </summary>
|
|||
public const int SMaxSampleValue = 341; |
|||
|
|||
/// <summary>
|
|||
/// WhitePoint (see Section 20: RGB Image Colorimetry).
|
|||
/// </summary>
|
|||
public const int WhitePoint = 318; |
|||
|
|||
/// <summary>
|
|||
/// PrimaryChromaticities (see Section 20: RGB Image Colorimetry).
|
|||
/// </summary>
|
|||
public const int PrimaryChromaticities = 319; |
|||
|
|||
/// <summary>
|
|||
/// TransferFunction (see Section 20: RGB Image Colorimetry).
|
|||
/// </summary>
|
|||
public const int TransferFunction = 301; |
|||
|
|||
/// <summary>
|
|||
/// TransferRange (see Section 20: RGB Image Colorimetry).
|
|||
/// </summary>
|
|||
public const int TransferRange = 342; |
|||
|
|||
/// <summary>
|
|||
/// ReferenceBlackWhite (see Section 20: RGB Image Colorimetry).
|
|||
/// </summary>
|
|||
public const int ReferenceBlackWhite = 532; |
|||
|
|||
/// <summary>
|
|||
/// YCbCrCoefficients (see Section 21: YCbCr Images).
|
|||
/// </summary>
|
|||
public const int YCbCrCoefficients = 529; |
|||
|
|||
/// <summary>
|
|||
/// YCbCrSubSampling (see Section 21: YCbCr Images).
|
|||
/// </summary>
|
|||
public const int YCbCrSubSampling = 530; |
|||
|
|||
/// <summary>
|
|||
/// YCbCrPositioning (see Section 21: YCbCr Images).
|
|||
/// </summary>
|
|||
public const int YCbCrPositioning = 531; |
|||
|
|||
/// <summary>
|
|||
/// JpegProc (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegProc = 512; |
|||
|
|||
/// <summary>
|
|||
/// JpegInterchangeFormat (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegInterchangeFormat = 513; |
|||
|
|||
/// <summary>
|
|||
/// JpegInterchangeFormatLength (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegInterchangeFormatLength = 514; |
|||
|
|||
/// <summary>
|
|||
/// JpegRestartInterval (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegRestartInterval = 515; |
|||
|
|||
/// <summary>
|
|||
/// JpegLosslessPredictors (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegLosslessPredictors = 517; |
|||
|
|||
/// <summary>
|
|||
/// JpegPointTransforms (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegPointTransforms = 518; |
|||
|
|||
/// <summary>
|
|||
/// JpegQTables (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegQTables = 519; |
|||
|
|||
/// <summary>
|
|||
/// JpegDCTables (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegDCTables = 520; |
|||
|
|||
/// <summary>
|
|||
/// JpegACTables (see Section 22: JPEG Compression).
|
|||
/// </summary>
|
|||
public const int JpegACTables = 521; |
|||
|
|||
/// <summary>
|
|||
/// SubIFDs (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int SubIFDs = 330; |
|||
|
|||
/// <summary>
|
|||
/// ClipPath (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int ClipPath = 343; |
|||
|
|||
/// <summary>
|
|||
/// XClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int XClipPathUnits = 344; |
|||
|
|||
/// <summary>
|
|||
/// YClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int YClipPathUnits = 345; |
|||
|
|||
/// <summary>
|
|||
/// Indexed (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int Indexed = 346; |
|||
|
|||
/// <summary>
|
|||
/// ImageID (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int ImageID = 32781; |
|||
|
|||
/// <summary>
|
|||
/// OpiProxy (see TIFF Supplement 1: Adobe Pagemaker 6.0).
|
|||
/// </summary>
|
|||
public const int OpiProxy = 351; |
|||
|
|||
/// <summary>
|
|||
/// ImageSourceData (see TIFF Supplement 2: Adobe Photoshop).
|
|||
/// </summary>
|
|||
public const int ImageSourceData = 37724; |
|||
|
|||
/// <summary>
|
|||
/// JPEGTables (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int JPEGTables = 0x015B; |
|||
|
|||
/// <summary>
|
|||
/// CFARepeatPatternDim (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int CFARepeatPatternDim = 0x828D; |
|||
|
|||
/// <summary>
|
|||
/// BatteryLevel (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int BatteryLevel = 0x828F; |
|||
|
|||
/// <summary>
|
|||
/// Interlace (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int Interlace = 0x8829; |
|||
|
|||
/// <summary>
|
|||
/// TimeZoneOffset (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int TimeZoneOffset = 0x882A; |
|||
|
|||
/// <summary>
|
|||
/// SelfTimerMode (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int SelfTimerMode = 0x882B; |
|||
|
|||
/// <summary>
|
|||
/// Noise (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int Noise = 0x920D; |
|||
|
|||
/// <summary>
|
|||
/// ImageNumber (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int ImageNumber = 0x9211; |
|||
|
|||
/// <summary>
|
|||
/// SecurityClassification (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int SecurityClassification = 0x9212; |
|||
|
|||
/// <summary>
|
|||
/// ImageHistory (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int ImageHistory = 0x9213; |
|||
|
|||
/// <summary>
|
|||
/// TiffEPStandardID (see TIFF/EP Specification: Additional Tags).
|
|||
/// </summary>
|
|||
public const int TiffEPStandardID = 0x9216; |
|||
|
|||
/// <summary>
|
|||
/// BadFaxLines (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int BadFaxLines = 326; |
|||
|
|||
/// <summary>
|
|||
/// CleanFaxData (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int CleanFaxData = 327; |
|||
|
|||
/// <summary>
|
|||
/// ConsecutiveBadFaxLines (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int ConsecutiveBadFaxLines = 328; |
|||
|
|||
/// <summary>
|
|||
/// GlobalParametersIFD (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int GlobalParametersIFD = 400; |
|||
|
|||
/// <summary>
|
|||
/// ProfileType (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int ProfileType = 401; |
|||
|
|||
/// <summary>
|
|||
/// FaxProfile (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int FaxProfile = 402; |
|||
|
|||
/// <summary>
|
|||
/// CodingMethod (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int CodingMethod = 403; |
|||
|
|||
/// <summary>
|
|||
/// VersionYear (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int VersionYear = 404; |
|||
|
|||
/// <summary>
|
|||
/// ModeNumber (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int ModeNumber = 405; |
|||
|
|||
/// <summary>
|
|||
/// Decode (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int Decode = 433; |
|||
|
|||
/// <summary>
|
|||
/// DefaultImageColor (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int DefaultImageColor = 434; |
|||
|
|||
/// <summary>
|
|||
/// StripRowCounts (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int StripRowCounts = 559; |
|||
|
|||
/// <summary>
|
|||
/// ImageLayer (see RFC2301: TIFF-F/FX Specification).
|
|||
/// </summary>
|
|||
public const int ImageLayer = 34732; |
|||
|
|||
/// <summary>
|
|||
/// Xmp (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int Xmp = 700; |
|||
|
|||
/// <summary>
|
|||
/// Iptc (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int Iptc = 33723; |
|||
|
|||
/// <summary>
|
|||
/// Photoshop (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int Photoshop = 34377; |
|||
|
|||
/// <summary>
|
|||
/// ExifIFD (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int ExifIFD = 34665; |
|||
|
|||
/// <summary>
|
|||
/// GpsIFD (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int GpsIFD = 34853; |
|||
|
|||
/// <summary>
|
|||
/// InteroperabilityIFD (Embedded Metadata).
|
|||
/// </summary>
|
|||
public const int InteroperabilityIFD = 40965; |
|||
|
|||
/// <summary>
|
|||
/// WangAnnotation (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int WangAnnotation = 32932; |
|||
|
|||
/// <summary>
|
|||
/// MDFileTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDFileTag = 33445; |
|||
|
|||
/// <summary>
|
|||
/// MDScalePixel (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDScalePixel = 33446; |
|||
|
|||
/// <summary>
|
|||
/// MDColorTable (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDColorTable = 33447; |
|||
|
|||
/// <summary>
|
|||
/// MDLabName (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDLabName = 33448; |
|||
|
|||
/// <summary>
|
|||
/// MDSampleInfo (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDSampleInfo = 33449; |
|||
|
|||
/// <summary>
|
|||
/// MDPrepDate (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDPrepDate = 33450; |
|||
|
|||
/// <summary>
|
|||
/// MDPrepTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDPrepTime = 33451; |
|||
|
|||
/// <summary>
|
|||
/// MDFileUnits (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int MDFileUnits = 33452; |
|||
|
|||
/// <summary>
|
|||
/// ModelPixelScaleTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int ModelPixelScaleTag = 33550; |
|||
|
|||
/// <summary>
|
|||
/// IngrPacketDataTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int IngrPacketDataTag = 33918; |
|||
|
|||
/// <summary>
|
|||
/// IngrFlagRegisters (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int IngrFlagRegisters = 33919; |
|||
|
|||
/// <summary>
|
|||
/// IrasBTransformationMatrix (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int IrasBTransformationMatrix = 33920; |
|||
|
|||
/// <summary>
|
|||
/// ModelTiePointTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int ModelTiePointTag = 33922; |
|||
|
|||
/// <summary>
|
|||
/// ModelTransformationTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int ModelTransformationTag = 34264; |
|||
|
|||
/// <summary>
|
|||
/// IccProfile (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int IccProfile = 34675; |
|||
|
|||
/// <summary>
|
|||
/// GeoKeyDirectoryTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int GeoKeyDirectoryTag = 34735; |
|||
|
|||
/// <summary>
|
|||
/// GeoDoubleParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int GeoDoubleParamsTag = 34736; |
|||
|
|||
/// <summary>
|
|||
/// GeoAsciiParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int GeoAsciiParamsTag = 34737; |
|||
|
|||
/// <summary>
|
|||
/// HylaFAXFaxRecvParams (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int HylaFAXFaxRecvParams = 34908; |
|||
|
|||
/// <summary>
|
|||
/// HylaFAXFaxSubAddress (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int HylaFAXFaxSubAddress = 34909; |
|||
|
|||
/// <summary>
|
|||
/// HylaFAXFaxRecvTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int HylaFAXFaxRecvTime = 34910; |
|||
|
|||
/// <summary>
|
|||
/// GdalMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int GdalMetadata = 42112; |
|||
|
|||
/// <summary>
|
|||
/// GdalNodata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int GdalNodata = 42113; |
|||
|
|||
/// <summary>
|
|||
/// OceScanjobDescription (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int OceScanjobDescription = 50215; |
|||
|
|||
/// <summary>
|
|||
/// OceApplicationSelector (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int OceApplicationSelector = 50216; |
|||
|
|||
/// <summary>
|
|||
/// OceIdentificationNumber (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int OceIdentificationNumber = 50217; |
|||
|
|||
/// <summary>
|
|||
/// OceImageLogicCharacteristics (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int OceImageLogicCharacteristics = 50218; |
|||
|
|||
/// <summary>
|
|||
/// AliasLayerMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
|
|||
/// </summary>
|
|||
public const int AliasLayerMetadata = 50784; |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Enumeration representing the data types understood by the Tiff file-format.
|
|||
/// </summary>
|
|||
internal enum TiffType |
|||
{ |
|||
/// <summary>
|
|||
/// Unsigned 8-bit integer.
|
|||
/// </summary>
|
|||
Byte = 1, |
|||
|
|||
/// <summary>
|
|||
/// ASCII formatted text.
|
|||
/// </summary>
|
|||
Ascii = 2, |
|||
|
|||
/// <summary>
|
|||
/// Unsigned 16-bit integer.
|
|||
/// </summary>
|
|||
Short = 3, |
|||
|
|||
/// <summary>
|
|||
/// Unsigned 32-bit integer.
|
|||
/// </summary>
|
|||
Long = 4, |
|||
|
|||
/// <summary>
|
|||
/// Unsigned rational number.
|
|||
/// </summary>
|
|||
Rational = 5, |
|||
|
|||
/// <summary>
|
|||
/// Signed 8-bit integer.
|
|||
/// </summary>
|
|||
SByte = 6, |
|||
|
|||
/// <summary>
|
|||
/// Undefined data type.
|
|||
/// </summary>
|
|||
Undefined = 7, |
|||
|
|||
/// <summary>
|
|||
/// Signed 16-bit integer.
|
|||
/// </summary>
|
|||
SShort = 8, |
|||
|
|||
/// <summary>
|
|||
/// Signed 32-bit integer.
|
|||
/// </summary>
|
|||
SLong = 9, |
|||
|
|||
/// <summary>
|
|||
/// Signed rational number.
|
|||
/// </summary>
|
|||
SRational = 10, |
|||
|
|||
/// <summary>
|
|||
/// Single precision (4-byte) IEEE format.
|
|||
/// </summary>
|
|||
Float = 11, |
|||
|
|||
/// <summary>
|
|||
/// Double precision (8-byte) IEEE format.
|
|||
/// </summary>
|
|||
Double = 12, |
|||
|
|||
/// <summary>
|
|||
/// Reference to an IFD.
|
|||
/// </summary>
|
|||
Ifd = 13 |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
using SDImage = System.Drawing.Image; |
|||
using SDSize = System.Drawing.Size; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class DecodeTiff : BenchmarkBase |
|||
{ |
|||
private byte[] tiffBytes; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Tiff.RgbPackbits)] |
|||
public string TestImage { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.tiffBytes == null) |
|||
{ |
|||
this.tiffBytes = File.ReadAllBytes(this.TestImageFullPath); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")] |
|||
public SDSize TiffSystemDrawing() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.tiffBytes)) |
|||
using (var image = SDImage.FromStream(memoryStream)) |
|||
{ |
|||
return image.Size; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Tiff")] |
|||
public Size TiffCore() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.tiffBytes)) |
|||
using (var image = Image.Load<Rgba32>(memoryStream)) |
|||
{ |
|||
return image.Size(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Environments; |
|||
using BenchmarkDotNet.Jobs; |
|||
using BenchmarkDotNet.Reports; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
using SDImage = System.Drawing.Image; |
|||
using SDSize = System.Drawing.Size; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
[Config(typeof(DecodeTiffBig.Config.LongClr))] |
|||
public class DecodeTiffBig : BenchmarkBase |
|||
{ |
|||
private class Config : SixLabors.ImageSharp.Benchmarks.Config |
|||
{ |
|||
public class LongClr : Config |
|||
{ |
|||
public LongClr() |
|||
{ |
|||
this.Add( |
|||
Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5), |
|||
Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5), |
|||
Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5)); |
|||
|
|||
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(60); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private string prevImage = null; |
|||
|
|||
private byte[] data; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Tiff.Benchmark_GrayscaleUncompressed, TestImages.Tiff.Benchmark_PaletteUncompressed, TestImages.Tiff.Benchmark_RgbDeflate, TestImages.Tiff.Benchmark_RgbLzw, TestImages.Tiff.Benchmark_RgbPackbits, TestImages.Tiff.Benchmark_RgbUncompressed)] |
|||
// [Params(TestImages.Tiff.GrayscaleUncompressed, TestImages.Tiff.PaletteUncompressed, TestImages.Tiff.RgbDeflate, TestImages.Tiff.RgbLzw, TestImages.Tiff.RgbPackbits, TestImages.Tiff.RgbUncompressed)]
|
|||
public string TestImage { get; set; } |
|||
|
|||
[IterationSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.prevImage != this.TestImage) |
|||
{ |
|||
this.data = File.ReadAllBytes(this.TestImageFullPath); |
|||
this.prevImage = this.TestImage; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")] |
|||
public SDSize TiffSystemDrawing() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.data)) |
|||
using (var image = SDImage.FromStream(memoryStream)) |
|||
{ |
|||
return image.Size; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Tiff")] |
|||
public Size TiffCore() |
|||
{ |
|||
using (var ms = new MemoryStream(this.data)) |
|||
using (var image = Image.Load<Rgba32>(ms)) |
|||
{ |
|||
return image.Size(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Png.Zlib; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class DeflateTiffCompressionTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(new byte[] { })] |
|||
[InlineData(new byte[] { 42 })] // One byte
|
|||
[InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
|
|||
[InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
|
|||
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
|
|||
public void Decompress_ReadsData(byte[] data) |
|||
{ |
|||
using (Stream stream = CreateCompressedStream(data)) |
|||
{ |
|||
byte[] buffer = new byte[data.Length]; |
|||
|
|||
new DeflateTiffCompression(null).Decompress(stream, (int)stream.Length, buffer); |
|||
|
|||
Assert.Equal(data, buffer); |
|||
} |
|||
} |
|||
|
|||
private static Stream CreateCompressedStream(byte[] data) |
|||
{ |
|||
Stream compressedStream = new MemoryStream(); |
|||
|
|||
using (Stream uncompressedStream = new MemoryStream(data), |
|||
deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, ImageSharp.Formats.Png.PngCompressionLevel.Level6)) |
|||
{ |
|||
uncompressedStream.CopyTo(deflateStream); |
|||
} |
|||
|
|||
compressedStream.Seek(0, SeekOrigin.Begin); |
|||
return compressedStream; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class LzwTiffCompressionTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(new byte[] { })] |
|||
[InlineData(new byte[] { 42 })] // One byte
|
|||
[InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
|
|||
[InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
|
|||
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
|
|||
public void Decompress_ReadsData(byte[] data) |
|||
{ |
|||
using (Stream stream = CreateCompressedStream(data)) |
|||
{ |
|||
byte[] buffer = new byte[data.Length]; |
|||
|
|||
new LzwTiffCompression(null).Decompress(stream, (int)stream.Length, buffer); |
|||
|
|||
Assert.Equal(data, buffer); |
|||
} |
|||
} |
|||
|
|||
private static Stream CreateCompressedStream(byte[] data) |
|||
{ |
|||
Stream compressedStream = new MemoryStream(); |
|||
|
|||
using (var encoder = new TiffLzwEncoder(data, 8)) |
|||
{ |
|||
encoder.Encode(compressedStream); |
|||
} |
|||
|
|||
compressedStream.Seek(0, SeekOrigin.Begin); |
|||
return compressedStream; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class NoneTiffCompressionTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })] |
|||
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] |
|||
public void Decompress_ReadsData(byte[] inputData, int byteCount, byte[] expectedResult) |
|||
{ |
|||
Stream stream = new MemoryStream(inputData); |
|||
byte[] buffer = new byte[expectedResult.Length]; |
|||
|
|||
new NoneTiffCompression(null).Decompress(stream, byteCount, buffer); |
|||
|
|||
Assert.Equal(expectedResult, buffer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class PackBitsTiffCompressionTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(new byte[] { }, new byte[] { })] |
|||
[InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte
|
|||
[InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes
|
|||
[InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes
|
|||
[InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes
|
|||
[InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte
|
|||
[InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes
|
|||
[InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, |
|||
new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
|
|||
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) |
|||
{ |
|||
Stream stream = new MemoryStream(inputData); |
|||
byte[] buffer = new byte[expectedResult.Length]; |
|||
|
|||
new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()).Decompress(stream, inputData.Length, buffer); |
|||
|
|||
Assert.Equal(expectedResult, buffer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff.BlackBox.Encoder")] |
|||
[Trait("Category", "Tiff")] |
|||
public class ImageExtensionsTest |
|||
{ |
|||
[Theory] |
|||
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)] |
|||
public void ThrowsSavingNotImplemented<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Assert.Throws<NotImplementedException>(() => |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); |
|||
using var image = provider.GetImage(new TiffDecoder()); |
|||
image.SaveAsTiff(file); |
|||
}); |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public void SaveAsTiff_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
image.SaveAsTiff(file); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public async Task SaveAsTiffAsync_Path() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); |
|||
string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
await image.SaveAsTiffAsync(file); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public void SaveAsTiff_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
image.SaveAsTiff(file, new TiffEncoder()); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public async Task SaveAsTiffAsync_Path_Encoder() |
|||
{ |
|||
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); |
|||
string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
await image.SaveAsTiffAsync(file, new TiffEncoder()); |
|||
} |
|||
|
|||
using (Image.Load(file, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public void SaveAsTiff_Stream() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
image.SaveAsTiff(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public async Task SaveAsTiffAsync_StreamAsync() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
await image.SaveAsTiffAsync(memoryStream); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public void SaveAsTiff_Stream_Encoder() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
image.SaveAsTiff(memoryStream, new TiffEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "Saving not implemented")] |
|||
public async Task SaveAsTiffAsync_Stream_Encoder() |
|||
{ |
|||
using var memoryStream = new MemoryStream(); |
|||
|
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); |
|||
} |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
using (Image.Load(memoryStream, out IImageFormat mime)) |
|||
{ |
|||
Assert.Equal("image/tiff", mime.DefaultMimeType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
private static Rgba32 Gray000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Gray128 = new Rgba32(128, 128, 128, 255); |
|||
private static Rgba32 Gray255 = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Gray0 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Gray8 = new Rgba32(136, 136, 136, 255); |
|||
private static Rgba32 GrayF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Bit0 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Bit1 = new Rgba32(255, 255, 255, 255); |
|||
|
|||
private static readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000, |
|||
0b11110000, |
|||
0b01110000, |
|||
0b10010000 }; |
|||
|
|||
private static readonly Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1 }}; |
|||
|
|||
private static readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000, |
|||
0b11111111, 0b11111111, |
|||
0b01101001, 0b10100000, |
|||
0b10010000, 0b01100000}; |
|||
|
|||
private static readonly Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 }}; |
|||
|
|||
private static readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F, |
|||
0xFF, 0xFF, |
|||
0x08, 0x8F, |
|||
0xF0, 0xF8 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF }, |
|||
new[] { GrayF, GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8, GrayF }, |
|||
new[] { GrayF, Gray0, GrayF, Gray8 }}; |
|||
|
|||
private static readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00, |
|||
0xFF, 0xF0, |
|||
0x08, 0x80, |
|||
0xF0, 0xF0 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 }, |
|||
new[] { GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8 }, |
|||
new[] { GrayF, Gray0, GrayF }}; |
|||
|
|||
private static readonly byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255, |
|||
255, 255, 255, 255, |
|||
000, 128, 128, 255, |
|||
255, 000, 255, 128 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 }, |
|||
new[] { Gray255, Gray255, Gray255, Gray255 }, |
|||
new[] { Gray000, Gray128, Gray128, Gray255 }, |
|||
new[] { Gray255, Gray000, Gray255, Gray128 }}; |
|||
|
|||
public static IEnumerable<object[]> Bilevel_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Bilevel_Result4x4 }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Offset(Bilevel_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 0, 4, 4, Offset(Bilevel_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 1, 4, 4, Offset(Bilevel_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 1, 4, 4, Offset(Bilevel_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Bilevel_Result12x4 }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Offset(Bilevel_Result12x4, 0, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 0, 12, 4, Offset(Bilevel_Result12x4, 1, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 1, 12, 4, Offset(Bilevel_Result12x4, 0, 1, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 1, 12, 4, Offset(Bilevel_Result12x4, 1, 1, 18, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Grayscale4_Result4x4 }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Offset(Grayscale4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 0, 4, 4, Offset(Grayscale4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 1, 4, 4, Offset(Grayscale4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 1, 4, 4, Offset(Grayscale4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Grayscale4_Result3x4 }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Offset(Grayscale4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 0, 3, 4, Offset(Grayscale4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 1, 3, 4, Offset(Grayscale4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 1, 3, 4, Offset(Grayscale4_Result3x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Grayscale8_Result4x4 }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Offset(Grayscale8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 0, 4, 4, Offset(Grayscale8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 1, 4, 4, Offset(Grayscale8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 1, 4, 4, Offset(Grayscale8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new BlackIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new BlackIsZero1TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new BlackIsZero4TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new BlackIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
public class PaletteTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); } |
|||
|
|||
public static ushort[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); } |
|||
|
|||
private static readonly byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23, |
|||
0x4A, 0xD2, |
|||
0x12, 0x34, |
|||
0xAB, 0xEF }; |
|||
|
|||
private static readonly Rgba32[][] Palette4_Result4x4 = GenerateResult(Palette4_ColorPalette, |
|||
new[] { new[] { 0x00, 0x01, 0x02, 0x03 }, |
|||
new[] { 0x04, 0x0A, 0x0D, 0x02 }, |
|||
new[] { 0x01, 0x02, 0x03, 0x04 }, |
|||
new[] { 0x0A, 0x0B, 0x0E, 0x0F }}); |
|||
|
|||
private static readonly byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20, |
|||
0x4A, 0xD0, |
|||
0x12, 0x30, |
|||
0xAB, 0xE0 }; |
|||
|
|||
private static readonly Rgba32[][] Palette4_Result3x4 = GenerateResult(Palette4_ColorPalette, |
|||
new[] { new[] { 0x00, 0x01, 0x02 }, |
|||
new[] { 0x04, 0x0A, 0x0D }, |
|||
new[] { 0x01, 0x02, 0x03 }, |
|||
new[] { 0x0A, 0x0B, 0x0E }}); |
|||
|
|||
public static IEnumerable<object[]> Palette4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Palette4_Result4x4 }; |
|||
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Offset(Palette4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 0, 4, 4, Offset(Palette4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 1, 4, 4, Offset(Palette4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 1, 4, 4, Offset(Palette4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Palette4_Result3x4 }; |
|||
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Offset(Palette4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 0, 3, 4, Offset(Palette4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 1, 3, 4, Offset(Palette4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 1, 3, 4, Offset(Palette4_Result3x4, 1, 1, 6, 6) }; |
|||
|
|||
} |
|||
} |
|||
|
|||
public static uint[][] Palette8_ColorPalette { get => GeneratePalette(256); } |
|||
|
|||
public static ushort[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); } |
|||
|
|||
private static readonly byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003, |
|||
100, 110, 120, 130, |
|||
000, 255, 128, 255, |
|||
050, 100, 150, 200 }; |
|||
|
|||
private static readonly Rgba32[][] Palette8_Result4x4 = GenerateResult(Palette8_ColorPalette, |
|||
new[] { new[] { 000, 001, 002, 003 }, |
|||
new[] { 100, 110, 120, 130 }, |
|||
new[] { 000, 255, 128, 255 }, |
|||
new[] { 050, 100, 150, 200 }}); |
|||
|
|||
public static IEnumerable<object[]> Palette8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Palette8_Result4x4 }; |
|||
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Offset(Palette8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 0, 4, 4, Offset(Palette8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 1, 4, 4, Offset(Palette8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 1, 4, 4, Offset(Palette8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Palette4_Data))] |
|||
[MemberData(nameof(Palette8_Data))] |
|||
public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new PaletteTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }, colorMap).Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
private static uint[][] GeneratePalette(int count) |
|||
{ |
|||
uint[][] palette = new uint[count][]; |
|||
|
|||
for (uint i = 0; i < count; i++) |
|||
{ |
|||
palette[i] = new uint[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u }; |
|||
} |
|||
|
|||
return palette; |
|||
} |
|||
|
|||
private static ushort[] GenerateColorMap(uint[][] colorPalette) |
|||
{ |
|||
int colorCount = colorPalette.Length; |
|||
ushort[] colorMap = new ushort[colorCount * 3]; |
|||
|
|||
for (int i = 0; i < colorCount; i++) |
|||
{ |
|||
colorMap[colorCount * 0 + i] = (ushort)colorPalette[i][0]; |
|||
colorMap[colorCount * 1 + i] = (ushort)colorPalette[i][1]; |
|||
colorMap[colorCount * 2 + i] = (ushort)colorPalette[i][2]; |
|||
} |
|||
|
|||
return colorMap; |
|||
} |
|||
|
|||
private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) |
|||
{ |
|||
var result = new Rgba32[pixelLookup.Length][]; |
|||
|
|||
for (int y = 0; y < pixelLookup.Length; y++) |
|||
{ |
|||
result[y] = new Rgba32[pixelLookup[y].Length]; |
|||
|
|||
for (int x = 0; x < pixelLookup[y].Length; x++) |
|||
{ |
|||
uint[] sourceColor = colorPalette[pixelLookup[y][x]]; |
|||
result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public abstract class PhotometricInterpretationTestBase |
|||
{ |
|||
public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128); |
|||
|
|||
public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height) |
|||
{ |
|||
int inputHeight = input.Length; |
|||
int inputWidth = input[0].Length; |
|||
|
|||
Rgba32[][] output = new Rgba32[height][]; |
|||
|
|||
for (int y = 0; y < output.Length; y++) |
|||
{ |
|||
output[y] = new Rgba32[width]; |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
output[y][x] = DefaultColor; |
|||
} |
|||
} |
|||
|
|||
for (int y = 0; y < inputHeight; y++) |
|||
{ |
|||
for (int x = 0; x < inputWidth; x++) |
|||
{ |
|||
output[y + yOffset][x + xOffset] = input[y][x]; |
|||
} |
|||
} |
|||
|
|||
return output; |
|||
} |
|||
|
|||
internal static void AssertDecode(Rgba32[][] expectedResult, Action<Buffer2D<Rgba32>> decodeAction) |
|||
{ |
|||
int resultWidth = expectedResult[0].Length; |
|||
int resultHeight = expectedResult.Length; |
|||
|
|||
using (Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight)) |
|||
{ |
|||
image.Mutate(x => x.BackgroundColor(DefaultColor)); |
|||
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer(); |
|||
|
|||
decodeAction(pixels); |
|||
|
|||
for (int y = 0; y < resultHeight; y++) |
|||
{ |
|||
for (int x = 0; x < resultWidth; x++) |
|||
{ |
|||
Assert.True( |
|||
expectedResult[y][x] == pixels[x, y], |
|||
$"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,206 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
private static Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); |
|||
private static Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); |
|||
private static Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); |
|||
private static Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); |
|||
private static Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); |
|||
private static Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); |
|||
private static Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); |
|||
|
|||
private static byte[] Rgb4_Bytes4x4_R = new byte[] { 0x0F, 0x0F, |
|||
0xF0, 0x0F, |
|||
0x48, 0xC4, |
|||
0x04, 0x8C }; |
|||
|
|||
private static byte[] Rgb4_Bytes4x4_G = new byte[] { 0x0F, 0x0F, |
|||
0x0F, 0x00, |
|||
0x00, 0x08, |
|||
0x04, 0x8C }; |
|||
|
|||
private static byte[] Rgb4_Bytes4x4_B = new byte[] { 0x0F, 0x0F, |
|||
0x00, 0xFF, |
|||
0x00, 0x0C, |
|||
0x04, 0x8C }; |
|||
|
|||
private static byte[][] Rgb4_Bytes4x4 = new[] { Rgb4_Bytes4x4_R, Rgb4_Bytes4x4_G, Rgb4_Bytes4x4_B }; |
|||
|
|||
private static Rgba32[][] Rgb4_Result4x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, |
|||
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, |
|||
new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, |
|||
new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC }}; |
|||
|
|||
private static byte[] Rgb4_Bytes3x4_R = new byte[] { 0x0F, 0x00, |
|||
0xF0, 0x00, |
|||
0x48, 0xC0, |
|||
0x04, 0x80 }; |
|||
|
|||
private static byte[] Rgb4_Bytes3x4_G = new byte[] { 0x0F, 0x00, |
|||
0x0F, 0x00, |
|||
0x00, 0x00, |
|||
0x04, 0x80 }; |
|||
|
|||
private static byte[] Rgb4_Bytes3x4_B = new byte[] { 0x0F, 0x00, |
|||
0x00, 0xF0, |
|||
0x00, 0x00, |
|||
0x04, 0x80 }; |
|||
|
|||
private static byte[][] Rgb4_Bytes3x4 = new[] { Rgb4_Bytes3x4_R, Rgb4_Bytes3x4_G, Rgb4_Bytes3x4_B }; |
|||
|
|||
private static Rgba32[][] Rgb4_Result3x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, |
|||
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, |
|||
new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, |
|||
new[] { Rgb4_000, Rgb4_444, Rgb4_888 }}; |
|||
|
|||
public static IEnumerable<object[]> Rgb4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4_Result4x4 }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4_Result3x4 }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
private static Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); |
|||
private static Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); |
|||
private static Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); |
|||
private static Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); |
|||
private static Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); |
|||
private static Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); |
|||
private static Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); |
|||
|
|||
private static byte[] Rgb8_Bytes4x4_R = new byte[] { 000, 255, 000, 255, |
|||
255, 000, 000, 255, |
|||
064, 128, 192, 064, |
|||
000, 064, 128, 192 }; |
|||
|
|||
private static byte[] Rgb8_Bytes4x4_G = new byte[] { 000, 255, 000, 255, |
|||
000, 255, 000, 000, |
|||
000, 000, 000, 128, |
|||
000, 064, 128, 192 }; |
|||
|
|||
private static byte[] Rgb8_Bytes4x4_B = new byte[] { 000, 255, 000, 255, |
|||
000, 000, 255, 255, |
|||
000, 000, 000, 192, |
|||
000, 064, 128, 192 }; |
|||
|
|||
private static byte[][] Rgb8_Bytes4x4 = new[] { Rgb8_Bytes4x4_R, Rgb8_Bytes4x4_G, Rgb8_Bytes4x4_B }; |
|||
|
|||
private static Rgba32[][] Rgb8_Result4x4 = new[] { new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, |
|||
new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, |
|||
new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, |
|||
new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC }}; |
|||
|
|||
public static IEnumerable<object[]> Rgb8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8_Result4x4 }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
private static Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); |
|||
private static Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); |
|||
private static Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); |
|||
private static Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); |
|||
private static Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); |
|||
private static Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); |
|||
private static Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); |
|||
|
|||
private static byte[] Rgb484_Bytes4x4_R = new byte[] { 0x0F, 0x0F, |
|||
0xF0, 0x0F, |
|||
0x48, 0xC4, |
|||
0x04, 0x8C }; |
|||
|
|||
private static byte[] Rgb484_Bytes4x4_G = new byte[] { 0x00, 0xFF, 0x00, 0xFF, |
|||
0x00, 0xFF, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x80, |
|||
0x00, 0x40, 0x80, 0xC0 }; |
|||
|
|||
private static byte[] Rgb484_Bytes4x4_B = new byte[] { 0x0F, 0x0F, |
|||
0x00, 0xFF, |
|||
0x00, 0x0C, |
|||
0x04, 0x8C }; |
|||
|
|||
private static Rgba32[][] Rgb484_Result4x4 = new[] { new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, |
|||
new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, |
|||
new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, |
|||
new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC }}; |
|||
|
|||
private static byte[][] Rgb484_Bytes4x4 = new[] { Rgb484_Bytes4x4_R, Rgb484_Bytes4x4_G, Rgb484_Bytes4x4_B }; |
|||
|
|||
public static IEnumerable<object[]> Rgb484_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484_Result4x4 }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Rgb4_Data))] |
|||
[MemberData(nameof(Rgb8_Data))] |
|||
[MemberData(nameof(Rgb484_Data))] |
|||
public void Decode_WritesPixelData(byte[][] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
var buffers = new IManagedByteBuffer[inputData.Length]; |
|||
for (int i = 0; i < buffers.Length; i++) |
|||
{ |
|||
buffers[i] = Configuration.Default.MemoryAllocator.AllocateManagedByteBuffer(inputData[i].Length); |
|||
((Span<byte>)inputData[i]).CopyTo(buffers[i].GetSpan()); |
|||
} |
|||
|
|||
new RgbPlanarTiffColor<Rgba32>(bitsPerSample).Decode(buffers, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
public class RgbTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
private static Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); |
|||
private static Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); |
|||
private static Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); |
|||
private static Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); |
|||
private static Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); |
|||
private static Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); |
|||
private static Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); |
|||
|
|||
private static byte[] Rgb4_Bytes4x4 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, |
|||
0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F, |
|||
0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C, |
|||
0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC }; |
|||
|
|||
private static Rgba32[][] Rgb4_Result4x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, |
|||
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, |
|||
new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, |
|||
new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC }}; |
|||
|
|||
private static byte[] Rgb4_Bytes3x4 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x00, |
|||
0xF0, 0x00, 0xF0, 0x00, 0xF0, |
|||
0x40, 0x08, 0x00, 0xC0, 0x00, |
|||
0x00, 0x04, 0x44, 0x88, 0x80 }; |
|||
|
|||
private static Rgba32[][] Rgb4_Result3x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, |
|||
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, |
|||
new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, |
|||
new[] { Rgb4_000, Rgb4_444, Rgb4_888 }}; |
|||
|
|||
public static IEnumerable<object[]> Rgb4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4_Result4x4 }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4_Result3x4 }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
private static Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); |
|||
private static Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); |
|||
private static Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); |
|||
private static Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); |
|||
private static Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); |
|||
private static Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); |
|||
private static Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); |
|||
|
|||
private static byte[] Rgb8_Bytes4x4 = new byte[] { 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255, |
|||
255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255, |
|||
064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192, |
|||
000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 }; |
|||
|
|||
private static Rgba32[][] Rgb8_Result4x4 = new[] { new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, |
|||
new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, |
|||
new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, |
|||
new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC }}; |
|||
|
|||
public static IEnumerable<object[]> Rgb8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8_Result4x4 }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
private static Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); |
|||
private static Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); |
|||
private static Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); |
|||
private static Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); |
|||
private static Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); |
|||
private static Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); |
|||
private static Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); |
|||
private static Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); |
|||
private static Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); |
|||
private static Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); |
|||
private static Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); |
|||
|
|||
private static byte[] Rgb484_Bytes4x4 = new byte[] { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, |
|||
0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, |
|||
0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C, |
|||
0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C }; |
|||
|
|||
private static Rgba32[][] Rgb484_Result4x4 = new[] { new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, |
|||
new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, |
|||
new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, |
|||
new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC }}; |
|||
|
|||
public static IEnumerable<object[]> Rgb484_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484_Result4x4 }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Rgb4_Data))] |
|||
[MemberData(nameof(Rgb8_Data))] |
|||
[MemberData(nameof(Rgb484_Data))] |
|||
public void Decode_WritesPixelData(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new RgbTiffColor<Rgba32>(bitsPerSample).Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Rgb8_Data))] |
|||
public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new Rgb888TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
private static Rgba32 Gray000 = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Gray128 = new Rgba32(127, 127, 127, 255); |
|||
private static Rgba32 Gray255 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Gray0 = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Gray8 = new Rgba32(119, 119, 119, 255); |
|||
private static Rgba32 GrayF = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Bit0 = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Bit1 = new Rgba32(0, 0, 0, 255); |
|||
|
|||
private static readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000, |
|||
0b11110000, |
|||
0b01110000, |
|||
0b10010000 }; |
|||
|
|||
private static readonly Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1 }}; |
|||
|
|||
private static readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000, |
|||
0b11111111, 0b11111111, |
|||
0b01101001, 0b10100000, |
|||
0b10010000, 0b01100000}; |
|||
|
|||
private static readonly Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 }}; |
|||
|
|||
private static readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F, |
|||
0xFF, 0xFF, |
|||
0x08, 0x8F, |
|||
0xF0, 0xF8 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF }, |
|||
new[] { GrayF, GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8, GrayF }, |
|||
new[] { GrayF, Gray0, GrayF, Gray8 }}; |
|||
|
|||
private static readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00, |
|||
0xFF, 0xF0, |
|||
0x08, 0x80, |
|||
0xF0, 0xF0 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 }, |
|||
new[] { GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8 }, |
|||
new[] { GrayF, Gray0, GrayF }}; |
|||
|
|||
private static readonly byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255, |
|||
255, 255, 255, 255, |
|||
000, 128, 128, 255, |
|||
255, 000, 255, 128 }; |
|||
|
|||
private static readonly Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 }, |
|||
new[] { Gray255, Gray255, Gray255, Gray255 }, |
|||
new[] { Gray000, Gray128, Gray128, Gray255 }, |
|||
new[] { Gray255, Gray000, Gray255, Gray128 }}; |
|||
|
|||
public static IEnumerable<object[]> Bilevel_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Bilevel_Result4x4 }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Offset(Bilevel_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 0, 4, 4, Offset(Bilevel_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 1, 4, 4, Offset(Bilevel_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 1, 4, 4, Offset(Bilevel_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Bilevel_Result12x4 }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Offset(Bilevel_Result12x4, 0, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 0, 12, 4, Offset(Bilevel_Result12x4, 1, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 1, 12, 4, Offset(Bilevel_Result12x4, 0, 1, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 1, 12, 4, Offset(Bilevel_Result12x4, 1, 1, 18, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Grayscale4_Result4x4 }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Offset(Grayscale4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 0, 4, 4, Offset(Grayscale4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 1, 4, 4, Offset(Grayscale4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 1, 4, 4, Offset(Grayscale4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Grayscale4_Result3x4 }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Offset(Grayscale4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 0, 3, 4, Offset(Grayscale4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 1, 3, 4, Offset(Grayscale4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 1, 3, 4, Offset(Grayscale4_Result3x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Grayscale8_Result4x4 }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Offset(Grayscale8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 0, 4, 4, Offset(Grayscale8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 1, 4, 4, Offset(Grayscale8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 1, 4, 4, Offset(Grayscale8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new WhiteIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new WhiteIsZero1TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new WhiteIsZero4TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
new WhiteIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff.BlackBox.Decoder")] |
|||
[Trait("Category", "Tiff")] |
|||
public class TiffDecoderTests |
|||
{ |
|||
public static readonly string[] SingleTestImages = TestImages.Tiff.All; |
|||
|
|||
public static readonly string[] MultiframeTestImages = TestImages.Tiff.Multiframes; |
|||
|
|||
public static readonly string[] NotSupportedImages = TestImages.Tiff.NotSupported; |
|||
|
|||
[Theory] |
|||
[WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)] |
|||
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Assert.Throws<NotSupportedException>(() => provider.GetImage(new TiffDecoder())); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] |
|||
[InlineData(TestImages.Tiff.SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] |
|||
[InlineData(TestImages.Tiff.Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)] |
|||
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using (var stream = new MemoryStream(testFile.Bytes, false)) |
|||
{ |
|||
IImageInfo info = Image.Identify(stream); |
|||
|
|||
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); |
|||
Assert.Equal(expectedWidth, info.Width); |
|||
Assert.Equal(expectedHeight, info.Height); |
|||
Assert.NotNull(info.Metadata); |
|||
Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); |
|||
Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); |
|||
Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFileCollection(nameof(SingleTestImages), PixelTypes.Rgba32)] |
|||
public void Decode<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TiffDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder()); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] |
|||
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TiffDecoder())) |
|||
{ |
|||
Assert.True(image.Frames.Count > 1); |
|||
|
|||
image.DebugSave(provider); |
|||
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder()); |
|||
|
|||
image.DebugSaveMultiFrame(provider); |
|||
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, new MagickReferenceDecoder()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class TiffEncoderHeaderTests |
|||
{ |
|||
[Fact] |
|||
public void WriteHeader_WritesValidHeader() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long firstIfdMarker = encoder.WriteHeader(writer); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteHeader_ReturnsFirstIfdMarker() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long firstIfdMarker = encoder.WriteHeader(writer); |
|||
Assert.Equal(4, firstIfdMarker); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class TiffFormatTests |
|||
{ |
|||
[Fact] |
|||
public void FormatProperties_AreAsExpected() |
|||
{ |
|||
TiffFormat tiffFormat = TiffFormat.Instance; |
|||
|
|||
Assert.Equal("TIFF", tiffFormat.Name); |
|||
Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); |
|||
Assert.Contains("image/tiff", tiffFormat.MimeTypes); |
|||
Assert.Contains("tif", tiffFormat.FileExtensions); |
|||
Assert.Contains("tiff", tiffFormat.FileExtensions); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class TiffMetadataTests |
|||
{ |
|||
public static readonly string[] MetadataImages = TestImages.Tiff.Metadata; |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32, false)] |
|||
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32, true)] |
|||
public void MetadataProfiles<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata })) |
|||
{ |
|||
TiffMetadata meta = image.Metadata.GetTiffMetadata(); |
|||
Assert.NotNull(meta); |
|||
if (ignoreMetadata) |
|||
{ |
|||
Assert.Null(meta.XmpProfile); |
|||
} |
|||
else |
|||
{ |
|||
Assert.NotNull(meta.XmpProfile); |
|||
Assert.Equal(2599, meta.XmpProfile.Length); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] |
|||
public void BaselineTags<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TiffDecoder())) |
|||
{ |
|||
TiffMetadata meta = image.Metadata.GetTiffMetadata(); |
|||
|
|||
Assert.NotNull(meta); |
|||
Assert.Equal(TiffByteOrder.LittleEndian, meta.ByteOrder); |
|||
Assert.Equal(PixelResolutionUnit.PixelsPerInch, image.Metadata.ResolutionUnits); |
|||
Assert.Equal(10, image.Metadata.HorizontalResolution); |
|||
Assert.Equal(10, image.Metadata.VerticalResolution); |
|||
|
|||
TiffFrameMetadata frame = image.Frames.RootFrame.Metadata.GetTiffMetadata(); |
|||
Assert.Equal(32u, frame.Width); |
|||
Assert.Equal(32u, frame.Height); |
|||
Assert.Equal(new ushort[] { 4 }, frame.BitsPerSample); |
|||
Assert.Equal(TiffCompression.Lzw, frame.Compression); |
|||
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame.PhotometricInterpretation); |
|||
Assert.Equal("This is Название", frame.ImageDescription); |
|||
Assert.Equal("This is Изготовитель камеры", frame.Make); |
|||
Assert.Equal("This is Модель камеры", frame.Model); |
|||
Assert.Equal(new uint[] { 8 }, frame.StripOffsets); |
|||
Assert.Equal(1, frame.SamplesPerPixel); |
|||
Assert.Equal(32u, frame.RowsPerStrip); |
|||
Assert.Equal(new uint[] { 297 }, frame.StripByteCounts); |
|||
Assert.Equal(10, frame.HorizontalResolution); |
|||
Assert.Equal(10, frame.VerticalResolution); |
|||
Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration); |
|||
Assert.Equal(TiffResolutionUnit.Inch, frame.ResolutionUnit); |
|||
Assert.Equal("IrfanView", frame.Software); |
|||
Assert.Equal(null, frame.DateTime); |
|||
Assert.Equal("This is author1;Author2", frame.Artist); |
|||
Assert.Equal(null, frame.HostComputer); |
|||
Assert.Equal(48, frame.ColorMap.Length); |
|||
Assert.Equal(10537, frame.ColorMap[0]); |
|||
Assert.Equal(14392, frame.ColorMap[1]); |
|||
Assert.Equal(58596, frame.ColorMap[46]); |
|||
Assert.Equal(3855, frame.ColorMap[47]); |
|||
|
|||
Assert.Equal(null, frame.ExtraSamples); |
|||
Assert.Equal(TiffPredictor.None, frame.Predictor); |
|||
Assert.Equal(null, frame.SampleFormat); |
|||
Assert.Equal("This is Авторские права", frame.Copyright); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Tiff.MultiframeDeflateWithPreview, PixelTypes.Rgba32)] |
|||
public void SubfileType<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TiffDecoder())) |
|||
{ |
|||
TiffMetadata meta = image.Metadata.GetTiffMetadata(); |
|||
Assert.NotNull(meta); |
|||
|
|||
Assert.Equal(2, image.Frames.Count); |
|||
|
|||
TiffFrameMetadata frame0 = image.Frames[0].Metadata.GetTiffMetadata(); |
|||
Assert.Equal(TiffNewSubfileType.FullImage, frame0.NewSubfileType); |
|||
Assert.Equal(null, frame0.SubfileType); |
|||
Assert.Equal(255u, frame0.Width); |
|||
Assert.Equal(255u, frame0.Height); |
|||
|
|||
TiffFrameMetadata frame1 = image.Frames[1].Metadata.GetTiffMetadata(); |
|||
Assert.Equal(TiffNewSubfileType.Preview, frame1.NewSubfileType); |
|||
Assert.Equal(TiffSubfileType.Preview, frame1.SubfileType); |
|||
Assert.Equal(255u, frame1.Width); |
|||
Assert.Equal(255u, frame1.Height); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,326 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tiff |
|||
{ |
|||
[Trait("Category", "Tiff")] |
|||
public class SubStreamTests |
|||
{ |
|||
[Fact] |
|||
public void Constructor_PositionsStreamCorrectly_WithSpecifiedOffset() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
innerStream.Position = 2; |
|||
|
|||
SubStream stream = new SubStream(innerStream, 4, 6); |
|||
|
|||
Assert.Equal(0, stream.Position); |
|||
Assert.Equal(6, stream.Length); |
|||
Assert.Equal(4, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_PositionsStreamCorrectly_WithCurrentOffset() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
innerStream.Position = 2; |
|||
|
|||
SubStream stream = new SubStream(innerStream, 6); |
|||
|
|||
Assert.Equal(0, stream.Position); |
|||
Assert.Equal(6, stream.Length); |
|||
Assert.Equal(2, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanRead_ReturnsTrue() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.True(stream.CanRead); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanWrite_ReturnsFalse() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.False(stream.CanWrite); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanSeek_ReturnsTrue() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.True(stream.CanSeek); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Length_ReturnsTheConstrainedLength() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Equal(6, stream.Length); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Position_ReturnsZeroBeforeReading() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Equal(0, stream.Position); |
|||
Assert.Equal(2, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Position_ReturnsPositionAfterReading() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Read(new byte[2], 0, 2); |
|||
|
|||
Assert.Equal(2, stream.Position); |
|||
Assert.Equal(4, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Position_ReturnsPositionAfterReadingTwice() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Read(new byte[2], 0, 2); |
|||
stream.Read(new byte[2], 0, 2); |
|||
|
|||
Assert.Equal(4, stream.Position); |
|||
Assert.Equal(6, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Position_SettingPropertySeeksToNewPosition() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 3; |
|||
|
|||
Assert.Equal(3, stream.Position); |
|||
Assert.Equal(5, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Flush_ThrowsNotSupportedException() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Throws<NotSupportedException>(() => stream.Flush()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Read_Reads_FromStartOfSubStream() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
byte[] buffer = new byte[3]; |
|||
var result = stream.Read(buffer, 0, 3); |
|||
|
|||
Assert.Equal(new byte[] { 3, 4, 5 }, buffer); |
|||
Assert.Equal(3, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2, SeekOrigin.Begin)] |
|||
[InlineData(1, SeekOrigin.Current)] |
|||
[InlineData(4, SeekOrigin.End)] |
|||
public void Read_Reads_FromMiddleOfSubStream(long offset, SeekOrigin origin) |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
stream.Seek(offset, origin); |
|||
byte[] buffer = new byte[3]; |
|||
var result = stream.Read(buffer, 0, 3); |
|||
|
|||
Assert.Equal(new byte[] { 5, 6, 7 }, buffer); |
|||
Assert.Equal(3, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(3, SeekOrigin.Begin)] |
|||
[InlineData(2, SeekOrigin.Current)] |
|||
[InlineData(3, SeekOrigin.End)] |
|||
public void Read_Reads_FromEndOfSubStream(long offset, SeekOrigin origin) |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
stream.Seek(offset, origin); |
|||
byte[] buffer = new byte[3]; |
|||
var result = stream.Read(buffer, 0, 3); |
|||
|
|||
Assert.Equal(new byte[] { 6, 7, 8 }, buffer); |
|||
Assert.Equal(3, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4, SeekOrigin.Begin)] |
|||
[InlineData(3, SeekOrigin.Current)] |
|||
[InlineData(2, SeekOrigin.End)] |
|||
public void Read_Reads_FromBeyondEndOfSubStream(long offset, SeekOrigin origin) |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
stream.Seek(offset, origin); |
|||
byte[] buffer = new byte[3]; |
|||
var result = stream.Read(buffer, 0, 3); |
|||
|
|||
Assert.Equal(new byte[] { 7, 8, 0 }, buffer); |
|||
Assert.Equal(2, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReadByte_Reads_FromStartOfSubStream() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
var result = stream.ReadByte(); |
|||
|
|||
Assert.Equal(3, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReadByte_Reads_FromMiddleOfSubStream() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 3; |
|||
var result = stream.ReadByte(); |
|||
|
|||
Assert.Equal(6, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReadByte_Reads_FromEndOfSubStream() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 5; |
|||
var result = stream.ReadByte(); |
|||
|
|||
Assert.Equal(8, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReadByte_Reads_FromBeyondEndOfSubStream() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 5; |
|||
stream.ReadByte(); |
|||
var result = stream.ReadByte(); |
|||
|
|||
Assert.Equal(-1, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Write_ThrowsNotSupportedException() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[] { 1, 2 }, 0, 2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteByte_ThrowsNotSupportedException() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Throws<NotSupportedException>(() => stream.WriteByte(42)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Seek_MovesToNewPosition_FromBegin() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
long result = stream.Seek(2, SeekOrigin.Begin); |
|||
|
|||
Assert.Equal(2, result); |
|||
Assert.Equal(2, stream.Position); |
|||
Assert.Equal(4, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Seek_MovesToNewPosition_FromCurrent() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
long result = stream.Seek(2, SeekOrigin.Current); |
|||
|
|||
Assert.Equal(3, result); |
|||
Assert.Equal(3, stream.Position); |
|||
Assert.Equal(5, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Seek_MovesToNewPosition_FromEnd() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
stream.Position = 1; |
|||
long result = stream.Seek(2, SeekOrigin.End); |
|||
|
|||
Assert.Equal(4, result); |
|||
Assert.Equal(4, stream.Position); |
|||
Assert.Equal(6, innerStream.Position); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Seek_ThrowsException_WithInvalidOrigin() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
var e = Assert.Throws<ArgumentException>(() => stream.Seek(2, (SeekOrigin)99)); |
|||
Assert.Equal("Invalid seek origin.", e.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SetLength_ThrowsNotSupportedException() |
|||
{ |
|||
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); |
|||
SubStream stream = new SubStream(innerStream, 2, 6); |
|||
|
|||
Assert.Throws<NotSupportedException>(() => stream.SetLength(5)); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue