mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
102 changed files with 14894 additions and 4105 deletions
@ -0,0 +1,57 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
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 static class DeflateTiffCompression |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decompress(Stream stream, int byteCount, 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; |
|||
SubStream subStream = new SubStream(stream, byteCount - headerLength); |
|||
using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true)) |
|||
{ |
|||
deflateStream.ReadFull(buffer); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed using LZW compression.
|
|||
/// </summary>
|
|||
internal static class LzwTiffCompression |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decompress(Stream stream, int byteCount, byte[] buffer) |
|||
{ |
|||
SubStream 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 and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is not compressed.
|
|||
/// </summary>
|
|||
internal static class NoneTiffCompression |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decompress(Stream stream, int byteCount, byte[] buffer) |
|||
{ |
|||
stream.ReadFull(buffer, byteCount); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
|
|||
/// </summary>
|
|||
internal static class PackBitsTiffCompression |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decompress(Stream stream, int byteCount, byte[] buffer) |
|||
{ |
|||
byte[] compressedData = ArrayPool<byte>.Shared.Rent(byteCount); |
|||
|
|||
try |
|||
{ |
|||
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; |
|||
|
|||
Array.Copy(compressedData, literalOffset, buffer, decompressedOffset, literalLength); |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
ArrayPool<byte>.Shared.Return(compressedData); |
|||
} |
|||
} |
|||
|
|||
private static void ArrayCopyRepeat(byte value, byte[] destinationArray, int destinationIndex, int length) |
|||
{ |
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
destinationArray[i + destinationIndex] = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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,71 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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>
|
|||
internal enum TiffCompression |
|||
{ |
|||
/// <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 and contributors.
|
|||
// 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 and contributors.
|
|||
// 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 and contributors.
|
|||
// 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 |
|||
{ |
|||
/// <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 and contributors.
|
|||
// 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] |
|||
internal enum TiffNewSubfileType |
|||
{ |
|||
/// <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 and contributors.
|
|||
// 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 and contributors.
|
|||
// 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>
|
|||
internal enum TiffPhotometricInterpretation |
|||
{ |
|||
/// <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 and contributors.
|
|||
// 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>
|
|||
internal enum TiffPlanarConfiguration |
|||
{ |
|||
/// <summary>
|
|||
/// Chunky format.
|
|||
/// </summary>
|
|||
Chunky = 1, |
|||
|
|||
/// <summary>
|
|||
/// Planar format.
|
|||
/// </summary>
|
|||
Planar = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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>
|
|||
internal enum TiffResolutionUnit |
|||
{ |
|||
/// <summary>
|
|||
/// No absolute unit of measurement.
|
|||
/// </summary>
|
|||
None = 1, |
|||
|
|||
/// <summary>
|
|||
/// Inch.
|
|||
/// </summary>
|
|||
Inch = 2, |
|||
|
|||
/// <summary>
|
|||
/// Centimeter.
|
|||
/// </summary>
|
|||
Centimeter = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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>
|
|||
internal enum TiffSubfileType |
|||
{ |
|||
/// <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,716 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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,26 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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,76 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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,16 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <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 and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
|
|||
/// </summary>
|
|||
public interface ITiffEncoderOptions |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats; |
|||
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 : struct, 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 : struct, IPixel<TPixel> |
|||
{ |
|||
encoder = encoder ?? new TiffEncoder(); |
|||
encoder.Encode(source, stream); |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
|
|||
/// </summary>
|
|||
internal static class BlackIsZero1TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,60 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
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 static class BlackIsZero4TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,44 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
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 static class BlackIsZero8TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,51 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class BlackIsZeroTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
BitReader bitReader = new BitReader(data); |
|||
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int value = bitReader.ReadBits(bitsPerSample[0]); |
|||
float intensity = ((float)value) / factor; |
|||
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class PaletteTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <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>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
int colorCount = (int)Math.Pow(2, bitsPerSample[0]); |
|||
TPixel[] palette = GeneratePalette<TPixel>(colorMap, colorCount); |
|||
|
|||
BitReader bitReader = new BitReader(data); |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int index = bitReader.ReadBits(bitsPerSample[0]); |
|||
pixels[x, y] = palette[index]; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static TPixel[] GeneratePalette<TPixel>(uint[] colorMap, int colorCount) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel[] 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,46 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
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 static class Rgb888TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint offset = 0; |
|||
|
|||
for (int y = top; y < top + height; 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)); |
|||
pixels[x, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class RgbPlanarTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffers to read image data from.</param>
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[][] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
BitReader rBitReader = new BitReader(data[0]); |
|||
BitReader gBitReader = new BitReader(data[1]); |
|||
BitReader bBitReader = new BitReader(data[2]); |
|||
float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f; |
|||
float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f; |
|||
float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
float r = ((float)rBitReader.ReadBits(bitsPerSample[0])) / rFactor; |
|||
float g = ((float)gBitReader.ReadBits(bitsPerSample[1])) / gFactor; |
|||
float b = ((float)bBitReader.ReadBits(bitsPerSample[2])) / bFactor; |
|||
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
rBitReader.NextRow(); |
|||
gBitReader.NextRow(); |
|||
bBitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class RgbTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
BitReader bitReader = new BitReader(data); |
|||
float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f; |
|||
float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f; |
|||
float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
float r = ((float)bitReader.ReadBits(bitsPerSample[0])) / rFactor; |
|||
float g = ((float)bitReader.ReadBits(bitsPerSample[1])) / gFactor; |
|||
float b = ((float)bitReader.ReadBits(bitsPerSample[2])) / bFactor; |
|||
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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,52 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class WhiteIsZero1TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,60 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
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 static class WhiteIsZero4TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,44 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
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 static class WhiteIsZero8TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint 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,51 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
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 static class WhiteIsZeroTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
BitReader bitReader = new BitReader(data); |
|||
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int value = bitReader.ReadBits(bitsPerSample[0]); |
|||
float intensity = 1.0f - (((float)value) / 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 Centre](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 | | | | |
|||
|ImageDescription | | Y | | |
|||
|Make | | Y | | |
|||
|Model | | Y | | |
|||
|StripOffsets | | Y | | |
|||
|Orientation | | | | |
|||
|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 | | | | |
|||
|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 | | | | |
|||
|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 | | | | |
|||
|INGR Packet Data Tag | | | | |
|||
|INGR Flag Registers | | | | |
|||
|IrasB Transformation Matrix| | | | |
|||
|ModelTiepointTag | | | | |
|||
|ModelTransformationTag | | | | |
|||
|Photoshop | | | | |
|||
|Exif IFD | | | | |
|||
|ICC Profile | | | | |
|||
|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,19 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <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,32 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a TIFF stream.
|
|||
/// </summary>
|
|||
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions |
|||
{ |
|||
/// <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 : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, "stream"); |
|||
|
|||
using (TiffDecoderCore decoder = new TiffDecoderCore(configuration, this)) |
|||
{ |
|||
return decoder.Decode<TPixel>(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Encoder for writing the data image to a stream in TIFF format.
|
|||
/// </summary>
|
|||
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions |
|||
{ |
|||
/// <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 : struct, IPixel<TPixel> |
|||
{ |
|||
var encode = new TiffEncoderCore(this); |
|||
encode.Encode(image, stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <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 : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(image, nameof(image)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long firstIfdMarker = this.WriteHeader(writer); |
|||
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<TiffIfdEntry> 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)); |
|||
List<byte[]> largeDataBlocks = new List<byte[]>(); |
|||
|
|||
entries.Sort((a, b) => a.Tag - b.Tag); |
|||
|
|||
writer.Write((ushort)entries.Count); |
|||
|
|||
foreach (TiffIfdEntry entry in entries) |
|||
{ |
|||
writer.Write(entry.Tag); |
|||
writer.Write((ushort)entry.Type); |
|||
writer.Write(entry.Count); |
|||
|
|||
if (entry.Value.Length <= 4) |
|||
{ |
|||
writer.WritePadded(entry.Value); |
|||
} |
|||
else |
|||
{ |
|||
largeDataBlocks.Add(entry.Value); |
|||
writer.Write(dataOffset); |
|||
dataOffset += (uint)(entry.Value.Length + (entry.Value.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 : struct, IPixel<TPixel> |
|||
{ |
|||
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>(); |
|||
|
|||
this.AddImageFormat(image, ifdEntries); |
|||
this.AddMetadata(image, ifdEntries); |
|||
|
|||
writer.WriteMarker(ifdOffset, (uint)writer.Position); |
|||
long nextIfdMarker = this.WriteIfd(writer, ifdEntries); |
|||
|
|||
return nextIfdMarker; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds image metadata 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 metadata entries to add to the IFD.</param>
|
|||
public void AddMetadata<TPixel>(Image<TPixel> image, List<TiffIfdEntry> ifdEntries) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ifdEntries.AddUnsignedRational(TiffTags.XResolution, new Rational(image.MetaData.HorizontalResolution)); |
|||
ifdEntries.AddUnsignedRational(TiffTags.YResolution, new Rational(image.MetaData.VerticalResolution)); |
|||
ifdEntries.AddUnsignedShort(TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch); |
|||
|
|||
foreach (ImageProperty metadata in image.MetaData.Properties) |
|||
{ |
|||
switch (metadata.Name) |
|||
{ |
|||
case TiffMetadataNames.Artist: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.Artist, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.Copyright: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.Copyright, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.DateTime: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.DateTime, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.HostComputer: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.HostComputer, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.ImageDescription: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.ImageDescription, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.Make: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.Make, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.Model: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.Model, metadata.Value); |
|||
break; |
|||
} |
|||
|
|||
case TiffMetadataNames.Software: |
|||
{ |
|||
ifdEntries.AddAscii(TiffTags.Software, metadata.Value); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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<TiffIfdEntry> ifdEntries) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Formats.Tiff; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the means to encode and decode Tiff images.
|
|||
/// </summary>
|
|||
public class TiffFormat : IImageFormat |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Data structure for holding details of each TIFF IFD.
|
|||
/// </summary>
|
|||
internal struct TiffIfd |
|||
{ |
|||
/// <summary>
|
|||
/// An array of the entries within this IFD.
|
|||
/// </summary>
|
|||
public TiffIfdEntry[] Entries; |
|||
|
|||
/// <summary>
|
|||
/// Offset (in bytes) to the next IFD, or zero if this is the last IFD.
|
|||
/// </summary>
|
|||
public uint NextIfdOffset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffIfd"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="entries">An array of the entries within the IFD.</param>
|
|||
/// <param name="nextIfdOffset">Offset (in bytes) to the next IFD, or zero if this is the last IFD.</param>
|
|||
public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset) |
|||
{ |
|||
this.Entries = entries; |
|||
this.NextIfdOffset = nextIfdOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
|
|||
/// </summary>
|
|||
/// <param name="tag">The tag ID to search for.</param>
|
|||
/// <returns>The resulting <see cref="TiffIfdEntry"/>, or null if it does not exists.</returns>
|
|||
public TiffIfdEntry? GetIfdEntry(ushort tag) |
|||
{ |
|||
for (int i = 0; i < this.Entries.Length; i++) |
|||
{ |
|||
if (this.Entries[i].Tag == tag) |
|||
{ |
|||
return this.Entries[i]; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
|
|||
/// </summary>
|
|||
/// <param name="tag">The tag ID to search for.</param>
|
|||
/// <param name="entry">The resulting <see cref="TiffIfdEntry"/>, if it exists.</param>
|
|||
/// <returns>A flag indicating whether the requested entry exists.</returns>
|
|||
public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry) |
|||
{ |
|||
TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag); |
|||
entry = nullableEntry ?? default(TiffIfdEntry); |
|||
return nullableEntry.HasValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Data structure for holding details of each TIFF IFD entry.
|
|||
/// </summary>
|
|||
internal struct TiffIfdEntry |
|||
{ |
|||
/// <summary>
|
|||
/// The Tag ID for this entry. See <see cref="TiffTags"/> for typical values.
|
|||
/// </summary>
|
|||
public ushort Tag; |
|||
|
|||
/// <summary>
|
|||
/// The data-type of this entry.
|
|||
/// </summary>
|
|||
public TiffType Type; |
|||
|
|||
/// <summary>
|
|||
/// The number of array items in this entry, or one if only a single value.
|
|||
/// </summary>
|
|||
public uint Count; |
|||
|
|||
/// <summary>
|
|||
/// The raw byte data for this entry.
|
|||
/// </summary>
|
|||
public byte[] Value; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffIfdEntry"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="tag">The Tag ID for this entry.</param>
|
|||
/// <param name="type">The data-type of this entry.</param>
|
|||
/// <param name="count">The number of array items in this entry.</param>
|
|||
/// <param name="value">The raw byte data for this entry.</param>
|
|||
public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value) |
|||
{ |
|||
this.Tag = tag; |
|||
this.Type = type; |
|||
this.Count = count; |
|||
this.Value = value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,354 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
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,34 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <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,51 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats |
|||
{ |
|||
/// <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,62 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff |
|||
{ |
|||
/// <summary>
|
|||
/// Utility class to read a sequence of bits from an array
|
|||
/// </summary>
|
|||
internal class BitReader |
|||
{ |
|||
private readonly byte[] array; |
|||
private int offset; |
|||
private int bitOffset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BitReader" /> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to read data from.</param>
|
|||
public BitReader(byte[] array) |
|||
{ |
|||
this.array = array; |
|||
} |
|||
|
|||
/// <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 and contributors.
|
|||
// 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 and contributors.
|
|||
// 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, 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 and contributors.
|
|||
// 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,47 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
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, 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, byte[] buffer) |
|||
{ |
|||
ReadFull(stream, buffer, buffer.Length); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// 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(); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -1,114 +1,114 @@ |
|||
<# |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ import namespace="System.Linq" #> |
|||
<#@ import namespace="System.Text" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
|
|||
// <auto-generated /> |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
/// <summary> |
|||
/// Collection of Porter Duff alpha blending functions applying different composition models. |
|||
/// </summary> |
|||
/// <remarks> |
|||
/// These functions are designed to be a general solution for all color cases, |
|||
/// that is, they take in account the alpha value of both the backdrop |
|||
/// and source, and there's no need to alpha-premultiply neither the backdrop |
|||
/// nor the source. |
|||
/// Note there are faster functions for when the backdrop color is known |
|||
/// to be opaque |
|||
/// </remarks> |
|||
internal static class DefaultPixelBlenders<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
|
|||
<# |
|||
string[] composers = new []{ |
|||
"Src", |
|||
"SrcAtop", |
|||
"SrcOver", |
|||
"SrcIn", |
|||
"SrcOut", |
|||
"Dest", |
|||
"DestAtop", |
|||
"DestOver", |
|||
"DestIn", |
|||
"DestOut", |
|||
"Clear", |
|||
"Xor", |
|||
}; |
|||
|
|||
string[] blenders = new []{ |
|||
"Normal", |
|||
"Multiply", |
|||
"Add", |
|||
"Subtract", |
|||
"Screen", |
|||
"Darken", |
|||
"Lighten", |
|||
"Overlay", |
|||
"HardLight" |
|||
}; |
|||
|
|||
foreach(var composer in composers) { |
|||
foreach(var blender in blenders) { |
|||
|
|||
string blender_composer= $"{blender}{composer}"; |
|||
|
|||
#> |
|||
internal class <#= blender_composer#> : PixelBlender<TPixel> |
|||
{ |
|||
/// <summary> |
|||
/// Gets the static instance of this blender. |
|||
/// </summary> |
|||
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); |
|||
|
|||
/// <inheritdoc /> |
|||
public override TPixel Blend(TPixel background, TPixel source, float amount) |
|||
{ |
|||
TPixel dest = default; |
|||
dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); |
|||
return dest; |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) |
|||
{ |
|||
amount = amount.Clamp(0, 1); |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount) |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
<# |
|||
} |
|||
} |
|||
|
|||
#> |
|||
} |
|||
<# |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ import namespace="System.Linq" #> |
|||
<#@ import namespace="System.Text" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
|
|||
// <auto-generated /> |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
/// <summary> |
|||
/// Collection of Porter Duff alpha blending functions applying different composition models. |
|||
/// </summary> |
|||
/// <remarks> |
|||
/// These functions are designed to be a general solution for all color cases, |
|||
/// that is, they take in account the alpha value of both the backdrop |
|||
/// and source, and there's no need to alpha-premultiply neither the backdrop |
|||
/// nor the source. |
|||
/// Note there are faster functions for when the backdrop color is known |
|||
/// to be opaque |
|||
/// </remarks> |
|||
internal static class DefaultPixelBlenders<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
|
|||
<# |
|||
string[] composers = new []{ |
|||
"Src", |
|||
"SrcAtop", |
|||
"SrcOver", |
|||
"SrcIn", |
|||
"SrcOut", |
|||
"Dest", |
|||
"DestAtop", |
|||
"DestOver", |
|||
"DestIn", |
|||
"DestOut", |
|||
"Clear", |
|||
"Xor", |
|||
}; |
|||
|
|||
string[] blenders = new []{ |
|||
"Normal", |
|||
"Multiply", |
|||
"Add", |
|||
"Subtract", |
|||
"Screen", |
|||
"Darken", |
|||
"Lighten", |
|||
"Overlay", |
|||
"HardLight" |
|||
}; |
|||
|
|||
foreach(var composer in composers) { |
|||
foreach(var blender in blenders) { |
|||
|
|||
string blender_composer= $"{blender}{composer}"; |
|||
|
|||
#> |
|||
internal class <#= blender_composer#> : PixelBlender<TPixel> |
|||
{ |
|||
/// <summary> |
|||
/// Gets the static instance of this blender. |
|||
/// </summary> |
|||
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); |
|||
|
|||
/// <inheritdoc /> |
|||
public override TPixel Blend(TPixel background, TPixel source, float amount) |
|||
{ |
|||
TPixel dest = default; |
|||
dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); |
|||
return dest; |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) |
|||
{ |
|||
amount = amount.Clamp(0, 1); |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc /> |
|||
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount) |
|||
{ |
|||
for (int i = 0; i < destination.Length; i++) |
|||
{ |
|||
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
<# |
|||
} |
|||
} |
|||
|
|||
#> |
|||
} |
|||
} |
|||
@ -1,182 +1,182 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats |
|||
{ |
|||
/// <summary>
|
|||
/// Abstract base class for calling pixel composition functions
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
|||
internal abstract class PixelBlender<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Blend 2 pixels together.
|
|||
/// </summary>
|
|||
/// <param name="background">The background color.</param>
|
|||
/// <param name="source">The source color.</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
/// <returns>The final pixel value after composition</returns>
|
|||
public abstract TPixel Blend(TPixel background, TPixel source, float amount); |
|||
|
|||
/// <summary>
|
|||
/// Blend 2 rows together.
|
|||
/// </summary>
|
|||
/// <param name="destination">destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
protected abstract void BlendFunction( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
float amount); |
|||
|
|||
/// <summary>
|
|||
/// Blend 2 rows together.
|
|||
/// </summary>
|
|||
/// <param name="destination">destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
protected abstract void BlendFunction( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
ReadOnlySpan<float> amount); |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixel> source, |
|||
ReadOnlySpan<float> amount) |
|||
{ |
|||
this.Blend<TPixel>(configuration, destination, background, source, amount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend<TPixelSrc>( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixelSrc> source, |
|||
ReadOnlySpan<float> amount) |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
|||
|
|||
using (IMemoryOwner<Vector4> buffer = |
|||
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) |
|||
{ |
|||
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
|||
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
|||
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToScaledVector4( |
|||
configuration, |
|||
background.Slice(0, background.Length), |
|||
backgroundSpan); |
|||
PixelOperations<TPixelSrc>.Instance.ToScaledVector4( |
|||
configuration, |
|||
source.Slice(0, background.Length), |
|||
sourceSpan); |
|||
|
|||
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromScaledVector4( |
|||
configuration, |
|||
destinationSpan.Slice(0, background.Length), |
|||
destination); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend<TPixelSrc>( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixelSrc> source, |
|||
float amount) |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
|||
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); |
|||
|
|||
using (IMemoryOwner<Vector4> buffer = |
|||
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) |
|||
{ |
|||
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
|||
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
|||
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToScaledVector4( |
|||
configuration, |
|||
background.Slice(0, background.Length), |
|||
backgroundSpan); |
|||
PixelOperations<TPixelSrc>.Instance.ToScaledVector4( |
|||
configuration, |
|||
source.Slice(0, background.Length), |
|||
sourceSpan); |
|||
|
|||
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromScaledVector4( |
|||
configuration, |
|||
destinationSpan.Slice(0, background.Length), |
|||
destination); |
|||
} |
|||
} |
|||
} |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats |
|||
{ |
|||
/// <summary>
|
|||
/// Abstract base class for calling pixel composition functions
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
|||
internal abstract class PixelBlender<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Blend 2 pixels together.
|
|||
/// </summary>
|
|||
/// <param name="background">The background color.</param>
|
|||
/// <param name="source">The source color.</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
/// <returns>The final pixel value after composition</returns>
|
|||
public abstract TPixel Blend(TPixel background, TPixel source, float amount); |
|||
|
|||
/// <summary>
|
|||
/// Blend 2 rows together.
|
|||
/// </summary>
|
|||
/// <param name="destination">destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
protected abstract void BlendFunction( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
float amount); |
|||
|
|||
/// <summary>
|
|||
/// Blend 2 rows together.
|
|||
/// </summary>
|
|||
/// <param name="destination">destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
protected abstract void BlendFunction( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
ReadOnlySpan<float> amount); |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixel> source, |
|||
ReadOnlySpan<float> amount) |
|||
{ |
|||
this.Blend<TPixel>(configuration, destination, background, source, amount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A span with values between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend<TPixelSrc>( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixelSrc> source, |
|||
ReadOnlySpan<float> amount) |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
|||
|
|||
using (IMemoryOwner<Vector4> buffer = |
|||
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) |
|||
{ |
|||
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
|||
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
|||
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToScaledVector4( |
|||
configuration, |
|||
background.Slice(0, background.Length), |
|||
backgroundSpan); |
|||
PixelOperations<TPixelSrc>.Instance.ToScaledVector4( |
|||
configuration, |
|||
source.Slice(0, background.Length), |
|||
sourceSpan); |
|||
|
|||
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromScaledVector4( |
|||
configuration, |
|||
destinationSpan.Slice(0, background.Length), |
|||
destination); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends 2 rows together
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
|
|||
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
|
|||
/// <param name="destination">the destination span</param>
|
|||
/// <param name="background">the background span</param>
|
|||
/// <param name="source">the source span</param>
|
|||
/// <param name="amount">
|
|||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|||
/// </param>
|
|||
public void Blend<TPixelSrc>( |
|||
Configuration configuration, |
|||
Span<TPixel> destination, |
|||
ReadOnlySpan<TPixel> background, |
|||
ReadOnlySpan<TPixelSrc> source, |
|||
float amount) |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
|||
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
|||
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); |
|||
|
|||
using (IMemoryOwner<Vector4> buffer = |
|||
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) |
|||
{ |
|||
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
|||
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
|||
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
|||
|
|||
PixelOperations<TPixel>.Instance.ToScaledVector4( |
|||
configuration, |
|||
background.Slice(0, background.Length), |
|||
backgroundSpan); |
|||
PixelOperations<TPixelSrc>.Instance.ToScaledVector4( |
|||
configuration, |
|||
source.Slice(0, background.Length), |
|||
sourceSpan); |
|||
|
|||
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromScaledVector4( |
|||
configuration, |
|||
destinationSpan.Slice(0, background.Length), |
|||
destination); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.IO; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using CoreImage = SixLabors.ImageSharp.Image; |
|||
using CoreSize = SixLabors.Primitives.Size; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
public class DecodeTiff : BenchmarkBase |
|||
{ |
|||
private byte[] tiffBytes; |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.tiffBytes == null) |
|||
{ |
|||
this.tiffBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff"); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")] |
|||
public Size TiffSystemDrawing() |
|||
{ |
|||
using (MemoryStream memoryStream = new MemoryStream(this.tiffBytes)) |
|||
{ |
|||
using (var image = System.Drawing.Image.FromStream(memoryStream)) |
|||
{ |
|||
return image.Size; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Tiff")] |
|||
public CoreSize TiffCore() |
|||
{ |
|||
using (MemoryStream memoryStream = new MemoryStream(this.tiffBytes)) |
|||
{ |
|||
using (Image<Rgba32> image = CoreImage.Load<Rgba32>(memoryStream)) |
|||
{ |
|||
return new CoreSize(image.Width, image.Height); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Png.Zlib; |
|||
|
|||
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]; |
|||
|
|||
DeflateTiffCompression.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(compressedStream, 6)) |
|||
{ |
|||
uncompressedStream.CopyTo(deflateStream); |
|||
} |
|||
|
|||
compressedStream.Seek(0, SeekOrigin.Begin); |
|||
return compressedStream; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
using ImageSharp.Formats.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]; |
|||
|
|||
LzwTiffCompression.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 and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.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]; |
|||
|
|||
NoneTiffCompression.Decompress(stream, byteCount, buffer); |
|||
|
|||
Assert.Equal(expectedResult, buffer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.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]; |
|||
|
|||
PackBitsTiffCompression.Decompress(stream, inputData.Length, buffer); |
|||
|
|||
Assert.Equal(expectedResult, buffer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.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 byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000, |
|||
0b11110000, |
|||
0b01110000, |
|||
0b10010000 }; |
|||
|
|||
private static 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 byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000, |
|||
0b11111111, 0b11111111, |
|||
0b01101001, 0b10100000, |
|||
0b10010000, 0b01100000}; |
|||
|
|||
private static 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 byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F, |
|||
0xFF, 0xFF, |
|||
0x08, 0x8F, |
|||
0xF0, 0xF8 }; |
|||
|
|||
private static 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 byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00, |
|||
0xFF, 0xF0, |
|||
0x08, 0x80, |
|||
0xF0, 0xF0 }; |
|||
|
|||
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 }, |
|||
new[] { GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8 }, |
|||
new[] { GrayF, Gray0, GrayF }}; |
|||
|
|||
private static byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255, |
|||
255, 255, 255, 255, |
|||
000, 128, 128, 255, |
|||
255, 000, 255, 128 }; |
|||
|
|||
private static 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 => |
|||
{ |
|||
BlackIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, 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 => |
|||
{ |
|||
BlackIsZero1TiffColor.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 => |
|||
{ |
|||
BlackIsZero4TiffColor.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 => |
|||
{ |
|||
BlackIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class PaletteTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); } |
|||
|
|||
public static uint[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); } |
|||
|
|||
private static byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23, |
|||
0x4A, 0xD2, |
|||
0x12, 0x34, |
|||
0xAB, 0xEF }; |
|||
|
|||
private static 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 byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20, |
|||
0x4A, 0xD0, |
|||
0x12, 0x30, |
|||
0xAB, 0xE0 }; |
|||
|
|||
private static 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 uint[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); } |
|||
|
|||
private static byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003, |
|||
100, 110, 120, 130, |
|||
000, 255, 128, 255, |
|||
050, 100, 150, 200 }; |
|||
|
|||
private static 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, int bitsPerSample, uint[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
PaletteTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, colorMap, 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 uint[] GenerateColorMap(uint[][] colorPalette) |
|||
{ |
|||
int colorCount = colorPalette.Length; |
|||
uint[] colorMap = new uint[colorCount * 3]; |
|||
|
|||
for (int i = 0; i < colorCount; i++) |
|||
{ |
|||
colorMap[colorCount * 0 + i] = colorPalette[i][0]; |
|||
colorMap[colorCount * 1 + i] = colorPalette[i][1]; |
|||
colorMap[colorCount * 2 + i] = 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,66 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using Xunit; |
|||
using ImageSharp; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
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; |
|||
Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight); |
|||
image.Mutate(x => x.Fill(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,199 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.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[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 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[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 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[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 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, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
RgbPlanarTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.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[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 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[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 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[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 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, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
RgbTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Rgb8_Data))] |
|||
public void Decode_WritesPixelData_8Bit(byte[] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
Rgb888TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.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 byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000, |
|||
0b11110000, |
|||
0b01110000, |
|||
0b10010000 }; |
|||
|
|||
private static 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 byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000, |
|||
0b11111111, 0b11111111, |
|||
0b01101001, 0b10100000, |
|||
0b10010000, 0b01100000}; |
|||
|
|||
private static 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 byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F, |
|||
0xFF, 0xFF, |
|||
0x08, 0x8F, |
|||
0xF0, 0xF8 }; |
|||
|
|||
private static 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 byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00, |
|||
0xFF, 0xF0, |
|||
0x08, 0x80, |
|||
0xF0, 0xF0 }; |
|||
|
|||
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 }, |
|||
new[] { GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8 }, |
|||
new[] { GrayF, Gray0, GrayF }}; |
|||
|
|||
private static byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255, |
|||
255, 255, 255, 255, |
|||
000, 128, 128, 255, |
|||
255, 000, 255, 128 }; |
|||
|
|||
private static 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 => |
|||
{ |
|||
WhiteIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, 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 => |
|||
{ |
|||
WhiteIsZero1TiffColor.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 => |
|||
{ |
|||
WhiteIsZero4TiffColor.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 => |
|||
{ |
|||
WhiteIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
|
|||
public class TiffDecoderHeaderTests |
|||
{ |
|||
public static object[][] IsLittleEndianValues = new[] { new object[] { false }, |
|||
new object[] { true } }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadHeader_ReadsEndianness(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd() |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, false, null, null); |
|||
|
|||
decoder.ReadHeader(); |
|||
|
|||
Assert.Equal(isLittleEndian, decoder.IsLittleEndian); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadHeader_ReadsFirstIfdOffset(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd() |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, false, null, null); |
|||
|
|||
uint firstIfdOffset = decoder.ReadHeader(); |
|||
|
|||
Assert.Equal(8u, firstIfdOffset); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0x1234)] |
|||
[InlineData(0x4912)] |
|||
[InlineData(0x1249)] |
|||
[InlineData(0x4D12)] |
|||
[InlineData(0x124D)] |
|||
[InlineData(0x494D)] |
|||
[InlineData(0x4D49)] |
|||
public void Decode_ThrowsException_WithInvalidByteOrderMarkers(ushort byteOrderMarker) |
|||
{ |
|||
Stream stream = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd(), |
|||
ByteOrderMarker = byteOrderMarker |
|||
} |
|||
.ToStream(true); |
|||
|
|||
TiffDecoder decoder = new TiffDecoder(); |
|||
|
|||
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); }); |
|||
|
|||
Assert.Equal("Invalid TIFF file header.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void Decode_ThrowsException_WithIncorrectMagicNumber(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd(), |
|||
MagicNumber = 32 |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoder decoder = new TiffDecoder(); |
|||
|
|||
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); }); |
|||
|
|||
Assert.Equal("Invalid TIFF file header.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void Decode_ThrowsException_WithNoIfdZero(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = null |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoder decoder = new TiffDecoder(); |
|||
|
|||
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); }); |
|||
|
|||
Assert.Equal("Invalid TIFF file header.", e.Message); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,846 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
public class TiffDecoderIfdEntryTests |
|||
{ |
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 1u, 1u)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii, 1u, 1u)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 1u, 2u)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 1u, 4u)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 1u, 8u)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 1u, 1u)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined, 1u, 1u)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 1u, 2u)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 1u, 4u)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational, 1u, 8u)] |
|||
[InlineDataAttribute((ushort)TiffType.Float, 1u, 4u)] |
|||
[InlineDataAttribute((ushort)TiffType.Double, 1u, 8u)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd, 1u, 4u)] |
|||
[InlineDataAttribute((ushort)999, 1u, 0u)] |
|||
public void GetSizeOfData_SingleItem_ReturnsCorrectSize(ushort type, uint count, uint expectedSize) |
|||
{ |
|||
TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]); |
|||
uint size = TiffDecoderCore.GetSizeOfData(entry); |
|||
Assert.Equal(expectedSize, size); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 15u, 15u)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii, 20u, 20u)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 18u, 36u)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 4u, 16u)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 9u, 72u)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 5u, 5u)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined, 136u, 136u)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 12u, 24u)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 15u, 60u)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational, 10u, 80u)] |
|||
[InlineDataAttribute((ushort)TiffType.Float, 2u, 8u)] |
|||
[InlineDataAttribute((ushort)TiffType.Double, 2u, 16u)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd, 10u, 40u)] |
|||
[InlineDataAttribute((ushort)999, 1050u, 0u)] |
|||
public void GetSizeOfData_Array_ReturnsCorrectSize(ushort type, uint count, uint expectedSize) |
|||
{ |
|||
TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]); |
|||
uint size = TiffDecoderCore.GetSizeOfData(entry); |
|||
Assert.Equal(expectedSize, size); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 1u, new byte[] { 17 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 1u, new byte[] { 17 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 2u, new byte[] { 17, 28 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 2u, new byte[] { 17, 28 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 1u, new byte[] { 17, 28 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 1u, new byte[] { 17, 28 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] |
|||
public void ReadBytes_ReturnsExpectedData(ushort type, uint count, byte[] bytes, bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian); |
|||
|
|||
byte[] result = decoder.ReadBytes(ref entry); |
|||
|
|||
if (bytes.Length < 4) |
|||
result = result.Take(bytes.Length).ToArray(); |
|||
|
|||
Assert.Equal(bytes, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)] |
|||
public void ReadBytes_CachesDataLongerThanFourBytes(ushort type, uint count, byte[] bytes, bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian); |
|||
|
|||
Assert.Equal(4, entry.Value.Length); |
|||
|
|||
byte[] result = decoder.ReadBytes(ref entry); |
|||
|
|||
Assert.Equal(bytes.Length, entry.Value.Length); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 0, 1, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 1, 2, 3, 4 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 255, 2, 3, 4 }, 255)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 0, 1, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 1, 2, 3, 4 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 255, 2, 3, 4 }, 255)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 0, 0, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 1, 0, 2, 3 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 0, 1, 2, 3 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 2, 1, 2, 3 }, 258)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 255, 255, 2, 3 }, UInt16.MaxValue)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 0, 0, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 0, 1, 2, 3 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 1, 0, 2, 3 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 1, 2, 2, 3 }, 258)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 255, 255, 2, 3 }, UInt16.MaxValue)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 1, 0, 0, 0 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 1, 0, 0 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 1, 0 }, 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 1, 2, 3, 4 }, 67305985)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 0, 1 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 1, 0 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 1, 0, 0 }, 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 1, 0, 0, 0 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 4, 3, 2, 1 }, 67305985)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)] |
|||
public void ReadUnsignedInteger_ReturnsValue(ushort type, bool isLittleEndian, byte[] bytes, uint expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, bytes), isLittleEndian); |
|||
|
|||
uint result = decoder.ReadUnsignedInteger(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadUnsignedInteger_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedInteger(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to an unsigned integer.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, true)] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Short, false)] |
|||
[InlineDataAttribute((ushort)TiffType.Long, false)] |
|||
public void ReadUnsignedInteger_ThrowsExceptionIfCountIsNotOne(ushort type, bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedInteger(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 0, 1, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 1, 2, 3, 4 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 255, 2, 3, 4 }, -1)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 0, 1, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 1, 2, 3, 4 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 255, 2, 3, 4 }, -1)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 0, 0, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 1, 0, 2, 3 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 0, 1, 2, 3 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 2, 1, 2, 3 }, 258)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 255, 255, 2, 3 }, -1)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 0, 0, 2, 3 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 0, 1, 2, 3 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 1, 0, 2, 3 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 1, 2, 2, 3 }, 258)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 255, 255, 2, 3 }, -1)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 1, 0, 0, 0 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 1, 0, 0 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 1, 0 }, 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 1, 2, 3, 4 }, 67305985)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 255, 255, 255, 255 }, -1)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 0, 1 }, 1)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 1, 0 }, 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 1, 0, 0 }, 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 1, 0, 0, 0 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 4, 3, 2, 1 }, 67305985)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 255, 255, 255, 255 }, -1)] |
|||
public void ReadSignedInteger_ReturnsValue(ushort type, bool isLittleEndian, byte[] bytes, int expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, bytes), isLittleEndian); |
|||
|
|||
int result = decoder.ReadSignedInteger(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadSignedInteger_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedInteger(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a signed integer.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, true)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, true)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, true)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, false)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, false)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, false)] |
|||
public void ReadSignedInteger_ThrowsExceptionIfCountIsNotOne(ushort type, bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedInteger(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 1, true, new byte[] { 0, 1, 2, 3 }, new uint[] { 0 })] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 3, true, new byte[] { 0, 1, 2, 3 }, new uint[] { 0, 1, 2 })] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 7, true, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 1, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 0 })] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 3, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 0, 1, 2 })] |
|||
[InlineDataAttribute((ushort)TiffType.Byte, 7, false, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 1, true, new byte[] { 1, 0, 3, 2 }, new uint[] { 1 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 2, true, new byte[] { 1, 0, 3, 2 }, new uint[] { 1, 515 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3, true, new byte[] { 1, 0, 3, 2, 5, 4, 6, 7, 8 }, new uint[] { 1, 515, 1029 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 1, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 1 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 2, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 1, 515 })] |
|||
[InlineDataAttribute((ushort)TiffType.Short, 3, false, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 1, 515, 1029 })] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 1, true, new byte[] { 4, 3, 2, 1 }, new uint[] { 0x01020304 })] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2, true, new byte[] { 4, 3, 2, 1, 6, 5, 4, 3, 99, 99 }, new uint[] { 0x01020304, 0x03040506 })] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 1, false, new byte[] { 1, 2, 3, 4 }, new uint[] { 0x01020304 })] |
|||
[InlineDataAttribute((ushort)TiffType.Long, 2, false, new byte[] { 1, 2, 3, 4, 3, 4, 5, 6, 99, 99 }, new uint[] { 0x01020304, 0x03040506 })] |
|||
public void ReadUnsignedIntegerArray_ReturnsValue(ushort type, int count, bool isLittleEndian, byte[] bytes, uint[] expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, (uint)expectedValue.Length, bytes), isLittleEndian); |
|||
|
|||
uint[] result = decoder.ReadUnsignedIntegerArray(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadUnsignedIntegerArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedIntegerArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to an unsigned integer.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 1, true, new byte[] { 0, 1, 2, 3 }, new int[] { 0 })] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 3, true, new byte[] { 0, 255, 2, 3 }, new int[] { 0, -1, 2 })] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 7, true, new byte[] { 0, 255, 2, 3, 4, 5, 6, 7, 8 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 1, false, new byte[] { 0, 1, 2, 3 }, new int[] { 0 })] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 3, false, new byte[] { 0, 255, 2, 3 }, new int[] { 0, -1, 2 })] |
|||
[InlineDataAttribute((ushort)TiffType.SByte, 7, false, new byte[] { 0, 255, 2, 3, 4, 5, 6, 7, 8 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 1, true, new byte[] { 1, 0, 3, 2 }, new int[] { 1 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 2, true, new byte[] { 1, 0, 255, 255 }, new int[] { 1, -1 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 3, true, new byte[] { 1, 0, 255, 255, 5, 4, 6, 7, 8 }, new int[] { 1, -1, 1029 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 1, false, new byte[] { 0, 1, 2, 3 }, new int[] { 1 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 2, false, new byte[] { 0, 1, 255, 255 }, new int[] { 1, -1 })] |
|||
[InlineDataAttribute((ushort)TiffType.SShort, 3, false, new byte[] { 0, 1, 255, 255, 4, 5, 6, 7, 8 }, new int[] { 1, -1, 1029 })] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 1, true, new byte[] { 4, 3, 2, 1 }, new int[] { 0x01020304 })] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 2, true, new byte[] { 4, 3, 2, 1, 255, 255, 255, 255, 99, 99 }, new int[] { 0x01020304, -1 })] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 1, false, new byte[] { 1, 2, 3, 4 }, new int[] { 0x01020304 })] |
|||
[InlineDataAttribute((ushort)TiffType.SLong, 2, false, new byte[] { 1, 2, 3, 4, 255, 255, 255, 255, 99, 99 }, new int[] { 0x01020304, -1 })] |
|||
public void ReadSignedIntegerArray_ReturnsValue(ushort type, int count, bool isLittleEndian, byte[] bytes, int[] expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, (uint)expectedValue.Length, bytes), isLittleEndian); |
|||
|
|||
int[] result = decoder.ReadSignedIntegerArray(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadSignedIntegerArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedIntegerArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a signed integer.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { 0 }, "")] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] |
|||
[InlineDataAttribute(false, new byte[] { 0 }, "")] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] |
|||
public void ReadString_ReturnsValue(bool isLittleEndian, byte[] bytes, string expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian); |
|||
|
|||
string result = decoder.ReadString(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadString_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadString(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a string.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A' })] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C' })] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })] |
|||
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A' })] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C' })] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })] |
|||
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })] |
|||
public void ReadString_ThrowsExceptionIfStringIsNotNullTerminated(bool isLittleEndian, byte[] bytes) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadString(ref entry)); |
|||
|
|||
Assert.Equal($"The retrieved string is not null terminated.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, 0, 2)] |
|||
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2)] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, 1, 2)] |
|||
public void ReadUnsignedRational_ReturnsValue(bool isLittleEndian, byte[] bytes, uint expectedNumerator, uint expectedDenominator) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, 1, bytes), isLittleEndian); |
|||
|
|||
Rational result = decoder.ReadUnsignedRational(ref entry); |
|||
Rational expectedValue = new Rational(expectedNumerator, expectedDenominator); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, 0, 2)] |
|||
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] |
|||
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, -1, 2)] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2)] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, 1, 2)] |
|||
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2 }, -1, 2)] |
|||
public void ReadSignedRational_ReturnsValue(bool isLittleEndian, byte[] bytes, int expectedNumerator, int expectedDenominator) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, 1, bytes), isLittleEndian); |
|||
|
|||
SignedRational result = decoder.ReadSignedRational(ref entry); |
|||
SignedRational expectedValue = new SignedRational(expectedNumerator, expectedDenominator); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 0 }, new uint[] { 2 })] |
|||
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 1 }, new uint[] { 2 })] |
|||
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, new uint[] { 0 }, new uint[] { 2 })] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, new uint[] { 1 }, new uint[] { 2 })] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })] |
|||
public void ReadUnsignedRationalArray_ReturnsValue(bool isLittleEndian, byte[] bytes, uint[] expectedNumerators, uint[] expectedDenominators) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, (uint)expectedNumerators.Length, bytes), isLittleEndian); |
|||
|
|||
Rational[] result = decoder.ReadUnsignedRationalArray(ref entry); |
|||
Rational[] expectedValue = Enumerable.Range(0, expectedNumerators.Length).Select(i => new Rational(expectedNumerators[i], expectedDenominators[i])).ToArray(); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 0 }, new int[] { 2 })] |
|||
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, new int[] { -1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new int[] { -1, 2 }, new int[] { 2, 3 })] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, new int[] { 0 }, new int[] { 2 })] |
|||
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, new int[] { 1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2 }, new int[] { -1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3 }, new int[] { -1, 2 }, new int[] { 2, 3 })] |
|||
public void ReadSignedRationalArray_ReturnsValue(bool isLittleEndian, byte[] bytes, int[] expectedNumerators, int[] expectedDenominators) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, (uint)expectedNumerators.Length, bytes), isLittleEndian); |
|||
|
|||
SignedRational[] result = decoder.ReadSignedRationalArray(ref entry); |
|||
SignedRational[] expectedValue = Enumerable.Range(0, expectedNumerators.Length).Select(i => new SignedRational(expectedNumerators[i], expectedDenominators[i])).ToArray(); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadUnsignedRational_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRational(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a Rational.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadSignedRational_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRational(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a SignedRational.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadUnsignedRationalArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRationalArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a Rational.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadSignedRationalArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRationalArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a SignedRational.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false)] |
|||
[InlineDataAttribute(true)] |
|||
public void ReadUnsignedRational_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRational(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false)] |
|||
[InlineDataAttribute(true)] |
|||
public void ReadSignedRational_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRational(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)] |
|||
[InlineDataAttribute(false, new byte[] { 0x3F, 0x80, 0x00, 0x00 }, 1.0F)] |
|||
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00 }, -2.0F)] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0x7F, 0xFF, 0xFF }, float.MaxValue)] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0x80, 0x00, 0x00 }, float.PositiveInfinity)] |
|||
[InlineDataAttribute(false, new byte[] { 0xFF, 0x80, 0x00, 0x00 }, float.NegativeInfinity)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x3F }, 1.0F)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0xC0 }, -2.0F)] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, float.MaxValue)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x7F }, float.PositiveInfinity)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0xFF }, float.NegativeInfinity)] |
|||
public void ReadFloat_ReturnsValue(bool isLittleEndian, byte[] bytes, float expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, 1, bytes), isLittleEndian); |
|||
|
|||
float result = decoder.ReadFloat(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadFloat_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloat(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a float.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false)] |
|||
[InlineDataAttribute(true)] |
|||
public void ReadFloat_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloat(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })] |
|||
[InlineDataAttribute(false, new byte[] { 0x3F, 0x80, 0x00, 0x00 }, new float[] { 1.0F })] |
|||
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00 }, new float[] { -2.0F })] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0x7F, 0xFF, 0xFF }, new float[] { float.MaxValue })] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0x80, 0x00, 0x00 }, new float[] { float.PositiveInfinity })] |
|||
[InlineDataAttribute(false, new byte[] { 0xFF, 0x80, 0x00, 0x00 }, new float[] { float.NegativeInfinity })] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00 }, new float[] { 0.0F, 1.0F, -2.0F })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x3F }, new float[] { 1.0F })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0xC0 }, new float[] { -2.0F })] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, new float[] { float.MaxValue })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x7F }, new float[] { float.PositiveInfinity })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0xFF }, new float[] { float.NegativeInfinity })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xC0 }, new float[] { 0.0F, 1.0F, -2.0F })] |
|||
|
|||
public void ReadFloatArray_ReturnsValue(bool isLittleEndian, byte[] bytes, float[] expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, (uint)expectedValue.Length, bytes), isLittleEndian); |
|||
|
|||
float[] result = decoder.ReadFloatArray(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Double)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadFloatArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloatArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a float.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)] |
|||
[InlineDataAttribute(false, new byte[] { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1.0)] |
|||
[InlineDataAttribute(false, new byte[] { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 2.0)] |
|||
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, -2.0)] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, double.MaxValue)] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, double.PositiveInfinity)] |
|||
[InlineDataAttribute(false, new byte[] { 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, double.NegativeInfinity)] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, double.NaN)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, 1.0)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, 2.0)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, -2.0)] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, double.MaxValue)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, double.PositiveInfinity)] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, double.NegativeInfinity)] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, double.NaN)] |
|||
public void ReadDouble_ReturnsValue(bool isLittleEndian, byte[] bytes, double expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, 1, bytes), isLittleEndian); |
|||
|
|||
double result = decoder.ReadDouble(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadDouble_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDouble(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a double.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false)] |
|||
[InlineDataAttribute(true)] |
|||
public void ReadDouble_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, 2, new byte[4]), isLittleEndian); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDouble(ref entry)); |
|||
|
|||
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })] |
|||
[InlineDataAttribute(false, new byte[] { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 1.0 })] |
|||
[InlineDataAttribute(false, new byte[] { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 2.0 })] |
|||
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { -2.0 })] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, new double[] { double.MaxValue })] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { double.PositiveInfinity })] |
|||
[InlineDataAttribute(false, new byte[] { 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { double.NegativeInfinity })] |
|||
[InlineDataAttribute(false, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, new double[] { double.NaN })] |
|||
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0, 1.0, -2.0 })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, new double[] { 1.0 })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, new double[] { 2.0 })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { -2.0 })] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, new double[] { double.MaxValue })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, new double[] { double.PositiveInfinity })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, new double[] { double.NegativeInfinity })] |
|||
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new double[] { double.NaN })] |
|||
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { 0.0, 1.0, -2.0 })] |
|||
public void ReadDoubleArray_ReturnsValue(bool isLittleEndian, byte[] bytes, double[] expectedValue) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, (uint)expectedValue.Length, bytes), isLittleEndian); |
|||
|
|||
double[] result = decoder.ReadDoubleArray(ref entry); |
|||
|
|||
Assert.Equal(expectedValue, result); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute((ushort)TiffType.Byte)] |
|||
[InlineDataAttribute((ushort)TiffType.Ascii)] |
|||
[InlineDataAttribute((ushort)TiffType.Short)] |
|||
[InlineDataAttribute((ushort)TiffType.Long)] |
|||
[InlineDataAttribute((ushort)TiffType.Rational)] |
|||
[InlineDataAttribute((ushort)TiffType.SByte)] |
|||
[InlineDataAttribute((ushort)TiffType.Undefined)] |
|||
[InlineDataAttribute((ushort)TiffType.SShort)] |
|||
[InlineDataAttribute((ushort)TiffType.SLong)] |
|||
[InlineDataAttribute((ushort)TiffType.SRational)] |
|||
[InlineDataAttribute((ushort)TiffType.Float)] |
|||
[InlineDataAttribute((ushort)TiffType.Ifd)] |
|||
[InlineDataAttribute((ushort)99)] |
|||
public void ReadDoubleArray_ThrowsExceptionIfInvalidType(ushort type) |
|||
{ |
|||
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDoubleArray(ref entry)); |
|||
|
|||
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a double.", e.Message); |
|||
} |
|||
|
|||
private (TiffDecoderCore, TiffIfdEntry) GenerateTestIfdEntry(TiffGenEntry entry, bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
entry |
|||
} |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfdEntry ifdEntry = decoder.ReadIfd(0).Entries[0]; |
|||
|
|||
return (decoder, ifdEntry); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffDecoderIfdTests |
|||
{ |
|||
public static object[][] IsLittleEndianValues = new[] { new object[] { false }, |
|||
new object[] { true } }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadIfd_ReadsNextIfdOffset_IfPresent(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150) |
|||
}, |
|||
NextIfd = new TiffGenIfd() |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
Assert.Equal(18u, ifd.NextIfdOffset); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadIfd_ReadsNextIfdOffset_ZeroIfLastIfd(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150) |
|||
} |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
Assert.Equal(0u, ifd.NextIfdOffset); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadIfd_ReturnsCorrectNumberOfEntries(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), |
|||
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), |
|||
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1), |
|||
TiffGenEntry.Ascii(TiffTags.Artist, "Image Artist Name"), |
|||
TiffGenEntry.Ascii(TiffTags.HostComputer, "Host Computer Name") |
|||
}, |
|||
NextIfd = new TiffGenIfd() |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
Assert.NotNull(ifd.Entries); |
|||
Assert.Equal(5, ifd.Entries.Length); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadIfd_ReadsRawTiffEntryData(bool isLittleEndian) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), |
|||
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), |
|||
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1) |
|||
}, |
|||
NextIfd = new TiffGenIfd() |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
TiffIfdEntry entry = ifd.Entries[1]; |
|||
|
|||
byte[] expectedData = isLittleEndian ? new byte[] { 210, 0, 0, 0 } : new byte[] { 0, 0, 0, 210 }; |
|||
|
|||
Assert.Equal(TiffTags.ImageLength, entry.Tag); |
|||
Assert.Equal(TiffType.Long, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(expectedData, entry.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,512 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffDecoderImageTests |
|||
{ |
|||
public const int ImageWidth = 200; |
|||
public const int ImageHeight = 150; |
|||
|
|||
public static object[][] IsLittleEndianValues = new[] { new object[] { false }, |
|||
new object[] { true } }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DecodeImage_SetsImageDimensions(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
Image<Rgba32> image = decoder.DecodeImage<Rgba32>(ifd); |
|||
|
|||
Assert.Equal(ImageWidth, image.Width); |
|||
Assert.Equal(ImageHeight, image.Height); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DecodeImage_ThrowsException_WithMissingImageWidth(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithoutEntry(TiffTags.ImageWidth) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage<Rgba32>(ifd)); |
|||
|
|||
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DecodeImage_ThrowsException_WithMissingImageLength(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithoutEntry(TiffTags.ImageLength) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage<Rgba32>(ifd)); |
|||
|
|||
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffCompression.None, (int)TiffCompressionType.None)] |
|||
[InlineData(true, (ushort)TiffCompression.None, (int)TiffCompressionType.None)] |
|||
[InlineData(false, (ushort)TiffCompression.PackBits, (int)TiffCompressionType.PackBits)] |
|||
[InlineData(true, (ushort)TiffCompression.PackBits, (int)TiffCompressionType.PackBits)] |
|||
[InlineData(false, (ushort)TiffCompression.Deflate, (int)TiffCompressionType.Deflate)] |
|||
[InlineData(true, (ushort)TiffCompression.Deflate, (int)TiffCompressionType.Deflate)] |
|||
[InlineData(false, (ushort)TiffCompression.OldDeflate, (int)TiffCompressionType.Deflate)] |
|||
[InlineData(true, (ushort)TiffCompression.OldDeflate, (int)TiffCompressionType.Deflate)] |
|||
[InlineData(false, (ushort)TiffCompression.Lzw, (int)TiffCompressionType.Lzw)] |
|||
[InlineData(true, (ushort)TiffCompression.Lzw, (int)TiffCompressionType.Lzw)] |
|||
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal((TiffCompressionType)compressionType, decoder.CompressionType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffCompression.Ccitt1D)] |
|||
[InlineData(false, (ushort)TiffCompression.CcittGroup3Fax)] |
|||
[InlineData(false, (ushort)TiffCompression.CcittGroup4Fax)] |
|||
[InlineData(false, (ushort)TiffCompression.ItuTRecT43)] |
|||
[InlineData(false, (ushort)TiffCompression.ItuTRecT82)] |
|||
[InlineData(false, (ushort)TiffCompression.Jpeg)] |
|||
[InlineData(false, (ushort)TiffCompression.OldJpeg)] |
|||
[InlineData(false, 999)] |
|||
[InlineData(true, (ushort)TiffCompression.Ccitt1D)] |
|||
[InlineData(true, (ushort)TiffCompression.CcittGroup3Fax)] |
|||
[InlineData(true, (ushort)TiffCompression.CcittGroup4Fax)] |
|||
[InlineData(true, (ushort)TiffCompression.ItuTRecT43)] |
|||
[InlineData(true, (ushort)TiffCompression.ItuTRecT82)] |
|||
[InlineData(true, (ushort)TiffCompression.Jpeg)] |
|||
[InlineData(true, (ushort)TiffCompression.OldJpeg)] |
|||
[InlineData(true, 999)] |
|||
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The specified TIFF compression format is not supported.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, (int)TiffColorType.WhiteIsZero)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, (int)TiffColorType.WhiteIsZero)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 4 }, (int)TiffColorType.WhiteIsZero4)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 4 }, (int)TiffColorType.WhiteIsZero4)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 1 }, (int)TiffColorType.WhiteIsZero1)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 1 }, (int)TiffColorType.WhiteIsZero1)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 3 }, (int)TiffColorType.BlackIsZero)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 3 }, (int)TiffColorType.BlackIsZero)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8 }, (int)TiffColorType.BlackIsZero8)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8 }, (int)TiffColorType.BlackIsZero8)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 4 }, (int)TiffColorType.BlackIsZero4)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 4 }, (int)TiffColorType.BlackIsZero4)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, (int)TiffColorType.BlackIsZero1)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, (int)TiffColorType.BlackIsZero1)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, (int)TiffColorType.PaletteColor)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.Rgb)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.Rgb)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.Rgb888)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.Rgb888)] |
|||
public void ReadImageFormat_DeterminesCorrectColorImplementation_Chunky(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Chunky)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal((TiffColorType)colorType, decoder.ColorType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.RgbPlanar)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.RgbPlanar)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.RgbPlanar)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.RgbPlanar)] |
|||
public void ReadImageFormat_DeterminesCorrectColorImplementation_Planar(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Planar)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal((TiffColorType)colorType, decoder.ColorType); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, (int)TiffColorType.WhiteIsZero1)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, (int)TiffColorType.WhiteIsZero1)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, (int)TiffColorType.BlackIsZero1)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, (int)TiffColorType.BlackIsZero1)] |
|||
public void ReadImageFormat_DeterminesCorrectColorImplementation_DefaultsToBilevel(bool isLittleEndian, ushort photometricInterpretation, int colorType) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.WithoutEntry(TiffTags.BitsPerSample) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal((TiffColorType)colorType, decoder.ColorType); |
|||
} |
|||
|
|||
// [Theory]
|
|||
// [InlineData(false, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
|
|||
// [InlineData(true, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
|
|||
// public void ReadImageFormat_UsesDefaultColorImplementationForCcitt1D(bool isLittleEndian, int[] bitsPerSample, int colorType)
|
|||
// {
|
|||
// Stream stream = CreateTiffGenIfd()
|
|||
// .WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.Ccitt1D))
|
|||
// .WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
|
|||
// .WithoutEntry(TiffTags.PhotometricInterpretation)
|
|||
// .ToStream(isLittleEndian);
|
|||
|
|||
// TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
|
|||
// TiffIfd ifd = decoder.ReadIfd(0);
|
|||
// decoder.ReadImageFormat(ifd);
|
|||
|
|||
// Assert.Equal((TiffColorType)colorType, decoder.ColorType);
|
|||
// }
|
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadImageFormat_ThrowsExceptionForMissingPhotometricInterpretation(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithoutEntry(TiffTags.PhotometricInterpretation) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The TIFF photometric interpretation entry is missing.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.CieLab)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.ColorFilterArray)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.IccLab)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.ItuLab)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.LinearRaw)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Separated)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.TransparencyMask)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.YCbCr)] |
|||
[InlineData(false, 999)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.CieLab)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.ColorFilterArray)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.IccLab)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.ItuLab)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.LinearRaw)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Separated)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.TransparencyMask)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.YCbCr)] |
|||
[InlineData(true, 999)] |
|||
public void ReadImageFormat_ThrowsExceptionForUnsupportedPhotometricInterpretation(bool isLittleEndian, ushort photometricInterpretation) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The specified TIFF photometric interpretation is not supported.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, new[] { 8u })] |
|||
[InlineData(true, new[] { 8u })] |
|||
[InlineData(false, new[] { 4u })] |
|||
[InlineData(true, new[] { 4u })] |
|||
[InlineData(false, new[] { 1u })] |
|||
[InlineData(true, new[] { 1u })] |
|||
// [InlineData(false, new[] { 1u, 2u, 3u })]
|
|||
// [InlineData(true, new[] { 1u, 2u, 3u })]
|
|||
// [InlineData(false, new[] { 8u, 8u, 8u })]
|
|||
// [InlineData(true, new[] { 8u, 8u, 8u })]
|
|||
public void ReadImageFormat_ReadsBitsPerSample(bool isLittleEndian, uint[] bitsPerSample) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal(bitsPerSample, decoder.BitsPerSample); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero)] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero)] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero)] |
|||
public void ReadImageFormat_ReadsBitsPerSample_DefaultsToBilevel(bool isLittleEndian, ushort photometricInterpretation) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.WithoutEntry(TiffTags.BitsPerSample) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal(new[] { 1u }, decoder.BitsPerSample); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadImageFormat_ThrowsExceptionForMissingBitsPerSample(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) |
|||
.WithoutEntry(TiffTags.BitsPerSample) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The TIFF BitsPerSample entry is missing.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new int[] { })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new int[] { })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new int[] { })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new int[] { })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new int[] { })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new int[] { })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new int[] { })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new int[] { })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8 })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8 })] |
|||
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })] |
|||
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })] |
|||
public void ReadImageFormat_ThrowsExceptionForUnsupportedNumberOfSamples(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The number of samples in the TIFF BitsPerSample entry is not supported.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadImageFormat_ReadsColorMap(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[] { 10, 20, 30, 40, 50, 60 })) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal(new uint[] { 10, 20, 30, 40, 50, 60 }, decoder.ColorMap); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadImageFormat_ThrowsExceptionForMissingColorMap(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) |
|||
.WithoutEntry(TiffTags.ColorMap) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
|
|||
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd)); |
|||
|
|||
Assert.Equal("The TIFF ColorMap entry is missing for a pallete color image.", e.Message); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false, (ushort)TiffPlanarConfiguration.Chunky)] |
|||
[InlineData(true, (ushort)TiffPlanarConfiguration.Chunky)] |
|||
[InlineData(false, (ushort)TiffPlanarConfiguration.Planar)] |
|||
[InlineData(true, (ushort)TiffPlanarConfiguration.Planar)] |
|||
public void ReadImageFormat_ReadsPlanarConfiguration(bool isLittleEndian, int planarConfiguration) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 })) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)planarConfiguration)) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal((TiffPlanarConfiguration)planarConfiguration, decoder.PlanarConfiguration); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void ReadImageFormat_DefaultsPlanarConfigurationToChunky(bool isLittleEndian) |
|||
{ |
|||
Stream stream = CreateTiffGenIfd() |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb)) |
|||
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 })) |
|||
.WithoutEntry(TiffTags.PlanarConfiguration) |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
decoder.ReadImageFormat(ifd); |
|||
|
|||
Assert.Equal(TiffPlanarConfiguration.Chunky, decoder.PlanarConfiguration); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 1 }, 160, 80, 20 * 80)] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 1 }, 153, 80, 20 * 80)] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 3 }, 100, 80, 38 * 80)] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 4 }, 100, 80, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 4 }, 99, 80, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 8 }, 100, 80, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 1 }, 160, 80, 20 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 1 }, 153, 80, 20 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 3 }, 100, 80, 38 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 4 }, 100, 80, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 4 }, 99, 80, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 8 }, 100, 80, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 300 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 150 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 200 * 80)] |
|||
public void CalculateImageBufferSize_ReturnsCorrectSize_Chunky(ushort colorType, uint[] bitsPerSample, int width, int height, int expectedResult) |
|||
{ |
|||
TiffDecoderCore decoder = new TiffDecoderCore(null, null); |
|||
decoder.ColorType = (TiffColorType)colorType; |
|||
decoder.PlanarConfiguration = TiffPlanarConfiguration.Chunky; |
|||
decoder.BitsPerSample = bitsPerSample; |
|||
|
|||
int bufferSize = decoder.CalculateImageBufferSize(width, height, 0); |
|||
|
|||
Assert.Equal(expectedResult, bufferSize); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 0, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 1, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 2, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 0, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 1, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 2, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 0, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 1, 100 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 2, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 0, 50 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 1, 99 * 80)] |
|||
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 2, 50 * 80)] |
|||
|
|||
public void CalculateImageBufferSize_ReturnsCorrectSize_Planar(ushort colorType, uint[] bitsPerSample, int width, int height, int plane, int expectedResult) |
|||
{ |
|||
TiffDecoderCore decoder = new TiffDecoderCore(null, null); |
|||
decoder.ColorType = (TiffColorType)colorType; |
|||
decoder.PlanarConfiguration = TiffPlanarConfiguration.Planar; |
|||
decoder.BitsPerSample = bitsPerSample; |
|||
|
|||
int bufferSize = decoder.CalculateImageBufferSize(width, height, plane); |
|||
|
|||
Assert.Equal(expectedResult, bufferSize); |
|||
} |
|||
|
|||
private TiffGenIfd CreateTiffGenIfd() |
|||
{ |
|||
return new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, ImageWidth), |
|||
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, ImageHeight), |
|||
TiffGenEntry.Rational(TiffTags.XResolution, 100, 1), |
|||
TiffGenEntry.Rational(TiffTags.YResolution, 200, 1), |
|||
TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, 2), |
|||
TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.WhiteIsZero), |
|||
TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8 }), |
|||
TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.None), |
|||
TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[256]) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffDecoderMetadataTests |
|||
{ |
|||
public static object[][] BaselineMetadataValues = new[] { new object[] { false, TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" }, |
|||
new object[] { false, TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" }, |
|||
new object[] { false, TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" }, |
|||
new object[] { false, TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" }, |
|||
new object[] { false, TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" }, |
|||
new object[] { false, TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" }, |
|||
new object[] { false, TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" }, |
|||
new object[] { false, TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }, |
|||
new object[] { true, TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" }, |
|||
new object[] { true, TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" }, |
|||
new object[] { true, TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" }, |
|||
new object[] { true, TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" }, |
|||
new object[] { true, TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" }, |
|||
new object[] { true, TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" }, |
|||
new object[] { true, TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" }, |
|||
new object[] { true, TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }}; |
|||
|
|||
[Theory] |
|||
[InlineData(false, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)] |
|||
[InlineData(false, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)] |
|||
[InlineData(false, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)] |
|||
[InlineData(false, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)] |
|||
[InlineData(false, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)] |
|||
[InlineData(false, null, null, null, null, null /* Inch */, 96.0, 96.0)] |
|||
[InlineData(false, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)] |
|||
[InlineData(false, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)] |
|||
[InlineData(true, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)] |
|||
[InlineData(true, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)] |
|||
[InlineData(true, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)] |
|||
[InlineData(true, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)] |
|||
[InlineData(true, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)] |
|||
[InlineData(true, null, null, null, null, null /* Inch */, 96.0, 96.0)] |
|||
[InlineData(true, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)] |
|||
[InlineData(true, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)] |
|||
public void ReadMetadata_SetsImageResolution(bool isLittleEndian, uint? xResolutionNumerator, uint? xResolutionDenominator, |
|||
uint? yResolutionNumerator, uint? yResolutionDenominator, uint? resolutionUnit, |
|||
double expectedHorizonalResolution, double expectedVerticalResolution) |
|||
{ |
|||
TiffGenIfd ifdGen = new TiffGenIfd(); |
|||
|
|||
if (xResolutionNumerator != null) |
|||
{ |
|||
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.XResolution, xResolutionNumerator.Value, xResolutionDenominator.Value)); |
|||
} |
|||
|
|||
if (yResolutionNumerator != null) |
|||
{ |
|||
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.YResolution, yResolutionNumerator.Value, yResolutionDenominator.Value)); |
|||
} |
|||
|
|||
if (resolutionUnit != null) |
|||
{ |
|||
ifdGen.WithEntry(TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, resolutionUnit.Value)); |
|||
} |
|||
|
|||
Stream stream = ifdGen.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20); |
|||
|
|||
decoder.ReadMetadata<Rgba32>(ifd, image); |
|||
|
|||
Assert.Equal(expectedHorizonalResolution, image.MetaData.HorizontalResolution, 10); |
|||
Assert.Equal(expectedVerticalResolution, image.MetaData.VerticalResolution, 10); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(BaselineMetadataValues))] |
|||
public void ReadMetadata_SetsAsciiMetadata(bool isLittleEndian, ushort tag, string metadataName, string metadataValue) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), |
|||
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), |
|||
TiffGenEntry.Ascii(tag, metadataValue), |
|||
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1) |
|||
} |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20); |
|||
|
|||
decoder.ReadMetadata<Rgba32>(ifd, image); |
|||
var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value; |
|||
|
|||
Assert.Equal(metadataValue, metadata); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(BaselineMetadataValues))] |
|||
public void ReadMetadata_DoesntSetMetadataIfIgnoring(bool isLittleEndian, ushort tag, string metadataName, string metadataValue) |
|||
{ |
|||
Stream stream = new TiffGenIfd() |
|||
{ |
|||
Entries = |
|||
{ |
|||
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150), |
|||
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210), |
|||
TiffGenEntry.Ascii(tag, metadataValue), |
|||
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1) |
|||
} |
|||
} |
|||
.ToStream(isLittleEndian); |
|||
|
|||
TiffDecoder options = new TiffDecoder() { IgnoreMetadata = true }; |
|||
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, options); |
|||
TiffIfd ifd = decoder.ReadIfd(0); |
|||
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20); |
|||
|
|||
decoder.ReadMetadata<Rgba32>(ifd, image); |
|||
var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value; |
|||
|
|||
Assert.Null(metadata); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
|
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.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,297 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
public class TiffEncoderIfdTests |
|||
{ |
|||
[Fact] |
|||
public void WriteIfd_DataIsCorrectLength() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
Assert.Equal(2 + 12 * 3 + 4, stream.Length); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesNumberOfEntries() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntryBytes = stream.ToArray().Take(2).ToArray(); |
|||
Assert.Equal(new byte[] { 3, 0 }, ifdEntryBytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_ReturnsNextIfdMarker() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
Assert.Equal(2 + 12 * 3, nextIfdMarker); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesTagIdForEachEntry() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesTypeForEachEntry() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(4 + 12 * 0).Take(2).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(4 + 12 * 1).Take(2).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(4 + 12 * 2).Take(2).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 4, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 3, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 2, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesCountForEachEntry() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(6 + 12 * 0).Take(4).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(6 + 12 * 1).Take(4).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(6 + 12 * 2).Take(4).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 1, 0, 0, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 2, 0, 0, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 4, 0, 0, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesDataInline() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(10 + 12 * 0).Take(4).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(10 + 12 * 1).Take(4).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(10 + 12 * 2).Take(4).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 1, 2, 3, 4 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 5, 6, 7, 8 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesDataByReference() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write(new byte[] { 1, 2, 3, 4 }); |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray(); |
|||
var ifdEntry1Data = stream.ToArray().Skip(46).Take(8).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray(); |
|||
var ifdEntry2Data = stream.ToArray().Skip(54).Take(8).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }, ifdEntry1Data); |
|||
Assert.Equal(new byte[] { 54, 0, 0, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data); |
|||
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesDataByReferenceOnWordBoundary() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 5 }), |
|||
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }), |
|||
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write(new byte[] { 1, 2, 3, 4 }); |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray(); |
|||
var ifdEntry1Data = stream.ToArray().Skip(46).Take(5).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray(); |
|||
var ifdEntry2Data = stream.ToArray().Skip(52).Take(8).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, ifdEntry1Data); |
|||
Assert.Equal(new byte[] { 52, 0, 0, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data); |
|||
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_WritesEntriesInCorrectOrder() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>() |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }), |
|||
new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }), |
|||
new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 }) |
|||
}; |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
long nextIfdMarker = encoder.WriteIfd(writer, entries); |
|||
} |
|||
|
|||
var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray(); |
|||
var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray(); |
|||
var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray(); |
|||
|
|||
Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes); |
|||
Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes); |
|||
Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteIfd_ThrowsException_IfNoEntriesArePresent() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> entries = new List<TiffIfdEntry>(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
ArgumentException e = Assert.Throws<ArgumentException>(() => { encoder.WriteIfd(writer, entries); }); |
|||
|
|||
Assert.Equal($"There must be at least one entry per IFD.{Environment.NewLine}Parameter name: entries", e.Message); |
|||
Assert.Equal("entries", e.ParamName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
using ImageSharp.Formats.Tiff; |
|||
using System.Collections.Generic; |
|||
|
|||
using SixLabors.ImageSharp.MetaData; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
public class TiffEncoderMetadataTests |
|||
{ |
|||
public static object[][] BaselineMetadataValues = new[] { new object[] { TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" }, |
|||
new object[] { TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" }, |
|||
new object[] { TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" }, |
|||
new object[] { TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" }, |
|||
new object[] { TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" }, |
|||
new object[] { TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" }, |
|||
new object[] { TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" }, |
|||
new object[] { TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }}; |
|||
|
|||
[Fact] |
|||
public void AddMetadata_SetsImageResolution() |
|||
{ |
|||
Image<Rgba32> image = new Image<Rgba32>(100, 100); |
|||
image.MetaData.HorizontalResolution = 40.0; |
|||
image.MetaData.VerticalResolution = 50.5; |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>(); |
|||
encoder.AddMetadata(image, ifdEntries); |
|||
|
|||
Assert.Equal(new Rational(40, 1), ifdEntries.GetUnsignedRational(TiffTags.XResolution)); |
|||
Assert.Equal(new Rational(101, 2), ifdEntries.GetUnsignedRational(TiffTags.YResolution)); |
|||
Assert.Equal(TiffResolutionUnit.Inch, (TiffResolutionUnit?)ifdEntries.GetInteger(TiffTags.ResolutionUnit)); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(BaselineMetadataValues))] |
|||
public void AddMetadata_SetsAsciiMetadata(ushort tag, string metadataName, string metadataValue) |
|||
{ |
|||
Image<Rgba32> image = new Image<Rgba32>(100, 100); |
|||
image.MetaData.Properties.Add(new ImageProperty(metadataName, metadataValue)); |
|||
TiffEncoderCore encoder = new TiffEncoderCore(null); |
|||
|
|||
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>(); |
|||
encoder.AddMetadata(image, ifdEntries); |
|||
|
|||
Assert.Equal(metadataValue + "\0", ifdEntries.GetAscii(tag)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
|
|||
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,409 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
public class TiffIfdEntryCreatorTests |
|||
{ |
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 255 }, 255)] |
|||
public void AddUnsignedByte_AddsSingleValue(byte[] bytes, uint value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedByte(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Byte, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0 }, new uint[] { 0 })] |
|||
[InlineDataAttribute(new byte[] { 0, 1, 2 }, new uint[] { 0, 1, 2 })] |
|||
[InlineDataAttribute(new byte[] { 0, 1, 2, 3, 4, 5, 6 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })] |
|||
public void AddUnsignedByte_AddsArray(byte[] bytes, uint[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedByte(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Byte, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1, 0 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 0, 1 }, 256)] |
|||
[InlineDataAttribute(new byte[] { 2, 1 }, 258)] |
|||
[InlineDataAttribute(new byte[] { 255, 255 }, UInt16.MaxValue)] |
|||
public void AddUnsignedShort_AddsSingleValue(byte[] bytes, uint value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedShort(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Short, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 1, 0 }, new uint[] { 1 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 3, 2 }, new uint[] { 1, 515 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 3, 2, 5, 4 }, new uint[] { 1, 515, 1029 })] |
|||
public void AddUnsignedShort_AddsArray(byte[] bytes, uint[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedShort(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Short, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)] |
|||
[InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)] |
|||
public void AddUnsignedLong_AddsSingleValue(byte[] bytes, uint value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedLong(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Long, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new uint[] { 0x01020304 })] |
|||
[InlineDataAttribute(new byte[] { 4, 3, 2, 1, 6, 5, 4, 3 }, new uint[] { 0x01020304, 0x03040506 })] |
|||
public void AddUnsignedLong_AddsArray(byte[] bytes, uint[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedLong(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Long, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 255 }, -1)] |
|||
public void AddSignedByte_AddsSingleValue(byte[] bytes, int value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedByte(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SByte, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0 }, new int[] { 0 })] |
|||
[InlineDataAttribute(new byte[] { 0, 255, 2 }, new int[] { 0, -1, 2 })] |
|||
[InlineDataAttribute(new byte[] { 0, 255, 2, 3, 4, 5, 6 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })] |
|||
public void AddSignedByte_AddsArray(byte[] bytes, int[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedByte(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SByte, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1, 0 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 0, 1 }, 256)] |
|||
[InlineDataAttribute(new byte[] { 2, 1 }, 258)] |
|||
[InlineDataAttribute(new byte[] { 255, 255 }, -1)] |
|||
public void AddSignedShort_AddsSingleValue(byte[] bytes, int value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedShort(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SShort, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 1, 0 }, new int[] { 1 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 255, 255 }, new int[] { 1, -1 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 255, 255, 5, 4 }, new int[] { 1, -1, 1029 })] |
|||
public void AddSignedShort_AddsArray(byte[] bytes, int[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedShort(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SShort, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)] |
|||
[InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)] |
|||
[InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)] |
|||
[InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, -1)] |
|||
public void AddSignedLong_AddsSingleValue(byte[] bytes, int value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedLong(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SLong, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new int[] { 0x01020304 })] |
|||
[InlineDataAttribute(new byte[] { 4, 3, 2, 1, 255, 255, 255, 255 }, new int[] { 0x01020304, -1 })] |
|||
public void AddSignedLong_AddsArray(byte[] bytes, int[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedLong(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SLong, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0 }, "")] |
|||
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] |
|||
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] |
|||
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] |
|||
public void AddAscii_AddsEntry(byte[] bytes, string value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddAscii(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Ascii, entry.Type); |
|||
Assert.Equal((uint)bytes.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)] |
|||
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] |
|||
public void AddUnsignedRational_AddsSingleValue(byte[] bytes, uint numerator, uint denominator) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddUnsignedRational(TiffTags.ImageWidth, new Rational(numerator, denominator)); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Rational, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new uint[] { 0 }, new uint[] { 0 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 1 }, new uint[] { 2 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })] |
|||
public void AddUnsignedRational_AddsArray(byte[] bytes, uint[] numerators, uint[] denominators) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
Rational[] value = Enumerable.Range(0, numerators.Length).Select(i => new Rational(numerators[i], denominators[i])).ToArray(); |
|||
|
|||
entries.AddUnsignedRational(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Rational, entry.Type); |
|||
Assert.Equal((uint)numerators.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)] |
|||
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)] |
|||
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, -1, 2)] |
|||
public void AddSignedRational_AddsSingleValue(byte[] bytes, int numerator, int denominator) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddSignedRational(TiffTags.ImageWidth, new SignedRational(numerator, denominator)); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SRational, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 0 }, new int[] { 0 })] |
|||
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, new int[] { 2 }, new int[] { 1 })] |
|||
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, new int[] { -1 }, new int[] { 2 })] |
|||
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new int[] { -1, 2 }, new int[] { 2, 3 })] |
|||
public void AddSignedRational_AddsArray(byte[] bytes, int[] numerators, int[] denominators) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
SignedRational[] value = Enumerable.Range(0, numerators.Length).Select(i => new SignedRational(numerators[i], denominators[i])).ToArray(); |
|||
|
|||
entries.AddSignedRational(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.SRational, entry.Type); |
|||
Assert.Equal((uint)numerators.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, 1.0F)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, -2.0F)] |
|||
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, float.MaxValue)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, float.PositiveInfinity)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, float.NegativeInfinity)] |
|||
public void AddFloat_AddsSingleValue(byte[] bytes, float value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddFloat(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Float, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, new float[] { 1.0F })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, new float[] { -2.0F })] |
|||
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, new float[] { float.MaxValue })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, new float[] { float.PositiveInfinity })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, new float[] { float.NegativeInfinity })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xC0 }, new float[] { 0.0F, 1.0F, -2.0F })] |
|||
public void AddFloat_AddsArray(byte[] bytes, float[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddFloat(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Float, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, 1.0)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, 2.0)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, -2.0)] |
|||
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, double.MaxValue)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, double.PositiveInfinity)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, double.NegativeInfinity)] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, double.NaN)] |
|||
public void AddDouble_AddsSingleValue(byte[] bytes, double value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddDouble(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Double, entry.Type); |
|||
Assert.Equal(1u, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, new double[] { 1.0 })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, new double[] { 2.0 })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { -2.0 })] |
|||
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, new double[] { double.MaxValue })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, new double[] { double.PositiveInfinity })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, new double[] { double.NegativeInfinity })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, new double[] { double.NaN })] |
|||
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { 0.0, 1.0, -2.0 })] |
|||
public void AddDouble_AddsArray(byte[] bytes, double[] value) |
|||
{ |
|||
var entries = new List<TiffIfdEntry>(); |
|||
|
|||
entries.AddDouble(TiffTags.ImageWidth, value); |
|||
|
|||
var entry = entries[0]; |
|||
Assert.Equal(TiffTags.ImageWidth, entry.Tag); |
|||
Assert.Equal(TiffType.Double, entry.Type); |
|||
Assert.Equal((uint)value.Length, entry.Count); |
|||
Assert.Equal(bytes, entry.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffIfdEntryTests |
|||
{ |
|||
[Fact] |
|||
public void Constructor_SetsProperties() |
|||
{ |
|||
var entry = new TiffIfdEntry((ushort)10u, TiffType.Short, 20u, new byte[] { 2, 4, 6, 8 }); |
|||
|
|||
Assert.Equal(10u, entry.Tag); |
|||
Assert.Equal(TiffType.Short, entry.Type); |
|||
Assert.Equal(20u, entry.Count); |
|||
Assert.Equal(new byte[] { 2, 4, 6, 8 }, entry.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffIfdTests |
|||
{ |
|||
[Fact] |
|||
public void Constructor_SetsProperties() |
|||
{ |
|||
var entries = new TiffIfdEntry[10]; |
|||
var ifd = new TiffIfd(entries, 1234u); |
|||
|
|||
Assert.Equal(entries, ifd.Entries); |
|||
Assert.Equal(1234u, ifd.NextIfdOffset); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetIfdEntry_ReturnsIfdIfExists() |
|||
{ |
|||
var entries = new[] |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) |
|||
}; |
|||
var ifd = new TiffIfd(entries, 1234u); |
|||
|
|||
TiffIfdEntry? entry = ifd.GetIfdEntry(30); |
|||
|
|||
Assert.True(entry.HasValue); |
|||
Assert.Equal(30, entry.Value.Tag); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetIfdEntry_ReturnsNullOtherwise() |
|||
{ |
|||
var entries = new[] |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) |
|||
}; |
|||
var ifd = new TiffIfd(entries, 1234u); |
|||
|
|||
TiffIfdEntry? entry = ifd.GetIfdEntry(25); |
|||
|
|||
Assert.False(entry.HasValue); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TryGetIfdEntry_ReturnsIfdIfExists() |
|||
{ |
|||
var entries = new[] |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) |
|||
}; |
|||
var ifd = new TiffIfd(entries, 1234u); |
|||
|
|||
bool success = ifd.TryGetIfdEntry(30, out var entry); |
|||
|
|||
Assert.True(success); |
|||
Assert.Equal(30, entry.Tag); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TryGetIfdEntry_ReturnsFalseOtherwise() |
|||
{ |
|||
var entries = new[] |
|||
{ |
|||
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]), |
|||
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4]) |
|||
}; |
|||
var ifd = new TiffIfd(entries, 1234u); |
|||
|
|||
bool success = ifd.TryGetIfdEntry(25, out var entry); |
|||
|
|||
Assert.False(success); |
|||
Assert.Equal(0, entry.Tag); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats; |
|||
|
|||
public class TiffImageFormatDetectorTests |
|||
{ |
|||
public static object[][] IsLittleEndianValues = new[] { new object[] { false }, |
|||
new object[] { true } }; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DetectFormat_ReturnsTiffFormat_ForValidFile(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd() |
|||
} |
|||
.ToBytes(isLittleEndian); |
|||
|
|||
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector(); |
|||
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray(); |
|||
var format = formatDetector.DetectFormat(headerBytes); |
|||
|
|||
Assert.NotNull(format); |
|||
Assert.IsType<TiffFormat>(format); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DetectFormat_ReturnsNull_WithInvalidByteOrderMarkers(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd(), |
|||
ByteOrderMarker = 0x1234 |
|||
} |
|||
.ToBytes(isLittleEndian); |
|||
|
|||
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector(); |
|||
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray(); |
|||
var format = formatDetector.DetectFormat(headerBytes); |
|||
|
|||
Assert.Null(format); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DetectFormat_ReturnsNull_WithIncorrectMagicNumber(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd(), |
|||
MagicNumber = 32 |
|||
} |
|||
.ToBytes(isLittleEndian); |
|||
|
|||
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector(); |
|||
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray(); |
|||
var format = formatDetector.DetectFormat(headerBytes); |
|||
|
|||
Assert.Null(format); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(IsLittleEndianValues))] |
|||
public void DetectFormat_ReturnsNull_WithShortHeader(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = new TiffGenHeader() |
|||
{ |
|||
FirstIfd = new TiffGenIfd() |
|||
} |
|||
.ToBytes(isLittleEndian); |
|||
|
|||
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector(); |
|||
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize - 1).ToArray(); |
|||
var format = formatDetector.DetectFormat(headerBytes); |
|||
|
|||
Assert.Null(format); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,326 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.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)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class TiffWriterTests |
|||
{ |
|||
[Fact] |
|||
public void IsLittleEndian_IsTrueOnWindows() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
Assert.True(writer.IsLittleEndian); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(new byte[] {}, 0)] |
|||
[InlineData(new byte[] { 42 }, 1)] |
|||
[InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] |
|||
public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write(data); |
|||
Assert.Equal(writer.Position, expectedResult); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Write_WritesByte() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write((byte)42); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 42 }, stream.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Write_WritesByteArray() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write(new byte[] { 2, 4, 6, 8 }); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Write_WritesUInt16() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write((ushort)1234); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Write_WritesUInt32() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write((uint)12345678); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(new byte[] { }, new byte[] { 0, 0, 0, 0 })] |
|||
[InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] |
|||
[InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] |
|||
[InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] |
|||
[InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] |
|||
[InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12 })] |
|||
public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.WritePadded(bytes); |
|||
} |
|||
|
|||
Assert.Equal(expectedResult, stream.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteMarker_WritesToPlacedPosition() |
|||
{ |
|||
MemoryStream stream = new MemoryStream(); |
|||
|
|||
using (TiffWriter writer = new TiffWriter(stream)) |
|||
{ |
|||
writer.Write((uint)0x11111111); |
|||
long marker = writer.PlaceMarker(); |
|||
writer.Write((uint)0x33333333); |
|||
|
|||
writer.WriteMarker(marker, 0x12345678); |
|||
|
|||
writer.Write((uint)0x44444444); |
|||
} |
|||
|
|||
Assert.Equal(new byte[] { 0x11, 0x11, 0x11, 0x11, |
|||
0x78, 0x56, 0x34, 0x12, |
|||
0x33, 0x33, 0x33, 0x33, |
|||
0x44, 0x44, 0x44, 0x44 }, stream.ToArray()); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@ |
|||
$Gm_Exe = "C:\Program Files\GraphicsMagick-1.3.25-Q16\gm.exe" |
|||
$Source_Image = "..\Jpg\baseline\Calliphora.jpg" |
|||
$Output_Prefix = ".\Calliphora" |
|||
|
|||
& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" |
|||
& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" |
|||
& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" |
|||
& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" |
|||
& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" |
|||
|
|||
& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" |
|||
& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
|
|||
public static class ByteArrayUtility |
|||
{ |
|||
public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian) |
|||
{ |
|||
if (BitConverter.IsLittleEndian != isLittleEndian) |
|||
{ |
|||
byte[] reversedBytes = new byte[bytes.Length]; |
|||
Array.Copy(bytes, reversedBytes, bytes.Length); |
|||
Array.Reverse(reversedBytes); |
|||
return reversedBytes; |
|||
} |
|||
else |
|||
{ |
|||
return bytes; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
public class ByteBuffer |
|||
{ |
|||
List<byte> bytes = new List<byte>(); |
|||
bool isLittleEndian; |
|||
|
|||
public ByteBuffer(bool isLittleEndian) |
|||
{ |
|||
this.isLittleEndian = isLittleEndian; |
|||
} |
|||
|
|||
public void AddByte(byte value) |
|||
{ |
|||
bytes.Add(value); |
|||
} |
|||
|
|||
public void AddUInt16(ushort value) |
|||
{ |
|||
bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(isLittleEndian)); |
|||
} |
|||
|
|||
public void AddUInt32(uint value) |
|||
{ |
|||
bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(isLittleEndian)); |
|||
} |
|||
|
|||
public byte[] ToArray() |
|||
{ |
|||
return bytes.ToArray(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
|
|||
/// <summary>
|
|||
/// An interface for any class within the Tiff generator that produces data to be included in the file.
|
|||
/// </summary>
|
|||
internal interface ITiffGenDataSource |
|||
{ |
|||
IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
|
|||
/// <summary>
|
|||
/// A utility data structure to represent an independent block of data in a Tiff file.
|
|||
/// These may be located in any order within a Tiff file.
|
|||
/// </summary>
|
|||
internal class TiffGenDataBlock |
|||
{ |
|||
public TiffGenDataBlock(byte[] bytes) |
|||
{ |
|||
this.Bytes = bytes; |
|||
this.References = new List<TiffGenDataReference>(); |
|||
} |
|||
|
|||
public byte[] Bytes { get; } |
|||
public IList<TiffGenDataReference> References { get; } |
|||
|
|||
public void AddReference(byte[] bytes, int offset) |
|||
{ |
|||
References.Add(new TiffGenDataReference(bytes, offset)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// A utility data structure to represent a reference from one block of data to another in a Tiff file.
|
|||
/// </summary>
|
|||
internal class TiffGenDataReference |
|||
{ |
|||
public TiffGenDataReference(byte[] bytes, int offset) |
|||
{ |
|||
this.Bytes = bytes; |
|||
this.Offset = offset; |
|||
} |
|||
|
|||
public byte[] Bytes { get; } |
|||
public int Offset { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
/// <summary>
|
|||
/// A utility data structure to represent Tiff IFD entries in unit tests.
|
|||
/// </summary>
|
|||
internal abstract class TiffGenEntry : ITiffGenDataSource |
|||
{ |
|||
private TiffGenEntry(ushort tag, TiffType type, uint count) |
|||
{ |
|||
this.Tag = tag; |
|||
this.Type = type; |
|||
this.Count = count; |
|||
} |
|||
|
|||
public uint Count { get; } |
|||
public ushort Tag { get; } |
|||
public TiffType Type { get; } |
|||
|
|||
public abstract IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian); |
|||
|
|||
public static TiffGenEntry Ascii(ushort tag, string value) |
|||
{ |
|||
return new TiffGenEntryAscii(tag, value); |
|||
} |
|||
|
|||
public static TiffGenEntry Bytes(ushort tag, TiffType type, uint count, byte[] value) |
|||
{ |
|||
return new TiffGenEntryBytes(tag, type, count, value); |
|||
} |
|||
|
|||
public static TiffGenEntry Integer(ushort tag, TiffType type, int value) |
|||
{ |
|||
return TiffGenEntry.Integer(tag, type, new int[] { value }); |
|||
} |
|||
|
|||
public static TiffGenEntry Integer(ushort tag, TiffType type, int[] value) |
|||
{ |
|||
if (type != TiffType.Byte && type != TiffType.Short && type != TiffType.Long && |
|||
type != TiffType.SByte && type != TiffType.SShort && type != TiffType.SLong) |
|||
throw new ArgumentException(nameof(type), "The specified type is not an integer type."); |
|||
|
|||
return new TiffGenEntryInteger(tag, type, value); |
|||
} |
|||
|
|||
public static TiffGenEntry Integer(ushort tag, TiffType type, uint value) |
|||
{ |
|||
return TiffGenEntry.Integer(tag, type, new uint[] { value }); |
|||
} |
|||
|
|||
public static TiffGenEntry Integer(ushort tag, TiffType type, uint[] value) |
|||
{ |
|||
if (type != TiffType.Byte && type != TiffType.Short && type != TiffType.Long && |
|||
type != TiffType.SByte && type != TiffType.SShort && type != TiffType.SLong) |
|||
throw new ArgumentException(nameof(type), "The specified type is not an integer type."); |
|||
|
|||
return new TiffGenEntryUnsignedInteger(tag, type, value); |
|||
} |
|||
|
|||
public static TiffGenEntry Rational(ushort tag, uint numerator, uint denominator) |
|||
{ |
|||
return new TiffGenEntryRational(tag, numerator, denominator); |
|||
} |
|||
|
|||
private class TiffGenEntryAscii : TiffGenEntry |
|||
{ |
|||
public TiffGenEntryAscii(ushort tag, string value) : base(tag, TiffType.Ascii, (uint)GetBytes(value).Length) |
|||
{ |
|||
this.Value = value; |
|||
} |
|||
|
|||
public string Value { get; } |
|||
|
|||
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = GetBytes(Value); |
|||
return new[] { new TiffGenDataBlock(bytes) }; |
|||
} |
|||
|
|||
private static byte[] GetBytes(string value) |
|||
{ |
|||
return Encoding.ASCII.GetBytes($"{value}\0"); |
|||
} |
|||
} |
|||
|
|||
private class TiffGenEntryBytes : TiffGenEntry |
|||
{ |
|||
public TiffGenEntryBytes(ushort tag, TiffType type, uint count, byte[] value) : base(tag, type, count) |
|||
{ |
|||
this.Value = value; |
|||
} |
|||
|
|||
public byte[] Value { get; } |
|||
|
|||
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
return new[] { new TiffGenDataBlock(Value) }; |
|||
} |
|||
} |
|||
|
|||
private class TiffGenEntryInteger : TiffGenEntry |
|||
{ |
|||
public TiffGenEntryInteger(ushort tag, TiffType type, int[] value) : base(tag, type, (uint)value.Length) |
|||
{ |
|||
this.Value = value; |
|||
} |
|||
|
|||
public int[] Value { get; } |
|||
|
|||
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray(); |
|||
return new[] { new TiffGenDataBlock(bytes) }; |
|||
} |
|||
|
|||
private IEnumerable<byte[]> GetBytes() |
|||
{ |
|||
switch (Type) |
|||
{ |
|||
case TiffType.Byte: |
|||
return Value.Select(i => new byte[] { (byte)i }); |
|||
case TiffType.Short: |
|||
return Value.Select(i => BitConverter.GetBytes((ushort)i)); |
|||
case TiffType.Long: |
|||
return Value.Select(i => BitConverter.GetBytes((uint)i)); |
|||
case TiffType.SByte: |
|||
return Value.Select(i => BitConverter.GetBytes((sbyte)i)); |
|||
case TiffType.SShort: |
|||
return Value.Select(i => BitConverter.GetBytes((short)i)); |
|||
case TiffType.SLong: |
|||
return Value.Select(i => BitConverter.GetBytes((int)i)); |
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class TiffGenEntryUnsignedInteger : TiffGenEntry |
|||
{ |
|||
public TiffGenEntryUnsignedInteger(ushort tag, TiffType type, uint[] value) : base(tag, type, (uint)value.Length) |
|||
{ |
|||
this.Value = value; |
|||
} |
|||
|
|||
public uint[] Value { get; } |
|||
|
|||
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray(); |
|||
return new[] { new TiffGenDataBlock(bytes) }; |
|||
} |
|||
|
|||
private IEnumerable<byte[]> GetBytes() |
|||
{ |
|||
switch (Type) |
|||
{ |
|||
case TiffType.Byte: |
|||
return Value.Select(i => new byte[] { (byte)i }); |
|||
case TiffType.Short: |
|||
return Value.Select(i => BitConverter.GetBytes((ushort)i)); |
|||
case TiffType.Long: |
|||
return Value.Select(i => BitConverter.GetBytes((uint)i)); |
|||
case TiffType.SByte: |
|||
return Value.Select(i => BitConverter.GetBytes((sbyte)i)); |
|||
case TiffType.SShort: |
|||
return Value.Select(i => BitConverter.GetBytes((short)i)); |
|||
case TiffType.SLong: |
|||
return Value.Select(i => BitConverter.GetBytes((int)i)); |
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class TiffGenEntryRational : TiffGenEntry |
|||
{ |
|||
public TiffGenEntryRational(ushort tag, uint numerator, uint denominator) : base(tag, TiffType.Rational, 1u) |
|||
{ |
|||
this.Numerator = numerator; |
|||
this.Denominator = denominator; |
|||
} |
|||
|
|||
public uint Numerator { get; } |
|||
|
|||
public uint Denominator { get; } |
|||
|
|||
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
byte[] numeratorBytes = BitConverter.GetBytes(Numerator).WithByteOrder(isLittleEndian); |
|||
byte[] denominatorBytes = BitConverter.GetBytes(Denominator).WithByteOrder(isLittleEndian); |
|||
byte[] bytes = Enumerable.Concat(numeratorBytes, denominatorBytes).ToArray(); |
|||
return new[] { new TiffGenDataBlock(bytes) }; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
|
|||
/// <summary>
|
|||
/// A utility class for generating in-memory Tiff files for use in unit tests.
|
|||
/// </summary>
|
|||
internal static class TiffGenExtensions |
|||
{ |
|||
public static byte[] ToBytes(this ITiffGenDataSource dataSource, bool isLittleEndian) |
|||
{ |
|||
var dataBlocks = dataSource.GetData(isLittleEndian); |
|||
|
|||
int offset = 0; |
|||
|
|||
foreach (var dataBlock in dataBlocks) |
|||
{ |
|||
byte[] offsetBytes = BitConverter.GetBytes(offset).WithByteOrder(isLittleEndian); |
|||
|
|||
foreach (var reference in dataBlock.References) |
|||
{ |
|||
reference.Bytes[reference.Offset + 0] = offsetBytes[0]; |
|||
reference.Bytes[reference.Offset + 1] = offsetBytes[1]; |
|||
reference.Bytes[reference.Offset + 2] = offsetBytes[2]; |
|||
reference.Bytes[reference.Offset + 3] = offsetBytes[3]; |
|||
} |
|||
|
|||
offset += dataBlock.Bytes.Length; |
|||
} |
|||
|
|||
return dataBlocks.SelectMany(b => b.Bytes).ToArray(); |
|||
} |
|||
|
|||
public static Stream ToStream(this ITiffGenDataSource dataSource, bool isLittleEndian) |
|||
{ |
|||
var bytes = dataSource.ToBytes(isLittleEndian); |
|||
return new MemoryStream(bytes); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
/// <summary>
|
|||
/// A utility data structure to represent a Tiff file-header.
|
|||
/// </summary>
|
|||
internal class TiffGenHeader : ITiffGenDataSource |
|||
{ |
|||
public TiffGenHeader() |
|||
{ |
|||
this.MagicNumber = 42; |
|||
} |
|||
|
|||
public ushort? ByteOrderMarker { get; set; } |
|||
public ushort MagicNumber { get; set; } |
|||
public TiffGenIfd FirstIfd { get; set; } |
|||
|
|||
public IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
ByteBuffer bytes = new ByteBuffer(isLittleEndian); |
|||
|
|||
bytes.AddUInt16(ByteOrderMarker ?? (isLittleEndian ? (ushort)0x4949 : (ushort)0x4D4D)); |
|||
bytes.AddUInt16(MagicNumber); |
|||
bytes.AddUInt32(0); |
|||
|
|||
var headerData = new TiffGenDataBlock(bytes.ToArray()); |
|||
|
|||
if (FirstIfd != null) |
|||
{ |
|||
var firstIfdData = FirstIfd.GetData(isLittleEndian); |
|||
firstIfdData.First().AddReference(headerData.Bytes, 4); |
|||
return new[] { headerData }.Concat(firstIfdData); |
|||
} |
|||
else |
|||
{ |
|||
return new[] { headerData }; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
/// <summary>
|
|||
/// A utility data structure to represent Tiff IFDs in unit tests.
|
|||
/// </summary>
|
|||
internal class TiffGenIfd : ITiffGenDataSource |
|||
{ |
|||
public TiffGenIfd() |
|||
{ |
|||
this.Entries = new List<TiffGenEntry>(); |
|||
} |
|||
|
|||
public List<TiffGenEntry> Entries { get; } |
|||
public TiffGenIfd NextIfd { get; set; } |
|||
|
|||
public IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian) |
|||
{ |
|||
ByteBuffer bytes = new ByteBuffer(isLittleEndian); |
|||
List<TiffGenDataBlock> dataBlocks = new List<TiffGenDataBlock>(); |
|||
List<Tuple<TiffGenDataBlock, int>> entryReferences = new List<Tuple<TiffGenDataBlock, int>>(); |
|||
|
|||
// Add the entry count
|
|||
|
|||
bytes.AddUInt16((ushort)Entries.Count); |
|||
|
|||
// Add all IFD entries
|
|||
|
|||
int entryOffset = 2; |
|||
|
|||
foreach (var entry in Entries) |
|||
{ |
|||
var entryData = entry.GetData(isLittleEndian); |
|||
var entryBytes = entryData.First().Bytes; |
|||
|
|||
bytes.AddUInt16(entry.Tag); |
|||
bytes.AddUInt16((ushort)entry.Type); |
|||
bytes.AddUInt32(entry.Count); |
|||
|
|||
if (entryBytes.Length <=4) |
|||
{ |
|||
bytes.AddByte(entryBytes.Length > 0 ? entryBytes[0] : (byte)0); |
|||
bytes.AddByte(entryBytes.Length > 1 ? entryBytes[1] : (byte)0); |
|||
bytes.AddByte(entryBytes.Length > 2 ? entryBytes[2] : (byte)0); |
|||
bytes.AddByte(entryBytes.Length > 3 ? entryBytes[3] : (byte)0); |
|||
|
|||
dataBlocks.AddRange(entryData.Skip(1)); |
|||
} |
|||
else |
|||
{ |
|||
bytes.AddUInt32(0); |
|||
dataBlocks.AddRange(entryData); |
|||
entryReferences.Add(Tuple.Create(entryData.First(), entryOffset + 8)); |
|||
} |
|||
|
|||
entryOffset += 12; |
|||
} |
|||
|
|||
// Add reference to next IFD
|
|||
|
|||
bytes.AddUInt32(0); |
|||
|
|||
// Build the data
|
|||
|
|||
var ifdData = new TiffGenDataBlock(bytes.ToArray()); |
|||
|
|||
foreach (var entryReference in entryReferences) |
|||
{ |
|||
entryReference.Item1.AddReference(ifdData.Bytes, entryReference.Item2); |
|||
} |
|||
|
|||
IEnumerable<TiffGenDataBlock> nextIfdData = new TiffGenDataBlock[0]; |
|||
if (NextIfd != null) |
|||
{ |
|||
nextIfdData = NextIfd.GetData(isLittleEndian); |
|||
nextIfdData.First().AddReference(ifdData.Bytes, ifdData.Bytes.Length - 4); |
|||
} |
|||
|
|||
return new [] { ifdData }.Concat(dataBlocks).Concat(nextIfdData); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
using System.Linq; |
|||
|
|||
/// <summary>
|
|||
/// A utility class for manipulating in-memory Tiff files for use in unit tests.
|
|||
/// </summary>
|
|||
internal static class TiffGenIfdExtensions |
|||
{ |
|||
public static TiffGenIfd WithoutEntry(this TiffGenIfd ifd, ushort tag) |
|||
{ |
|||
TiffGenEntry entry = ifd.Entries.FirstOrDefault(e => e.Tag == tag); |
|||
if (entry != null) |
|||
{ |
|||
ifd.Entries.Remove(entry); |
|||
} |
|||
return ifd; |
|||
} |
|||
|
|||
public static TiffGenIfd WithEntry(this TiffGenIfd ifd, TiffGenEntry entry) |
|||
{ |
|||
ifd.WithoutEntry(entry.Tag); |
|||
ifd.Entries.Add(entry); |
|||
|
|||
return ifd; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue