diff --git a/ImageSharp.sln b/ImageSharp.sln index 8f3bc68602..40282d8bcd 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.12 diff --git a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs new file mode 100644 index 0000000000..00d69dbd5c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs @@ -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 +{ + /// + /// Class to handle cases where TIFF image data is compressed using Deflate compression. + /// + /// + /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. + /// + internal static class DeflateTiffCompression + { + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + [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); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs new file mode 100644 index 0000000000..5de966554d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs @@ -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 +{ + /// + /// Class to handle cases where TIFF image data is compressed using LZW compression. + /// + internal static class LzwTiffCompression + { + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + [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); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs new file mode 100644 index 0000000000..a9587d1990 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs @@ -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 +{ + /// + /// Class to handle cases where TIFF image data is not compressed. + /// + internal static class NoneTiffCompression + { + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decompress(Stream stream, int byteCount, byte[] buffer) + { + stream.ReadFull(buffer, byteCount); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs new file mode 100644 index 0000000000..a6cd8f88d6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs @@ -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 +{ + /// + /// Class to handle cases where TIFF image data is compressed using PackBits compression. + /// + internal static class PackBitsTiffCompression + { + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The output buffer for uncompressed data. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decompress(Stream stream, int byteCount, byte[] buffer) + { + byte[] compressedData = ArrayPool.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.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; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs new file mode 100644 index 0000000000..4121f90b2d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Provides enumeration of the various TIFF compression types. + /// + internal enum TiffCompressionType + { + /// + /// Image data is stored uncompressed in the TIFF file. + /// + None = 0, + + /// + /// Image data is compressed using PackBits compression. + /// + PackBits = 1, + + /// + /// Image data is compressed using Deflate compression. + /// + Deflate = 2, + + /// + /// Image data is compressed using LZW compression. + /// + Lzw = 3, + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs new file mode 100644 index 0000000000..e5ee8b1952 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the compression formats defined by the Tiff file-format. + /// + internal enum TiffCompression + { + /// + /// No compression. + /// + None = 1, + + /// + /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding. + /// + Ccitt1D = 2, + + /// + /// PackBits compression + /// + PackBits = 32773, + + /// + /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + CcittGroup3Fax = 3, + + /// + /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + CcittGroup4Fax = 4, + + /// + /// LZW compression (see Section 13 of the TIFF 6.0 specification). + /// + Lzw = 5, + + /// + /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification). + /// + OldJpeg = 6, + + /// + /// JPEG compression (see TIFF Specification, supplement 2). + /// + Jpeg = 7, + + /// + /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2). + /// + Deflate = 8, + + /// + /// Deflate compression - old. + /// + OldDeflate = 32946, + + /// + /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). + /// + ItuTRecT82 = 9, + + /// + /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). + /// + ItuTRecT43 = 10 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs new file mode 100644 index 0000000000..a2044314ae --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -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 +{ + /// + /// Defines constants defined in the TIFF specification. + /// + internal static class TiffConstants + { + /// + /// Byte order markers for indicating little endian encoding. + /// + public const byte ByteOrderLittleEndian = 0x49; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const byte ByteOrderBigEndian = 0x4D; + + /// + /// Byte order markers for indicating little endian encoding. + /// + public const ushort ByteOrderLittleEndianShort = 0x4949; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const ushort ByteOrderBigEndianShort = 0x4D4D; + + /// + /// Magic number used within the image file header to identify a TIFF format file. + /// + public const ushort HeaderMagicNumber = 42; + + /// + /// Size (in bytes) of the TIFF file header. + /// + public const int SizeOfTiffHeader = 8; + + /// + /// Size (in bytes) of each individual TIFF IFD entry + /// + public const int SizeOfIfdEntry = 12; + + /// + /// Size (in bytes) of the Short and SShort data types + /// + public const int SizeOfShort = 2; + + /// + /// Size (in bytes) of the Long and SLong data types + /// + public const int SizeOfLong = 4; + + /// + /// Size (in bytes) of the Rational and SRational data types + /// + public const int SizeOfRational = 8; + + /// + /// Size (in bytes) of the Float data type + /// + public const int SizeOfFloat = 4; + + /// + /// Size (in bytes) of the Double data type + /// + public const int SizeOfDouble = 8; + + /// + /// The list of mimetypes that equate to a tiff. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; + + /// + /// The list of file extensions that equate to a tiff. + /// + public static readonly IEnumerable FileExtensions = new[] { "tiff", "tif" }; + } +} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs new file mode 100644 index 0000000000..d34d999b95 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the possible uses of extra components in TIFF format files. + /// + internal enum TiffExtraSamples + { + /// + /// Unspecified data. + /// + Unspecified = 0, + + /// + /// Associated alpha data (with pre-multiplied color). + /// + AssociatedAlpha = 1, + + /// + /// Unassociated alpha data. + /// + UnassociatedAlpha = 2 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs new file mode 100644 index 0000000000..e4d30a324d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the fill orders defined by the Tiff file-format. + /// + internal enum TiffFillOrder + { + /// + /// Pixels with lower column values are stored in the higher-order bits of the byte. + /// + MostSignificantBitFirst = 1, + + /// + /// Pixels with lower column values are stored in the lower-order bits of the byte. + /// + LeastSignificantBitFirst = 2 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs new file mode 100644 index 0000000000..b881ac209f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs @@ -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 +{ + /// + /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// + [Flags] + internal enum TiffNewSubfileType + { + /// + /// A full-resolution image. + /// + FullImage = 0x0000, + + /// + /// Reduced-resolution version of another image in this TIFF file. + /// + Preview = 0x0001, + + /// + /// A single page of a multi-page image. + /// + SinglePage = 0x0002, + + /// + /// A transparency mask for another image in this TIFF file. + /// + TransparencyMask = 0x0004, + + /// + /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification). + /// + AlternativePreview = 0x10000, + + /// + /// Mixed raster content (see RFC2301). + /// + MixedRasterContent = 0x0008 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs new file mode 100644 index 0000000000..035f88809c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the image orientations defined by the Tiff file-format. + /// + internal enum TiffOrientation + { + /// + /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively. + /// + TopLeft = 1, + + /// + /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively. + /// + TopRight = 2, + + /// + /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively. + /// + BottomRight = 3, + + /// + /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively. + /// + BottomLeft = 4, + + /// + /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively. + /// + LeftTop = 5, + + /// + /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively. + /// + RightTop = 6, + + /// + /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively. + /// + RightBottom = 7, + + /// + /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively. + /// + LeftBottom = 8 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs new file mode 100644 index 0000000000..dd4d923b8c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the photometric interpretation formats defined by the Tiff file-format. + /// + internal enum TiffPhotometricInterpretation + { + /// + /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + WhiteIsZero = 0, + + /// + /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero = 1, + + /// + /// RGB + /// + Rgb = 2, + + /// + /// Palette Color + /// + PaletteColor = 3, + + /// + /// A transparency mask + /// + TransparencyMask = 4, + + /// + /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). + /// + Separated = 5, + + /// + /// YCbCr (see Section 21 of the TIFF 6.0 specification). + /// + YCbCr = 6, + + /// + /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). + /// + CieLab = 8, + + /// + /// ICC L*a*b* (see TIFF Specification, supplement 1). + /// + IccLab = 9, + + /// + /// ITU L*a*b* (see RFC2301). + /// + ItuLab = 10, + + /// + /// Color Filter Array (see the DNG specification). + /// + ColorFilterArray = 32803, + + /// + /// Linear Raw (see the DNG specification). + /// + LinearRaw = 34892 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs new file mode 100644 index 0000000000..4fc0aa4c86 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing how the components of each pixel are stored the Tiff file-format. + /// + internal enum TiffPlanarConfiguration + { + /// + /// Chunky format. + /// + Chunky = 1, + + /// + /// Planar format. + /// + Planar = 2 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs new file mode 100644 index 0000000000..7bb3dbd6e3 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the resolution units defined by the Tiff file-format. + /// + internal enum TiffResolutionUnit + { + /// + /// No absolute unit of measurement. + /// + None = 1, + + /// + /// Inch. + /// + Inch = 2, + + /// + /// Centimeter. + /// + Centimeter = 3 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs new file mode 100644 index 0000000000..4039ae9e2d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// + internal enum TiffSubfileType + { + /// + /// Full-resolution image data. + /// + FullImage = 1, + + /// + /// Reduced-resolution image data. + /// + Preview = 2, + + /// + /// A single page of a multi-page image. + /// + SinglePage = 3 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs new file mode 100644 index 0000000000..38cf4280e1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs @@ -0,0 +1,716 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Constants representing tag IDs in the Tiff file-format. + /// + internal class TiffTags + { + /// + /// Artist (see Section 8: Baseline Fields). + /// + public const int Artist = 315; + + /// + /// BitsPerSample (see Section 8: Baseline Fields). + /// + public const int BitsPerSample = 258; + + /// + /// CellLength (see Section 8: Baseline Fields). + /// + public const int CellLength = 265; + + /// + /// CellWidth (see Section 8: Baseline Fields). + /// + public const int CellWidth = 264; + + /// + /// ColorMap (see Section 8: Baseline Fields). + /// + public const int ColorMap = 320; + + /// + /// Compression (see Section 8: Baseline Fields). + /// + public const int Compression = 259; + + /// + /// Copyright (see Section 8: Baseline Fields). + /// + public const int Copyright = 33432; + + /// + /// DateTime (see Section 8: Baseline Fields). + /// + public const int DateTime = 306; + + /// + /// ExtraSamples (see Section 8: Baseline Fields). + /// + public const int ExtraSamples = 338; + + /// + /// FillOrder (see Section 8: Baseline Fields). + /// + public const int FillOrder = 266; + + /// + /// FreeByteCounts (see Section 8: Baseline Fields). + /// + public const int FreeByteCounts = 289; + + /// + /// FreeOffsets (see Section 8: Baseline Fields). + /// + public const int FreeOffsets = 288; + + /// + /// GrayResponseCurve (see Section 8: Baseline Fields). + /// + public const int GrayResponseCurve = 291; + + /// + /// GrayResponseUnit (see Section 8: Baseline Fields). + /// + public const int GrayResponseUnit = 290; + + /// + /// HostComputer (see Section 8: Baseline Fields). + /// + public const int HostComputer = 316; + + /// + /// ImageDescription (see Section 8: Baseline Fields). + /// + public const int ImageDescription = 270; + + /// + /// ImageLength (see Section 8: Baseline Fields). + /// + public const int ImageLength = 257; + + /// + /// ImageWidth (see Section 8: Baseline Fields). + /// + public const int ImageWidth = 256; + + /// + /// Make (see Section 8: Baseline Fields). + /// + public const int Make = 271; + + /// + /// MaxSampleValue (see Section 8: Baseline Fields). + /// + public const int MaxSampleValue = 281; + + /// + /// MinSampleValue (see Section 8: Baseline Fields). + /// + public const int MinSampleValue = 280; + + /// + /// Model (see Section 8: Baseline Fields). + /// + public const int Model = 272; + + /// + /// NewSubfileType (see Section 8: Baseline Fields). + /// + public const int NewSubfileType = 254; + + /// + /// Orientation (see Section 8: Baseline Fields). + /// + public const int Orientation = 274; + + /// + /// PhotometricInterpretation (see Section 8: Baseline Fields). + /// + public const int PhotometricInterpretation = 262; + + /// + /// PlanarConfiguration (see Section 8: Baseline Fields). + /// + public const int PlanarConfiguration = 284; + + /// + /// ResolutionUnit (see Section 8: Baseline Fields). + /// + public const int ResolutionUnit = 296; + + /// + /// RowsPerStrip (see Section 8: Baseline Fields). + /// + public const int RowsPerStrip = 278; + + /// + /// SamplesPerPixel (see Section 8: Baseline Fields). + /// + public const int SamplesPerPixel = 277; + + /// + /// Software (see Section 8: Baseline Fields). + /// + public const int Software = 305; + + /// + /// StripByteCounts (see Section 8: Baseline Fields). + /// + public const int StripByteCounts = 279; + + /// + /// StripOffsets (see Section 8: Baseline Fields). + /// + public const int StripOffsets = 273; + + /// + /// SubfileType (see Section 8: Baseline Fields). + /// + public const int SubfileType = 255; + + /// + /// Threshholding (see Section 8: Baseline Fields). + /// + public const int Threshholding = 263; + + /// + /// XResolution (see Section 8: Baseline Fields). + /// + public const int XResolution = 282; + + /// + /// YResolution (see Section 8: Baseline Fields). + /// + public const int YResolution = 283; + + /// + /// T4Options (see Section 11: CCITT Bilevel Encodings). + /// + public const int T4Options = 292; + + /// + /// T6Options (see Section 11: CCITT Bilevel Encodings). + /// + public const int T6Options = 293; + + /// + /// DocumentName (see Section 12: Document Storage and Retrieval). + /// + public const int DocumentName = 269; + + /// + /// PageName (see Section 12: Document Storage and Retrieval). + /// + public const int PageName = 285; + + /// + /// PageNumber (see Section 12: Document Storage and Retrieval). + /// + public const int PageNumber = 297; + + /// + /// XPosition (see Section 12: Document Storage and Retrieval). + /// + public const int XPosition = 286; + + /// + /// YPosition (see Section 12: Document Storage and Retrieval). + /// + public const int YPosition = 287; + + /// + /// Predictor (see Section 14: Differencing Predictor). + /// + public const int Predictor = 317; + + /// + /// TileWidth (see Section 15: Tiled Images). + /// + public const int TileWidth = 322; + + /// + /// TileLength (see Section 15: Tiled Images). + /// + public const int TileLength = 323; + + /// + /// TileOffsets (see Section 15: Tiled Images). + /// + public const int TileOffsets = 324; + + /// + /// TileByteCounts (see Section 15: Tiled Images). + /// + public const int TileByteCounts = 325; + + /// + /// InkSet (see Section 16: CMYK Images). + /// + public const int InkSet = 332; + + /// + /// NumberOfInks (see Section 16: CMYK Images). + /// + public const int NumberOfInks = 334; + + /// + /// InkNames (see Section 16: CMYK Images). + /// + public const int InkNames = 333; + + /// + /// DotRange (see Section 16: CMYK Images). + /// + public const int DotRange = 336; + + /// + /// TargetPrinter (see Section 16: CMYK Images). + /// + public const int TargetPrinter = 337; + + /// + /// HalftoneHints (see Section 17: Halftone Hints). + /// + public const int HalftoneHints = 321; + + /// + /// SampleFormat (see Section 19: Data Sample Format). + /// + public const int SampleFormat = 339; + + /// + /// SMinSampleValue (see Section 19: Data Sample Format). + /// + public const int SMinSampleValue = 340; + + /// + /// SMaxSampleValue (see Section 19: Data Sample Format). + /// + public const int SMaxSampleValue = 341; + + /// + /// WhitePoint (see Section 20: RGB Image Colorimetry). + /// + public const int WhitePoint = 318; + + /// + /// PrimaryChromaticities (see Section 20: RGB Image Colorimetry). + /// + public const int PrimaryChromaticities = 319; + + /// + /// TransferFunction (see Section 20: RGB Image Colorimetry). + /// + public const int TransferFunction = 301; + + /// + /// TransferRange (see Section 20: RGB Image Colorimetry). + /// + public const int TransferRange = 342; + + /// + /// ReferenceBlackWhite (see Section 20: RGB Image Colorimetry). + /// + public const int ReferenceBlackWhite = 532; + + /// + /// YCbCrCoefficients (see Section 21: YCbCr Images). + /// + public const int YCbCrCoefficients = 529; + + /// + /// YCbCrSubSampling (see Section 21: YCbCr Images). + /// + public const int YCbCrSubSampling = 530; + + /// + /// YCbCrPositioning (see Section 21: YCbCr Images). + /// + public const int YCbCrPositioning = 531; + + /// + /// JpegProc (see Section 22: JPEG Compression). + /// + public const int JpegProc = 512; + + /// + /// JpegInterchangeFormat (see Section 22: JPEG Compression). + /// + public const int JpegInterchangeFormat = 513; + + /// + /// JpegInterchangeFormatLength (see Section 22: JPEG Compression). + /// + public const int JpegInterchangeFormatLength = 514; + + /// + /// JpegRestartInterval (see Section 22: JPEG Compression). + /// + public const int JpegRestartInterval = 515; + + /// + /// JpegLosslessPredictors (see Section 22: JPEG Compression). + /// + public const int JpegLosslessPredictors = 517; + + /// + /// JpegPointTransforms (see Section 22: JPEG Compression). + /// + public const int JpegPointTransforms = 518; + + /// + /// JpegQTables (see Section 22: JPEG Compression). + /// + public const int JpegQTables = 519; + + /// + /// JpegDCTables (see Section 22: JPEG Compression). + /// + public const int JpegDCTables = 520; + + /// + /// JpegACTables (see Section 22: JPEG Compression). + /// + public const int JpegACTables = 521; + + /// + /// SubIFDs (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int SubIFDs = 330; + + /// + /// ClipPath (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int ClipPath = 343; + + /// + /// XClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int XClipPathUnits = 344; + + /// + /// YClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int YClipPathUnits = 345; + + /// + /// Indexed (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int Indexed = 346; + + /// + /// ImageID (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int ImageID = 32781; + + /// + /// OpiProxy (see TIFF Supplement 1: Adobe Pagemaker 6.0). + /// + public const int OpiProxy = 351; + + /// + /// ImageSourceData (see TIFF Supplement 2: Adobe Photoshop). + /// + public const int ImageSourceData = 37724; + + /// + /// JPEGTables (see TIFF/EP Specification: Additional Tags). + /// + public const int JPEGTables = 0x015B; + + /// + /// CFARepeatPatternDim (see TIFF/EP Specification: Additional Tags). + /// + public const int CFARepeatPatternDim = 0x828D; + + /// + /// BatteryLevel (see TIFF/EP Specification: Additional Tags). + /// + public const int BatteryLevel = 0x828F; + + /// + /// Interlace (see TIFF/EP Specification: Additional Tags). + /// + public const int Interlace = 0x8829; + + /// + /// TimeZoneOffset (see TIFF/EP Specification: Additional Tags). + /// + public const int TimeZoneOffset = 0x882A; + + /// + /// SelfTimerMode (see TIFF/EP Specification: Additional Tags). + /// + public const int SelfTimerMode = 0x882B; + + /// + /// Noise (see TIFF/EP Specification: Additional Tags). + /// + public const int Noise = 0x920D; + + /// + /// ImageNumber (see TIFF/EP Specification: Additional Tags). + /// + public const int ImageNumber = 0x9211; + + /// + /// SecurityClassification (see TIFF/EP Specification: Additional Tags). + /// + public const int SecurityClassification = 0x9212; + + /// + /// ImageHistory (see TIFF/EP Specification: Additional Tags). + /// + public const int ImageHistory = 0x9213; + + /// + /// TiffEPStandardID (see TIFF/EP Specification: Additional Tags). + /// + public const int TiffEPStandardID = 0x9216; + + /// + /// BadFaxLines (see RFC2301: TIFF-F/FX Specification). + /// + public const int BadFaxLines = 326; + + /// + /// CleanFaxData (see RFC2301: TIFF-F/FX Specification). + /// + public const int CleanFaxData = 327; + + /// + /// ConsecutiveBadFaxLines (see RFC2301: TIFF-F/FX Specification). + /// + public const int ConsecutiveBadFaxLines = 328; + + /// + /// GlobalParametersIFD (see RFC2301: TIFF-F/FX Specification). + /// + public const int GlobalParametersIFD = 400; + + /// + /// ProfileType (see RFC2301: TIFF-F/FX Specification). + /// + public const int ProfileType = 401; + + /// + /// FaxProfile (see RFC2301: TIFF-F/FX Specification). + /// + public const int FaxProfile = 402; + + /// + /// CodingMethod (see RFC2301: TIFF-F/FX Specification). + /// + public const int CodingMethod = 403; + + /// + /// VersionYear (see RFC2301: TIFF-F/FX Specification). + /// + public const int VersionYear = 404; + + /// + /// ModeNumber (see RFC2301: TIFF-F/FX Specification). + /// + public const int ModeNumber = 405; + + /// + /// Decode (see RFC2301: TIFF-F/FX Specification). + /// + public const int Decode = 433; + + /// + /// DefaultImageColor (see RFC2301: TIFF-F/FX Specification). + /// + public const int DefaultImageColor = 434; + + /// + /// StripRowCounts (see RFC2301: TIFF-F/FX Specification). + /// + public const int StripRowCounts = 559; + + /// + /// ImageLayer (see RFC2301: TIFF-F/FX Specification). + /// + public const int ImageLayer = 34732; + + /// + /// Xmp (Embedded Metadata). + /// + public const int Xmp = 700; + + /// + /// Iptc (Embedded Metadata). + /// + public const int Iptc = 33723; + + /// + /// Photoshop (Embedded Metadata). + /// + public const int Photoshop = 34377; + + /// + /// ExifIFD (Embedded Metadata). + /// + public const int ExifIFD = 34665; + + /// + /// GpsIFD (Embedded Metadata). + /// + public const int GpsIFD = 34853; + + /// + /// InteroperabilityIFD (Embedded Metadata). + /// + public const int InteroperabilityIFD = 40965; + + /// + /// WangAnnotation (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int WangAnnotation = 32932; + + /// + /// MDFileTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDFileTag = 33445; + + /// + /// MDScalePixel (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDScalePixel = 33446; + + /// + /// MDColorTable (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDColorTable = 33447; + + /// + /// MDLabName (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDLabName = 33448; + + /// + /// MDSampleInfo (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDSampleInfo = 33449; + + /// + /// MDPrepDate (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDPrepDate = 33450; + + /// + /// MDPrepTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDPrepTime = 33451; + + /// + /// MDFileUnits (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int MDFileUnits = 33452; + + /// + /// ModelPixelScaleTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int ModelPixelScaleTag = 33550; + + /// + /// IngrPacketDataTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int IngrPacketDataTag = 33918; + + /// + /// IngrFlagRegisters (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int IngrFlagRegisters = 33919; + + /// + /// IrasBTransformationMatrix (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int IrasBTransformationMatrix = 33920; + + /// + /// ModelTiePointTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int ModelTiePointTag = 33922; + + /// + /// ModelTransformationTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int ModelTransformationTag = 34264; + + /// + /// IccProfile (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int IccProfile = 34675; + + /// + /// GeoKeyDirectoryTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int GeoKeyDirectoryTag = 34735; + + /// + /// GeoDoubleParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int GeoDoubleParamsTag = 34736; + + /// + /// GeoAsciiParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int GeoAsciiParamsTag = 34737; + + /// + /// HylaFAXFaxRecvParams (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int HylaFAXFaxRecvParams = 34908; + + /// + /// HylaFAXFaxSubAddress (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int HylaFAXFaxSubAddress = 34909; + + /// + /// HylaFAXFaxRecvTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int HylaFAXFaxRecvTime = 34910; + + /// + /// GdalMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int GdalMetadata = 42112; + + /// + /// GdalNodata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int GdalNodata = 42113; + + /// + /// OceScanjobDescription (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int OceScanjobDescription = 50215; + + /// + /// OceApplicationSelector (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int OceApplicationSelector = 50216; + + /// + /// OceIdentificationNumber (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int OceIdentificationNumber = 50217; + + /// + /// OceImageLogicCharacteristics (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int OceImageLogicCharacteristics = 50218; + + /// + /// AliasLayerMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html). + /// + public const int AliasLayerMetadata = 50784; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs new file mode 100644 index 0000000000..0a398d231b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the threshholding applied to image data defined by the Tiff file-format. + /// + internal enum TiffThreshholding + { + /// + /// No dithering or halftoning. + /// + None = 1, + + /// + /// An ordered dither or halftone technique. + /// + Ordered = 2, + + /// + /// A randomized process such as error diffusion. + /// + Random = 3 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs new file mode 100644 index 0000000000..8e55d80cc7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Enumeration representing the data types understood by the Tiff file-format. + /// + internal enum TiffType + { + /// + /// Unsigned 8-bit integer. + /// + Byte = 1, + + /// + /// ASCII formatted text. + /// + Ascii = 2, + + /// + /// Unsigned 16-bit integer. + /// + Short = 3, + + /// + /// Unsigned 32-bit integer. + /// + Long = 4, + + /// + /// Unsigned rational number. + /// + Rational = 5, + + /// + /// Signed 8-bit integer. + /// + SByte = 6, + + /// + /// Undefined data type. + /// + Undefined = 7, + + /// + /// Signed 16-bit integer. + /// + SShort = 8, + + /// + /// Signed 32-bit integer. + /// + SLong = 9, + + /// + /// Signed rational number. + /// + SRational = 10, + + /// + /// Single precision (4-byte) IEEE format. + /// + Float = 11, + + /// + /// Double precision (8-byte) IEEE format. + /// + Double = 12, + + /// + /// Reference to an IFD. + /// + Ifd = 13 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs new file mode 100644 index 0000000000..c718102b8b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface ITiffDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs new file mode 100644 index 0000000000..e10396d5fc --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface ITiffEncoderOptions + { + } +} diff --git a/src/ImageSharp/Formats/Tiff/ImageExtensions.cs b/src/ImageSharp/Formats/Tiff/ImageExtensions.cs new file mode 100644 index 0000000000..3414f84d3b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/ImageExtensions.cs @@ -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 +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the tiff format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsTiff(this Image source, Stream stream) + where TPixel : struct, IPixel + { + return SaveAsTiff(source, stream, null); + } + + /// + /// Saves the image to the given stream with the tiff format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) + where TPixel : struct, IPixel + { + encoder = encoder ?? new TiffEncoder(); + encoder.Encode(source, stream); + + return source; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs new file mode 100644 index 0000000000..e317a7af7f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs @@ -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 +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images). + /// + internal static class BlackIsZero1TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs new file mode 100644 index 0000000000..62fff4737c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs @@ -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 +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images). + /// + internal static class BlackIsZero4TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs new file mode 100644 index 0000000000..949549d17b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs @@ -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 +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images). + /// + internal static class BlackIsZero8TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs new file mode 100644 index 0000000000..689a305ca7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs @@ -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 +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation (for all bit depths). + /// + internal static class BlackIsZeroTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The number of bits per sample for each pixel. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs new file mode 100644 index 0000000000..6c4bb56127 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs @@ -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 +{ + /// + /// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). + /// + internal static class PaletteTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The number of bits per sample for each pixel. + /// The RGB color lookup table to use for decoding the image. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + int colorCount = (int)Math.Pow(2, bitsPerSample[0]); + TPixel[] palette = GeneratePalette(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(uint[] colorMap, int colorCount) + where TPixel : struct, IPixel + { + 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; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs new file mode 100644 index 0000000000..7582220f7b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs @@ -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 +{ + /// + /// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images). + /// + internal static class Rgb888TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs new file mode 100644 index 0000000000..df7671d760 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs @@ -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 +{ + /// + /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). + /// + internal static class RgbPlanarTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffers to read image data from. + /// The number of bits per sample for each pixel. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[][] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs new file mode 100644 index 0000000000..ec33417998 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs @@ -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 +{ + /// + /// Implements the 'RGB' photometric interpretation (for all bit depths). + /// + internal static class RgbTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The number of bits per sample for each pixel. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs new file mode 100644 index 0000000000..7aea15885a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Provides enumeration of the various TIFF photometric interpretation implementation types. + /// + internal enum TiffColorType + { + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for bilevel images. + /// + BlackIsZero1, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 4-bit images. + /// + BlackIsZero4, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 8-bit images. + /// + BlackIsZero8, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + WhiteIsZero, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images. + /// + WhiteIsZero1, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 4-bit images. + /// + WhiteIsZero4, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images. + /// + WhiteIsZero8, + + /// + /// Palette-color. + /// + PaletteColor, + + /// + /// RGB Full Color. + /// + Rgb, + + /// + /// RGB Full Color. Optimised implementation for 8-bit images. + /// + Rgb888, + + /// + /// RGB Full Color. Planar configuration of data. + /// + RgbPlanar, + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs new file mode 100644 index 0000000000..2d9914de71 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs @@ -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 +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images). + /// + internal static class WhiteIsZero1TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs new file mode 100644 index 0000000000..965abec819 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs @@ -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 +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images). + /// + internal static class WhiteIsZero4TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs new file mode 100644 index 0000000000..fb209cecbc --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs @@ -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 +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images). + /// + internal static class WhiteIsZero8TiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs new file mode 100644 index 0000000000..8bb720bb9c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs @@ -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 +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths). + /// + internal static class WhiteIsZeroTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The number of bits per sample for each pixel. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + 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(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md new file mode 100644 index 0000000000..c2527b0080 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -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 | | | | diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs new file mode 100644 index 0000000000..0da193239d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the TIFF format. + /// + public sealed class TiffConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); + configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs new file mode 100644 index 0000000000..1d4521b0b6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -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 +{ + /// + /// Image decoder for generating an image out of a TIFF stream. + /// + public class TiffDecoder : IImageDecoder, ITiffDecoderOptions + { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(stream, "stream"); + + using (TiffDecoderCore decoder = new TiffDecoderCore(configuration, this)) + { + return decoder.Decode(stream); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs new file mode 100644 index 0000000000..608c2e5583 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -0,0 +1,1281 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using System.Text; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Performs the tiff decoding operation. + /// + internal class TiffDecoderCore : IDisposable + { + /// + /// The global configuration + /// + private readonly Configuration configuration; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private bool ignoreMetadata; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The decoder options. + public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) + { + options = options ?? new TiffDecoder(); + + this.configuration = configuration ?? Configuration.Default; + this.ignoreMetadata = options.IgnoreMetadata; + } + + /// + /// Initializes a new instance of the class. + /// + /// The input stream. + /// A flag indicating if the file is encoded in little-endian or big-endian format. + /// The decoder options. + /// The configuration. + public TiffDecoderCore(Stream stream, bool isLittleEndian, Configuration configuration, ITiffDecoderOptions options) + : this(configuration, options) + { + this.InputStream = stream; + this.IsLittleEndian = isLittleEndian; + } + + /// + /// Gets or sets the number of bits for each sample of the pixel format used to encode the image. + /// + public uint[] BitsPerSample { get; set; } + + /// + /// Gets or sets the lookup table for RGB palette colored images. + /// + public uint[] ColorMap { get; set; } + + /// + /// Gets or sets the photometric interpretation implementation to use when decoding the image. + /// + public TiffColorType ColorType { get; set; } + + /// + /// Gets or sets the compression implementation to use when decoding the image. + /// + public TiffCompressionType CompressionType { get; set; } + + /// + /// Gets the input stream. + /// + public Stream InputStream { get; private set; } + + /// + /// Gets a value indicating whether the file is encoded in little-endian or big-endian format. + /// + public bool IsLittleEndian { get; private set; } + + /// + /// Gets or sets the planar configuration type to use when decoding the image. + /// + public TiffPlanarConfiguration PlanarConfiguration { get; set; } + + /// + /// Calculates the size (in bytes) of the data contained within an IFD entry. + /// + /// The IFD entry to calculate the size for. + /// The size of the data (in bytes). + public static uint GetSizeOfData(TiffIfdEntry entry) => SizeOfDataType(entry.Type) * entry.Count; + + /// + /// Decodes the image from the specified and sets + /// the data to image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + this.InputStream = stream; + + uint firstIfdOffset = this.ReadHeader(); + TiffIfd firstIfd = this.ReadIfd(firstIfdOffset); + Image image = this.DecodeImage(firstIfd); + + return image; + } + + /// + /// Dispose + /// + public void Dispose() + { + } + + /// + /// Reads the TIFF header from the input stream. + /// + /// The byte offset to the first IFD in the file. + /// + /// Thrown if the TIFF file header is invalid. + /// + public uint ReadHeader() + { + byte[] headerBytes = new byte[TiffConstants.SizeOfTiffHeader]; + this.InputStream.ReadFull(headerBytes, TiffConstants.SizeOfTiffHeader); + + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) + { + this.IsLittleEndian = true; + } + else if (headerBytes[0] != TiffConstants.ByteOrderBigEndian && headerBytes[1] != TiffConstants.ByteOrderBigEndian) + { + throw new ImageFormatException("Invalid TIFF file header."); + } + + if (this.ToUInt16(headerBytes, 2) != TiffConstants.HeaderMagicNumber) + { + throw new ImageFormatException("Invalid TIFF file header."); + } + + uint firstIfdOffset = this.ToUInt32(headerBytes, 4); + if (firstIfdOffset == 0) + { + throw new ImageFormatException("Invalid TIFF file header."); + } + + return firstIfdOffset; + } + + /// + /// Reads a from the input stream. + /// + /// The byte offset within the file to find the IFD. + /// A containing the retrieved data. + public TiffIfd ReadIfd(uint offset) + { + this.InputStream.Seek(offset, SeekOrigin.Begin); + + byte[] buffer = new byte[TiffConstants.SizeOfIfdEntry]; + + this.InputStream.ReadFull(buffer, 2); + ushort entryCount = this.ToUInt16(buffer, 0); + + TiffIfdEntry[] entries = new TiffIfdEntry[entryCount]; + for (int i = 0; i < entryCount; i++) + { + this.InputStream.ReadFull(buffer, TiffConstants.SizeOfIfdEntry); + + ushort tag = this.ToUInt16(buffer, 0); + TiffType type = (TiffType)this.ToUInt16(buffer, 2); + uint count = this.ToUInt32(buffer, 4); + byte[] value = new byte[] { buffer[8], buffer[9], buffer[10], buffer[11] }; + + entries[i] = new TiffIfdEntry(tag, type, count, value); + } + + this.InputStream.ReadFull(buffer, 4); + uint nextIfdOffset = this.ToUInt32(buffer, 0); + + return new TiffIfd(entries, nextIfdOffset); + } + + /// + /// Decodes the image data from a specified IFD. + /// + /// The pixel format. + /// The IFD to read the image from. + /// The decoded image. + public Image DecodeImage(TiffIfd ifd) + where TPixel : struct, IPixel + { + if (!ifd.TryGetIfdEntry(TiffTags.ImageLength, out TiffIfdEntry imageLengthEntry) + || !ifd.TryGetIfdEntry(TiffTags.ImageWidth, out TiffIfdEntry imageWidthEntry)) + { + throw new ImageFormatException("The TIFF IFD does not specify the image dimensions."); + } + + int width = (int)this.ReadUnsignedInteger(ref imageWidthEntry); + int height = (int)this.ReadUnsignedInteger(ref imageLengthEntry); + + Image image = new Image(this.configuration, width, height); + + this.ReadMetadata(ifd, image); + this.ReadImageFormat(ifd); + + if (ifd.TryGetIfdEntry(TiffTags.RowsPerStrip, out TiffIfdEntry rowsPerStripEntry) + && ifd.TryGetIfdEntry(TiffTags.StripOffsets, out TiffIfdEntry stripOffsetsEntry) + && ifd.TryGetIfdEntry(TiffTags.StripByteCounts, out TiffIfdEntry stripByteCountsEntry)) + { + int rowsPerStrip = (int)this.ReadUnsignedInteger(ref rowsPerStripEntry); + uint[] stripOffsets = this.ReadUnsignedIntegerArray(ref stripOffsetsEntry); + uint[] stripByteCounts = this.ReadUnsignedIntegerArray(ref stripByteCountsEntry); + this.DecodeImageStrips(image, rowsPerStrip, stripOffsets, stripByteCounts); + } + + return image; + } + + /// + /// Reads the image metadata from a specified IFD. + /// + /// The pixel format. + /// The IFD to read the image from. + /// The image to write the metadata to. + public void ReadMetadata(TiffIfd ifd, Image image) + where TPixel : struct, IPixel + { + TiffResolutionUnit resolutionUnit = (TiffResolutionUnit)this.ReadUnsignedInteger(ifd, TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch); + + if (resolutionUnit != TiffResolutionUnit.None) + { + double resolutionUnitFactor = resolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0; + + if (ifd.TryGetIfdEntry(TiffTags.XResolution, out TiffIfdEntry xResolutionEntry)) + { + Rational xResolution = this.ReadUnsignedRational(ref xResolutionEntry); + image.MetaData.HorizontalResolution = xResolution.ToDouble() * resolutionUnitFactor; + } + + if (ifd.TryGetIfdEntry(TiffTags.YResolution, out TiffIfdEntry yResolutionEntry)) + { + Rational yResolution = this.ReadUnsignedRational(ref yResolutionEntry); + image.MetaData.VerticalResolution = yResolution.ToDouble() * resolutionUnitFactor; + } + } + + if (!this.ignoreMetadata) + { + if (ifd.TryGetIfdEntry(TiffTags.Artist, out TiffIfdEntry artistEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Artist, this.ReadString(ref artistEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.Copyright, out TiffIfdEntry copyrightEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Copyright, this.ReadString(ref copyrightEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.DateTime, out TiffIfdEntry dateTimeEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.DateTime, this.ReadString(ref dateTimeEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.HostComputer, out TiffIfdEntry hostComputerEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.HostComputer, this.ReadString(ref hostComputerEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.ImageDescription, out TiffIfdEntry imageDescriptionEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.ImageDescription, this.ReadString(ref imageDescriptionEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.Make, out TiffIfdEntry makeEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Make, this.ReadString(ref makeEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.Model, out TiffIfdEntry modelEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Model, this.ReadString(ref modelEntry))); + } + + if (ifd.TryGetIfdEntry(TiffTags.Software, out TiffIfdEntry softwareEntry)) + { + image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Software, this.ReadString(ref softwareEntry))); + } + } + } + + /// + /// Determines the TIFF compression and color types, and reads any associated parameters. + /// + /// The IFD to read the image format information for. + public void ReadImageFormat(TiffIfd ifd) + { + TiffCompression compression = (TiffCompression)this.ReadUnsignedInteger(ifd, TiffTags.Compression, (uint)TiffCompression.None); + + switch (compression) + { + case TiffCompression.None: + { + this.CompressionType = TiffCompressionType.None; + break; + } + + case TiffCompression.PackBits: + { + this.CompressionType = TiffCompressionType.PackBits; + break; + } + + case TiffCompression.Deflate: + case TiffCompression.OldDeflate: + { + this.CompressionType = TiffCompressionType.Deflate; + break; + } + + case TiffCompression.Lzw: + { + this.CompressionType = TiffCompressionType.Lzw; + break; + } + + default: + { + throw new NotSupportedException("The specified TIFF compression format is not supported."); + } + } + + this.PlanarConfiguration = (TiffPlanarConfiguration)this.ReadUnsignedInteger(ifd, TiffTags.PlanarConfiguration, (uint)TiffPlanarConfiguration.Chunky); + + TiffPhotometricInterpretation photometricInterpretation; + + if (ifd.TryGetIfdEntry(TiffTags.PhotometricInterpretation, out TiffIfdEntry photometricInterpretationEntry)) + { + photometricInterpretation = (TiffPhotometricInterpretation)this.ReadUnsignedInteger(ref photometricInterpretationEntry); + } + else + { + if (compression == TiffCompression.Ccitt1D) + { + photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; + } + else + { + throw new ImageFormatException("The TIFF photometric interpretation entry is missing."); + } + } + + if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry)) + { + this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry); + } + else + { + if (photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero || + photometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + this.BitsPerSample = new[] { 1u }; + } + else + { + throw new ImageFormatException("The TIFF BitsPerSample entry is missing."); + } + } + + switch (photometricInterpretation) + { + case TiffPhotometricInterpretation.WhiteIsZero: + { + if (this.BitsPerSample.Length == 1) + { + switch (this.BitsPerSample[0]) + { + case 8: + { + this.ColorType = TiffColorType.WhiteIsZero8; + break; + } + + case 4: + { + this.ColorType = TiffColorType.WhiteIsZero4; + break; + } + + case 1: + { + this.ColorType = TiffColorType.WhiteIsZero1; + break; + } + + default: + { + this.ColorType = TiffColorType.WhiteIsZero; + break; + } + } + } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + break; + } + + case TiffPhotometricInterpretation.BlackIsZero: + { + if (this.BitsPerSample.Length == 1) + { + switch (this.BitsPerSample[0]) + { + case 8: + { + this.ColorType = TiffColorType.BlackIsZero8; + break; + } + + case 4: + { + this.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case 1: + { + this.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + this.ColorType = TiffColorType.BlackIsZero; + break; + } + } + } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + break; + } + + case TiffPhotometricInterpretation.Rgb: + { + if (this.BitsPerSample.Length == 3) + { + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + if (this.BitsPerSample[0] == 8 && this.BitsPerSample[1] == 8 && this.BitsPerSample[2] == 8) + { + this.ColorType = TiffColorType.Rgb888; + } + else + { + this.ColorType = TiffColorType.Rgb; + } + } + else + { + this.ColorType = TiffColorType.RgbPlanar; + } + } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + break; + } + + case TiffPhotometricInterpretation.PaletteColor: + { + if (ifd.TryGetIfdEntry(TiffTags.ColorMap, out TiffIfdEntry colorMapEntry)) + { + this.ColorMap = this.ReadUnsignedIntegerArray(ref colorMapEntry); + + if (this.BitsPerSample.Length == 1) + { + switch (this.BitsPerSample[0]) + { + default: + { + this.ColorType = TiffColorType.PaletteColor; + break; + } + } + } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + } + else + { + throw new ImageFormatException("The TIFF ColorMap entry is missing for a pallete color image."); + } + + break; + } + + default: + throw new NotSupportedException("The specified TIFF photometric interpretation is not supported."); + } + } + + /// + /// Calculates the size (in bytes) for a pixel buffer using the determined color format. + /// + /// The width for the desired pixel buffer. + /// The height for the desired pixel buffer. + /// The index of the plane for planar image configuration (or zero for chunky). + /// The size (in bytes) of the required pixel buffer. + public int CalculateImageBufferSize(int width, int height, int plane) + { + uint bitsPerPixel = 0; + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + for (int i = 0; i < this.BitsPerSample.Length; i++) + { + bitsPerPixel += this.BitsPerSample[i]; + } + } + else + { + bitsPerPixel = this.BitsPerSample[plane]; + } + + int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8; + return bytesPerRow * height; + } + + /// + /// Decompresses an image block from the input stream into the specified buffer. + /// + /// The offset within the file of the image block. + /// The size (in bytes) of the compressed data. + /// The buffer to write the uncompressed data. + public void DecompressImageBlock(uint offset, uint byteCount, byte[] buffer) + { + this.InputStream.Seek(offset, SeekOrigin.Begin); + + switch (this.CompressionType) + { + case TiffCompressionType.None: + NoneTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer); + break; + case TiffCompressionType.PackBits: + PackBitsTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer); + break; + case TiffCompressionType.Deflate: + DeflateTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer); + break; + case TiffCompressionType.Lzw: + LzwTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer); + break; + default: + throw new InvalidOperationException(); + } + } + + /// + /// Decodes pixel data using the current photometric interpretation (chunky configuration). + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public void ProcessImageBlockChunky(byte[] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + switch (this.ColorType) + { + case TiffColorType.WhiteIsZero: + WhiteIsZeroTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height); + break; + case TiffColorType.WhiteIsZero1: + WhiteIsZero1TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.WhiteIsZero4: + WhiteIsZero4TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.WhiteIsZero8: + WhiteIsZero8TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.BlackIsZero: + BlackIsZeroTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height); + break; + case TiffColorType.BlackIsZero1: + BlackIsZero1TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.BlackIsZero4: + BlackIsZero4TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.BlackIsZero8: + BlackIsZero8TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.Rgb: + RgbTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height); + break; + case TiffColorType.Rgb888: + Rgb888TiffColor.Decode(data, pixels, left, top, width, height); + break; + case TiffColorType.PaletteColor: + PaletteTiffColor.Decode(data, this.BitsPerSample, this.ColorMap, pixels, left, top, width, height); + break; + default: + throw new InvalidOperationException(); + } + } + + /// + /// Decodes pixel data using the current photometric interpretation (planar configuration). + /// + /// The pixel format. + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public void ProcessImageBlockPlanar(byte[][] data, Buffer2D pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + switch (this.ColorType) + { + case TiffColorType.RgbPlanar: + RgbPlanarTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height); + break; + default: + throw new InvalidOperationException(); + } + } + + /// + /// Reads the data from a as an array of bytes. + /// + /// The to read. + /// The data. + public byte[] ReadBytes(ref TiffIfdEntry entry) + { + uint byteLength = GetSizeOfData(entry); + + if (entry.Value.Length < byteLength) + { + uint offset = this.ToUInt32(entry.Value, 0); + this.InputStream.Seek(offset, SeekOrigin.Begin); + + byte[] data = new byte[byteLength]; + this.InputStream.ReadFull(data, (int)byteLength); + entry.Value = data; + } + + return entry.Value; + } + + /// + /// Reads the data from a as an unsigned integer value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public uint ReadUnsignedInteger(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + switch (entry.Type) + { + case TiffType.Byte: + return (uint)this.ToByte(entry.Value, 0); + case TiffType.Short: + return (uint)this.ToUInt16(entry.Value, 0); + case TiffType.Long: + return this.ToUInt32(entry.Value, 0); + default: + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to an unsigned integer."); + } + } + + /// + /// Reads the data for a specified tag of a as an unsigned integer value. + /// + /// The to read from. + /// The tag ID to search for. + /// The default value if the entry is missing + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public uint ReadUnsignedInteger(TiffIfd ifd, ushort tag, uint defaultValue) + { + if (ifd.TryGetIfdEntry(tag, out TiffIfdEntry entry)) + { + return this.ReadUnsignedInteger(ref entry); + } + else + { + return defaultValue; + } + } + + /// + /// Reads the data from a as a signed integer value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to an , or if + /// there is an array of items. + /// + public int ReadSignedInteger(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + switch (entry.Type) + { + case TiffType.SByte: + return (int)this.ToSByte(entry.Value, 0); + case TiffType.SShort: + return (int)this.ToInt16(entry.Value, 0); + case TiffType.SLong: + return this.ToInt32(entry.Value, 0); + default: + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a signed integer."); + } + } + + /// + /// Reads the data from a as an array of unsigned integer values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public uint[] ReadUnsignedIntegerArray(ref TiffIfdEntry entry) + { + byte[] bytes = this.ReadBytes(ref entry); + uint[] result = new uint[entry.Count]; + + switch (entry.Type) + { + case TiffType.Byte: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = (uint)this.ToByte(bytes, i); + } + + break; + } + + case TiffType.Short: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = (uint)this.ToUInt16(bytes, i * TiffConstants.SizeOfShort); + } + + break; + } + + case TiffType.Long: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = this.ToUInt32(bytes, i * TiffConstants.SizeOfLong); + } + + break; + } + + default: + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to an unsigned integer."); + } + + return result; + } + + /// + /// Reads the data from a as an array of signed integer values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to an . + /// + public int[] ReadSignedIntegerArray(ref TiffIfdEntry entry) + { + byte[] bytes = this.ReadBytes(ref entry); + int[] result = new int[entry.Count]; + + switch (entry.Type) + { + case TiffType.SByte: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = (int)this.ToSByte(bytes, i); + } + + break; + } + + case TiffType.SShort: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = (int)this.ToInt16(bytes, i * TiffConstants.SizeOfShort); + } + + break; + } + + case TiffType.SLong: + { + for (int i = 0; i < result.Length; i++) + { + result[i] = this.ToInt32(bytes, i * TiffConstants.SizeOfLong); + } + + break; + } + + default: + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a signed integer."); + } + + return result; + } + + /// + /// Reads the data from a as a value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public string ReadString(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.Ascii) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a string."); + } + + byte[] bytes = this.ReadBytes(ref entry); + + if (bytes[entry.Count - 1] != 0) + { + throw new ImageFormatException("The retrieved string is not null terminated."); + } + + return Encoding.UTF8.GetString(bytes, 0, (int)entry.Count - 1); + } + + /// + /// Reads the data from a as a value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public Rational ReadUnsignedRational(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + return this.ReadUnsignedRationalArray(ref entry)[0]; + } + + /// + /// Reads the data from a as a value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public SignedRational ReadSignedRational(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + return this.ReadSignedRationalArray(ref entry)[0]; + } + + /// + /// Reads the data from a as an array of values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public Rational[] ReadUnsignedRationalArray(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.Rational) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a Rational."); + } + + byte[] bytes = this.ReadBytes(ref entry); + Rational[] result = new Rational[entry.Count]; + + for (int i = 0; i < result.Length; i++) + { + uint numerator = this.ToUInt32(bytes, i * TiffConstants.SizeOfRational); + uint denominator = this.ToUInt32(bytes, (i * TiffConstants.SizeOfRational) + TiffConstants.SizeOfLong); + result[i] = new Rational(numerator, denominator); + } + + return result; + } + + /// + /// Reads the data from a as an array of values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public SignedRational[] ReadSignedRationalArray(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.SRational) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a SignedRational."); + } + + byte[] bytes = this.ReadBytes(ref entry); + SignedRational[] result = new SignedRational[entry.Count]; + + for (int i = 0; i < result.Length; i++) + { + int numerator = this.ToInt32(bytes, i * TiffConstants.SizeOfRational); + int denominator = this.ToInt32(bytes, (i * TiffConstants.SizeOfRational) + TiffConstants.SizeOfLong); + result[i] = new SignedRational(numerator, denominator); + } + + return result; + } + + /// + /// Reads the data from a as a value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public float ReadFloat(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + if (entry.Type != TiffType.Float) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a float."); + } + + return this.ToSingle(entry.Value, 0); + } + + /// + /// Reads the data from a as a value. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a , or if + /// there is an array of items. + /// + public double ReadDouble(ref TiffIfdEntry entry) + { + if (entry.Count != 1) + { + throw new ImageFormatException($"Cannot read a single value from an array of multiple items."); + } + + return this.ReadDoubleArray(ref entry)[0]; + } + + /// + /// Reads the data from a as an array of values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public float[] ReadFloatArray(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.Float) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a float."); + } + + byte[] bytes = this.ReadBytes(ref entry); + float[] result = new float[entry.Count]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = this.ToSingle(bytes, i * TiffConstants.SizeOfFloat); + } + + return result; + } + + /// + /// Reads the data from a as an array of values. + /// + /// The to read. + /// The data. + /// + /// Thrown if the data-type specified by the file cannot be converted to a . + /// + public double[] ReadDoubleArray(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.Double) + { + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a double."); + } + + byte[] bytes = this.ReadBytes(ref entry); + double[] result = new double[entry.Count]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = this.ToDouble(bytes, i * TiffConstants.SizeOfDouble); + } + + return result; + } + + /// + /// Calculates the size (in bytes) for the specified TIFF data-type. + /// + /// The data-type to calculate the size for. + /// The size of the data-type (in bytes). + private static uint SizeOfDataType(TiffType type) + { + switch (type) + { + case TiffType.Byte: + case TiffType.Ascii: + case TiffType.SByte: + case TiffType.Undefined: + return 1u; + case TiffType.Short: + case TiffType.SShort: + return 2u; + case TiffType.Long: + case TiffType.SLong: + case TiffType.Float: + case TiffType.Ifd: + return 4u; + case TiffType.Rational: + case TiffType.SRational: + case TiffType.Double: + return 8u; + default: + return 0u; + } + } + + /// + /// Converts buffer data into an using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private sbyte ToSByte(byte[] bytes, int offset) + { + return (sbyte)bytes[offset]; + } + + /// + /// Converts buffer data into an using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private short ToInt16(byte[] bytes, int offset) + { + if (this.IsLittleEndian) + { + return (short)(bytes[offset + 0] | (bytes[offset + 1] << 8)); + } + else + { + return (short)((bytes[offset + 0] << 8) | bytes[offset + 1]); + } + } + + /// + /// Converts buffer data into an using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private int ToInt32(byte[] bytes, int offset) + { + if (this.IsLittleEndian) + { + return bytes[offset + 0] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24); + } + else + { + return (bytes[offset + 0] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; + } + } + + /// + /// Converts buffer data into a using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private byte ToByte(byte[] bytes, int offset) + { + return bytes[offset]; + } + + /// + /// Converts buffer data into a using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private uint ToUInt32(byte[] bytes, int offset) + { + return (uint)this.ToInt32(bytes, offset); + } + + /// + /// Converts buffer data into a using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private ushort ToUInt16(byte[] bytes, int offset) + { + return (ushort)this.ToInt16(bytes, offset); + } + + /// + /// Converts buffer data into a using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private float ToSingle(byte[] bytes, int offset) + { + byte[] buffer = new byte[4]; + Array.Copy(bytes, offset, buffer, 0, 4); + + if (this.IsLittleEndian != BitConverter.IsLittleEndian) + { + Array.Reverse(buffer); + } + + return BitConverter.ToSingle(buffer, 0); + } + + /// + /// Converts buffer data into a using the correct endianness. + /// + /// The buffer. + /// The byte offset within the buffer. + /// The converted value. + private double ToDouble(byte[] bytes, int offset) + { + byte[] buffer = new byte[8]; + Array.Copy(bytes, offset, buffer, 0, 8); + + if (this.IsLittleEndian != BitConverter.IsLittleEndian) + { + Array.Reverse(buffer); + } + + return BitConverter.ToDouble(buffer, 0); + } + + /// + /// Decodes the image data for strip encoded data. + /// + /// The pixel format. + /// The image to decode data into. + /// The number of rows per strip of data. + /// An array of byte offsets to each strip in the image. + /// An array of the size of each strip (in bytes). + private void DecodeImageStrips(Image image, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) + where TPixel : struct, IPixel + { + int stripsPerPixel = this.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? 1 : this.BitsPerSample.Length; + int stripsPerPlane = stripOffsets.Length / stripsPerPixel; + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + byte[][] stripBytes = new byte[stripsPerPixel][]; + + for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) + { + int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip, stripIndex); + stripBytes[stripIndex] = ArrayPool.Shared.Rent(uncompressedStripSize); + } + + try + { + for (int i = 0; i < stripsPerPlane; i++) + { + int stripHeight = i < stripsPerPlane - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip; + + for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) + { + int stripIndex = (i * stripsPerPixel) + planeIndex; + this.DecompressImageBlock(stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]); + } + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + this.ProcessImageBlockChunky(stripBytes[0], pixels, 0, rowsPerStrip * i, image.Width, stripHeight); + } + else + { + this.ProcessImageBlockPlanar(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight); + } + } + } + finally + { + for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) + { + ArrayPool.Shared.Return(stripBytes[stripIndex]); + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs new file mode 100644 index 0000000000..63886a0751 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -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 +{ + /// + /// Encoder for writing the data image to a stream in TIFF format. + /// + public class TiffEncoder : IImageEncoder, ITiffEncoderOptions + { + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + var encode = new TiffEncoderCore(this); + encode.Encode(image, stream); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs new file mode 100644 index 0000000000..9ab72f316a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -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 +{ + /// + /// Performs the TIFF encoding operation. + /// + internal sealed class TiffEncoderCore + { + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public TiffEncoderCore(ITiffEncoderOptions options) + { + options = options ?? new TiffEncoder(); + } + + /// + /// Gets or sets the photometric interpretation implementation to use when encoding the image. + /// + public TiffColorType ColorType { get; set; } + + /// + /// Gets or sets the compression implementation to use when encoding the image. + /// + public TiffCompressionType CompressionType { get; set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + 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); + } + } + + /// + /// Writes the TIFF file header. + /// + /// The to write data to. + /// The marker to write the first IFD offset. + 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; + } + + /// + /// Writes a TIFF IFD block. + /// + /// The to write data to. + /// The IFD entries to write to the file. + /// The marker to write the next IFD offset (if present). + public long WriteIfd(TiffWriter writer, List 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 largeDataBlocks = new List(); + + 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; + } + + /// + /// Writes all data required to define an image + /// + /// The pixel format. + /// The to write data to. + /// The to encode from. + /// The marker to write this IFD offset. + /// The marker to write the next IFD offset (if present). + public long WriteImage(TiffWriter writer, Image image, long ifdOffset) + where TPixel : struct, IPixel + { + List ifdEntries = new List(); + + this.AddImageFormat(image, ifdEntries); + this.AddMetadata(image, ifdEntries); + + writer.WriteMarker(ifdOffset, (uint)writer.Position); + long nextIfdMarker = this.WriteIfd(writer, ifdEntries); + + return nextIfdMarker; + } + + /// + /// Adds image metadata to the specified IFD. + /// + /// The pixel format. + /// The to encode from. + /// The metadata entries to add to the IFD. + public void AddMetadata(Image image, List ifdEntries) + where TPixel : struct, IPixel + { + 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; + } + } + } + } + + /// + /// Adds image format information to the specified IFD. + /// + /// The pixel format. + /// The to encode from. + /// The image format entries to add to the IFD. + public void AddImageFormat(Image image, List ifdEntries) + where TPixel : struct, IPixel + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs new file mode 100644 index 0000000000..3f2807a06f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffFormat.cs @@ -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 +{ + /// + /// Encapsulates the means to encode and decode Tiff images. + /// + public class TiffFormat : IImageFormat + { + private TiffFormat() + { + } + + /// + /// Gets the current instance. + /// + public static TiffFormat Instance { get; } = new TiffFormat(); + + /// + public string Name => "TIFF"; + + /// + public string DefaultMimeType => "image/tiff"; + + /// + public IEnumerable MimeTypes => TiffConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => TiffConstants.FileExtensions; + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs new file mode 100644 index 0000000000..a6534c1558 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Data structure for holding details of each TIFF IFD. + /// + internal struct TiffIfd + { + /// + /// An array of the entries within this IFD. + /// + public TiffIfdEntry[] Entries; + + /// + /// Offset (in bytes) to the next IFD, or zero if this is the last IFD. + /// + public uint NextIfdOffset; + + /// + /// Initializes a new instance of the struct. + /// + /// An array of the entries within the IFD. + /// Offset (in bytes) to the next IFD, or zero if this is the last IFD. + public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset) + { + this.Entries = entries; + this.NextIfdOffset = nextIfdOffset; + } + + /// + /// Gets the child with the specified tag ID. + /// + /// The tag ID to search for. + /// The resulting , or null if it does not exists. + 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; + } + + /// + /// Gets the child with the specified tag ID. + /// + /// The tag ID to search for. + /// The resulting , if it exists. + /// A flag indicating whether the requested entry exists. + public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry) + { + TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag); + entry = nullableEntry ?? default(TiffIfdEntry); + return nullableEntry.HasValue; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs new file mode 100644 index 0000000000..de5974035c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Data structure for holding details of each TIFF IFD entry. + /// + internal struct TiffIfdEntry + { + /// + /// The Tag ID for this entry. See for typical values. + /// + public ushort Tag; + + /// + /// The data-type of this entry. + /// + public TiffType Type; + + /// + /// The number of array items in this entry, or one if only a single value. + /// + public uint Count; + + /// + /// The raw byte data for this entry. + /// + public byte[] Value; + + /// + /// Initializes a new instance of the struct. + /// + /// The Tag ID for this entry. + /// The data-type of this entry. + /// The number of array items in this entry. + /// The raw byte data for this entry. + public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value) + { + this.Tag = tag; + this.Type = type; + this.Count = count; + this.Value = value; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs new file mode 100644 index 0000000000..35517d1901 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs @@ -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 +{ + /// + /// Utility class for generating TIFF IFD entries. + /// + internal static class TiffIfdEntryCreator + { + /// + /// Adds a new of type 'Byte' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedByte(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedByte(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Byte' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedByte(this List 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)); + } + + /// + /// Adds a new of type 'Short' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedShort(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedShort(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Short' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedShort(this List 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)); + } + + /// + /// Adds a new of type 'Long' from a unsigned integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedLong(this List entries, ushort tag, uint value) + { + TiffIfdEntryCreator.AddUnsignedLong(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Long' from an array of unsigned integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedLong(this List 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)); + } + + /// + /// Adds a new of type 'SByte' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedByte(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedByte(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SByte' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedByte(this List 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)); + } + + /// + /// Adds a new of type 'SShort' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedShort(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedShort(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SShort' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedShort(this List 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)); + } + + /// + /// Adds a new of type 'SLong' from a signed integer. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedLong(this List entries, ushort tag, int value) + { + TiffIfdEntryCreator.AddSignedLong(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SLong' from an array of signed integers. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedLong(this List 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)); + } + + /// + /// Adds a new of type 'Ascii' from a string. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddAscii(this List entries, ushort tag, string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value + "\0"); + + entries.Add(new TiffIfdEntry(tag, TiffType.Ascii, (uint)bytes.Length, bytes)); + } + + /// + /// Adds a new of type 'Rational' from a . + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedRational(this List entries, ushort tag, Rational value) + { + TiffIfdEntryCreator.AddUnsignedRational(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Rational' from an array of values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddUnsignedRational(this List 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)); + } + + /// + /// Adds a new of type 'SRational' from a . + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedRational(this List entries, ushort tag, SignedRational value) + { + TiffIfdEntryCreator.AddSignedRational(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'SRational' from an array of values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddSignedRational(this List 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)); + } + + /// + /// Adds a new of type 'Float' from a floating-point value. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddFloat(this List entries, ushort tag, float value) + { + TiffIfdEntryCreator.AddFloat(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Float' from an array of floating-point values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddFloat(this List 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)); + } + + /// + /// Adds a new of type 'Double' from a floating-point value. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddDouble(this List entries, ushort tag, double value) + { + TiffIfdEntryCreator.AddDouble(entries, tag, new[] { value }); + } + + /// + /// Adds a new of type 'Double' from an array of floating-point values. + /// + /// The list of to add the new entry to. + /// The tag for the resulting entry. + /// The value for the resulting entry. + public static void AddDouble(this List 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs new file mode 100644 index 0000000000..12ffd5ed5a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Detects tiff file headers + /// + public sealed class TiffImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 4; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return TiffFormat.Instance; + } + + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan 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 + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs b/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs new file mode 100644 index 0000000000..10f558b29e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Defines constants for each of the supported TIFF metadata types. + /// + public static class TiffMetadataNames + { + /// + /// Person who created the image. + /// + public const string Artist = "Artist"; + + /// + /// Copyright notice. + /// + public const string Copyright = "Copyright"; + + /// + /// Date and time of image creation. + /// + public const string DateTime = "DateTime"; + + /// + /// The computer and/or operating system in use at the time of image creation. + /// + public const string HostComputer = "HostComputer"; + + /// + /// A string that describes the subject of the image. + /// + public const string ImageDescription = "ImageDescription"; + + /// + /// The scanner/camera manufacturer. + /// + public const string Make = "Make"; + + /// + /// The scanner/camera model name or number. + /// + public const string Model = "Model"; + + /// + /// Name and version number of the software package(s) used to create the image. + /// + public const string Software = "Software"; + } +} diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs new file mode 100644 index 0000000000..cbd7256ed6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Utility class to read a sequence of bits from an array + /// + internal class BitReader + { + private readonly byte[] array; + private int offset; + private int bitOffset; + + /// + /// Initializes a new instance of the class. + /// + /// The array to read data from. + public BitReader(byte[] array) + { + this.array = array; + } + + /// + /// Reads the specified number of bits from the array. + /// + /// The number of bits to read. + /// The value read from the array. + 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; + } + + /// + /// Moves the reader to the next row of byte-aligned data. + /// + public void NextRow() + { + if (this.bitOffset > 0) + { + this.bitOffset = 0; + this.offset++; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs new file mode 100644 index 0000000000..aaf9af23a7 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs @@ -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 +{ + /// + /// Utility class to encapsulate a sub-portion of another . + /// + /// + /// Note that disposing of the does not dispose the underlying + /// . + /// + internal class SubStream : Stream + { + private Stream innerStream; + private long offset; + private long endOffset; + private long length; + + /// + /// Initializes a new instance of the class. + /// + /// The underlying to wrap. + /// The length of the sub-stream. + /// + /// Note that calling the sub-stream with start from the current offset of the + /// underlying + /// + public SubStream(Stream innerStream, long length) + { + this.innerStream = innerStream; + this.offset = this.innerStream.Position; + this.endOffset = this.offset + length; + this.length = length; + } + + /// + /// Initializes a new instance of the class. + /// + /// The underlying to wrap. + /// The offset of the sub-stream within the underlying . + /// The length of the sub-stream. + /// + /// Note that calling the constructor will immediately move the underlying + /// to the specified offset. + /// + 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); + } + + /// + public override bool CanRead + { + get + { + return true; + } + } + + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + public override bool CanSeek + { + get + { + return this.innerStream.CanSeek; + } + } + + /// + public override long Length + { + get + { + return this.length; + } + } + + /// + public override long Position + { + get + { + return this.innerStream.Position - this.offset; + } + + set + { + this.Seek(value, SeekOrigin.Begin); + } + } + + /// + public override void Flush() + { + throw new NotSupportedException(); + } + + /// + 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); + } + + /// + public override int ReadByte() + { + if (this.innerStream.Position < this.endOffset) + { + return this.innerStream.ReadByte(); + } + else + { + return -1; + } + } + + /// + public override void Write(byte[] array, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + public override void WriteByte(byte value) + { + throw new NotSupportedException(); + } + + /// + 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."); + } + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs new file mode 100644 index 0000000000..6ac09f3916 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs @@ -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 +{ + /// + /// Decompresses and decodes data using the dynamic LZW algorithms. + /// + /// + /// This code is based on the 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). + /// + internal sealed class TiffLzwDecoder : IDisposable + { + /// + /// The max decoder pixel stack size. + /// + private const int MaxStackSize = 4096; + + /// + /// The null code. + /// + private const int NullCode = -1; + + /// + /// The stream to decode. + /// + private readonly Stream stream; + + /// + /// The prefix buffer. + /// + private readonly int[] prefix; + + /// + /// The suffix buffer. + /// + private readonly int[] suffix; + + /// + /// The pixel stack buffer. + /// + private readonly int[] pixelStack; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// 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. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream to read from. + /// is null. + public TiffLzwDecoder(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + this.stream = stream; + + this.prefix = ArrayPool.Shared.Rent(MaxStackSize); + this.suffix = ArrayPool.Shared.Rent(MaxStackSize); + this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1); + + Array.Clear(this.prefix, 0, MaxStackSize); + Array.Clear(this.suffix, 0, MaxStackSize); + Array.Clear(this.pixelStack, 0, MaxStackSize + 1); + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// The length of the compressed data. + /// Size of the data. + /// The pixel array to decode to. + 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]; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + this.Dispose(true); + } + + /// + /// 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 + /// + /// The buffer to store the block in. + /// + /// The . + /// + private int ReadBlock(byte[] buffer) + { + return this.stream.Read(buffer, 0, 255); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + ArrayPool.Shared.Return(this.prefix); + ArrayPool.Shared.Return(this.suffix); + ArrayPool.Shared.Return(this.pixelStack); + } + + this.isDisposed = true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs new file mode 100644 index 0000000000..e024b59fa4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs @@ -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 +{ + /// + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. + /// + /// + /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 + /// + /// GIFCOMPR.C - GIF Image compression routines + /// + /// + /// Lempel-Ziv compression based on 'compress'. GIF modifications by + /// David Rowley (mgardi@watdcsu.waterloo.edu) + /// + /// GIF Image compression - modified 'compress' + /// + /// 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) + /// + /// + /// This code is based on the 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). + /// + /// + internal sealed class TiffLzwEncoder : IDisposable + { + /// + /// The end-of-file marker + /// + private const int Eof = -1; + + /// + /// The maximum number of bits. + /// + private const int Bits = 12; + + /// + /// 80% occupancy + /// + private const int HashSize = 5003; + + /// + /// Mask used when shifting pixel values + /// + private static readonly int[] Masks = + { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + /// + /// The working pixel array + /// + private readonly byte[] pixelArray; + + /// + /// The initial code size. + /// + private readonly int initialCodeSize; + + /// + /// The hash table. + /// + private readonly int[] hashTable; + + /// + /// The code table. + /// + private readonly int[] codeTable; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// 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. + /// + private bool isDisposed; + + /// + /// The current pixel + /// + private int currentPixel; + + /// + /// Number of bits/code + /// + private int bitCount; + + /// + /// User settable max # bits/code + /// + private int maxbits = Bits; + + /// + /// maximum code, given bitCount + /// + private int maxcode; + + /// + /// should NEVER generate this code + /// + private int maxmaxcode = 1 << Bits; + + /// + /// For dynamic table sizing + /// + private int hsize = HashSize; + + /// + /// First unused entry + /// + private int freeEntry; + + /// + /// Block compression parameters -- after all codes are used up, + /// and compression rate changes, start over. + /// + private bool clearFlag; + + /// + /// 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. + /// + private int globalInitialBits; + + /// + /// The clear code. + /// + private int clearCode; + + /// + /// The end-of-file code. + /// + private int eofCode; + + /// + /// 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. + /// + private int currentAccumulator; + + /// + /// The current bits. + /// + private int currentBits; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Initializes a new instance of the class. + /// + /// The array of indexed pixels. + /// The color depth in bits. + public TiffLzwEncoder(byte[] indexedPixels, int colorDepth) + { + this.pixelArray = indexedPixels; + this.initialCodeSize = Math.Max(2, colorDepth); + + this.hashTable = ArrayPool.Shared.Rent(HashSize); + this.codeTable = ArrayPool.Shared.Rent(HashSize); + Array.Clear(this.hashTable, 0, HashSize); + Array.Clear(this.codeTable, 0, HashSize); + } + + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The stream to write to. + public void Encode(Stream stream) + { + this.currentPixel = 0; + + // Compress and write the pixel data + this.Compress(this.initialCodeSize + 1, stream); + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + this.Dispose(true); + } + + /// + /// Gets the maximum code value + /// + /// The number of bits + /// See + private static int GetMaxcode(int bitCount) + { + return (1 << bitCount) - 1; + } + + /// + /// Add a character to the end of the current packet, and if it is 254 characters, + /// flush the packet to disk. + /// + /// The character to add. + /// The stream to write to. + private void AddCharacter(byte c, Stream stream) + { + this.accumulators[this.accumulatorCount++] = c; + if (this.accumulatorCount >= 254) + { + this.FlushPacket(stream); + } + } + + /// + /// Table clear for block compress + /// + /// The output stream. + private void ClearBlock(Stream stream) + { + this.ResetCodeTable(this.hsize); + this.freeEntry = this.clearCode + 2; + this.clearFlag = true; + + this.Output(this.clearCode, stream); + } + + /// + /// Reset the code table. + /// + /// The hash size. + private void ResetCodeTable(int size) + { + for (int i = 0; i < size; ++i) + { + this.hashTable[i] = -1; + } + } + + /// + /// Compress the packets to the stream. + /// + /// The initial bits. + /// The stream to write to. + 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); + } + + /// + /// Flush the packet to disk, and reset the accumulator. + /// + /// The output stream. + private void FlushPacket(Stream outStream) + { + if (this.accumulatorCount > 0) + { + outStream.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + } + + /// + /// Return the next pixel from the image + /// + /// + /// The + /// + private int NextPixel() + { + if (this.currentPixel == this.pixelArray.Length) + { + return Eof; + } + + this.currentPixel++; + return this.pixelArray[this.currentPixel - 1] & 0xff; + } + + /// + /// Output the current code to the stream. + /// + /// The code. + /// The stream to write to. + 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); + } + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + ArrayPool.Shared.Return(this.hashTable); + ArrayPool.Shared.Return(this.codeTable); + } + + this.isDisposed = true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs new file mode 100644 index 0000000000..7842a71c1a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -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 +{ + /// + /// TIFF specific utilities and extension methods. + /// + internal static class TiffUtils + { + /// + /// Reads a sequence of bytes from the input stream into a buffer. + /// + /// The stream to read from. + /// A buffer to store the retrieved data. + /// The number of bytes to read. + 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; + } + } + + /// + /// Reads all bytes from the input stream into a buffer until the end of stream or the buffer is full. + /// + /// The stream to read from. + /// A buffer to store the retrieved data. + public static void ReadFull(this Stream stream, byte[] buffer) + { + ReadFull(stream, buffer, buffer.Length); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs new file mode 100644 index 0000000000..5aa59c1082 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -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 +{ + /// + /// Utility class for writing TIFF data to a . + /// + internal class TiffWriter : IDisposable + { + private readonly Stream output; + + private readonly byte[] paddingBytes = new byte[4]; + + private readonly List references = new List(); + + /// Initializes a new instance of the class. + /// The output stream. + public TiffWriter(Stream output) + { + this.output = output; + } + + /// + /// Gets a value indicating whether the architecture is little-endian. + /// + public bool IsLittleEndian => BitConverter.IsLittleEndian; + + /// + /// Gets the current position within the stream. + /// + public long Position => this.output.Position; + + /// Writes an empty four bytes to the stream, returning the offset to be written later. + /// The offset to be written later + public long PlaceMarker() + { + long offset = this.output.Position; + this.Write(0u); + return offset; + } + + /// Writes an array of bytes to the current stream. + /// The bytes to write. + public void Write(byte[] value) + { + this.output.Write(value, 0, value.Length); + } + + /// Writes a byte to the current stream. + /// The byte to write. + public void Write(byte value) + { + this.output.Write(new byte[] { value }, 0, 1); + } + + /// Writes a two-byte unsigned integer to the current stream. + /// The two-byte unsigned integer to write. + public void Write(ushort value) + { + byte[] bytes = BitConverter.GetBytes(value); + this.output.Write(bytes, 0, 2); + } + + /// Writes a four-byte unsigned integer to the current stream. + /// The four-byte unsigned integer to write. + public void Write(uint value) + { + byte[] bytes = BitConverter.GetBytes(value); + this.output.Write(bytes, 0, 4); + } + + /// Writes an array of bytes to the current stream, padded to four-bytes. + /// The bytes to write. + 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); + } + } + + /// Writes a four-byte unsigned integer to the specified marker in the stream. + /// The offset returned when placing the marker + /// The four-byte unsigned integer to write. + 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); + } + + /// + /// Disposes instance, ensuring any unwritten data is flushed. + /// + public void Dispose() + { + this.output.Flush(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 1d3cb53afc..0cf8d6bbbf 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -1,3810 +1,3810 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// -using System; -using System.Numerics; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders -{ - /// - /// Collection of Porter Duff alpha blending functions applying different composition models. - /// - /// - /// 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 - /// - internal static class DefaultPixelBlenders - where TPixel : struct, IPixel - { - - internal class NormalSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrc Instance { get; } = new NormalSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrc Instance { get; } = new MultiplySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrc Instance { get; } = new AddSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrc Instance { get; } = new SubtractSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrc Instance { get; } = new DarkenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrc Instance { get; } = new LightenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlaySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrc Instance { get; } = new OverlaySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrc Instance { get; } = new HardLightSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcAtop Instance { get; } = new AddSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlaySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOver Instance { get; } = new AddSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlaySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcIn Instance { get; } = new NormalSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcIn Instance { get; } = new AddSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcIn Instance { get; } = new LightenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlaySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlaySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDest Instance { get; } = new NormalDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDest Instance { get; } = new AddDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDest Instance { get; } = new SubtractDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDest Instance { get; } = new ScreenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDest Instance { get; } = new DarkenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDest Instance { get; } = new LightenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDest Instance { get; } = new OverlayDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDest Instance { get; } = new HardLightDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOver Instance { get; } = new NormalDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOver Instance { get; } = new AddDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOver Instance { get; } = new ScreenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOver Instance { get; } = new LightenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOver Instance { get; } = new HardLightDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestIn Instance { get; } = new AddDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestIn Instance { get; } = new SubtractDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestIn Instance { get; } = new DarkenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestIn Instance { get; } = new OverlayDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOut Instance { get; } = new NormalDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOut Instance { get; } = new AddDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOut Instance { get; } = new ScreenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOut Instance { get; } = new LightenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOut Instance { get; } = new HardLightDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalClear Instance { get; } = new NormalClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyClear Instance { get; } = new MultiplyClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddClear Instance { get; } = new AddClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractClear Instance { get; } = new SubtractClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenClear Instance { get; } = new ScreenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenClear Instance { get; } = new DarkenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenClear Instance { get; } = new LightenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayClear Instance { get; } = new OverlayClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightClear Instance { get; } = new HardLightClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class NormalXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalXor Instance { get; } = new NormalXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class MultiplyXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class AddXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddXor Instance { get; } = new AddXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class SubtractXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractXor Instance { get; } = new SubtractXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class ScreenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenXor Instance { get; } = new ScreenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class DarkenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenXor Instance { get; } = new DarkenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class LightenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenXor Instance { get; } = new LightenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class OverlayXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayXor Instance { get; } = new OverlayXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - internal class HardLightXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightXor Instance { get; } = new HardLightXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = amount.Clamp(0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount[i].Clamp(0, 1)); - } - } - } - - } +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +using System; +using System.Numerics; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// 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 + /// + internal static class DefaultPixelBlenders + where TPixel : struct, IPixel + { + + internal class NormalSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrc Instance { get; } = new NormalSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrc Instance { get; } = new MultiplySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrc Instance { get; } = new AddSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrc Instance { get; } = new SubtractSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrc Instance { get; } = new ScreenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrc Instance { get; } = new DarkenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrc Instance { get; } = new LightenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrc Instance { get; } = new OverlaySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrc Instance { get; } = new HardLightSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcAtop Instance { get; } = new AddSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOver Instance { get; } = new AddSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcIn Instance { get; } = new NormalSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcIn Instance { get; } = new AddSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcIn Instance { get; } = new LightenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOut Instance { get; } = new AddSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDest Instance { get; } = new NormalDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDest Instance { get; } = new MultiplyDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDest Instance { get; } = new AddDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDest Instance { get; } = new SubtractDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDest Instance { get; } = new ScreenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDest Instance { get; } = new DarkenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDest Instance { get; } = new LightenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDest Instance { get; } = new OverlayDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDest Instance { get; } = new HardLightDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestAtop Instance { get; } = new AddDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOver Instance { get; } = new NormalDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOver Instance { get; } = new AddDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOver Instance { get; } = new LightenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestIn Instance { get; } = new NormalDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestIn Instance { get; } = new AddDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestIn Instance { get; } = new LightenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOut Instance { get; } = new NormalDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOut Instance { get; } = new AddDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOut Instance { get; } = new LightenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalClear Instance { get; } = new NormalClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyClear Instance { get; } = new MultiplyClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddClear Instance { get; } = new AddClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractClear Instance { get; } = new SubtractClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenClear Instance { get; } = new ScreenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenClear Instance { get; } = new DarkenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenClear Instance { get; } = new LightenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayClear Instance { get; } = new OverlayClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightClear Instance { get; } = new HardLightClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalXor Instance { get; } = new NormalXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyXor Instance { get; } = new MultiplyXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddXor Instance { get; } = new AddXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractXor Instance { get; } = new SubtractXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenXor Instance { get; } = new ScreenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenXor Instance { get; } = new DarkenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenXor Instance { get; } = new LightenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayXor Instance { get; } = new OverlayXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightXor Instance { get; } = new HardLightXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index b7ea7a9d43..2cca55e4c3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -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. - -// -using System; -using System.Numerics; -using System.Buffers; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders -{ - /// - /// Collection of Porter Duff alpha blending functions applying different composition models. - /// - /// - /// 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 - /// - internal static class DefaultPixelBlenders - where TPixel : struct, IPixel - { - -<# - 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 - { - /// - /// Gets the static instance of this blender. - /// - public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); - - /// - 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; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan 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. + +// +using System; +using System.Numerics; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// 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 + /// + internal static class DefaultPixelBlenders + where TPixel : struct, IPixel + { + +<# + 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 + { + /// + /// Gets the static instance of this blender. + /// + public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); + + /// + 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; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan 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); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + +<# + } + } + +#> + } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 5c8e506ae1..e5109fd424 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -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 -{ - /// - /// Abstract base class for calling pixel composition functions - /// - /// The type of the pixel - internal abstract class PixelBlender - where TPixel : struct, IPixel - { - /// - /// Blend 2 pixels together. - /// - /// The background color. - /// The source color. - /// - /// 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. - /// - /// The final pixel value after composition - public abstract TPixel Blend(TPixel background, TPixel source, float amount); - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// 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. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount); - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// 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. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount); - - /// - /// Blends 2 rows together - /// - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// 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. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - { - this.Blend(configuration, destination, background, source, amount); - } - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// 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. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - where TPixelSrc : struct, IPixel - { - 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 buffer = - configuration.MemoryAllocator.Allocate(destination.Length * 3)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); - - this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); - - PixelOperations.Instance.FromScaledVector4( - configuration, - destinationSpan.Slice(0, background.Length), - destination); - } - } - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// 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. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount) - where TPixelSrc : struct, IPixel - { - 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 buffer = - configuration.MemoryAllocator.Allocate(destination.Length * 3)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToScaledVector4( - configuration, - background.Slice(0, background.Length), - backgroundSpan); - PixelOperations.Instance.ToScaledVector4( - configuration, - source.Slice(0, background.Length), - sourceSpan); - - this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); - - PixelOperations.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 +{ + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal abstract class PixelBlender + where TPixel : struct, IPixel + { + /// + /// Blend 2 pixels together. + /// + /// The background color. + /// The source color. + /// + /// 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. + /// + /// The final pixel value after composition + public abstract TPixel Blend(TPixel background, TPixel source, float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// 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. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// 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. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount); + + /// + /// Blends 2 rows together + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// 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. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + { + this.Blend(configuration, destination, background, source, amount); + } + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// 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. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + where TPixelSrc : struct, IPixel + { + 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 buffer = + configuration.MemoryAllocator.Allocate(destination.Length * 3)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToScaledVector4( + configuration, + background.Slice(0, background.Length), + backgroundSpan); + PixelOperations.Instance.ToScaledVector4( + configuration, + source.Slice(0, background.Length), + sourceSpan); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + PixelOperations.Instance.FromScaledVector4( + configuration, + destinationSpan.Slice(0, background.Length), + destination); + } + } + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// 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. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + where TPixelSrc : struct, IPixel + { + 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 buffer = + configuration.MemoryAllocator.Allocate(destination.Length * 3)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToScaledVector4( + configuration, + background.Slice(0, background.Length), + backgroundSpan); + PixelOperations.Instance.ToScaledVector4( + configuration, + source.Slice(0, background.Length), + sourceSpan); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + PixelOperations.Instance.FromScaledVector4( + configuration, + destinationSpan.Slice(0, background.Length), + destination); + } + } + } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs new file mode 100644 index 0000000000..dd6fc34b3e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs @@ -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 image = CoreImage.Load(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs new file mode 100644 index 0000000000..c739adcaf1 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs new file mode 100644 index 0000000000..3f379f8f6b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs new file mode 100644 index 0000000000..6f638cf9e2 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs new file mode 100644 index 0000000000..b60524025e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs new file mode 100644 index 0000000000..70ebd21332 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs @@ -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 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 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 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); + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs new file mode 100644 index 0000000000..56e3a0598e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -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 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 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; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs new file mode 100644 index 0000000000..1c8341fad6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs @@ -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> decodeAction) + { + int resultWidth = expectedResult[0].Length; + int resultHeight = expectedResult.Length; + Image image = new Image(resultWidth, resultHeight); + image.Mutate(x => x.Fill(DefaultColor)); + + Buffer2D 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]}"); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs new file mode 100644 index 0000000000..09f2af0d11 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -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 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 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 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); + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs new file mode 100644 index 0000000000..7d5cb17826 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs @@ -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 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 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 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); + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs new file mode 100644 index 0000000000..cddf053934 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs @@ -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 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 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 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); + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs new file mode 100644 index 0000000000..73f2a8862c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs @@ -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(() => { decoder.Decode(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(() => { decoder.Decode(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(() => { decoder.Decode(Configuration.Default, stream); }); + + Assert.Equal("Invalid TIFF file header.", e.Message); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs new file mode 100644 index 0000000000..64a5b95161 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs @@ -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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs new file mode 100644 index 0000000000..6accdf995e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs new file mode 100644 index 0000000000..3b17177056 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs @@ -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 image = decoder.DecodeImage(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(() => decoder.DecodeImage(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(() => decoder.DecodeImage(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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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]) + } + }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs new file mode 100644 index 0000000000..6766ba80f7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs @@ -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 image = new Image(null, 20, 20); + + decoder.ReadMetadata(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 image = new Image(null, 20, 20); + + decoder.ReadMetadata(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 image = new Image(null, 20, 20); + + decoder.ReadMetadata(ifd, image); + var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value; + + Assert.Null(metadata); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs new file mode 100644 index 0000000000..667d4e2324 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs new file mode 100644 index 0000000000..edcf5eb4e7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs @@ -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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List() + { + 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 entries = new List(); + + using (TiffWriter writer = new TiffWriter(stream)) + { + ArgumentException e = Assert.Throws(() => { 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); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs new file mode 100644 index 0000000000..c90d77f4a6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs @@ -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 image = new Image(100, 100); + image.MetaData.HorizontalResolution = 40.0; + image.MetaData.VerticalResolution = 50.5; + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List ifdEntries = new List(); + 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 image = new Image(100, 100); + image.MetaData.Properties.Add(new ImageProperty(metadataName, metadataValue)); + TiffEncoderCore encoder = new TiffEncoderCore(null); + + List ifdEntries = new List(); + encoder.AddMetadata(image, ifdEntries); + + Assert.Equal(metadataValue + "\0", ifdEntries.GetAscii(tag)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs new file mode 100644 index 0000000000..a6bcfb9ef5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs @@ -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); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs new file mode 100644 index 0000000000..7c0f55ee70 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs @@ -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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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(); + 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(); + + 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(); + 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(); + + 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(); + + 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(); + + 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(); + + 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs new file mode 100644 index 0000000000..627042f42c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs new file mode 100644 index 0000000000..f6a3c90b77 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs new file mode 100644 index 0000000000..9800567f55 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs @@ -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(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); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs new file mode 100644 index 0000000000..1f8f746641 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs @@ -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(() => 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(() => 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(() => 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(() => 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(() => stream.SetLength(5)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs new file mode 100644 index 0000000000..ce09cd72e9 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_grayscale_uncompressed.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_grayscale_uncompressed.tiff new file mode 100644 index 0000000000..5db7ef564e --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_grayscale_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0283f2be39a151ca3ed19be97ebe4a6b17978ed251dd4d0d568895865fec24c7 +size 964588 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_palette_uncompressed.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_palette_uncompressed.tiff new file mode 100644 index 0000000000..1592645c8e --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_palette_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b70500348b1af7828c15e7782eaca105ff749136d7c45eb4cab8c5cd5269c3f6 +size 966134 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_deflate.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_deflate.tiff new file mode 100644 index 0000000000..c2ebed3649 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_deflate.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da6e6a35a0bb0f5f2d49e3c5f0eb2deb7118718dd08844f66a6cb72f48b5c489 +size 1476294 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_jpeg.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_jpeg.tiff new file mode 100644 index 0000000000..c9f5fadee8 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_jpeg.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba79ffac35e16208406e073492d770d3a77e51a47112aa02ab8fd98b5a8487b2 +size 198564 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_lzw.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_lzw.tiff new file mode 100644 index 0000000000..3a37054ccc --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_lzw.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36b828df14ffda9b64f8eed99714e7af9d6324efe2349a972003af7166fc4629 +size 1792988 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_packbits.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_packbits.tiff new file mode 100644 index 0000000000..862db0b39f --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_packbits.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59dbb48f10c40cbbd4f5617a9f57536790ce0b9a4cc241dc8d6257095598cb76 +size 2891292 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff new file mode 100644 index 0000000000..7ebd74d9d4 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acb46c990af78fcb0e63849f0f26acffe26833d5cfd2fc77a728baf92166eea3 +size 2893218 diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Tiff/genimages.ps1 b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/genimages.ps1 new file mode 100644 index 0000000000..6ed0c080cd --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Tiff/genimages.ps1 @@ -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" \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs new file mode 100644 index 0000000000..dbe1d4755f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs new file mode 100644 index 0000000000..f646ab5bea --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs @@ -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 bytes = new List(); + 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(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs new file mode 100644 index 0000000000..3b84dbbc2a --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs @@ -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; + + /// + /// An interface for any class within the Tiff generator that produces data to be included in the file. + /// + internal interface ITiffGenDataSource + { + IEnumerable GetData(bool isLittleEndian); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs new file mode 100644 index 0000000000..8764b4d516 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs @@ -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; + + /// + /// 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. + /// + internal class TiffGenDataBlock + { + public TiffGenDataBlock(byte[] bytes) + { + this.Bytes = bytes; + this.References = new List(); + } + + public byte[] Bytes { get; } + public IList References { get; } + + public void AddReference(byte[] bytes, int offset) + { + References.Add(new TiffGenDataReference(bytes, offset)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs new file mode 100644 index 0000000000..f72f56b2ca --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// A utility data structure to represent a reference from one block of data to another in a Tiff file. + /// + internal class TiffGenDataReference + { + public TiffGenDataReference(byte[] bytes, int offset) + { + this.Bytes = bytes; + this.Offset = offset; + } + + public byte[] Bytes { get; } + public int Offset { get; } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs new file mode 100644 index 0000000000..cf4892edee --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs @@ -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; + + /// + /// A utility data structure to represent Tiff IFD entries in unit tests. + /// + 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 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 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 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 GetData(bool isLittleEndian) + { + byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray(); + return new[] { new TiffGenDataBlock(bytes) }; + } + + private IEnumerable 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 GetData(bool isLittleEndian) + { + byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray(); + return new[] { new TiffGenDataBlock(bytes) }; + } + + private IEnumerable 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 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) }; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs new file mode 100644 index 0000000000..cd1382c3b1 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs @@ -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; + + /// + /// A utility class for generating in-memory Tiff files for use in unit tests. + /// + 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs new file mode 100644 index 0000000000..e22128f772 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs @@ -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; + + /// + /// A utility data structure to represent a Tiff file-header. + /// + 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 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 }; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs new file mode 100644 index 0000000000..4736a6fdf4 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs @@ -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; + + /// + /// A utility data structure to represent Tiff IFDs in unit tests. + /// + internal class TiffGenIfd : ITiffGenDataSource + { + public TiffGenIfd() + { + this.Entries = new List(); + } + + public List Entries { get; } + public TiffGenIfd NextIfd { get; set; } + + public IEnumerable GetData(bool isLittleEndian) + { + ByteBuffer bytes = new ByteBuffer(isLittleEndian); + List dataBlocks = new List(); + List> entryReferences = new List>(); + + // 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 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs new file mode 100644 index 0000000000..e03f6ae3a7 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs @@ -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; + + /// + /// A utility class for manipulating in-memory Tiff files for use in unit tests. + /// + 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; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs new file mode 100644 index 0000000000..55e95dc7f1 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs @@ -0,0 +1,78 @@ +// 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; + + using SixLabors.ImageSharp.MetaData.Profiles.Exif; + using SixLabors.ImageSharp.Primitives; + using Xunit; + + /// + /// A utility data structure to decode Tiff IFD entries in unit tests. + /// + internal static class TiffIfdParser + { + public static int? GetInteger(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(1u, entry.Count); + + switch (entry.Type) + { + case TiffType.Byte: + return entry.Value[0]; + case TiffType.SByte: + return (sbyte)entry.Value[0]; + case TiffType.Short: + return BitConverter.ToUInt16(entry.Value, 0); + case TiffType.SShort: + return BitConverter.ToInt16(entry.Value, 0); + case TiffType.Long: + return (int)BitConverter.ToUInt32(entry.Value, 0); + case TiffType.SLong: + return BitConverter.ToInt32(entry.Value, 0); + default: + Assert.True(1 == 1, "TIFF IFD entry is not convertable to an integer."); + return null; + } + } + + public static Rational? GetUnsignedRational(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(TiffType.Rational, entry.Type); + Assert.Equal(1u, entry.Count); + + uint numerator = BitConverter.ToUInt32(entry.Value, 0); + uint denominator = BitConverter.ToUInt32(entry.Value, 4); + + return new Rational(numerator, denominator); + } + + public static string GetAscii(this List entries, ushort tag) + { + TiffIfdEntry entry = entries.FirstOrDefault(e => e.Tag == tag); + + if (entry.Tag == 0) + return null; + + Assert.Equal(TiffType.Ascii, entry.Type); + + return Encoding.UTF8.GetString(entry.Value, 0, (int)entry.Count); + } + } +} \ No newline at end of file diff --git a/tests/Images/External b/tests/Images/External index 5b18d8c95a..1edb0f3e04 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 5b18d8c95acffb773012881870ba6f521ba13128 +Subproject commit 1edb0f3e04c18974821a3012a87f7c2e073c8019