From f62e2baac8957b73f0b910d61549087b40f0761a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:00 +0800 Subject: [PATCH 01/28] Make the BMP Decoder Core can skip the file header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make BmpDecoderCore support AlphaMask. link #687 Signed-off-by: 舰队的偶像-岛风酱! --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 226 +++++++++++++++--- .../Formats/Bmp/BmpDecoderOptions.cs | 24 ++ 2 files changed, 211 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index bed489752..9bea6867a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -71,7 +71,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals /// /// The file header containing general information. /// - private BmpFileHeader fileHeader; + private BmpFileHeader? fileHeader; /// /// Indicates which bitmap file marker was read. @@ -99,6 +99,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals /// private readonly RleSkippedPixelHandling rleSkippedPixelHandling; + /// + private readonly bool processedAlphaMask; + + /// + private readonly bool skipFileHeader; + + /// + private readonly bool isDoubleHeight; + /// /// Initializes a new instance of the class. /// @@ -109,6 +118,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals this.rleSkippedPixelHandling = options.RleSkippedPixelHandling; this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; + this.processedAlphaMask = options.ProcessedAlphaMask; + this.skipFileHeader = options.SkipFileHeader; + this.isDoubleHeight = options.IsDoubleHeight; } /// @@ -132,38 +144,44 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals switch (this.infoHeader.Compression) { - case BmpCompression.RGB: - if (this.infoHeader.BitsPerPixel == 32) - { - if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) - { - this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else - { - this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - } - else if (this.infoHeader.BitsPerPixel == 24) - { - this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel == 16) - { - this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel <= 8) - { - this.ReadRgbPalette( - stream, - pixels, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - bytesPerColorMapEntry, - inverted); - } + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: + this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32: + this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24: + this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16: + this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + + break; + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask: + this.ReadRgbPaletteWithAlphaMask( + stream, + pixels, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + bytesPerColorMapEntry, + inverted); + + break; + case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8: + this.ReadRgbPalette( + stream, + pixels, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + bytesPerColorMapEntry, + inverted); break; @@ -839,6 +857,108 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals } } + /// + private void ReadRgbPaletteWithAlphaMask(BufferedReadStream stream, Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) + where TPixel : unmanaged, IPixel + { + // Pixels per byte (bits per pixel). + int ppb = 8 / bitsPerPixel; + + int arrayWidth = (width + ppb - 1) / ppb; + + // Bit mask + int mask = 0xFF >> (8 - bitsPerPixel); + + // Rows are aligned on 4 byte boundaries. + int padding = arrayWidth % 4; + if (padding != 0) + { + padding = 4 - padding; + } + + Bgra32[,] image = new Bgra32[height, width]; + using (IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean)) + { + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + if (stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + + int offset = 0; + + for (int x = 0; x < arrayWidth; x++) + { + int colOffset = x * ppb; + for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) + { + int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; + + image[newY, newX].FromBgr24(Unsafe.As(ref colors[colorIndex])); + } + + offset++; + } + } + } + + arrayWidth = width / 8; + padding = arrayWidth % 4; + if (padding != 0) + { + padding = 4 - padding; + } + + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + + for (int i = 0; i < arrayWidth; i++) + { + int x = i * 8; + int and = stream.ReadByte(); + if (and is -1) + { + throw new EndOfStreamException(); + } + + for (int j = 0; j < 8; j++) + { + SetAlpha(ref image[newY, x + j], and, j); + } + } + + stream.Skip(padding); + } + + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + + for (int x = 0; x < width; x++) + { + pixelRow[x].FromBgra32(image[newY, x]); + } + } + } + + /// + /// Set pixel's alpha with alpha mask. + /// + /// Bgra32 pixel. + /// alpha mask. + /// bit index of pixel. + private static void SetAlpha(ref Bgra32 pixel, in int mask, in int index) + { + bool isTransparently = (mask & (0b10000000 >> index)) is not 0; + pixel.A = isTransparently ? byte.MinValue : byte.MaxValue; + } + /// /// Reads the 16 bit color palette from the stream. /// @@ -1333,6 +1453,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); } + if (this.isDoubleHeight) + { + this.infoHeader.Height >>= 1; + } + ushort bitsPerPixel = this.infoHeader.BitsPerPixel; this.bmpMetadata = this.metadata.GetBmpMetadata(); this.bmpMetadata.InfoHeaderType = infoHeaderType; @@ -1362,9 +1487,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals // The bitmap file header of the first image follows the array header. stream.Read(buffer, 0, BmpFileHeader.Size); this.fileHeader = BmpFileHeader.Parse(buffer); - if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) + if (this.fileHeader.Value.Type != BmpConstants.TypeMarkers.Bitmap) { - BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'."); + BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Value.Type}'."); } break; @@ -1387,7 +1512,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals [MemberNotNull(nameof(bmpMetadata))] private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) { - this.ReadFileHeader(stream); + if (!this.skipFileHeader) + { + this.ReadFileHeader(stream); + } + this.ReadInfoHeader(stream); // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 @@ -1411,7 +1540,21 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals switch (this.fileMarkerType) { case BmpFileMarkerType.Bitmap: - colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; + if (this.fileHeader.HasValue) + { + colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; + } + else + { + colorMapSizeBytes = this.infoHeader.ClrUsed; + if (colorMapSizeBytes is 0 && this.infoHeader.BitsPerPixel is <= 8) + { + colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); + } + + colorMapSizeBytes *= 4; + } + int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; @@ -1442,7 +1585,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals { // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit. // Make sure, that we will not read pass the bitmap offset (starting position of image data). - if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes) + if (this.fileHeader.HasValue && stream.Position > this.fileHeader.Value.Offset - colorMapSizeBytes) { BmpThrowHelper.ThrowInvalidImageContentException( $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset."); @@ -1456,7 +1599,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals } } - int skipAmount = this.fileHeader.Offset - (int)stream.Position; + int skipAmount = 0; + if (this.fileHeader.HasValue) + { + skipAmount = this.fileHeader.Value.Offset - (int)stream.Position; + } + if ((skipAmount + (int)stream.Position) > stream.Length) { BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length."); diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index b3387ce80..17d37cab2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -16,4 +16,28 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions /// which can occur during decoding run length encoded bitmaps. /// public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; } + + /// + /// Gets a value indicating whether the additional AlphaMask is processed at decoding time. + /// + /// + /// It will be used at IcoDecoder. + /// + internal bool ProcessedAlphaMask { get; init; } + + /// + /// Gets a value indicating whether to skip loading the BMP file header. + /// + /// + /// It will be used at IcoDecoder. + /// + internal bool SkipFileHeader { get; init; } + + /// + /// Gets a value indicating whether the height is double of true height. + /// + /// + /// It will be used at IcoDecoder. + /// + internal bool IsDoubleHeight { get; init; } } From 520ff46baaf9927a734947d167072a40bd5b85a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:02 +0800 Subject: [PATCH 02/28] Append BMP's MIME Type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- src/ImageSharp/Formats/Bmp/BmpConstants.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 5cf0c9732..62edfdfdf 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -11,7 +11,12 @@ internal static class BmpConstants /// /// The list of mimetypes that equate to a bmp. /// - public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; + public static readonly IEnumerable MimeTypes = new[] + { + "image/bmp", + "image/x-windows-bmp", + "image/x-win-bitmap" + }; /// /// The list of file extensions that equate to a bmp. From 11293824e3213133eb6e1e4bcdec8a67fed3cbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:03 +0800 Subject: [PATCH 03/28] Add UnsafeSetFormatMetadata in ImageFrameMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- src/ImageSharp/Metadata/ImageFrameMetadata.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 1c0330d5d..0567c8916 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -99,6 +99,11 @@ public sealed class ImageFrameMetadata : IDeepCloneable return newMeta; } + internal void UnsafeSetFormatMetadata( + IImageFormat key, + IDeepCloneable value) + => this.formatMetadata[key] = value; + /// /// Gets the metadata value associated with the specified key. /// From 4832d8f24aaadef2827d46f988be0a77bca5018a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:03 +0800 Subject: [PATCH 04/28] Add Icon Support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ico Decoder - Ico Detector - Ico Detector UnitTest - Cur Decoder - Cur Detector - Cur Detector UnitTest Signed-off-by: 舰队的偶像-岛风酱! --- ImageSharp.sln | 7 + src/ImageSharp/Configuration.cs | 6 +- .../Icon/Cur/CurConfigurationModule.cs | 19 +++ .../Formats/Icon/Cur/CurConstants.cs | 28 ++++ src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs | 47 ++++++ .../Formats/Icon/Cur/CurDecoderCore.cs | 16 +++ src/ImageSharp/Formats/Icon/Cur/CurFormat.cs | 37 +++++ .../Formats/Icon/Cur/CurFrameMetadata.cs | 52 +++++++ .../Formats/Icon/Cur/CurMetadata.cs | 16 +++ .../Formats/Icon/Cur/MetadataExtensions.cs | 44 ++++++ .../Icon/Ico/IcoConfigurationModule.cs | 19 +++ .../Formats/Icon/Ico/IcoConstants.cs | 38 +++++ src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs | 47 ++++++ .../Formats/Icon/Ico/IcoDecoderCore.cs | 16 +++ src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs | 37 +++++ .../Formats/Icon/Ico/IcoFrameMetadata.cs | 50 +++++++ .../Formats/Icon/Ico/IcoMetadata.cs | 16 +++ .../Formats/Icon/Ico/MetadataExtensions.cs | 44 ++++++ src/ImageSharp/Formats/Icon/IconAssert.cs | 43 ++++++ .../Formats/Icon/IconDecoderCore.cs | 136 ++++++++++++++++++ src/ImageSharp/Formats/Icon/IconDir.cs | 26 ++++ src/ImageSharp/Formats/Icon/IconDirEntry.cs | 28 ++++ src/ImageSharp/Formats/Icon/IconFileType.cs | 20 +++ .../Formats/Icon/IconFrameCompression.cs | 25 ++++ .../Formats/Icon/IconFrameMetadata.cs | 103 +++++++++++++ .../Formats/Icon/IconImageFormatDetector.cs | 33 +++++ tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- .../Formats/Icon/Cur/CurDecoderTests.cs | 23 +++ .../Formats/Icon/Ico/IcoDecoderTests.cs | 23 +++ tests/ImageSharp.Tests/TestImages.cs | 10 ++ tests/Images/Input/Icon/aero_arrow.cur | 3 + tests/Images/Input/Icon/flutter.ico | 3 + 32 files changed, 1015 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurConstants.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurFormat.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs create mode 100644 src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs create mode 100644 src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs create mode 100644 src/ImageSharp/Formats/Icon/IconAssert.cs create mode 100644 src/ImageSharp/Formats/Icon/IconDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Icon/IconDir.cs create mode 100644 src/ImageSharp/Formats/Icon/IconDirEntry.cs create mode 100644 src/ImageSharp/Formats/Icon/IconFileType.cs create mode 100644 src/ImageSharp/Formats/Icon/IconFrameCompression.cs create mode 100644 src/ImageSharp/Formats/Icon/IconFrameMetadata.cs create mode 100644 src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs create mode 100644 tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs create mode 100644 tests/Images/Input/Icon/aero_arrow.cur create mode 100644 tests/Images/Input/Icon/flutter.ico diff --git a/ImageSharp.sln b/ImageSharp.sln index 162de8416..7ccd92c07 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -661,6 +661,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493 tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur + tests\Images\Input\Icon\flutter.ico = tests\Images\Input\Icon\flutter.ico + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -714,6 +720,7 @@ Global {670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} {5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 1ca5d0a46..d6cfd480d 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Icon.Cur; +using SixLabors.ImageSharp.Formats.Icon.Ico; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; @@ -222,5 +224,7 @@ public sealed class Configuration new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule(), - new QoiConfigurationModule()); + new QoiConfigurationModule(), + new IcoConfigurationModule(), + new CurConfigurationModule()); } diff --git a/src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs b/src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs new file mode 100644 index 000000000..c975bc609 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Registers the image encoders, decoders and mime type detectors for the Ico format. +/// +public sealed class CurConfigurationModule : IImageFormatConfigurationModule +{ + /// + public void Configure(Configuration configuration) + { + // TODO: CurEncoder + // configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder()); + configuration.ImageFormatsManager.SetDecoder(CurFormat.Instance, CurDecoder.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); + } +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurConstants.cs b/src/ImageSharp/Formats/Icon/Cur/CurConstants.cs new file mode 100644 index 000000000..701b40cf4 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurConstants.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Defines constants relating to ICOs +/// +internal static class CurConstants +{ + /// + /// The list of mimetypes that equate to a ico. + /// + /// + /// See + /// + public static readonly IEnumerable MimeTypes = new[] + { + "application/octet-stream", + }; + + /// + /// The list of file extensions that equate to a ico. + /// + public static readonly IEnumerable FileExtensions = new[] { "cur" }; + + public const uint FileHeader = 0x00_02_00_00; +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs b/src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs new file mode 100644 index 000000000..ceefdcaba --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Decoder for generating an image out of a ico encoded stream. +/// +public sealed class CurDecoder : ImageDecoder +{ + private CurDecoder() + { + } + + /// + /// Gets the shared instance. + /// + public static CurDecoder Instance { get; } = new(); + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + Image image = new CurDecoderCore(options).Decode(options.Configuration, stream, cancellationToken); + + ScaleToTargetSize(options, image); + + return image; + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); + + /// + protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new CurDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs new file mode 100644 index 000000000..8b08f127d --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +internal sealed class CurDecoderCore : IconDecoderCore +{ + public CurDecoderCore(DecoderOptions options) + : base(options) + { + } + + protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetCurMetadata(); +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFormat.cs b/src/ImageSharp/Formats/Icon/Cur/CurFormat.cs new file mode 100644 index 000000000..1e5758bc4 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurFormat.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Registers the image encoders, decoders and mime type detectors for the ICO format. +/// +public sealed class CurFormat : IImageFormat +{ + private CurFormat() + { + } + + /// + /// Gets the shared instance. + /// + public static CurFormat Instance { get; } = new(); + + /// + public string Name => "ICO"; + + /// + public string DefaultMimeType => CurConstants.MimeTypes.First(); + + /// + public IEnumerable MimeTypes => CurConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => CurConstants.FileExtensions; + + /// + public CurMetadata CreateDefaultFormatMetadata() => new(); + + /// + public CurFrameMetadata CreateDefaultFormatFrameMetadata() => new(); +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs new file mode 100644 index 000000000..c94afdd3a --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// IcoFrameMetadata +/// +public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + public CurFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// metadata + public CurFrameMetadata(IconFrameMetadata metadata) + : base(metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// width + /// height + /// colorCount + /// field1 + /// field2 + public CurFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) + : base(width, height, colorCount, field1, field2) + { + } + + /// + /// Gets or sets Specifies the horizontal coordinates of the hotspot in number of pixels from the left. + /// + public ushort HotspotX { get => this.Field1; set => this.Field1 = value; } + + /// + /// Gets or sets Specifies the vertical coordinates of the hotspot in number of pixels from the top. + /// + public ushort HotspotY { get => this.Field2; set => this.Field2 = value; } + + /// + public override CurFrameMetadata DeepClone() => new(this); +} diff --git a/src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs new file mode 100644 index 000000000..ed3c322b4 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Provides Ico specific metadata information for the image. +/// +public class CurMetadata : IDeepCloneable, IDeepCloneable +{ + /// + public CurMetadata DeepClone() => new(); + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); +} diff --git a/src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs b/src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs new file mode 100644 index 000000000..400ece648 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Icon.Cur; + +/// +/// Extension methods for the type. +/// +public static class MetadataExtensions +{ + /// + /// Gets the Icon format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static CurMetadata GetCurMetadata(this ImageMetadata source) + => source.GetFormatMetadata(CurFormat.Instance); + + /// + /// Gets the Icon format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source) + => source.GetFormatMetadata(CurFormat.Instance); + + /// + /// Gets the Icon format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// + /// When this method returns, contains the metadata associated with the specified frame, + /// if found; otherwise, the default value for the type of the metadata parameter. + /// This parameter is passed uninitialized. + /// + /// + /// if the Icon frame metadata exists; otherwise, . + /// + public static bool TryGetCurMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out CurFrameMetadata? metadata) + => source.TryGetFormatMetadata(CurFormat.Instance, out metadata); +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs b/src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs new file mode 100644 index 000000000..7074189c7 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Registers the image encoders, decoders and mime type detectors for the Ico format. +/// +public sealed class IcoConfigurationModule : IImageFormatConfigurationModule +{ + /// + public void Configure(Configuration configuration) + { + // TODO: IcoEncoder + // configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder()); + configuration.ImageFormatsManager.SetDecoder(IcoFormat.Instance, IcoDecoder.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); + } +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs b/src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs new file mode 100644 index 000000000..345711705 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Defines constants relating to ICOs +/// +internal static class IcoConstants +{ + /// + /// The list of mimetypes that equate to a ico. + /// + /// + /// See + /// + public static readonly IEnumerable MimeTypes = new[] + { + // IANA-registered + "image/vnd.microsoft.icon", + + // ICO & CUR types used by Windows + "image/x-icon", + + // Erroneous types but have been used + "image/ico", + "image/icon", + "text/ico", + "application/ico", + }; + + /// + /// The list of file extensions that equate to a ico. + /// + public static readonly IEnumerable FileExtensions = new[] { "ico" }; + + public const uint FileHeader = 0x00_01_00_00; +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs b/src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs new file mode 100644 index 000000000..5d6137920 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Decoder for generating an image out of a ico encoded stream. +/// +public sealed class IcoDecoder : ImageDecoder +{ + private IcoDecoder() + { + } + + /// + /// Gets the shared instance. + /// + public static IcoDecoder Instance { get; } = new(); + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + Image image = new IcoDecoderCore(options).Decode(options.Configuration, stream, cancellationToken); + + ScaleToTargetSize(options, image); + + return image; + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); + + /// + protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new IcoDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs new file mode 100644 index 000000000..0782c2128 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +internal sealed class IcoDecoderCore : IconDecoderCore +{ + public IcoDecoderCore(DecoderOptions options) + : base(options) + { + } + + protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetIcoMetadata(); +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs b/src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs new file mode 100644 index 000000000..27b0525bf --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Registers the image encoders, decoders and mime type detectors for the ICO format. +/// +public sealed class IcoFormat : IImageFormat +{ + private IcoFormat() + { + } + + /// + /// Gets the shared instance. + /// + public static IcoFormat Instance { get; } = new(); + + /// + public string Name => "ICO"; + + /// + public string DefaultMimeType => IcoConstants.MimeTypes.First(); + + /// + public IEnumerable MimeTypes => IcoConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => IcoConstants.FileExtensions; + + /// + public IcoMetadata CreateDefaultFormatMetadata() => new(); + + /// + public IcoFrameMetadata CreateDefaultFormatFrameMetadata() => new(); +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs new file mode 100644 index 000000000..7c903facd --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// IcoFrameMetadata +/// +public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + public IcoFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// metadata + public IcoFrameMetadata(IconFrameMetadata metadata) + : base(metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// width + /// height + /// colorCount + /// field1 + /// field2 + public IcoFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) + : base(width, height, colorCount, field1, field2) + { + } + + /// + /// Gets or sets Specifies bits per pixel. + /// + /// + /// It may used by Encoder. + /// + public ushort BitCount { get => this.Field2; set => this.Field2 = value; } + + /// + public override IcoFrameMetadata DeepClone() => new(this); +} diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs new file mode 100644 index 000000000..b227d0bd6 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Provides Ico specific metadata information for the image. +/// +public class IcoMetadata : IDeepCloneable, IDeepCloneable +{ + /// + public IcoMetadata DeepClone() => new(); + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); +} diff --git a/src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs b/src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs new file mode 100644 index 000000000..fb5b4afe7 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Icon.Ico; + +/// +/// Extension methods for the type. +/// +public static class MetadataExtensions +{ + /// + /// Gets the Ico format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static IcoMetadata GetIcoMetadata(this ImageMetadata source) + => source.GetFormatMetadata(IcoFormat.Instance); + + /// + /// Gets the Ico format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source) + => source.GetFormatMetadata(IcoFormat.Instance); + + /// + /// Gets the Ico format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// + /// When this method returns, contains the metadata associated with the specified frame, + /// if found; otherwise, the default value for the type of the metadata parameter. + /// This parameter is passed uninitialized. + /// + /// + /// if the Ico frame metadata exists; otherwise, . + /// + public static bool TryGetIcoMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out IcoFrameMetadata? metadata) + => source.TryGetFormatMetadata(IcoFormat.Instance, out metadata); +} diff --git a/src/ImageSharp/Formats/Icon/IconAssert.cs b/src/ImageSharp/Formats/Icon/IconAssert.cs new file mode 100644 index 000000000..bcb427c1a --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconAssert.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon; + +internal class IconAssert +{ + internal static void CanSeek(Stream stream) + { + if (!stream.CanSeek) + { + throw new NotSupportedException("This stream cannot support seek"); + } + } + + internal static int EndOfStream(int v, int length) + { + if (v != length) + { + throw new EndOfStreamException(); + } + + return v; + } + + internal static long EndOfStream(long v, long length) + { + if (v != length) + { + throw new EndOfStreamException(); + } + + return v; + } + + internal static void NotSquare(in Size size) + { + if (size.Width != size.Height) + { + throw new FormatException("This image is not square."); + } + } +} diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs new file mode 100644 index 000000000..d9a578ff2 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Icon; + +internal abstract class IconDecoderCore : IImageDecoderInternals +{ + private IconDir fileHeader; + + public IconDecoderCore(DecoderOptions options) => this.Options = options; + + public DecoderOptions Options { get; } + + public Size Dimensions { get; private set; } + + protected IconDir FileHeader { get => this.fileHeader; private set => this.fileHeader = value; } + + protected IconDirEntry[] Entries { get; private set; } = Array.Empty(); + + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // Stream may not at 0. + long basePosition = stream.Position; + this.ReadHeader(stream); + + Span flag = stackalloc byte[Png.PngConstants.HeaderBytes.Length]; + + List<(Image Image, bool IsPng, int Index)> frames = new(this.Entries.Length); + for (int i = 0; i < this.Entries.Length; i++) + { + _ = IconAssert.EndOfStream(stream.Seek(basePosition + this.Entries[i].ImageOffset, SeekOrigin.Begin), basePosition + this.Entries[i].ImageOffset); + _ = IconAssert.EndOfStream(stream.Read(flag), Png.PngConstants.HeaderBytes.Length); + _ = stream.Seek(-Png.PngConstants.HeaderBytes.Length, SeekOrigin.Current); + + bool isPng = flag.SequenceEqual(Png.PngConstants.HeaderBytes); + + Image img = this.GetDecoder(isPng).Decode(stream, cancellationToken); + IconAssert.NotSquare(img.Size); + frames.Add((img, isPng, i)); + if (isPng && img.Size.Width > this.Dimensions.Width) + { + this.Dimensions = img.Size; + } + } + + ImageMetadata metadata = new(); + return new(this.Options.Configuration, metadata, frames.Select(i => + { + ImageFrame target = new(this.Options.Configuration, this.Dimensions); + ImageFrame source = i.Image.Frames.RootFrameUnsafe; + for (int h = 0; h < source.Height; h++) + { + source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h)); + } + + if (i.IsPng) + { + target.Metadata.UnsafeSetFormatMetadata(Png.PngFormat.Instance, i.Image.Metadata.GetPngMetadata()); + } + else + { + target.Metadata.UnsafeSetFormatMetadata(Bmp.BmpFormat.Instance, i.Image.Metadata.GetBmpMetadata()); + } + + this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[i.Index]); + + i.Image.Dispose(); + return target; + }).ToArray()); + } + + public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ReadHeader(stream); + + ImageMetadata metadata = new(); + ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count]; + for (int i = 0; i < frames.Length; i++) + { + frames[i] = new(); + IconFrameMetadata icoFrameMetadata = this.GetFrameMetadata(frames[i]); + icoFrameMetadata.FromIconDirEntry(this.Entries[i]); + } + + return new(new(32), new(0), metadata, frames); + } + + protected abstract IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata); + + protected void ReadHeader(Stream stream) + { + _ = Read(stream, out this.fileHeader, IconDir.Size); + this.Entries = new IconDirEntry[this.FileHeader.Count]; + for (int i = 0; i < this.Entries.Length; i++) + { + _ = Read(stream, out this.Entries[i], IconDirEntry.Size); + } + + this.Dimensions = new( + this.Entries.Max(i => i.Width), + this.Entries.Max(i => i.Height)); + } + + private static int Read(Stream stream, out T data, int size) + where T : unmanaged + { + Span buffer = stackalloc byte[size]; + _ = IconAssert.EndOfStream(stream.Read(buffer), size); + data = MemoryMarshal.Cast(buffer)[0]; + return size; + } + + private IImageDecoderInternals GetDecoder(bool isPng) + { + if (isPng) + { + return new Png.PngDecoderCore(this.Options); + } + else + { + return new Bmp.BmpDecoderCore(new() + { + ProcessedAlphaMask = true, + SkipFileHeader = true, + IsDoubleHeight = true, + }); + } + } +} diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs new file mode 100644 index 000000000..f1281a568 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconDir.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Icon; + +[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] +internal struct IconDir +{ + public const int Size = 3 * sizeof(ushort); + public ushort Reserved; + public IconFileType Type; + public ushort Count; + + public IconDir(IconFileType type) + : this(type, 0) + => this.Type = type; + + public IconDir(IconFileType type, ushort count) + { + this.Reserved = 0; + this.Type = type; + this.Count = count; + } +} diff --git a/src/ImageSharp/Formats/Icon/IconDirEntry.cs b/src/ImageSharp/Formats/Icon/IconDirEntry.cs new file mode 100644 index 000000000..43254f89d --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconDirEntry.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Icon; + +[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] +internal struct IconDirEntry +{ + public const int Size = (4 * sizeof(byte)) + (2 * sizeof(ushort)) + (2 * sizeof(uint)); + + public byte Width; + + public byte Height; + + public byte ColorCount; + + public byte Reserved; + + public ushort Planes; + + public ushort BitCount; + + public uint BytesInRes; + + public uint ImageOffset; +} diff --git a/src/ImageSharp/Formats/Icon/IconFileType.cs b/src/ImageSharp/Formats/Icon/IconFileType.cs new file mode 100644 index 000000000..3450698f1 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconFileType.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon; + +/// +/// Ico file type +/// +internal enum IconFileType : ushort +{ + /// + /// ICO file + /// + ICO = 1, + + /// + /// CUR file + /// + CUR = 2, +} diff --git a/src/ImageSharp/Formats/Icon/IconFrameCompression.cs b/src/ImageSharp/Formats/Icon/IconFrameCompression.cs new file mode 100644 index 000000000..f6509f40c --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconFrameCompression.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon; + +/// +/// IconFrameCompression +/// +public enum IconFrameCompression +{ + /// + /// Unknown + /// + Unknown, + + /// + /// Bmp + /// + Bmp, + + /// + /// Png + /// + Png +} diff --git a/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs b/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs new file mode 100644 index 000000000..17e641c71 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Icon; + +/// +/// IconFrameMetadata +/// +public abstract class IconFrameMetadata : IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + protected IconFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// width + /// height + /// colorCount + /// field1 + /// field2 + protected IconFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) + { + this.EncodingWidth = width; + this.EncodingHeight = height; + this.ColorCount = colorCount; + this.Field1 = field1; + this.Field2 = field2; + } + + /// + protected IconFrameMetadata(IconFrameMetadata metadata) + { + this.EncodingWidth = metadata.EncodingWidth; + this.EncodingHeight = metadata.EncodingHeight; + this.ColorCount = metadata.ColorCount; + this.Field1 = metadata.Field1; + this.Field2 = metadata.Field2; + } + + /// + /// Gets or sets icoFrameCompression. + /// + public IconFrameCompression Compression { get; protected set; } + + /// + /// Gets or sets ColorCount field.
+ /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + ///
+ // TODO: BmpMetadata does not supported palette yet. + public byte ColorCount { get; set; } + + /// + /// Gets or sets Planes field.
+ /// In ICO format: Specifies color planes. Should be 0 or 1.
+ /// In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. + ///
+ protected ushort Field1 { get; set; } + + /// + /// Gets or sets BitCount field.
+ /// In ICO format: Specifies bits per pixel.
+ /// In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top. + ///
+ protected ushort Field2 { get; set; } + + /// + /// Gets or sets Height field.
+ /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. + ///
+ public byte EncodingHeight { get; set; } + + /// + /// Gets or sets Width field.
+ /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + ///
+ public byte EncodingWidth { get; set; } + + /// + public abstract IDeepCloneable DeepClone(); + + internal void FromIconDirEntry(in IconDirEntry metadata) + { + this.EncodingWidth = metadata.Width; + this.EncodingHeight = metadata.Height; + this.ColorCount = metadata.ColorCount; + this.Field1 = metadata.Planes; + this.Field2 = metadata.BitCount; + } + + internal IconDirEntry ToIconDirEntry() => new() + { + Width = this.EncodingWidth, + Height = this.EncodingHeight, + ColorCount = this.ColorCount, + Planes = this.Field1, + BitCount = this.Field2, + }; +} diff --git a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs new file mode 100644 index 000000000..aff8dfe0a --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Icon; + +/// +/// Detects ico file headers. +/// +public class IconImageFormatDetector : IImageFormatDetector +{ + /// + public int HeaderSize { get; } = 4; + + /// + public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + { + switch (MemoryMarshal.Cast(header)[0]) + { + case Ico.IcoConstants.FileHeader: + format = Ico.IcoFormat.Instance; + return true; + case Cur.CurConstants.FileHeader: + format = Cur.CurFormat.Instance; + return true; + default: + format = default; + return false; + } + } +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index c5d61726c..c8e6cd265 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -20,7 +20,7 @@ public class ConfigurationTests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 9; + private readonly int expectedDefaultConfigurationCount = 11; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs new file mode 100644 index 000000000..c3c9ad1c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Icon.Cur; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.TestImages.Cur; + +namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; + +[Trait("Format", "Cur")] +[ValidateDisposedMemoryAllocations] +public class CurDecoderTests +{ + [Theory] + [WithFile(WindowsMouse, PixelTypes.Rgba32)] + public void CurDecoder_Decode(TestImageProvider provider) + { + using Image image = provider.GetImage(CurDecoder.Instance); + + image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs new file mode 100644 index 000000000..6e7b35111 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Icon.Ico; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.TestImages.Ico; + +namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; + +[Trait("Format", "Icon")] +[ValidateDisposedMemoryAllocations] +public class IcoDecoderTests +{ + [Theory] + [WithFile(Flutter, PixelTypes.Rgba32)] + public void IcoDecoder_Decode(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a45931e29..c64bf2b34 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1121,4 +1121,14 @@ public static class TestImages public const string TestCardRGBA = "Qoi/testcard_rgba.qoi"; public const string Wikipedia008 = "Qoi/wikipedia_008.qoi"; } + + public static class Ico + { + public const string Flutter = "Icon/flutter.ico"; + } + + public static class Cur + { + public const string WindowsMouse = "Icon/aero_arrow.cur"; + } } diff --git a/tests/Images/Input/Icon/aero_arrow.cur b/tests/Images/Input/Icon/aero_arrow.cur new file mode 100644 index 000000000..82cbbd33e --- /dev/null +++ b/tests/Images/Input/Icon/aero_arrow.cur @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06678bbf954f0bece61062633dc63a52a34a6f3c27ac7108f28c0f0d26bb22a7 +size 136606 diff --git a/tests/Images/Input/Icon/flutter.ico b/tests/Images/Input/Icon/flutter.ico new file mode 100644 index 000000000..4001f1426 --- /dev/null +++ b/tests/Images/Input/Icon/flutter.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c098d3fc85cacff98b8e69811b48e9f0d852fcee278132d794411d978869cbf8 +size 33772 From ffde9a9380698c19b693847ad24f7789ccae9207 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 14 Dec 2023 14:13:53 +0800 Subject: [PATCH 05/28] Refactor decoder and add notes --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 4 +- .../Formats/Bmp/BmpDecoderOptions.cs | 12 +- .../Formats/Icon/Cur/CurDecoderCore.cs | 2 + .../Formats/Icon/Cur/CurFrameMetadata.cs | 2 +- .../Formats/Icon/Ico/IcoDecoderCore.cs | 2 + .../Formats/Icon/Ico/IcoFrameMetadata.cs | 8 +- src/ImageSharp/Formats/Icon/IconAssert.cs | 8 -- .../Formats/Icon/IconDecoderCore.cs | 107 ++++++++++++------ .../Formats/Icon/IconFrameCompression.cs | 5 - .../Formats/Icon/IconFrameMetadata.cs | 1 + src/ImageSharp/Metadata/ImageFrameMetadata.cs | 6 +- src/ImageSharp/Metadata/ImageMetadata.cs | 3 + .../Formats/Icon/Cur/CurDecoderTests.cs | 7 +- .../Formats/Icon/Ico/IcoDecoderTests.cs | 5 +- 14 files changed, 106 insertions(+), 66 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 9bea6867a..568eea00a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -105,7 +105,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals /// private readonly bool skipFileHeader; - /// + /// private readonly bool isDoubleHeight; /// @@ -120,7 +120,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals this.memoryAllocator = this.configuration.MemoryAllocator; this.processedAlphaMask = options.ProcessedAlphaMask; this.skipFileHeader = options.SkipFileHeader; - this.isDoubleHeight = options.IsDoubleHeight; + this.isDoubleHeight = options.UseDoubleHeight; } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index 17d37cab2..158a9d479 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -18,10 +18,10 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; } /// - /// Gets a value indicating whether the additional AlphaMask is processed at decoding time. + /// Gets a value indicating whether the additional alpha mask is processed at decoding time. /// /// - /// It will be used at IcoDecoder. + /// Used by the icon decoder. /// internal bool ProcessedAlphaMask { get; init; } @@ -29,15 +29,15 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions /// Gets a value indicating whether to skip loading the BMP file header. /// /// - /// It will be used at IcoDecoder. + /// Used by the icon decoder. /// internal bool SkipFileHeader { get; init; } /// - /// Gets a value indicating whether the height is double of true height. + /// Gets a value indicating whether to treat the height as double of true height. /// /// - /// It will be used at IcoDecoder. + /// Used by the icon decoder. /// - internal bool IsDoubleHeight { get; init; } + internal bool UseDoubleHeight { get; init; } } diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs index 8b08f127d..44fcc8fcc 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs @@ -3,6 +3,8 @@ using SixLabors.ImageSharp.Metadata; +// TODO: flatten namespace. +// namespace SixLabors.ImageSharp.Formats.Cur; namespace SixLabors.ImageSharp.Formats.Icon.Cur; internal sealed class CurDecoderCore : IconDecoderCore diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs index c94afdd3a..60ced16ea 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Icon.Cur; /// -/// IcoFrameMetadata +/// IcoFrameMetadata. TODO: Remove base class and merge into this class. /// public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable { diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs index 0782c2128..677a9c07a 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs @@ -3,6 +3,8 @@ using SixLabors.ImageSharp.Metadata; +// TODO: flatten namespace. +// namespace SixLabors.ImageSharp.Formats.Ico; namespace SixLabors.ImageSharp.Formats.Icon.Ico; internal sealed class IcoDecoderCore : IconDecoderCore diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs index 7c903facd..4f557715d 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Icon.Ico; /// -/// IcoFrameMetadata +/// IcoFrameMetadata. TODO: Remove base class and merge into this class. /// public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable { @@ -38,11 +38,9 @@ public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable - /// Gets or sets Specifies bits per pixel. + /// Gets or sets the bits per pixel. + /// TODO: This needs to be constrained and calculated using the metadata returned from the png/bmp. ///
- /// - /// It may used by Encoder. - /// public ushort BitCount { get => this.Field2; set => this.Field2 = value; } /// diff --git a/src/ImageSharp/Formats/Icon/IconAssert.cs b/src/ImageSharp/Formats/Icon/IconAssert.cs index bcb427c1a..04a9527b9 100644 --- a/src/ImageSharp/Formats/Icon/IconAssert.cs +++ b/src/ImageSharp/Formats/Icon/IconAssert.cs @@ -32,12 +32,4 @@ internal class IconAssert return v; } - - internal static void NotSquare(in Size size) - { - if (size.Width != size.Height) - { - throw new FormatException("This image is not square."); - } - } } diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index d9a578ff2..d7fc25c69 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; -using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -30,50 +31,63 @@ internal abstract class IconDecoderCore : IImageDecoderInternals long basePosition = stream.Position; this.ReadHeader(stream); - Span flag = stackalloc byte[Png.PngConstants.HeaderBytes.Length]; + Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; + Image result = new(this.Dimensions.Width, this.Dimensions.Height); - List<(Image Image, bool IsPng, int Index)> frames = new(this.Entries.Length); for (int i = 0; i < this.Entries.Length; i++) { - _ = IconAssert.EndOfStream(stream.Seek(basePosition + this.Entries[i].ImageOffset, SeekOrigin.Begin), basePosition + this.Entries[i].ImageOffset); - _ = IconAssert.EndOfStream(stream.Read(flag), Png.PngConstants.HeaderBytes.Length); - _ = stream.Seek(-Png.PngConstants.HeaderBytes.Length, SeekOrigin.Current); + ref IconDirEntry entry = ref this.Entries[i]; - bool isPng = flag.SequenceEqual(Png.PngConstants.HeaderBytes); + // If we hit the end of the stream we should break. + if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) + { + break; + } - Image img = this.GetDecoder(isPng).Decode(stream, cancellationToken); - IconAssert.NotSquare(img.Size); - frames.Add((img, isPng, i)); - if (isPng && img.Size.Width > this.Dimensions.Width) + // There should always be enough bytes for this regardless of the entry type. + if (stream.Read(flag) != PngConstants.HeaderBytes.Length) { - this.Dimensions = img.Size; + break; } - } - ImageMetadata metadata = new(); - return new(this.Options.Configuration, metadata, frames.Select(i => - { - ImageFrame target = new(this.Options.Configuration, this.Dimensions); - ImageFrame source = i.Image.Frames.RootFrameUnsafe; + // Reset the stream position. + stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); + + bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); + using Image temp = this.GetDecoder(isPng).Decode(stream, cancellationToken); + + ImageFrame source = temp.Frames.RootFrameUnsafe; + ImageFrame target = i == 0 ? result.Frames.RootFrameUnsafe : result.Frames.CreateFrame(); + + // Draw the new frame at position 0,0. We capture the dimensions for cropping during encoding + // via the icon entry. for (int h = 0; h < source.Height; h++) { source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h)); } - if (i.IsPng) + // Copy the format specific metadata to the image. + if (isPng) { - target.Metadata.UnsafeSetFormatMetadata(Png.PngFormat.Instance, i.Image.Metadata.GetPngMetadata()); + if (i == 0) + { + result.Metadata.SetFormatMetadata(PngFormat.Instance, temp.Metadata.GetPngMetadata()); + } + + target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata()); } - else + else if (i == 0) { - target.Metadata.UnsafeSetFormatMetadata(Bmp.BmpFormat.Instance, i.Image.Metadata.GetBmpMetadata()); + // Bmp does not contain frame specific metadata. + result.Metadata.SetFormatMetadata(BmpFormat.Instance, temp.Metadata.GetBmpMetadata()); } - this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[i.Index]); + // TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry) + // so we can avoid the protected Field1 and Field2 properties and use strong typing. + this.GetFrameMetadata(target.Metadata).FromIconDirEntry(entry); + } - i.Image.Dispose(); - return target; - }).ToArray()); + return result; } public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) @@ -84,11 +98,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count]; for (int i = 0; i < frames.Length; i++) { + // TODO: Use the Identify methods in each decoder to return the + // format specific metadata for the image and frame. frames[i] = new(); IconFrameMetadata icoFrameMetadata = this.GetFrameMetadata(frames[i]); icoFrameMetadata.FromIconDirEntry(this.Entries[i]); } + // TODO: Use real values from the metadata. return new(new(32), new(0), metadata, frames); } @@ -96,6 +113,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals protected void ReadHeader(Stream stream) { + // TODO: Check length and throw if the header cannot be read. _ = Read(stream, out this.fileHeader, IconDir.Size); this.Entries = new IconDirEntry[this.FileHeader.Count]; for (int i = 0; i < this.Entries.Length; i++) @@ -103,15 +121,39 @@ internal abstract class IconDecoderCore : IImageDecoderInternals _ = Read(stream, out this.Entries[i], IconDirEntry.Size); } - this.Dimensions = new( - this.Entries.Max(i => i.Width), - this.Entries.Max(i => i.Height)); + int width = 0; + int height = 0; + foreach (IconDirEntry entry in this.Entries) + { + if (entry.Width == 0) + { + width = 256; + } + + if (entry.Height == 0) + { + height = 256; + } + + if (width == 256 && height == 256) + { + break; + } + + width = Math.Max(width, entry.Width); + height = Math.Max(height, entry.Height); + } + + this.Dimensions = new(width, height); } private static int Read(Stream stream, out T data, int size) where T : unmanaged { + // TODO: Use explicit parsing methods for each T type. + // See PngHeader.Parse() for example. Span buffer = stackalloc byte[size]; + _ = IconAssert.EndOfStream(stream.Read(buffer), size); data = MemoryMarshal.Cast(buffer)[0]; return size; @@ -121,15 +163,16 @@ internal abstract class IconDecoderCore : IImageDecoderInternals { if (isPng) { - return new Png.PngDecoderCore(this.Options); + return new PngDecoderCore(this.Options); } else { - return new Bmp.BmpDecoderCore(new() + return new BmpDecoderCore(new() { + GeneralOptions = this.Options, ProcessedAlphaMask = true, SkipFileHeader = true, - IsDoubleHeight = true, + UseDoubleHeight = true, }); } } diff --git a/src/ImageSharp/Formats/Icon/IconFrameCompression.cs b/src/ImageSharp/Formats/Icon/IconFrameCompression.cs index f6509f40c..5c772c3fe 100644 --- a/src/ImageSharp/Formats/Icon/IconFrameCompression.cs +++ b/src/ImageSharp/Formats/Icon/IconFrameCompression.cs @@ -8,11 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Icon; /// public enum IconFrameCompression { - /// - /// Unknown - /// - Unknown, - /// /// Bmp /// diff --git a/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs b/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs index 17e641c71..5c23388e7 100644 --- a/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs +++ b/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs @@ -5,6 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Icon; /// /// IconFrameMetadata +/// TODO: Delete this. Treat the individual metadata types as separate types so we can avoid Field1, Field2 and use strong typing with constaints. /// public abstract class IconFrameMetadata : IDeepCloneable { diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 0567c8916..562e47803 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -99,9 +99,9 @@ public sealed class ImageFrameMetadata : IDeepCloneable return newMeta; } - internal void UnsafeSetFormatMetadata( - IImageFormat key, - IDeepCloneable value) + internal void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) + where TFormatMetadata : class + where TFormatFrameMetadata : class, IDeepCloneable => this.formatMetadata[key] = value; /// diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 6b62be08f..d9aba6631 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -215,6 +215,9 @@ public sealed class ImageMetadata : IDeepCloneable metadata = default; return false; } + internal void SetFormatMetadata(IImageFormat key, TFormatMetadata value) + where TFormatMetadata : class, IDeepCloneable + => this.formatMetadata[key] = value; /// public ImageMetadata DeepClone() => new(this); diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs index c3c9ad1c4..f78c04cdf 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Icon.Cur; -using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -18,6 +17,10 @@ public class CurDecoderTests { using Image image = provider.GetImage(CurDecoder.Instance); - image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); + image.DebugSaveMultiFrame(provider, extension: "png"); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 6e7b35111..86f8b003b 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Icon.Ico; -using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Ico; @@ -18,6 +17,8 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSave(provider, extension: "tiff", encoder: new TiffEncoder()); + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc } } From 79f538723f6500350be073bf66bfc22d65784d56 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 14 Dec 2023 14:13:54 +0800 Subject: [PATCH 06/28] Handle frames exceeding 256 pixels --- .../Formats/Icon/IconDecoderCore.cs | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index d7fc25c69..874e93411 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -32,7 +32,8 @@ internal abstract class IconDecoderCore : IImageDecoderInternals this.ReadHeader(stream); Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - Image result = new(this.Dimensions.Width, this.Dimensions.Height); + + List<(Image Image, bool IsPng, int Index)> decodedEntries = new(this.Entries.Length); for (int i = 0; i < this.Entries.Length; i++) { @@ -54,37 +55,62 @@ internal abstract class IconDecoderCore : IImageDecoderInternals stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); - using Image temp = this.GetDecoder(isPng).Decode(stream, cancellationToken); - ImageFrame source = temp.Frames.RootFrameUnsafe; - ImageFrame target = i == 0 ? result.Frames.RootFrameUnsafe : result.Frames.CreateFrame(); + // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. + Image temp = this.GetDecoder(isPng).Decode(stream, cancellationToken); + decodedEntries.Add((temp, isPng, i)); + + // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data + // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. + this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height)); + } - // Draw the new frame at position 0,0. We capture the dimensions for cropping during encoding - // via the icon entry. - for (int h = 0; h < source.Height; h++) + ImageMetadata metadata = new(); + BmpMetadata? bmpMetadata = null; + PngMetadata? pngMetadata = null; + Image result = new(this.Options.Configuration, metadata, decodedEntries.Select(x => + { + ImageFrame target = new(this.Options.Configuration, this.Dimensions); + ImageFrame source = x.Image.Frames.RootFrameUnsafe; + for (int y = 0; y < source.Height; y++) { - source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h)); + source.PixelBuffer.DangerousGetRowSpan(y).CopyTo(target.PixelBuffer.DangerousGetRowSpan(y)); } - // Copy the format specific metadata to the image. - if (isPng) + // Copy the format specific frame metadata to the image. + if (x.IsPng) { - if (i == 0) + if (x.Index == 0) { - result.Metadata.SetFormatMetadata(PngFormat.Instance, temp.Metadata.GetPngMetadata()); + pngMetadata = x.Image.Metadata.GetPngMetadata(); } + // Bmp does not contain frame specific metadata. target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata()); } - else if (i == 0) + else if (x.Index == 0) { - // Bmp does not contain frame specific metadata. - result.Metadata.SetFormatMetadata(BmpFormat.Instance, temp.Metadata.GetBmpMetadata()); + bmpMetadata = x.Image.Metadata.GetBmpMetadata(); } // TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry) // so we can avoid the protected Field1 and Field2 properties and use strong typing. - this.GetFrameMetadata(target.Metadata).FromIconDirEntry(entry); + this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[x.Index]); + + x.Image.Dispose(); + + return target; + }).ToArray()); + + // Copy the format specific metadata to the image. + if (bmpMetadata != null) + { + result.Metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); + } + + if (pngMetadata != null) + { + result.Metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); } return result; @@ -125,6 +151,8 @@ internal abstract class IconDecoderCore : IImageDecoderInternals int height = 0; foreach (IconDirEntry entry in this.Entries) { + // Since Windows 95 size of an image in the ICONDIRENTRY structure might + // be set to zero, which means 256 pixels. if (entry.Width == 0) { width = 256; From a811784d34c87e46322f8b8e1d8b3f48ebe9e3ef Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 14 Dec 2023 14:13:55 +0800 Subject: [PATCH 07/28] Update TgaFileHeaderTests.cs --- tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs index bf24ba350..2f96b6d43 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -9,6 +9,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga; [Trait("Format", "Tga")] public class TgaFileHeaderTests { + // TODO: Some of these clash with the ICO magic bytes. Check correctness. + // https://en.wikipedia.org/wiki/Truevision_TGA#Header [Theory] [InlineData(new byte[] { 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid tga image type. [InlineData(new byte[] { 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid colormap type. From 43ea25fe8b612cf1da5713b1149ac2a078be64e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:55 +0800 Subject: [PATCH 08/28] Add Parse Method and Check Stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/IconDecoderCore.cs | 24 +++++++------------ src/ImageSharp/Formats/Icon/IconDir.cs | 15 +++++++++--- src/ImageSharp/Formats/Icon/IconDirEntry.cs | 3 +++ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 874e93411..1077bde51 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -139,12 +139,18 @@ internal abstract class IconDecoderCore : IImageDecoderInternals protected void ReadHeader(Stream stream) { - // TODO: Check length and throw if the header cannot be read. - _ = Read(stream, out this.fileHeader, IconDir.Size); + Span buffer = stackalloc byte[IconDirEntry.Size]; + + // ICONDIR + _ = IconAssert.EndOfStream(stream.Read(buffer[..IconDir.Size]), IconDir.Size); + this.fileHeader = IconDir.Parse(buffer); + + // ICONDIRENTRY this.Entries = new IconDirEntry[this.FileHeader.Count]; for (int i = 0; i < this.Entries.Length; i++) { - _ = Read(stream, out this.Entries[i], IconDirEntry.Size); + _ = IconAssert.EndOfStream(stream.Read(buffer[..IconDirEntry.Size]), IconDirEntry.Size); + this.Entries[i] = IconDirEntry.Parse(buffer); } int width = 0; @@ -175,18 +181,6 @@ internal abstract class IconDecoderCore : IImageDecoderInternals this.Dimensions = new(width, height); } - private static int Read(Stream stream, out T data, int size) - where T : unmanaged - { - // TODO: Use explicit parsing methods for each T type. - // See PngHeader.Parse() for example. - Span buffer = stackalloc byte[size]; - - _ = IconAssert.EndOfStream(stream.Read(buffer), size); - data = MemoryMarshal.Cast(buffer)[0]; - return size; - } - private IImageDecoderInternals GetDecoder(bool isPng) { if (isPng) diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs index f1281a568..53b87bc9f 100644 --- a/src/ImageSharp/Formats/Icon/IconDir.cs +++ b/src/ImageSharp/Formats/Icon/IconDir.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; @@ -15,12 +15,21 @@ internal struct IconDir public IconDir(IconFileType type) : this(type, 0) - => this.Type = type; + { + } public IconDir(IconFileType type, ushort count) + : this(0, type, count) + { + } + + public IconDir(ushort reserved, IconFileType type, ushort count) { - this.Reserved = 0; + this.Reserved = reserved; this.Type = type; this.Count = count; } + + public static IconDir Parse(in ReadOnlySpan data) + => MemoryMarshal.Cast(data)[0]; } diff --git a/src/ImageSharp/Formats/Icon/IconDirEntry.cs b/src/ImageSharp/Formats/Icon/IconDirEntry.cs index 43254f89d..edd778f7e 100644 --- a/src/ImageSharp/Formats/Icon/IconDirEntry.cs +++ b/src/ImageSharp/Formats/Icon/IconDirEntry.cs @@ -25,4 +25,7 @@ internal struct IconDirEntry public uint BytesInRes; public uint ImageOffset; + + public static IconDirEntry Parse(in ReadOnlySpan data) + => MemoryMarshal.Cast(data)[0]; } From 05d33f2895db1505940b92427b164091088cece6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:56 +0800 Subject: [PATCH 09/28] Remove IconFrameMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Cur/CurDecoderCore.cs | 2 +- .../Formats/Icon/Cur/CurFrameMetadata.cs | 90 ++++++++++++--- .../Formats/Icon/Ico/IcoDecoderCore.cs | 3 +- .../Formats/Icon/Ico/IcoFrameMetadata.cs | 80 +++++++++++--- .../Formats/Icon/IconDecoderCore.cs | 9 +- .../Formats/Icon/IconFrameMetadata.cs | 104 ------------------ 6 files changed, 140 insertions(+), 148 deletions(-) delete mode 100644 src/ImageSharp/Formats/Icon/IconFrameMetadata.cs diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs index 44fcc8fcc..afd751279 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs @@ -14,5 +14,5 @@ internal sealed class CurDecoderCore : IconDecoderCore { } - protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetCurMetadata(); + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry) => metadata.GetCurMetadata().FromIconDirEntry(entry); } diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs index 60ced16ea..2d1c22776 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs @@ -4,9 +4,9 @@ namespace SixLabors.ImageSharp.Formats.Icon.Cur; /// -/// IcoFrameMetadata. TODO: Remove base class and merge into this class. +/// IcoFrameMetadata. /// -public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable +public class CurFrameMetadata : IDeepCloneable, IDeepCloneable { /// /// Initializes a new instance of the class. @@ -15,38 +15,92 @@ public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable - /// Initializes a new instance of the class. - /// - /// metadata - public CurFrameMetadata(IconFrameMetadata metadata) - : base(metadata) - { - } - /// /// Initializes a new instance of the class. /// /// width /// height /// colorCount - /// field1 - /// field2 - public CurFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) - : base(width, height, colorCount, field1, field2) + /// hotspotX + /// hotspotY + public CurFrameMetadata(byte width, byte height, byte colorCount, ushort hotspotX, ushort hotspotY) { + this.EncodingWidth = width; + this.EncodingHeight = height; + this.ColorCount = colorCount; + this.HotspotX = hotspotX; + this.HotspotY = hotspotY; } + /// + public CurFrameMetadata(CurFrameMetadata metadata) + { + this.EncodingWidth = metadata.EncodingWidth; + this.EncodingHeight = metadata.EncodingHeight; + this.ColorCount = metadata.ColorCount; + this.HotspotX = metadata.HotspotX; + this.HotspotY = metadata.HotspotY; + this.Compression = metadata.Compression; + } + + /// + /// Gets or sets icoFrameCompression. + /// + public IconFrameCompression Compression { get; set; } + + /// + /// Gets or sets ColorCount field.
+ /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + ///
+ // TODO: BmpMetadata does not supported palette yet. + public byte ColorCount { get; set; } + /// /// Gets or sets Specifies the horizontal coordinates of the hotspot in number of pixels from the left. /// - public ushort HotspotX { get => this.Field1; set => this.Field1 = value; } + public ushort HotspotX { get; set; } /// /// Gets or sets Specifies the vertical coordinates of the hotspot in number of pixels from the top. /// - public ushort HotspotY { get => this.Field2; set => this.Field2 = value; } + public ushort HotspotY { get; set; } + + /// + /// Gets or sets Height field.
+ /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. + ///
+ public byte EncodingHeight { get; set; } + + /// + /// Gets or sets Width field.
+ /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + ///
+ public byte EncodingWidth { get; set; } + + /// + public Bmp.BmpBitsPerPixel BitsPerPixel { get; set; } = Bmp.BmpBitsPerPixel.Pixel24; /// - public override CurFrameMetadata DeepClone() => new(this); + public CurFrameMetadata DeepClone() => new(this); + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); + + internal void FromIconDirEntry(in IconDirEntry entry) + { + this.EncodingWidth = entry.Width; + this.EncodingHeight = entry.Height; + this.ColorCount = entry.ColorCount; + this.HotspotX = entry.Planes; + this.HotspotY = entry.BitCount; + } + + internal IconDirEntry ToIconDirEntry() => new() + { + Width = this.EncodingWidth, + Height = this.EncodingHeight, + ColorCount = this.ColorCount, + Planes = this.HotspotX, + BitCount = this.HotspotY, + }; } diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs index 677a9c07a..4884fdf9a 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs @@ -14,5 +14,6 @@ internal sealed class IcoDecoderCore : IconDecoderCore { } - protected override IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata) => metadata.GetIcoMetadata(); + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry) + => metadata.GetIcoMetadata().FromIconDirEntry(entry); } diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs index 4f557715d..82e3c1db3 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Icon.Ico; /// /// IcoFrameMetadata. TODO: Remove base class and merge into this class. /// -public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable, IDeepCloneable +public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable { /// /// Initializes a new instance of the class. @@ -15,34 +15,78 @@ public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable - /// Initializes a new instance of the class. - /// - /// metadata - public IcoFrameMetadata(IconFrameMetadata metadata) - : base(metadata) - { - } - /// /// Initializes a new instance of the class. /// /// width /// height /// colorCount - /// field1 - /// field2 - public IcoFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) - : base(width, height, colorCount, field1, field2) + public IcoFrameMetadata(byte width, byte height, byte colorCount) { + this.EncodingWidth = width; + this.EncodingHeight = height; + this.ColorCount = colorCount; } + /// + public IcoFrameMetadata(IcoFrameMetadata metadata) + { + this.EncodingWidth = metadata.EncodingWidth; + this.EncodingHeight = metadata.EncodingHeight; + this.ColorCount = metadata.ColorCount; + this.Compression = metadata.Compression; + } + + /// + /// Gets or sets icoFrameCompression. + /// + public IconFrameCompression Compression { get; set; } + + /// + /// Gets or sets ColorCount field.
+ /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + ///
+ // TODO: BmpMetadata does not supported palette yet. + public byte ColorCount { get; set; } + /// - /// Gets or sets the bits per pixel. - /// TODO: This needs to be constrained and calculated using the metadata returned from the png/bmp. + /// Gets or sets Height field.
+ /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. ///
- public ushort BitCount { get => this.Field2; set => this.Field2 = value; } + public byte EncodingHeight { get; set; } + + /// + /// Gets or sets Width field.
+ /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + ///
+ public byte EncodingWidth { get; set; } + + /// + public Bmp.BmpBitsPerPixel BitsPerPixel { get; set; } = Bmp.BmpBitsPerPixel.Pixel24; /// - public override IcoFrameMetadata DeepClone() => new(this); + public IcoFrameMetadata DeepClone() => new(this); + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); + + internal void FromIconDirEntry(in IconDirEntry entry) + { + this.EncodingWidth = entry.Width; + this.EncodingHeight = entry.Height; + this.ColorCount = entry.ColorCount; + } + + internal IconDirEntry ToIconDirEntry() => new() + { + Width = this.EncodingWidth, + Height = this.EncodingHeight, + ColorCount = this.ColorCount, + Planes = 1, + BitCount = this.Compression switch + { + IconFrameCompression.Bmp => (ushort)this.BitsPerPixel, + _ => 0, + }, + }; } diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 1077bde51..21c0a3bc4 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -93,9 +93,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals bmpMetadata = x.Image.Metadata.GetBmpMetadata(); } - // TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry) - // so we can avoid the protected Field1 and Field2 properties and use strong typing. - this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[x.Index]); + this.SetFrameMetadata(target.Metadata, this.Entries[x.Index]); x.Image.Dispose(); @@ -127,15 +125,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals // TODO: Use the Identify methods in each decoder to return the // format specific metadata for the image and frame. frames[i] = new(); - IconFrameMetadata icoFrameMetadata = this.GetFrameMetadata(frames[i]); - icoFrameMetadata.FromIconDirEntry(this.Entries[i]); + this.SetFrameMetadata(frames[i], this.Entries[i]); } // TODO: Use real values from the metadata. return new(new(32), new(0), metadata, frames); } - protected abstract IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata); + protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry); protected void ReadHeader(Stream stream) { diff --git a/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs b/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs deleted file mode 100644 index 5c23388e7..000000000 --- a/src/ImageSharp/Formats/Icon/IconFrameMetadata.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Icon; - -/// -/// IconFrameMetadata -/// TODO: Delete this. Treat the individual metadata types as separate types so we can avoid Field1, Field2 and use strong typing with constaints. -/// -public abstract class IconFrameMetadata : IDeepCloneable -{ - /// - /// Initializes a new instance of the class. - /// - protected IconFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// width - /// height - /// colorCount - /// field1 - /// field2 - protected IconFrameMetadata(byte width, byte height, byte colorCount, ushort field1, ushort field2) - { - this.EncodingWidth = width; - this.EncodingHeight = height; - this.ColorCount = colorCount; - this.Field1 = field1; - this.Field2 = field2; - } - - /// - protected IconFrameMetadata(IconFrameMetadata metadata) - { - this.EncodingWidth = metadata.EncodingWidth; - this.EncodingHeight = metadata.EncodingHeight; - this.ColorCount = metadata.ColorCount; - this.Field1 = metadata.Field1; - this.Field2 = metadata.Field2; - } - - /// - /// Gets or sets icoFrameCompression. - /// - public IconFrameCompression Compression { get; protected set; } - - /// - /// Gets or sets ColorCount field.
- /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. - ///
- // TODO: BmpMetadata does not supported palette yet. - public byte ColorCount { get; set; } - - /// - /// Gets or sets Planes field.
- /// In ICO format: Specifies color planes. Should be 0 or 1.
- /// In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. - ///
- protected ushort Field1 { get; set; } - - /// - /// Gets or sets BitCount field.
- /// In ICO format: Specifies bits per pixel.
- /// In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top. - ///
- protected ushort Field2 { get; set; } - - /// - /// Gets or sets Height field.
- /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. - ///
- public byte EncodingHeight { get; set; } - - /// - /// Gets or sets Width field.
- /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. - ///
- public byte EncodingWidth { get; set; } - - /// - public abstract IDeepCloneable DeepClone(); - - internal void FromIconDirEntry(in IconDirEntry metadata) - { - this.EncodingWidth = metadata.Width; - this.EncodingHeight = metadata.Height; - this.ColorCount = metadata.ColorCount; - this.Field1 = metadata.Planes; - this.Field2 = metadata.BitCount; - } - - internal IconDirEntry ToIconDirEntry() => new() - { - Width = this.EncodingWidth, - Height = this.EncodingHeight, - ColorCount = this.ColorCount, - Planes = this.Field1, - BitCount = this.Field2, - }; -} From 729d64ec757536c30c3f3ed5255f2460762206d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:56 +0800 Subject: [PATCH 10/28] Refactor SetFrameMetadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Cur/CurDecoderCore.cs | 8 ++- .../Formats/Icon/Ico/IcoDecoderCore.cs | 9 ++- .../Formats/Icon/IconDecoderCore.cs | 64 +++++++++++++------ 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs index afd751279..2ed89483a 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs @@ -14,5 +14,11 @@ internal sealed class CurDecoderCore : IconDecoderCore { } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry) => metadata.GetCurMetadata().FromIconDirEntry(entry); + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) + { + CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata(); + curFrameMetadata.FromIconDirEntry(entry); + curFrameMetadata.Compression = compression; + curFrameMetadata.BitsPerPixel = bitsPerPixel; + } } diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs index 4884fdf9a..c7841ce39 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs @@ -14,6 +14,11 @@ internal sealed class IcoDecoderCore : IconDecoderCore { } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry) - => metadata.GetIcoMetadata().FromIconDirEntry(entry); + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) + { + IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata(); + icoFrameMetadata.FromIconDirEntry(entry); + icoFrameMetadata.Compression = compression; + icoFrameMetadata.BitsPerPixel = bitsPerPixel; + } } diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 21c0a3bc4..6af9553cc 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -33,7 +33,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - List<(Image Image, bool IsPng, int Index)> decodedEntries = new(this.Entries.Length); + List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.Entries.Length); for (int i = 0; i < this.Entries.Length; i++) { @@ -58,7 +58,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. Image temp = this.GetDecoder(isPng).Decode(stream, cancellationToken); - decodedEntries.Add((temp, isPng, i)); + decodedEntries.Add((temp, isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, i)); // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. @@ -66,10 +66,10 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } ImageMetadata metadata = new(); - BmpMetadata? bmpMetadata = null; PngMetadata? pngMetadata = null; Image result = new(this.Options.Configuration, metadata, decodedEntries.Select(x => { + BmpBitsPerPixel bitsPerPixel = default; ImageFrame target = new(this.Options.Configuration, this.Dimensions); ImageFrame source = x.Image.Frames.RootFrameUnsafe; for (int y = 0; y < source.Height; y++) @@ -78,7 +78,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } // Copy the format specific frame metadata to the image. - if (x.IsPng) + if (x.Compression is IconFrameCompression.Png) { if (x.Index == 0) { @@ -88,12 +88,12 @@ internal abstract class IconDecoderCore : IImageDecoderInternals // Bmp does not contain frame specific metadata. target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata()); } - else if (x.Index == 0) + else { - bmpMetadata = x.Image.Metadata.GetBmpMetadata(); + bitsPerPixel = x.Image.Metadata.GetBmpMetadata().BitsPerPixel; } - this.SetFrameMetadata(target.Metadata, this.Entries[x.Index]); + this.SetFrameMetadata(target.Metadata, this.Entries[x.Index], x.Compression, bitsPerPixel); x.Image.Dispose(); @@ -101,11 +101,6 @@ internal abstract class IconDecoderCore : IImageDecoderInternals }).ToArray()); // Copy the format specific metadata to the image. - if (bmpMetadata != null) - { - result.Metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); - } - if (pngMetadata != null) { result.Metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); @@ -116,23 +111,56 @@ internal abstract class IconDecoderCore : IImageDecoderInternals public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { + // Stream may not at 0. + long basePosition = stream.Position; this.ReadHeader(stream); + Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; + ImageMetadata metadata = new(); ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count]; for (int i = 0; i < frames.Length; i++) { - // TODO: Use the Identify methods in each decoder to return the - // format specific metadata for the image and frame. + BmpBitsPerPixel bitsPerPixel = default; + ref IconDirEntry entry = ref this.Entries[i]; + + // If we hit the end of the stream we should break. + if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) + { + break; + } + + // There should always be enough bytes for this regardless of the entry type. + if (stream.Read(flag) != PngConstants.HeaderBytes.Length) + { + break; + } + + // Reset the stream position. + stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); + + bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); + + // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. + ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken); + frames[i] = new(); - this.SetFrameMetadata(frames[i], this.Entries[i]); + if (isPng) + { + bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel; + } + + this.SetFrameMetadata(frames[i], this.Entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel); + + // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data + // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. + this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height)); } - // TODO: Use real values from the metadata. - return new(new(32), new(0), metadata, frames); + return new(new(32), this.Dimensions, metadata, frames); } - protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry); + protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel); protected void ReadHeader(Stream stream) { From 63da967c7f8e41467243993ef64cc45079f46b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:57 +0800 Subject: [PATCH 11/28] Flatten namespace. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- src/ImageSharp/Configuration.cs | 4 ++-- .../Formats/{Icon => }/Cur/CurConfigurationModule.cs | 4 +++- src/ImageSharp/Formats/{Icon => }/Cur/CurConstants.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Cur/CurDecoder.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Cur/CurDecoderCore.cs | 5 ++--- src/ImageSharp/Formats/{Icon => }/Cur/CurFormat.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Cur/CurFrameMetadata.cs | 4 +++- src/ImageSharp/Formats/{Icon => }/Cur/CurMetadata.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Cur/MetadataExtensions.cs | 2 +- .../Formats/{Icon => }/Ico/IcoConfigurationModule.cs | 4 +++- src/ImageSharp/Formats/{Icon => }/Ico/IcoConstants.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Ico/IcoDecoder.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Ico/IcoDecoderCore.cs | 5 ++--- src/ImageSharp/Formats/{Icon => }/Ico/IcoFormat.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Ico/IcoFrameMetadata.cs | 4 +++- src/ImageSharp/Formats/{Icon => }/Ico/IcoMetadata.cs | 2 +- src/ImageSharp/Formats/{Icon => }/Ico/MetadataExtensions.cs | 2 +- tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs | 4 +--- tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs | 2 +- 19 files changed, 30 insertions(+), 26 deletions(-) rename src/ImageSharp/Formats/{Icon => }/Cur/CurConfigurationModule.cs (88%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurConstants.cs (93%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurDecoder.cs (96%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurDecoderCore.cs (84%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurFormat.cs (95%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurFrameMetadata.cs (97%) rename src/ImageSharp/Formats/{Icon => }/Cur/CurMetadata.cs (89%) rename src/ImageSharp/Formats/{Icon => }/Cur/MetadataExtensions.cs (97%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoConfigurationModule.cs (88%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoConstants.cs (95%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoDecoder.cs (96%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoDecoderCore.cs (84%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoFormat.cs (95%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoFrameMetadata.cs (97%) rename src/ImageSharp/Formats/{Icon => }/Ico/IcoMetadata.cs (89%) rename src/ImageSharp/Formats/{Icon => }/Ico/MetadataExtensions.cs (97%) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index d6cfd480d..1d9f3bb85 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -4,9 +4,9 @@ using System.Collections.Concurrent; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Icon.Cur; -using SixLabors.ImageSharp.Formats.Icon.Ico; +using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; diff --git a/src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs b/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs similarity index 88% rename from src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs rename to src/ImageSharp/Formats/Cur/CurConfigurationModule.cs index c975bc609..1c7db4bab 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurConfigurationModule.cs +++ b/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Registers the image encoders, decoders and mime type detectors for the Ico format. diff --git a/src/ImageSharp/Formats/Icon/Cur/CurConstants.cs b/src/ImageSharp/Formats/Cur/CurConstants.cs similarity index 93% rename from src/ImageSharp/Formats/Icon/Cur/CurConstants.cs rename to src/ImageSharp/Formats/Cur/CurConstants.cs index 701b40cf4..6efd2817c 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurConstants.cs +++ b/src/ImageSharp/Formats/Cur/CurConstants.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Defines constants relating to ICOs diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs b/src/ImageSharp/Formats/Cur/CurDecoder.cs similarity index 96% rename from src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs rename to src/ImageSharp/Formats/Cur/CurDecoder.cs index ceefdcaba..cbe646c47 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurDecoder.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoder.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Decoder for generating an image out of a ico encoded stream. diff --git a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs similarity index 84% rename from src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs rename to src/ImageSharp/Formats/Cur/CurDecoderCore.cs index 2ed89483a..4c0224652 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -1,11 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.Metadata; -// TODO: flatten namespace. -// namespace SixLabors.ImageSharp.Formats.Cur; -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; internal sealed class CurDecoderCore : IconDecoderCore { diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFormat.cs b/src/ImageSharp/Formats/Cur/CurFormat.cs similarity index 95% rename from src/ImageSharp/Formats/Icon/Cur/CurFormat.cs rename to src/ImageSharp/Formats/Cur/CurFormat.cs index 1e5758bc4..af93382ec 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurFormat.cs +++ b/src/ImageSharp/Formats/Cur/CurFormat.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Registers the image encoders, decoders and mime type detectors for the ICO format. diff --git a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs similarity index 97% rename from src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs rename to src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index 2d1c22776..e8f3cfe8e 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Cur; /// /// IcoFrameMetadata. diff --git a/src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs similarity index 89% rename from src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs rename to src/ImageSharp/Formats/Cur/CurMetadata.cs index ed3c322b4..5c3486d4a 100644 --- a/src/ImageSharp/Formats/Icon/Cur/CurMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Provides Ico specific metadata information for the image. diff --git a/src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs b/src/ImageSharp/Formats/Cur/MetadataExtensions.cs similarity index 97% rename from src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs rename to src/ImageSharp/Formats/Cur/MetadataExtensions.cs index 400ece648..2b410a0f9 100644 --- a/src/ImageSharp/Formats/Icon/Cur/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Cur/MetadataExtensions.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Icon.Cur; +namespace SixLabors.ImageSharp.Formats.Cur; /// /// Extension methods for the type. diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs b/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs similarity index 88% rename from src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs rename to src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs index 7074189c7..b27d91465 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoConfigurationModule.cs +++ b/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Registers the image encoders, decoders and mime type detectors for the Ico format. diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs b/src/ImageSharp/Formats/Ico/IcoConstants.cs similarity index 95% rename from src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs rename to src/ImageSharp/Formats/Ico/IcoConstants.cs index 345711705..0b963a431 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoConstants.cs +++ b/src/ImageSharp/Formats/Ico/IcoConstants.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Defines constants relating to ICOs diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs b/src/ImageSharp/Formats/Ico/IcoDecoder.cs similarity index 96% rename from src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs rename to src/ImageSharp/Formats/Ico/IcoDecoder.cs index 5d6137920..a0c8a3075 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoDecoder.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoder.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Decoder for generating an image out of a ico encoded stream. diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs similarity index 84% rename from src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs rename to src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index c7841ce39..859340fb4 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -1,11 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.Metadata; -// TODO: flatten namespace. -// namespace SixLabors.ImageSharp.Formats.Ico; -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; internal sealed class IcoDecoderCore : IconDecoderCore { diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs b/src/ImageSharp/Formats/Ico/IcoFormat.cs similarity index 95% rename from src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs rename to src/ImageSharp/Formats/Ico/IcoFormat.cs index 27b0525bf..5f89ab7f2 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoFormat.cs +++ b/src/ImageSharp/Formats/Ico/IcoFormat.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Registers the image encoders, decoders and mime type detectors for the ICO format. diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs similarity index 97% rename from src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs rename to src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 82e3c1db3..2a275d1a1 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Ico; /// /// IcoFrameMetadata. TODO: Remove base class and merge into this class. diff --git a/src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs similarity index 89% rename from src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs rename to src/ImageSharp/Formats/Ico/IcoMetadata.cs index b227d0bd6..f165bf916 100644 --- a/src/ImageSharp/Formats/Icon/Ico/IcoMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Provides Ico specific metadata information for the image. diff --git a/src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs b/src/ImageSharp/Formats/Ico/MetadataExtensions.cs similarity index 97% rename from src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs rename to src/ImageSharp/Formats/Ico/MetadataExtensions.cs index fb5b4afe7..ba21d0abc 100644 --- a/src/ImageSharp/Formats/Icon/Ico/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Ico/MetadataExtensions.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Icon.Ico; +namespace SixLabors.ImageSharp.Formats.Ico; /// /// Extension methods for the type. diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs index f78c04cdf..d1587d35b 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Icon.Cur; +using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -19,8 +19,6 @@ public class CurDecoderTests image.DebugSaveMultiFrame(provider, extension: "png"); - image.DebugSaveMultiFrame(provider, extension: "png"); - // TODO: Assert metadata, frame count, etc } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 86f8b003b..56378653a 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Icon.Ico; +using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Ico; From 794b2a6433cad5183901bd22e0c08c26126f9110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:13:58 +0800 Subject: [PATCH 12/28] Refactor IconImageFormatDetector. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/IconImageFormatDetector.cs | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs index aff8dfe0a..4fceef3b8 100644 --- a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Icon; @@ -12,22 +11,58 @@ namespace SixLabors.ImageSharp.Formats.Icon; public class IconImageFormatDetector : IImageFormatDetector { /// - public int HeaderSize { get; } = 4; + public int HeaderSize { get; } = IconDir.Size + IconDirEntry.Size; /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) { - switch (MemoryMarshal.Cast(header)[0]) + format = this.IsSupportedFileFormat(header) switch { - case Ico.IcoConstants.FileHeader: - format = Ico.IcoFormat.Instance; - return true; - case Cur.CurConstants.FileHeader: - format = Cur.CurFormat.Instance; - return true; - default: - format = default; - return false; + true => Ico.IcoFormat.Instance, + false => Cur.CurFormat.Instance, + null => default + }; + + return format is not null; + } + + private bool? IsSupportedFileFormat(ReadOnlySpan header) + { + // There are no magic bytes in the first few bytes of a tga file, + // so we try to figure out if its a valid tga by checking for valid tga header bytes. + if (header.Length < this.HeaderSize) + { + return null; + } + + IconDir dir = IconDir.Parse(header); + if (dir is not { Reserved: 0 } // Should be 0. + or not { Type: IconFileType.ICO or IconFileType.CUR } // Unknown Type. + or { Count: 0 }) // Should not be 0. + { + return null; + } + + IconDirEntry entry = IconDirEntry.Parse(header[IconDir.Size..]); + if (entry is not { Reserved: 0 } // Should be 0. + or { BytesInRes: 0 } // Should not be 0. + || entry.ImageOffset < IconDir.Size + (dir.Count * IconDirEntry.Size)) + { + return null; + } + + if (dir.Type is IconFileType.ICO) + { + if (entry is not { BitCount: 1 or 4 or 8 or 16 or 24 or 32 } or not { Planes: 0 or 1 }) + { + return null; + } + + return true; + } + else + { + return false; } } } From 746e742183e671d83c42219a69ff9ea578daeeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 14:32:56 +0800 Subject: [PATCH 13/28] Fixed errors and warnings --- src/ImageSharp/Formats/Cur/CurDecoderCore.cs | 7 +------ src/ImageSharp/Formats/Ico/IcoDecoderCore.cs | 7 +------ .../Formats/Icon/IconDecoderCore.cs | 20 +++++++++---------- src/ImageSharp/Formats/Icon/IconDir.cs | 15 ++++---------- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs index 4c0224652..538f9a2c6 100644 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -6,13 +6,8 @@ using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Cur; -internal sealed class CurDecoderCore : IconDecoderCore +internal sealed class CurDecoderCore(DecoderOptions options) : IconDecoderCore(options) { - public CurDecoderCore(DecoderOptions options) - : base(options) - { - } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) { CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata(); diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index 859340fb4..78cb0d961 100644 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -6,13 +6,8 @@ using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Ico; -internal sealed class IcoDecoderCore : IconDecoderCore +internal sealed class IcoDecoderCore(DecoderOptions options) : IconDecoderCore(options) { - public IcoDecoderCore(DecoderOptions options) - : base(options) - { - } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) { IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata(); diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 6af9553cc..983d4c346 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; @@ -10,19 +9,17 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Icon; -internal abstract class IconDecoderCore : IImageDecoderInternals +internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderInternals { private IconDir fileHeader; - public IconDecoderCore(DecoderOptions options) => this.Options = options; - - public DecoderOptions Options { get; } + public DecoderOptions Options { get; } = options; public Size Dimensions { get; private set; } protected IconDir FileHeader { get => this.fileHeader; private set => this.fileHeader = value; } - protected IconDirEntry[] Entries { get; private set; } = Array.Empty(); + protected IconDirEntry[] Entries { get; private set; } = []; public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -52,7 +49,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } // Reset the stream position. - stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); + _ = stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); @@ -86,7 +83,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } // Bmp does not contain frame specific metadata. - target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata()); + target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngMetadata()); } else { @@ -137,7 +134,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } // Reset the stream position. - stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); + _ = stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); @@ -210,7 +207,10 @@ internal abstract class IconDecoderCore : IImageDecoderInternals { if (isPng) { - return new PngDecoderCore(this.Options); + return new PngDecoderCore(new() + { + GeneralOptions = this.Options, + }); } else { diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs index 53b87bc9f..390a4de65 100644 --- a/src/ImageSharp/Formats/Icon/IconDir.cs +++ b/src/ImageSharp/Formats/Icon/IconDir.cs @@ -6,12 +6,12 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Icon; [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] -internal struct IconDir +internal struct IconDir(ushort reserved, IconFileType type, ushort count) { public const int Size = 3 * sizeof(ushort); - public ushort Reserved; - public IconFileType Type; - public ushort Count; + public ushort Reserved = reserved; + public IconFileType Type = type; + public ushort Count = count; public IconDir(IconFileType type) : this(type, 0) @@ -23,13 +23,6 @@ internal struct IconDir { } - public IconDir(ushort reserved, IconFileType type, ushort count) - { - this.Reserved = reserved; - this.Type = type; - this.Count = count; - } - public static IconDir Parse(in ReadOnlySpan data) => MemoryMarshal.Cast(data)[0]; } From dbd178392f21d3cf36d2bb2129193453acb58fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 17:34:38 +0800 Subject: [PATCH 14/28] Add encoders that cannot be used. --- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 9 +++ src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 81 ++++++++++++++++++- .../Formats/Cur/CurConfigurationModule.cs | 3 +- src/ImageSharp/Formats/Cur/CurEncoder.cs | 17 ++++ src/ImageSharp/Formats/Cur/CurEncoderCore.cs | 19 +++++ .../Formats/Ico/IcoConfigurationModule.cs | 3 +- src/ImageSharp/Formats/Ico/IcoEncoder.cs | 17 ++++ src/ImageSharp/Formats/Ico/IcoEncoderCore.cs | 19 +++++ src/ImageSharp/Formats/Icon/IconAssert.cs | 14 ++++ src/ImageSharp/Formats/Icon/IconDir.cs | 3 + src/ImageSharp/Formats/Icon/IconDirEntry.cs | 3 + .../Formats/Icon/IconEncoderCore.cs | 69 ++++++++++++++++ src/ImageSharp/Metadata/ImageMetadata.cs | 1 + .../Formats/Icon/Cur/CurEncoderTests.cs | 32 ++++++++ .../Formats/Icon/Ico/IcoEncoderTests.cs | 32 ++++++++ 15 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/Cur/CurEncoder.cs create mode 100644 src/ImageSharp/Formats/Cur/CurEncoderCore.cs create mode 100644 src/ImageSharp/Formats/Ico/IcoEncoder.cs create mode 100644 src/ImageSharp/Formats/Ico/IcoEncoderCore.cs create mode 100644 src/ImageSharp/Formats/Icon/IconEncoderCore.cs create mode 100644 tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 0081f6a1a..0be243f9a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -29,6 +29,15 @@ public sealed class BmpEncoder : QuantizingImageEncoder /// public bool SupportTransparency { get; init; } + /// + internal bool ProcessedAlphaMask { get; init; } + + /// + internal bool SkipFileHeader { get; init; } + + /// + internal bool UseDoubleHeight { get; init; } + /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 076d1adf0..b888fa400 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Buffers.Binary; +using System.IO; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; @@ -11,6 +12,7 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using static System.Net.Mime.MediaTypeNames; namespace SixLabors.ImageSharp.Formats.Bmp; @@ -92,6 +94,15 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals /// private readonly IPixelSamplingStrategy pixelSamplingStrategy; + /// + private readonly bool processedAlphaMask; + + /// + private readonly bool skipFileHeader; + + /// + private readonly bool isDoubleHeight; + /// /// Initializes a new instance of the class. /// @@ -104,6 +115,9 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; + this.processedAlphaMask = encoder.ProcessedAlphaMask; + this.skipFileHeader = encoder.SkipFileHeader; + this.isDoubleHeight = encoder.UseDoubleHeight; } /// @@ -154,11 +168,23 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals _ => BmpInfoHeader.SizeV3 }; - BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData); + // for ico/cur encoder. + int height = image.Height; + if (this.isDoubleHeight) + { + height <<= 1; + } + + BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData); Span buffer = stackalloc byte[infoHeaderSize]; - WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); + // for ico/cur encoder. + if (!this.skipFileHeader) + { + WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); + } + this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); this.WriteImage(configuration, stream, image); WriteColorProfile(stream, iccProfileData, buffer); @@ -455,6 +481,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals { this.Write8BitColor(configuration, stream, image, colorPalette); } + + if (this.processedAlphaMask) + { + ProcessedAlphaMask(stream, image); + } } /// @@ -572,6 +603,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } + + if (this.processedAlphaMask) + { + ProcessedAlphaMask(stream, image); + } } /// @@ -629,6 +665,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } + + if (this.processedAlphaMask) + { + ProcessedAlphaMask(stream, image); + } } /// @@ -679,6 +720,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } + + if (this.processedAlphaMask) + { + ProcessedAlphaMask(stream, image); + } } /// @@ -722,4 +768,35 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(indices); } + + private static void ProcessedAlphaMask(Stream stream, Image image) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + int arrayWidth = image.Width / 8; + int padding = arrayWidth % 4; + if (padding is not 0) + { + padding = 4 - padding; + } + + Span mask = stackalloc byte[arrayWidth]; + for (int y = image.Height - 1; y >= 0; y--) + { + mask.Clear(); + for (int x = 0; x < image.Width; x++) + { + int bit = x % 8; + int i = x / 8; + TPixel pixel = image[x, y]; + pixel.ToRgba32(ref rgba); + if (rgba.A is not 0) + { + mask[i] &= unchecked((byte)(0b10000000 >> bit)); + } + } + + stream.Write(mask); + } + } } diff --git a/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs b/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs index 1c7db4bab..879b3f112 100644 --- a/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs +++ b/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs @@ -13,8 +13,7 @@ public sealed class CurConfigurationModule : IImageFormatConfigurationModule /// public void Configure(Configuration configuration) { - // TODO: CurEncoder - // configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder()); + configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder()); configuration.ImageFormatsManager.SetDecoder(CurFormat.Instance, CurDecoder.Instance); configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); } diff --git a/src/ImageSharp/Formats/Cur/CurEncoder.cs b/src/ImageSharp/Formats/Cur/CurEncoder.cs new file mode 100644 index 000000000..d237fe7d0 --- /dev/null +++ b/src/ImageSharp/Formats/Cur/CurEncoder.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Cur; + +/// +/// Image encoder for writing an image to a stream as a Windows Cursor. +/// +public sealed class CurEncoder : QuantizingImageEncoder +{ + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + { + CurEncoderCore encoderCore = new(); + encoderCore.Encode(image, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs new file mode 100644 index 000000000..0cf8c97a5 --- /dev/null +++ b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Cur; + +internal sealed class CurEncoderCore : IconEncoderCore +{ + protected override void GetHeader(in Image image) + { + this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count); + this.Entries = image.Frames.Select(i => + { + CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); + return metadata.ToIconDirEntry(); + }).ToArray(); + } +} diff --git a/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs b/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs index b27d91465..224aaa31e 100644 --- a/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs +++ b/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs @@ -13,8 +13,7 @@ public sealed class IcoConfigurationModule : IImageFormatConfigurationModule /// public void Configure(Configuration configuration) { - // TODO: IcoEncoder - // configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder()); + configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder()); configuration.ImageFormatsManager.SetDecoder(IcoFormat.Instance, IcoDecoder.Instance); configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); } diff --git a/src/ImageSharp/Formats/Ico/IcoEncoder.cs b/src/ImageSharp/Formats/Ico/IcoEncoder.cs new file mode 100644 index 000000000..0668a7e23 --- /dev/null +++ b/src/ImageSharp/Formats/Ico/IcoEncoder.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Ico; + +/// +/// Image encoder for writing an image to a stream as a Windows Icon. +/// +public sealed class IcoEncoder : QuantizingImageEncoder +{ + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + { + IcoEncoderCore encoderCore = new(); + encoderCore.Encode(image, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs new file mode 100644 index 000000000..6c2f3abe4 --- /dev/null +++ b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Icon; + +namespace SixLabors.ImageSharp.Formats.Ico; + +internal sealed class IcoEncoderCore : IconEncoderCore +{ + protected override void GetHeader(in Image image) + { + this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count); + this.Entries = image.Frames.Select(i => + { + IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); + return metadata.ToIconDirEntry(); + }).ToArray(); + } +} diff --git a/src/ImageSharp/Formats/Icon/IconAssert.cs b/src/ImageSharp/Formats/Icon/IconAssert.cs index 04a9527b9..547b6a6eb 100644 --- a/src/ImageSharp/Formats/Icon/IconAssert.cs +++ b/src/ImageSharp/Formats/Icon/IconAssert.cs @@ -32,4 +32,18 @@ internal class IconAssert return v; } + + internal static byte Is1ByteSize(int i) + { + if (i is 256) + { + return 0; + } + else if (i > byte.MaxValue) + { + throw new FormatException("Image size Too Large."); + } + + return (byte)i; + } } diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs index 390a4de65..aa583ee1e 100644 --- a/src/ImageSharp/Formats/Icon/IconDir.cs +++ b/src/ImageSharp/Formats/Icon/IconDir.cs @@ -25,4 +25,7 @@ internal struct IconDir(ushort reserved, IconFileType type, ushort count) public static IconDir Parse(in ReadOnlySpan data) => MemoryMarshal.Cast(data)[0]; + + public unsafe void WriteTo(in Stream stream) + => stream.Write(MemoryMarshal.Cast([this])); } diff --git a/src/ImageSharp/Formats/Icon/IconDirEntry.cs b/src/ImageSharp/Formats/Icon/IconDirEntry.cs index edd778f7e..7a8e09e37 100644 --- a/src/ImageSharp/Formats/Icon/IconDirEntry.cs +++ b/src/ImageSharp/Formats/Icon/IconDirEntry.cs @@ -28,4 +28,7 @@ internal struct IconDirEntry public static IconDirEntry Parse(in ReadOnlySpan data) => MemoryMarshal.Cast(data)[0]; + + public unsafe void WriteTo(in Stream stream) + => stream.Write(MemoryMarshal.Cast([this])); } diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs new file mode 100644 index 000000000..05a657b51 --- /dev/null +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Icon; + +internal abstract class IconEncoderCore : IImageEncoderInternals +{ + protected IconDir FileHeader { get; set; } + + protected IconDirEntry[]? Entries { get; set; } + + public void Encode( + Image image, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + IconAssert.CanSeek(stream); + + // Stream may not at 0. + long basePosition = stream.Position; + this.GetHeader(image); + + int dataOffset = IconDir.Size + (IconDirEntry.Size * this.Entries.Length); + _ = stream.Seek(dataOffset, SeekOrigin.Current); + + for (int i = 0; i < image.Frames.Count; i++) + { + ImageFrame frame = image.Frames[i]; + this.Entries[i].ImageOffset = (uint)stream.Position; + Image img = new(Configuration.Default, frame.PixelBuffer, new()); + + // Note: this encoder are not supported PNG Data. + BmpEncoder encoder = new() + { + ProcessedAlphaMask = true, + UseDoubleHeight = true, + SkipFileHeader = true, + SupportTransparency = false, + BitsPerPixel = this.Entries[i].BitCount is 0 + ? BmpBitsPerPixel.Pixel8 + : (BmpBitsPerPixel?)this.Entries[i].BitCount + }; + + encoder.Encode(img, stream); + this.Entries[i].BytesInRes = this.Entries[i].ImageOffset - (uint)stream.Position; + } + + long endPosition = stream.Position; + _ = stream.Seek(basePosition, SeekOrigin.Begin); + this.FileHeader.WriteTo(stream); + foreach (IconDirEntry entry in this.Entries) + { + entry.WriteTo(stream); + } + + _ = stream.Seek(endPosition, SeekOrigin.Begin); + } + + [MemberNotNull(nameof(Entries))] + protected abstract void GetHeader(in Image image); +} diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index d9aba6631..e811cc1f7 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -215,6 +215,7 @@ public sealed class ImageMetadata : IDeepCloneable metadata = default; return false; } + internal void SetFormatMetadata(IImageFormat key, TFormatMetadata value) where TFormatMetadata : class, IDeepCloneable => this.formatMetadata[key] = value; diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs new file mode 100644 index 000000000..9908786f1 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Cur; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.TestImages.Cur; + +namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; + +[Trait("Format", "Cur")] +public class CurEncoderTests +{ + private static CurEncoder CurEncoder => new(); + + public static readonly TheoryData Files = new() + { + { WindowsMouse }, + }; + + [Theory] + [MemberData(nameof(Files))] + public void Encode(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using MemoryStream memStream = new(); + input.Save(memStream, CurEncoder); + + memStream.Seek(0, SeekOrigin.Begin); + CurDecoder.Instance.Decode(new(), memStream); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs new file mode 100644 index 000000000..9a239bdd4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.TestImages.Ico; + +namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; + +[Trait("Format", "Icon")] +public class IcoEncoderTests +{ + private static IcoEncoder CurEncoder => new(); + + public static readonly TheoryData Files = new() + { + { Flutter }, + }; + + [Theory] + [MemberData(nameof(Files))] + public void Encode(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using MemoryStream memStream = new(); + input.Save(memStream, CurEncoder); + + memStream.Seek(0, SeekOrigin.Begin); + IcoDecoder.Instance.Decode(new(), memStream); + } +} From 54e87ab63ace1daa546f0476408fb84ab1557967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 19:43:12 +0800 Subject: [PATCH 15/28] Remove a comment --- src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs index 4fceef3b8..b66b6c79f 100644 --- a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs @@ -38,7 +38,7 @@ public class IconImageFormatDetector : IImageFormatDetector IconDir dir = IconDir.Parse(header); if (dir is not { Reserved: 0 } // Should be 0. or not { Type: IconFileType.ICO or IconFileType.CUR } // Unknown Type. - or { Count: 0 }) // Should not be 0. + or { Count: 0 }) { return null; } From 21b1e71062915603e16792b88e5cb528d5444d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 21:47:27 +0800 Subject: [PATCH 16/28] Encoder Worked! but AlphaMask have some problems --- src/ImageSharp/Formats/Cur/CurEncoderCore.cs | 11 +-- .../Formats/Cur/MetadataExtensions.cs | 5 +- src/ImageSharp/Formats/Ico/IcoEncoderCore.cs | 11 +-- .../Formats/Ico/IcoFrameMetadata.cs | 3 +- .../Formats/Ico/MetadataExtensions.cs | 5 +- .../Formats/Icon/IconEncoderCore.cs | 97 ++++++++++++++----- 6 files changed, 84 insertions(+), 48 deletions(-) diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs index 0cf8c97a5..3a7288b71 100644 --- a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs @@ -5,15 +5,6 @@ using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Cur; -internal sealed class CurEncoderCore : IconEncoderCore +internal sealed class CurEncoderCore() : IconEncoderCore(IconFileType.CUR) { - protected override void GetHeader(in Image image) - { - this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count); - this.Entries = image.Frames.Select(i => - { - CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); - return metadata.ToIconDirEntry(); - }).ToArray(); - } } diff --git a/src/ImageSharp/Formats/Cur/MetadataExtensions.cs b/src/ImageSharp/Formats/Cur/MetadataExtensions.cs index 2b410a0f9..6394c564b 100644 --- a/src/ImageSharp/Formats/Cur/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Cur/MetadataExtensions.cs @@ -2,14 +2,15 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Cur; +namespace SixLabors.ImageSharp; /// /// Extension methods for the type. /// -public static class MetadataExtensions +public static partial class MetadataExtensions { /// /// Gets the Icon format specific metadata for the image. diff --git a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs index 6c2f3abe4..12ced58fd 100644 --- a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs @@ -5,15 +5,6 @@ using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Ico; -internal sealed class IcoEncoderCore : IconEncoderCore +internal sealed class IcoEncoderCore() : IconEncoderCore(IconFileType.ICO) { - protected override void GetHeader(in Image image) - { - this.FileHeader = new(IconFileType.ICO, (ushort)image.Frames.Count); - this.Entries = image.Frames.Select(i => - { - IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); - return metadata.ToIconDirEntry(); - }).ToArray(); - } } diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 2a275d1a1..8d7eb17b5 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -88,7 +88,8 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable BitCount = this.Compression switch { IconFrameCompression.Bmp => (ushort)this.BitsPerPixel, - _ => 0, + IconFrameCompression.Png => 32, + _ => throw new NotSupportedException($"Value: {this.Compression}"), }, }; } diff --git a/src/ImageSharp/Formats/Ico/MetadataExtensions.cs b/src/ImageSharp/Formats/Ico/MetadataExtensions.cs index ba21d0abc..497375f99 100644 --- a/src/ImageSharp/Formats/Ico/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Ico/MetadataExtensions.cs @@ -2,14 +2,15 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Ico; +namespace SixLabors.ImageSharp; /// /// Extension methods for the type. /// -public static class MetadataExtensions +public static partial class MetadataExtensions { /// /// Gets the Ico format specific metadata for the image. diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs index 05a657b51..b4d563d4b 100644 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -2,16 +2,18 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Cur; +using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Icon; -internal abstract class IconEncoderCore : IImageEncoderInternals +internal abstract class IconEncoderCore(IconFileType iconFileType) + : IImageEncoderInternals { - protected IconDir FileHeader { get; set; } + private IconDir fileHeader; - protected IconDirEntry[]? Entries { get; set; } + private IconFrameMetadata[]? entries; public void Encode( Image image, @@ -26,44 +28,93 @@ internal abstract class IconEncoderCore : IImageEncoderInternals // Stream may not at 0. long basePosition = stream.Position; - this.GetHeader(image); + this.InitHeader(image); - int dataOffset = IconDir.Size + (IconDirEntry.Size * this.Entries.Length); + int dataOffset = IconDir.Size + (IconDirEntry.Size * this.entries.Length); _ = stream.Seek(dataOffset, SeekOrigin.Current); for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - this.Entries[i].ImageOffset = (uint)stream.Position; - Image img = new(Configuration.Default, frame.PixelBuffer, new()); + int width = this.entries[i].Entry.Width; + if (width is 0) + { + width = frame.Width; + } + + int height = this.entries[i].Entry.Height; + if (height is 0) + { + height = frame.Height; + } + + this.entries[i].Entry.ImageOffset = (uint)stream.Position; - // Note: this encoder are not supported PNG Data. - BmpEncoder encoder = new() + Image img = new(width, height); + for (int y = 0; y < height; y++) { - ProcessedAlphaMask = true, - UseDoubleHeight = true, - SkipFileHeader = true, - SupportTransparency = false, - BitsPerPixel = this.Entries[i].BitCount is 0 - ? BmpBitsPerPixel.Pixel8 - : (BmpBitsPerPixel?)this.Entries[i].BitCount + frame.PixelBuffer.DangerousGetRowSpan(y)[..width].CopyTo(img.GetRootFramePixelBuffer().DangerousGetRowSpan(y)); + } + + QuantizingImageEncoder encoder = this.entries[i].Compression switch + { + IconFrameCompression.Bmp => new Bmp.BmpEncoder() + { + ProcessedAlphaMask = true, + UseDoubleHeight = true, + SkipFileHeader = true, + SupportTransparency = false, + BitsPerPixel = iconFileType is IconFileType.ICO + ? (Bmp.BmpBitsPerPixel?)this.entries[i].Entry.BitCount + : Bmp.BmpBitsPerPixel.Pixel24 // TODO: Here you need to switch to selecting the corresponding value according to the size of the image + }, + IconFrameCompression.Png => new Png.PngEncoder(), + _ => throw new NotSupportedException(), }; encoder.Encode(img, stream); - this.Entries[i].BytesInRes = this.Entries[i].ImageOffset - (uint)stream.Position; + this.entries[i].Entry.BytesInRes = (uint)stream.Position - this.entries[i].Entry.ImageOffset; } long endPosition = stream.Position; _ = stream.Seek(basePosition, SeekOrigin.Begin); - this.FileHeader.WriteTo(stream); - foreach (IconDirEntry entry in this.Entries) + this.fileHeader.WriteTo(stream); + foreach (IconFrameMetadata frame in this.entries) { - entry.WriteTo(stream); + frame.Entry.WriteTo(stream); } _ = stream.Seek(endPosition, SeekOrigin.Begin); } - [MemberNotNull(nameof(Entries))] - protected abstract void GetHeader(in Image image); + [MemberNotNull(nameof(entries))] + private void InitHeader(in Image image) + { + this.fileHeader = new(iconFileType, (ushort)image.Frames.Count); + this.entries = iconFileType switch + { + IconFileType.ICO => + image.Frames.Select(i => + { + IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); + return new IconFrameMetadata(metadata.Compression, metadata.ToIconDirEntry()); + }).ToArray(), + IconFileType.CUR => + image.Frames.Select(i => + { + CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); + return new IconFrameMetadata(metadata.Compression, metadata.ToIconDirEntry()); + }).ToArray(), + _ => throw new NotSupportedException(), + }; + } + + internal sealed class IconFrameMetadata(IconFrameCompression compression, IconDirEntry iconDirEntry) + { + private IconDirEntry iconDirEntry = iconDirEntry; + + public IconFrameCompression Compression { get; set; } = compression; + + public ref IconDirEntry Entry => ref this.iconDirEntry; + } } From e252a6f223120357fe00cc9c3aa842d250b131ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 21:48:34 +0800 Subject: [PATCH 17/28] Using Seek requires consideration of whether this stream allows Seek and the case where the stream start position is not 0. --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b888fa400..dcccc8363 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -133,6 +133,9 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + // Stream may not at 0. + long basePosition = stream.Position; + Configuration configuration = image.Configuration; ImageMetadata metadata = image.Metadata; BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); @@ -187,7 +190,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); this.WriteImage(configuration, stream, image); - WriteColorProfile(stream, iccProfileData, buffer); + WriteColorProfile(stream, iccProfileData, buffer, basePosition); stream.Flush(); } @@ -271,16 +274,20 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals /// The stream to write to. /// The color profile data. /// The buffer. - private static void WriteColorProfile(Stream stream, byte[]? iccProfileData, Span buffer) + /// The Stream may not be start with 0. + private static void WriteColorProfile(Stream stream, byte[]? iccProfileData, Span buffer, long basePosition) { if (iccProfileData != null) { // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size; stream.Write(iccProfileData); + long position = stream.Position; // Storage Position BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData); - stream.Position = BmpFileHeader.Size + 112; + _ = stream.Seek(basePosition, SeekOrigin.Begin); + _ = stream.Seek(BmpFileHeader.Size + 112, SeekOrigin.Current); stream.Write(buffer[..4]); + _ = stream.Seek(position, SeekOrigin.Begin); // Reset Position } } From fae15ae88aa1b8131076159b199f51c1d32ac70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 22:51:52 +0800 Subject: [PATCH 18/28] Fixed AlphaMask --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 55 +++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index dcccc8363..fe1c9e0be 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -3,16 +3,13 @@ using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using static System.Net.Mime.MediaTypeNames; namespace SixLabors.ImageSharp.Formats.Bmp; @@ -380,6 +377,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals this.Write1BitPixelData(configuration, stream, image); break; } + + if (this.processedAlphaMask) + { + ProcessedAlphaMask(stream, image); + } } private IMemoryOwner AllocateRow(int width, int bytesPerPixel) @@ -488,11 +490,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals { this.Write8BitColor(configuration, stream, image, colorPalette); } - - if (this.processedAlphaMask) - { - ProcessedAlphaMask(stream, image); - } } /// @@ -610,11 +607,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } - - if (this.processedAlphaMask) - { - ProcessedAlphaMask(stream, image); - } } /// @@ -672,11 +664,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } - - if (this.processedAlphaMask) - { - ProcessedAlphaMask(stream, image); - } } /// @@ -727,11 +714,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals stream.WriteByte(0); } } - - if (this.processedAlphaMask) - { - ProcessedAlphaMask(stream, image); - } } /// @@ -779,7 +761,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals private static void ProcessedAlphaMask(Stream stream, Image image) where TPixel : unmanaged, IPixel { - Rgba32 rgba = default; int arrayWidth = image.Width / 8; int padding = arrayWidth % 4; if (padding is not 0) @@ -791,19 +772,31 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals for (int y = image.Height - 1; y >= 0; y--) { mask.Clear(); - for (int x = 0; x < image.Width; x++) + Span row = image.GetRootFramePixelBuffer().DangerousGetRowSpan(y); + + for (int i = 0; i < arrayWidth; i++) { - int bit = x % 8; - int i = x / 8; - TPixel pixel = image[x, y]; - pixel.ToRgba32(ref rgba); - if (rgba.A is not 0) + int x = i * 8; + + for (int j = 0; j < 8; j++) { - mask[i] &= unchecked((byte)(0b10000000 >> bit)); + WriteAlphaMask(row[x + j], ref mask[i], j); } } stream.Write(mask); + stream.Skip(padding); + } + } + + private static void WriteAlphaMask(in TPixel pixel, ref byte mask, in int index) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + if (rgba.A is 0) + { + mask |= unchecked((byte)(0b10000000 >> index)); } } } From 52f392db568bf27cbe127ae1812f6d05a9ab7df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Thu, 14 Dec 2023 22:58:15 +0800 Subject: [PATCH 19/28] Fixed some warnings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/IconDecoderCore.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 983d4c346..a502b91c9 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; @@ -12,15 +13,12 @@ namespace SixLabors.ImageSharp.Formats.Icon; internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderInternals { private IconDir fileHeader; + private IconDirEntry[]? entries; public DecoderOptions Options { get; } = options; public Size Dimensions { get; private set; } - protected IconDir FileHeader { get => this.fileHeader; private set => this.fileHeader = value; } - - protected IconDirEntry[] Entries { get; private set; } = []; - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -30,11 +28,11 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.Entries.Length); + List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.entries.Length); - for (int i = 0; i < this.Entries.Length; i++) + for (int i = 0; i < this.entries.Length; i++) { - ref IconDirEntry entry = ref this.Entries[i]; + ref IconDirEntry entry = ref this.entries[i]; // If we hit the end of the stream we should break. if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) @@ -90,7 +88,7 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI bitsPerPixel = x.Image.Metadata.GetBmpMetadata().BitsPerPixel; } - this.SetFrameMetadata(target.Metadata, this.Entries[x.Index], x.Compression, bitsPerPixel); + this.SetFrameMetadata(target.Metadata, this.entries[x.Index], x.Compression, bitsPerPixel); x.Image.Dispose(); @@ -115,11 +113,11 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; ImageMetadata metadata = new(); - ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count]; + ImageFrameMetadata[] frames = new ImageFrameMetadata[this.fileHeader.Count]; for (int i = 0; i < frames.Length; i++) { BmpBitsPerPixel bitsPerPixel = default; - ref IconDirEntry entry = ref this.Entries[i]; + ref IconDirEntry entry = ref this.entries[i]; // If we hit the end of the stream we should break. if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) @@ -147,7 +145,7 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel; } - this.SetFrameMetadata(frames[i], this.Entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel); + this.SetFrameMetadata(frames[i], this.entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel); // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. @@ -159,6 +157,7 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel); + [MemberNotNull(nameof(entries))] protected void ReadHeader(Stream stream) { Span buffer = stackalloc byte[IconDirEntry.Size]; @@ -168,16 +167,16 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI this.fileHeader = IconDir.Parse(buffer); // ICONDIRENTRY - this.Entries = new IconDirEntry[this.FileHeader.Count]; - for (int i = 0; i < this.Entries.Length; i++) + this.entries = new IconDirEntry[this.fileHeader.Count]; + for (int i = 0; i < this.entries.Length; i++) { _ = IconAssert.EndOfStream(stream.Read(buffer[..IconDirEntry.Size]), IconDirEntry.Size); - this.Entries[i] = IconDirEntry.Parse(buffer); + this.entries[i] = IconDirEntry.Parse(buffer); } int width = 0; int height = 0; - foreach (IconDirEntry entry in this.Entries) + foreach (IconDirEntry entry in this.entries) { // Since Windows 95 size of an image in the ICONDIRENTRY structure might // be set to zero, which means 256 pixels. From 5be5d64882e5e85fed199cbc621bcd16734b008b Mon Sep 17 00:00:00 2001 From: Poker Date: Thu, 14 Mar 2024 12:17:44 +0800 Subject: [PATCH 20/28] fix --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 4 ++-- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 568eea00a..11c6aef29 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -898,7 +898,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals { int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - image[newY, newX].FromBgr24(Unsafe.As(ref colors[colorIndex])); + image[newY, newX] = Bgra32.FromBgr24(Unsafe.As(ref colors[colorIndex])); } offset++; @@ -942,7 +942,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals for (int x = 0; x < width; x++) { - pixelRow[x].FromBgra32(image[newY, x]); + pixelRow[x] = TPixel.FromBgra32(image[newY, x]); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index fe1c9e0be..f84e4f9c2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -792,8 +792,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals private static void WriteAlphaMask(in TPixel pixel, ref byte mask, in int index) where TPixel : unmanaged, IPixel { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + Rgba32 rgba = pixel.ToRgba32(); if (rgba.A is 0) { mask |= unchecked((byte)(0b10000000 >> index)); From 6beeba1575ed58dbde57332714c639a2d1451611 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Jun 2024 21:41:08 +1000 Subject: [PATCH 21/28] Cleanup and fix issues --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 9 ++ src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 + src/ImageSharp/Formats/Bmp/BmpMetadata.cs | 14 +++- src/ImageSharp/Formats/Cur/CurConstants.cs | 25 ++++-- src/ImageSharp/Formats/Cur/CurDecoderCore.cs | 12 ++- src/ImageSharp/Formats/Cur/CurEncoderCore.cs | 6 +- .../Formats/Cur/CurFrameMetadata.cs | 83 ++++++++----------- src/ImageSharp/Formats/Ico/IcoConstants.cs | 11 +-- src/ImageSharp/Formats/Ico/IcoDecoderCore.cs | 12 ++- src/ImageSharp/Formats/Ico/IcoEncoder.cs | 4 +- src/ImageSharp/Formats/Ico/IcoEncoderCore.cs | 6 +- .../Formats/Ico/IcoFrameMetadata.cs | 76 +++++++---------- src/ImageSharp/Formats/Icon/IconAssert.cs | 32 ------- .../Formats/Icon/IconDecoderCore.cs | 44 ++++++---- src/ImageSharp/Formats/Icon/IconDir.cs | 16 +++- src/ImageSharp/Formats/Icon/IconDirEntry.cs | 28 ++++++- .../Formats/Icon/IconEncoderCore.cs | 73 +++++++++++----- .../Formats/Icon/IconImageFormatDetector.cs | 6 +- 18 files changed, 263 insertions(+), 196 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 11c6aef29..c26536fd1 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -1599,6 +1600,14 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals } } + if (palette.Length > 0) + { + Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf()]; + ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette); + Color.FromPixel(rgbTable, colorTable); + this.bmpMetadata.ColorTable = colorTable; + } + int skipAmount = 0; if (this.fileHeader.HasValue) { diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index f84e4f9c2..151da1828 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -109,6 +109,8 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals { this.memoryAllocator = memoryAllocator; this.bitsPerPixel = encoder.BitsPerPixel; + + // TODO: Use a palette quantizer if supplied. this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index a2ed1d21d..a50023b27 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Bmp; @@ -23,6 +23,11 @@ public class BmpMetadata : IDeepCloneable { this.BitsPerPixel = other.BitsPerPixel; this.InfoHeaderType = other.InfoHeaderType; + + if (other.ColorTable?.Length > 0) + { + this.ColorTable = other.ColorTable.Value.ToArray(); + } } /// @@ -35,8 +40,11 @@ public class BmpMetadata : IDeepCloneable /// public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + /// + /// Gets or sets the color table, if any. + /// + public ReadOnlyMemory? ColorTable { get; set; } + /// public IDeepCloneable DeepClone() => new BmpMetadata(this); - - // TODO: Colors used once we support encoding palette bmps. } diff --git a/src/ImageSharp/Formats/Cur/CurConstants.cs b/src/ImageSharp/Formats/Cur/CurConstants.cs index 6efd2817c..7abf4c812 100644 --- a/src/ImageSharp/Formats/Cur/CurConstants.cs +++ b/src/ImageSharp/Formats/Cur/CurConstants.cs @@ -9,20 +9,31 @@ namespace SixLabors.ImageSharp.Formats.Cur; internal static class CurConstants { /// - /// The list of mimetypes that equate to a ico. + /// The list of mime types that equate to a cur. /// /// /// See /// - public static readonly IEnumerable MimeTypes = new[] - { - "application/octet-stream", - }; + public static readonly IEnumerable MimeTypes = + [ + + // IANA-registered + "image/vnd.microsoft.icon", + + // ICO & CUR types used by Windows + "image/x-icon", + + // Erroneous types but have been used + "image/ico", + "image/icon", + "text/ico", + "application/ico", + ]; /// - /// The list of file extensions that equate to a ico. + /// The list of file extensions that equate to a cur. /// - public static readonly IEnumerable FileExtensions = new[] { "cur" }; + public static readonly IEnumerable FileExtensions = ["cur"]; public const uint FileHeader = 0x00_02_00_00; } diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs index 538f9a2c6..18ab8c75a 100644 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -1,18 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Cur; -internal sealed class CurDecoderCore(DecoderOptions options) : IconDecoderCore(options) +internal sealed class CurDecoderCore : IconDecoderCore { - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) + public CurDecoderCore(DecoderOptions options) + : base(options) + { + } + + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel) { CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata(); curFrameMetadata.FromIconDirEntry(entry); curFrameMetadata.Compression = compression; - curFrameMetadata.BitsPerPixel = bitsPerPixel; + curFrameMetadata.BmpBitsPerPixel = bitsPerPixel; } } diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs index 3a7288b71..a6922d431 100644 --- a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs @@ -5,6 +5,10 @@ using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Cur; -internal sealed class CurEncoderCore() : IconEncoderCore(IconFileType.CUR) +internal sealed class CurEncoderCore : IconEncoderCore { + public CurEncoderCore() + : base(IconFileType.CUR) + { + } } diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index e8f3cfe8e..fc5cc5b2c 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Cur; @@ -17,70 +18,48 @@ public class CurFrameMetadata : IDeepCloneable, IDeepCloneable { } - /// - /// Initializes a new instance of the class. - /// - /// width - /// height - /// colorCount - /// hotspotX - /// hotspotY - public CurFrameMetadata(byte width, byte height, byte colorCount, ushort hotspotX, ushort hotspotY) - { - this.EncodingWidth = width; - this.EncodingHeight = height; - this.ColorCount = colorCount; - this.HotspotX = hotspotX; - this.HotspotY = hotspotY; - } - - /// - public CurFrameMetadata(CurFrameMetadata metadata) + private CurFrameMetadata(CurFrameMetadata metadata) { - this.EncodingWidth = metadata.EncodingWidth; - this.EncodingHeight = metadata.EncodingHeight; - this.ColorCount = metadata.ColorCount; + this.Compression = metadata.Compression; this.HotspotX = metadata.HotspotX; this.HotspotY = metadata.HotspotY; - this.Compression = metadata.Compression; + this.EncodingWidth = metadata.EncodingWidth; + this.EncodingHeight = metadata.EncodingHeight; + this.BmpBitsPerPixel = metadata.BmpBitsPerPixel; } /// - /// Gets or sets icoFrameCompression. + /// Gets or sets the frame compressions format. /// public IconFrameCompression Compression { get; set; } /// - /// Gets or sets ColorCount field.
- /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + /// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. ///
- // TODO: BmpMetadata does not supported palette yet. - public byte ColorCount { get; set; } + public ushort HotspotX { get; set; } /// - /// Gets or sets Specifies the horizontal coordinates of the hotspot in number of pixels from the left. + /// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. /// - public ushort HotspotX { get; set; } + public ushort HotspotY { get; set; } /// - /// Gets or sets Specifies the vertical coordinates of the hotspot in number of pixels from the top. + /// Gets or sets the encoding width.
+ /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. ///
- public ushort HotspotY { get; set; } + public byte EncodingWidth { get; set; } /// - /// Gets or sets Height field.
- /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. + /// Gets or sets the encoding height.
+ /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. ///
public byte EncodingHeight { get; set; } /// - /// Gets or sets Width field.
- /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + /// Gets or sets the number of bits per pixel.
+ /// Used when is ///
- public byte EncodingWidth { get; set; } - - /// - public Bmp.BmpBitsPerPixel BitsPerPixel { get; set; } = Bmp.BmpBitsPerPixel.Pixel24; + public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32; /// public CurFrameMetadata DeepClone() => new(this); @@ -88,21 +67,27 @@ public class CurFrameMetadata : IDeepCloneable, IDeepCloneable /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - internal void FromIconDirEntry(in IconDirEntry entry) + internal void FromIconDirEntry(IconDirEntry entry) { this.EncodingWidth = entry.Width; this.EncodingHeight = entry.Height; - this.ColorCount = entry.ColorCount; this.HotspotX = entry.Planes; this.HotspotY = entry.BitCount; } - internal IconDirEntry ToIconDirEntry() => new() + internal IconDirEntry ToIconDirEntry() { - Width = this.EncodingWidth, - Height = this.EncodingHeight, - ColorCount = this.ColorCount, - Planes = this.HotspotX, - BitCount = this.HotspotY, - }; + byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8 + ? (byte)0 + : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel); + + return new() + { + Width = this.EncodingWidth, + Height = this.EncodingHeight, + Planes = this.HotspotX, + BitCount = this.HotspotY, + ColorCount = colorCount + }; + } } diff --git a/src/ImageSharp/Formats/Ico/IcoConstants.cs b/src/ImageSharp/Formats/Ico/IcoConstants.cs index 0b963a431..116579368 100644 --- a/src/ImageSharp/Formats/Ico/IcoConstants.cs +++ b/src/ImageSharp/Formats/Ico/IcoConstants.cs @@ -9,13 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Ico; internal static class IcoConstants { /// - /// The list of mimetypes that equate to a ico. + /// The list of mime types that equate to a ico. /// /// /// See /// - public static readonly IEnumerable MimeTypes = new[] - { + public static readonly IEnumerable MimeTypes = + [ + // IANA-registered "image/vnd.microsoft.icon", @@ -27,12 +28,12 @@ internal static class IcoConstants "image/icon", "text/ico", "application/ico", - }; + ]; /// /// The list of file extensions that equate to a ico. /// - public static readonly IEnumerable FileExtensions = new[] { "ico" }; + public static readonly IEnumerable FileExtensions = ["ico"]; public const uint FileHeader = 0x00_01_00_00; } diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index 78cb0d961..e8629e35b 100644 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -1,18 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp.Formats.Ico; -internal sealed class IcoDecoderCore(DecoderOptions options) : IconDecoderCore(options) +internal sealed class IcoDecoderCore : IconDecoderCore { - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, Bmp.BmpBitsPerPixel bitsPerPixel) + public IcoDecoderCore(DecoderOptions options) + : base(options) + { + } + + protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel) { IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata(); icoFrameMetadata.FromIconDirEntry(entry); icoFrameMetadata.Compression = compression; - icoFrameMetadata.BitsPerPixel = bitsPerPixel; + icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel; } } diff --git a/src/ImageSharp/Formats/Ico/IcoEncoder.cs b/src/ImageSharp/Formats/Ico/IcoEncoder.cs index 0668a7e23..298f93dec 100644 --- a/src/ImageSharp/Formats/Ico/IcoEncoder.cs +++ b/src/ImageSharp/Formats/Ico/IcoEncoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Ico; @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Ico; /// /// Image encoder for writing an image to a stream as a Windows Icon. /// -public sealed class IcoEncoder : QuantizingImageEncoder +public sealed class IcoEncoder : ImageEncoder { /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs index 12ced58fd..ab3edfbd3 100644 --- a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs @@ -5,6 +5,10 @@ using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Ico; -internal sealed class IcoEncoderCore() : IconEncoderCore(IconFileType.ICO) +internal sealed class IcoEncoderCore : IconEncoderCore { + public IcoEncoderCore() + : base(IconFileType.ICO) + { + } } diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 8d7eb17b5..82e4ce3b2 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -1,12 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; namespace SixLabors.ImageSharp.Formats.Ico; /// -/// IcoFrameMetadata. TODO: Remove base class and merge into this class. +/// Provides Ico specific metadata information for the image frame. /// public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable { @@ -17,54 +18,36 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable { } - /// - /// Initializes a new instance of the class. - /// - /// width - /// height - /// colorCount - public IcoFrameMetadata(byte width, byte height, byte colorCount) - { - this.EncodingWidth = width; - this.EncodingHeight = height; - this.ColorCount = colorCount; - } - - /// - public IcoFrameMetadata(IcoFrameMetadata metadata) + private IcoFrameMetadata(IcoFrameMetadata metadata) { + this.Compression = metadata.Compression; this.EncodingWidth = metadata.EncodingWidth; this.EncodingHeight = metadata.EncodingHeight; - this.ColorCount = metadata.ColorCount; - this.Compression = metadata.Compression; + this.BmpBitsPerPixel = metadata.BmpBitsPerPixel; } /// - /// Gets or sets icoFrameCompression. + /// Gets or sets the frame compressions format. /// public IconFrameCompression Compression { get; set; } /// - /// Gets or sets ColorCount field.
- /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + /// Gets or sets the encoding width.
+ /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. ///
- // TODO: BmpMetadata does not supported palette yet. - public byte ColorCount { get; set; } + public byte EncodingWidth { get; set; } /// - /// Gets or sets Height field.
- /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. + /// Gets or sets the encoding height.
+ /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. ///
public byte EncodingHeight { get; set; } /// - /// Gets or sets Width field.
- /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + /// Gets or sets the number of bits per pixel.
+ /// Used when is ///
- public byte EncodingWidth { get; set; } - - /// - public Bmp.BmpBitsPerPixel BitsPerPixel { get; set; } = Bmp.BmpBitsPerPixel.Pixel24; + public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32; /// public IcoFrameMetadata DeepClone() => new(this); @@ -72,24 +55,29 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - internal void FromIconDirEntry(in IconDirEntry entry) + internal void FromIconDirEntry(IconDirEntry entry) { this.EncodingWidth = entry.Width; this.EncodingHeight = entry.Height; - this.ColorCount = entry.ColorCount; } - internal IconDirEntry ToIconDirEntry() => new() + internal IconDirEntry ToIconDirEntry() { - Width = this.EncodingWidth, - Height = this.EncodingHeight, - ColorCount = this.ColorCount, - Planes = 1, - BitCount = this.Compression switch + byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8 + ? (byte)0 + : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel); + + return new() { - IconFrameCompression.Bmp => (ushort)this.BitsPerPixel, - IconFrameCompression.Png => 32, - _ => throw new NotSupportedException($"Value: {this.Compression}"), - }, - }; + Width = this.EncodingWidth, + Height = this.EncodingHeight, + Planes = 1, + ColorCount = colorCount, + BitCount = this.Compression switch + { + IconFrameCompression.Bmp => (ushort)this.BmpBitsPerPixel, + IconFrameCompression.Png or _ => 32, + }, + }; + } } diff --git a/src/ImageSharp/Formats/Icon/IconAssert.cs b/src/ImageSharp/Formats/Icon/IconAssert.cs index 547b6a6eb..398a3e5f4 100644 --- a/src/ImageSharp/Formats/Icon/IconAssert.cs +++ b/src/ImageSharp/Formats/Icon/IconAssert.cs @@ -5,14 +5,6 @@ namespace SixLabors.ImageSharp.Formats.Icon; internal class IconAssert { - internal static void CanSeek(Stream stream) - { - if (!stream.CanSeek) - { - throw new NotSupportedException("This stream cannot support seek"); - } - } - internal static int EndOfStream(int v, int length) { if (v != length) @@ -22,28 +14,4 @@ internal class IconAssert return v; } - - internal static long EndOfStream(long v, long length) - { - if (v != length) - { - throw new EndOfStreamException(); - } - - return v; - } - - internal static byte Is1ByteSize(int i) - { - if (i is 256) - { - return 0; - } - else if (i > byte.MaxValue) - { - throw new FormatException("Image size Too Large."); - } - - return (byte)i; - } } diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index a502b91c9..97d0aec6d 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -10,12 +10,15 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Icon; -internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderInternals +internal abstract class IconDecoderCore : IImageDecoderInternals { private IconDir fileHeader; private IconDirEntry[]? entries; - public DecoderOptions Options { get; } = options; + protected IconDecoderCore(DecoderOptions options) + => this.Options = options; + + public DecoderOptions Options { get; } public Size Dimensions { get; private set; } @@ -61,10 +64,11 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI } ImageMetadata metadata = new(); + BmpMetadata? bmpMetadata = null; PngMetadata? pngMetadata = null; Image result = new(this.Options.Configuration, metadata, decodedEntries.Select(x => { - BmpBitsPerPixel bitsPerPixel = default; + BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32; ImageFrame target = new(this.Options.Configuration, this.Dimensions); ImageFrame source = x.Image.Frames.RootFrameUnsafe; for (int y = 0; y < source.Height; y++) @@ -80,12 +84,12 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI pngMetadata = x.Image.Metadata.GetPngMetadata(); } - // Bmp does not contain frame specific metadata. target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngMetadata()); } else { - bitsPerPixel = x.Image.Metadata.GetBmpMetadata().BitsPerPixel; + bmpMetadata = x.Image.Metadata.GetBmpMetadata(); + bitsPerPixel = bmpMetadata.BitsPerPixel; } this.SetFrameMetadata(target.Metadata, this.entries[x.Index], x.Compression, bitsPerPixel); @@ -96,6 +100,11 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI }).ToArray()); // Copy the format specific metadata to the image. + if (bmpMetadata != null) + { + result.Metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); + } + if (pngMetadata != null) { result.Metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); @@ -114,9 +123,10 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI ImageMetadata metadata = new(); ImageFrameMetadata[] frames = new ImageFrameMetadata[this.fileHeader.Count]; + int bpp = 0; for (int i = 0; i < frames.Length; i++) { - BmpBitsPerPixel bitsPerPixel = default; + BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32; ref IconDirEntry entry = ref this.entries[i]; // If we hit the end of the stream we should break. @@ -140,11 +150,13 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken); frames[i] = new(); - if (isPng) + if (!isPng) { bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel; } + bpp = Math.Max(bpp, (int)bitsPerPixel); + this.SetFrameMetadata(frames[i], this.entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel); // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data @@ -152,7 +164,7 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height)); } - return new(new(32), this.Dimensions, metadata, frames); + return new(new(bpp), this.Dimensions, metadata, frames); } protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel); @@ -211,15 +223,13 @@ internal abstract class IconDecoderCore(DecoderOptions options) : IImageDecoderI GeneralOptions = this.Options, }); } - else + + return new BmpDecoderCore(new() { - return new BmpDecoderCore(new() - { - GeneralOptions = this.Options, - ProcessedAlphaMask = true, - SkipFileHeader = true, - UseDoubleHeight = true, - }); - } + GeneralOptions = this.Options, + ProcessedAlphaMask = true, + SkipFileHeader = true, + UseDoubleHeight = true, + }); } } diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs index aa583ee1e..3e02538c8 100644 --- a/src/ImageSharp/Formats/Icon/IconDir.cs +++ b/src/ImageSharp/Formats/Icon/IconDir.cs @@ -9,8 +9,20 @@ namespace SixLabors.ImageSharp.Formats.Icon; internal struct IconDir(ushort reserved, IconFileType type, ushort count) { public const int Size = 3 * sizeof(ushort); + + /// + /// Reserved. Must always be 0. + /// public ushort Reserved = reserved; + + /// + /// Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. + /// public IconFileType Type = type; + + /// + /// Specifies number of images in the file. + /// public ushort Count = count; public IconDir(IconFileType type) @@ -23,9 +35,9 @@ internal struct IconDir(ushort reserved, IconFileType type, ushort count) { } - public static IconDir Parse(in ReadOnlySpan data) + public static IconDir Parse(ReadOnlySpan data) => MemoryMarshal.Cast(data)[0]; - public unsafe void WriteTo(in Stream stream) + public readonly unsafe void WriteTo(Stream stream) => stream.Write(MemoryMarshal.Cast([this])); } diff --git a/src/ImageSharp/Formats/Icon/IconDirEntry.cs b/src/ImageSharp/Formats/Icon/IconDirEntry.cs index 7a8e09e37..eab15dd87 100644 --- a/src/ImageSharp/Formats/Icon/IconDirEntry.cs +++ b/src/ImageSharp/Formats/Icon/IconDirEntry.cs @@ -10,25 +10,51 @@ internal struct IconDirEntry { public const int Size = (4 * sizeof(byte)) + (2 * sizeof(ushort)) + (2 * sizeof(uint)); + /// + /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + /// public byte Width; + /// + /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels.[ + /// public byte Height; + /// + /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + /// public byte ColorCount; + /// + /// Reserved. Should be 0. + /// public byte Reserved; + /// + /// In ICO format: Specifies color planes. Should be 0 or 1.
+ /// In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. + ///
public ushort Planes; + /// + /// In ICO format: Specifies bits per pixel.
+ /// In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top. + ///
public ushort BitCount; + /// + /// Specifies the size of the image's data in bytes + /// public uint BytesInRes; + /// + /// Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file. + /// public uint ImageOffset; public static IconDirEntry Parse(in ReadOnlySpan data) => MemoryMarshal.Cast(data)[0]; - public unsafe void WriteTo(in Stream stream) + public readonly unsafe void WriteTo(in Stream stream) => stream.Write(MemoryMarshal.Cast([this])); } diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs index b4d563d4b..5332d9a86 100644 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -2,18 +2,23 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Icon; -internal abstract class IconEncoderCore(IconFileType iconFileType) - : IImageEncoderInternals +internal abstract class IconEncoderCore : IImageEncoderInternals { + private readonly IconFileType iconFileType; private IconDir fileHeader; + private EncodingFrameMetadata[]? entries; - private IconFrameMetadata[]? entries; + protected IconEncoderCore(IconFileType iconFileType) + => this.iconFileType = iconFileType; public void Encode( Image image, @@ -24,17 +29,18 @@ internal abstract class IconEncoderCore(IconFileType iconFileType) Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - IconAssert.CanSeek(stream); - // Stream may not at 0. long basePosition = stream.Position; this.InitHeader(image); + // We don't write the header and entries yet as we need to write the image data first. int dataOffset = IconDir.Size + (IconDirEntry.Size * this.entries.Length); _ = stream.Seek(dataOffset, SeekOrigin.Current); for (int i = 0; i < image.Frames.Count; i++) { + // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data + // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. ImageFrame frame = image.Frames[i]; int width = this.entries[i].Entry.Width; if (width is 0) @@ -50,36 +56,54 @@ internal abstract class IconEncoderCore(IconFileType iconFileType) this.entries[i].Entry.ImageOffset = (uint)stream.Position; - Image img = new(width, height); + // We crop the frame to the size specified in the metadata. + // TODO: we can optimize this by cropping the frame only if the new size is both required and different. + using Image encodingFrame = new(width, height); for (int y = 0; y < height; y++) { - frame.PixelBuffer.DangerousGetRowSpan(y)[..width].CopyTo(img.GetRootFramePixelBuffer().DangerousGetRowSpan(y)); + frame.PixelBuffer.DangerousGetRowSpan(y)[..width] + .CopyTo(encodingFrame.GetRootFramePixelBuffer().DangerousGetRowSpan(y)); } - QuantizingImageEncoder encoder = this.entries[i].Compression switch + ref EncodingFrameMetadata encodingMetadata = ref this.entries[i]; + + QuantizingImageEncoder encoder = encodingMetadata.Compression switch { - IconFrameCompression.Bmp => new Bmp.BmpEncoder() + IconFrameCompression.Bmp => new BmpEncoder() { + // We don't have access to the palette in the metadata so we need to quantize the image + // using a new one generated from the pixel data. + Quantizer = encodingMetadata.Entry.BitCount <= 8 + ? new WuQuantizer(new() + { + MaxColors = encodingMetadata.Entry.ColorCount + }) + : null, ProcessedAlphaMask = true, UseDoubleHeight = true, SkipFileHeader = true, SupportTransparency = false, - BitsPerPixel = iconFileType is IconFileType.ICO - ? (Bmp.BmpBitsPerPixel?)this.entries[i].Entry.BitCount - : Bmp.BmpBitsPerPixel.Pixel24 // TODO: Here you need to switch to selecting the corresponding value according to the size of the image + BitsPerPixel = encodingMetadata.BmpBitsPerPixel + }, + IconFrameCompression.Png => new PngEncoder() + { + // Only 32bit Png supported. + // https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473 + BitDepth = PngBitDepth.Bit8, + ColorType = PngColorType.RgbWithAlpha }, - IconFrameCompression.Png => new Png.PngEncoder(), _ => throw new NotSupportedException(), }; - encoder.Encode(img, stream); - this.entries[i].Entry.BytesInRes = (uint)stream.Position - this.entries[i].Entry.ImageOffset; + encoder.Encode(encodingFrame, stream); + encodingMetadata.Entry.BytesInRes = (uint)stream.Position - encodingMetadata.Entry.ImageOffset; } + // We now need to rewind the stream and write the header and the entries. long endPosition = stream.Position; _ = stream.Seek(basePosition, SeekOrigin.Begin); this.fileHeader.WriteTo(stream); - foreach (IconFrameMetadata frame in this.entries) + foreach (EncodingFrameMetadata frame in this.entries) { frame.Entry.WriteTo(stream); } @@ -88,33 +112,38 @@ internal abstract class IconEncoderCore(IconFileType iconFileType) } [MemberNotNull(nameof(entries))] - private void InitHeader(in Image image) + private void InitHeader(Image image) { - this.fileHeader = new(iconFileType, (ushort)image.Frames.Count); - this.entries = iconFileType switch + this.fileHeader = new(this.iconFileType, (ushort)image.Frames.Count); + this.entries = this.iconFileType switch { IconFileType.ICO => image.Frames.Select(i => { IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); - return new IconFrameMetadata(metadata.Compression, metadata.ToIconDirEntry()); + return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry()); }).ToArray(), IconFileType.CUR => image.Frames.Select(i => { CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); - return new IconFrameMetadata(metadata.Compression, metadata.ToIconDirEntry()); + return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry()); }).ToArray(), _ => throw new NotSupportedException(), }; } - internal sealed class IconFrameMetadata(IconFrameCompression compression, IconDirEntry iconDirEntry) + internal sealed class EncodingFrameMetadata( + IconFrameCompression compression, + BmpBitsPerPixel bmpBitsPerPixel, + IconDirEntry iconDirEntry) { private IconDirEntry iconDirEntry = iconDirEntry; public IconFrameCompression Compression { get; set; } = compression; + public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = bmpBitsPerPixel; + public ref IconDirEntry Entry => ref this.iconDirEntry; } } diff --git a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs index b66b6c79f..9e7d22de2 100644 --- a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs @@ -60,9 +60,7 @@ public class IconImageFormatDetector : IImageFormatDetector return true; } - else - { - return false; - } + + return false; } } From 7c0cd0baab036af0d1726976631beb9beef1487d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Jun 2024 21:55:51 +1000 Subject: [PATCH 22/28] Merge separate assert file --- src/ImageSharp/Formats/Icon/IconAssert.cs | 17 ----------------- src/ImageSharp/Formats/Icon/IconDecoderCore.cs | 14 ++++++++++++-- src/ImageSharp/Formats/Icon/IconEncoderCore.cs | 3 ++- 3 files changed, 14 insertions(+), 20 deletions(-) delete mode 100644 src/ImageSharp/Formats/Icon/IconAssert.cs diff --git a/src/ImageSharp/Formats/Icon/IconAssert.cs b/src/ImageSharp/Formats/Icon/IconAssert.cs deleted file mode 100644 index 398a3e5f4..000000000 --- a/src/ImageSharp/Formats/Icon/IconAssert.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Icon; - -internal class IconAssert -{ - internal static int EndOfStream(int v, int length) - { - if (v != length) - { - throw new EndOfStreamException(); - } - - return v; - } -} diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 97d0aec6d..a0849fa61 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -175,14 +175,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals Span buffer = stackalloc byte[IconDirEntry.Size]; // ICONDIR - _ = IconAssert.EndOfStream(stream.Read(buffer[..IconDir.Size]), IconDir.Size); + _ = CheckEndOfStream(stream.Read(buffer[..IconDir.Size]), IconDir.Size); this.fileHeader = IconDir.Parse(buffer); // ICONDIRENTRY this.entries = new IconDirEntry[this.fileHeader.Count]; for (int i = 0; i < this.entries.Length; i++) { - _ = IconAssert.EndOfStream(stream.Read(buffer[..IconDirEntry.Size]), IconDirEntry.Size); + _ = CheckEndOfStream(stream.Read(buffer[..IconDirEntry.Size]), IconDirEntry.Size); this.entries[i] = IconDirEntry.Parse(buffer); } @@ -232,4 +232,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals UseDoubleHeight = true, }); } + + private static int CheckEndOfStream(int v, int length) + { + if (v != length) + { + throw new InvalidImageContentException("Not enough bytes to read icon header."); + } + + return v; + } } diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs index 5332d9a86..eb07ab483 100644 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -90,7 +90,8 @@ internal abstract class IconEncoderCore : IImageEncoderInternals // Only 32bit Png supported. // https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473 BitDepth = PngBitDepth.Bit8, - ColorType = PngColorType.RgbWithAlpha + ColorType = PngColorType.RgbWithAlpha, + CompressionLevel = PngCompressionLevel.BestCompression }, _ => throw new NotSupportedException(), }; From b222a6733e2a978a950b783a7a96979271001841 Mon Sep 17 00:00:00 2001 From: Poker Date: Tue, 18 Jun 2024 21:07:08 +0800 Subject: [PATCH 23/28] Add test files --- .../Formats/Icon/Cur/CurEncoderTests.cs | 9 ++ .../Formats/Icon/Ico/IcoDecoderTests.cs | 119 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 114 +++++++++++++++++ tests/Images/Input/Icon/1bpp_size_15x15.ico | 3 + tests/Images/Input/Icon/1bpp_size_16x16.ico | 3 + tests/Images/Input/Icon/1bpp_size_17x17.ico | 3 + tests/Images/Input/Icon/1bpp_size_1x1.ico | 3 + tests/Images/Input/Icon/1bpp_size_256x256.ico | 3 + tests/Images/Input/Icon/1bpp_size_2x2.ico | 3 + tests/Images/Input/Icon/1bpp_size_31x31.ico | 3 + tests/Images/Input/Icon/1bpp_size_32x32.ico | 3 + tests/Images/Input/Icon/1bpp_size_33x33.ico | 3 + tests/Images/Input/Icon/1bpp_size_3x3.ico | 3 + tests/Images/Input/Icon/1bpp_size_4x4.ico | 3 + tests/Images/Input/Icon/1bpp_size_5x5.ico | 3 + tests/Images/Input/Icon/1bpp_size_6x6.ico | 3 + tests/Images/Input/Icon/1bpp_size_7x7.ico | 3 + tests/Images/Input/Icon/1bpp_size_8x8.ico | 3 + tests/Images/Input/Icon/1bpp_size_9x9.ico | 3 + .../Input/Icon/1bpp_transp_not_square.ico | 3 + .../Images/Input/Icon/1bpp_transp_partial.ico | 3 + tests/Images/Input/Icon/24bpp_size_15x15.ico | 3 + tests/Images/Input/Icon/24bpp_size_16x16.ico | 3 + tests/Images/Input/Icon/24bpp_size_17x17.ico | 3 + tests/Images/Input/Icon/24bpp_size_1x1.ico | 3 + .../Images/Input/Icon/24bpp_size_256x256.ico | 3 + tests/Images/Input/Icon/24bpp_size_2x2.ico | 3 + tests/Images/Input/Icon/24bpp_size_31x31.ico | 3 + tests/Images/Input/Icon/24bpp_size_32x32.ico | 3 + tests/Images/Input/Icon/24bpp_size_33x33.ico | 3 + tests/Images/Input/Icon/24bpp_size_3x3.ico | 3 + tests/Images/Input/Icon/24bpp_size_4x4.ico | 3 + tests/Images/Input/Icon/24bpp_size_5x5.ico | 3 + tests/Images/Input/Icon/24bpp_size_6x6.ico | 3 + tests/Images/Input/Icon/24bpp_size_7x7.ico | 3 + tests/Images/Input/Icon/24bpp_size_8x8.ico | 3 + tests/Images/Input/Icon/24bpp_size_9x9.ico | 3 + tests/Images/Input/Icon/24bpp_transp.ico | 3 + .../Input/Icon/24bpp_transp_not_square.ico | 3 + .../Input/Icon/24bpp_transp_partial.ico | 3 + tests/Images/Input/Icon/32bpp_size_15x15.ico | 3 + tests/Images/Input/Icon/32bpp_size_16x16.ico | 3 + tests/Images/Input/Icon/32bpp_size_17x17.ico | 3 + tests/Images/Input/Icon/32bpp_size_1x1.ico | 3 + .../Images/Input/Icon/32bpp_size_256x256.ico | 3 + tests/Images/Input/Icon/32bpp_size_2x2.ico | 3 + tests/Images/Input/Icon/32bpp_size_31x31.ico | 3 + tests/Images/Input/Icon/32bpp_size_32x32.ico | 3 + tests/Images/Input/Icon/32bpp_size_33x33.ico | 3 + tests/Images/Input/Icon/32bpp_size_3x3.ico | 3 + tests/Images/Input/Icon/32bpp_size_4x4.ico | 3 + tests/Images/Input/Icon/32bpp_size_5x5.ico | 3 + tests/Images/Input/Icon/32bpp_size_6x6.ico | 3 + tests/Images/Input/Icon/32bpp_size_7x7.ico | 3 + tests/Images/Input/Icon/32bpp_size_8x8.ico | 3 + tests/Images/Input/Icon/32bpp_size_9x9.ico | 3 + tests/Images/Input/Icon/32bpp_transp.ico | 3 + .../Input/Icon/32bpp_transp_not_square.ico | 3 + .../Input/Icon/32bpp_transp_partial.ico | 3 + tests/Images/Input/Icon/4bpp_size_15x15.ico | 3 + tests/Images/Input/Icon/4bpp_size_16x16.ico | 3 + tests/Images/Input/Icon/4bpp_size_17x17.ico | 3 + tests/Images/Input/Icon/4bpp_size_1x1.ico | 3 + tests/Images/Input/Icon/4bpp_size_256x256.ico | 3 + tests/Images/Input/Icon/4bpp_size_2x2.ico | 3 + tests/Images/Input/Icon/4bpp_size_31x31.ico | 3 + tests/Images/Input/Icon/4bpp_size_32x32.ico | 3 + tests/Images/Input/Icon/4bpp_size_33x33.ico | 3 + tests/Images/Input/Icon/4bpp_size_3x3.ico | 3 + tests/Images/Input/Icon/4bpp_size_4x4.ico | 3 + tests/Images/Input/Icon/4bpp_size_5x5.ico | 3 + tests/Images/Input/Icon/4bpp_size_6x6.ico | 3 + tests/Images/Input/Icon/4bpp_size_7x7.ico | 3 + tests/Images/Input/Icon/4bpp_size_8x8.ico | 3 + tests/Images/Input/Icon/4bpp_size_9x9.ico | 3 + .../Input/Icon/4bpp_transp_not_square.ico | 3 + .../Images/Input/Icon/4bpp_transp_partial.ico | 3 + tests/Images/Input/Icon/8bpp_size_15x15.ico | 3 + tests/Images/Input/Icon/8bpp_size_16x16.ico | 3 + tests/Images/Input/Icon/8bpp_size_17x17.ico | 3 + tests/Images/Input/Icon/8bpp_size_1x1.ico | 3 + tests/Images/Input/Icon/8bpp_size_256x256.ico | 3 + tests/Images/Input/Icon/8bpp_size_2x2.ico | 3 + tests/Images/Input/Icon/8bpp_size_31x31.ico | 3 + tests/Images/Input/Icon/8bpp_size_32x32.ico | 3 + tests/Images/Input/Icon/8bpp_size_33x33.ico | 3 + tests/Images/Input/Icon/8bpp_size_3x3.ico | 3 + tests/Images/Input/Icon/8bpp_size_4x4.ico | 3 + tests/Images/Input/Icon/8bpp_size_5x5.ico | 3 + tests/Images/Input/Icon/8bpp_size_6x6.ico | 3 + tests/Images/Input/Icon/8bpp_size_7x7.ico | 3 + tests/Images/Input/Icon/8bpp_size_8x8.ico | 3 + tests/Images/Input/Icon/8bpp_size_9x9.ico | 3 + .../Input/Icon/8bpp_transp_not_square.ico | 3 + .../Images/Input/Icon/8bpp_transp_partial.ico | 3 + tests/Images/Input/Icon/cur_fake.ico | 3 + tests/Images/Input/Icon/cur_real.cur | 3 + tests/Images/Input/Icon/ico_fake.cur | 3 + tests/Images/Input/Icon/invalid_RLE4.ico | 3 + tests/Images/Input/Icon/invalid_RLE8.ico | 3 + tests/Images/Input/Icon/invalid_all.ico | 3 + tests/Images/Input/Icon/invalid_bpp.ico | 3 + .../Images/Input/Icon/invalid_compression.ico | 3 + tests/Images/Input/Icon/invalid_png.ico | 3 + tests/Images/Input/Icon/mixed_bmp_png_a.ico | 3 + tests/Images/Input/Icon/mixed_bmp_png_b.ico | 3 + tests/Images/Input/Icon/mixed_bmp_png_c.ico | 3 + tests/Images/Input/Icon/multi_size_a.ico | 3 + tests/Images/Input/Icon/multi_size_b.ico | 3 + tests/Images/Input/Icon/multi_size_c.ico | 3 + tests/Images/Input/Icon/multi_size_d.ico | 3 + tests/Images/Input/Icon/multi_size_e.ico | 3 + tests/Images/Input/Icon/multi_size_f.ico | 3 + .../Input/Icon/multi_size_multi_bits_a.ico | 3 + .../Input/Icon/multi_size_multi_bits_b.ico | 3 + .../Input/Icon/multi_size_multi_bits_c.ico | 3 + .../Input/Icon/multi_size_multi_bits_d.ico | 3 + 117 files changed, 584 insertions(+) create mode 100644 tests/Images/Input/Icon/1bpp_size_15x15.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_16x16.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_17x17.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_1x1.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_256x256.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_2x2.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_31x31.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_32x32.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_33x33.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_3x3.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_4x4.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_5x5.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_6x6.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_7x7.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_8x8.ico create mode 100644 tests/Images/Input/Icon/1bpp_size_9x9.ico create mode 100644 tests/Images/Input/Icon/1bpp_transp_not_square.ico create mode 100644 tests/Images/Input/Icon/1bpp_transp_partial.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_15x15.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_16x16.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_17x17.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_1x1.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_256x256.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_2x2.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_31x31.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_32x32.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_33x33.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_3x3.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_4x4.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_5x5.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_6x6.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_7x7.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_8x8.ico create mode 100644 tests/Images/Input/Icon/24bpp_size_9x9.ico create mode 100644 tests/Images/Input/Icon/24bpp_transp.ico create mode 100644 tests/Images/Input/Icon/24bpp_transp_not_square.ico create mode 100644 tests/Images/Input/Icon/24bpp_transp_partial.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_15x15.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_16x16.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_17x17.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_1x1.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_256x256.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_2x2.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_31x31.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_32x32.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_33x33.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_3x3.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_4x4.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_5x5.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_6x6.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_7x7.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_8x8.ico create mode 100644 tests/Images/Input/Icon/32bpp_size_9x9.ico create mode 100644 tests/Images/Input/Icon/32bpp_transp.ico create mode 100644 tests/Images/Input/Icon/32bpp_transp_not_square.ico create mode 100644 tests/Images/Input/Icon/32bpp_transp_partial.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_15x15.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_16x16.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_17x17.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_1x1.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_256x256.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_2x2.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_31x31.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_32x32.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_33x33.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_3x3.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_4x4.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_5x5.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_6x6.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_7x7.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_8x8.ico create mode 100644 tests/Images/Input/Icon/4bpp_size_9x9.ico create mode 100644 tests/Images/Input/Icon/4bpp_transp_not_square.ico create mode 100644 tests/Images/Input/Icon/4bpp_transp_partial.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_15x15.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_16x16.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_17x17.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_1x1.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_256x256.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_2x2.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_31x31.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_32x32.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_33x33.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_3x3.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_4x4.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_5x5.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_6x6.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_7x7.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_8x8.ico create mode 100644 tests/Images/Input/Icon/8bpp_size_9x9.ico create mode 100644 tests/Images/Input/Icon/8bpp_transp_not_square.ico create mode 100644 tests/Images/Input/Icon/8bpp_transp_partial.ico create mode 100644 tests/Images/Input/Icon/cur_fake.ico create mode 100644 tests/Images/Input/Icon/cur_real.cur create mode 100644 tests/Images/Input/Icon/ico_fake.cur create mode 100644 tests/Images/Input/Icon/invalid_RLE4.ico create mode 100644 tests/Images/Input/Icon/invalid_RLE8.ico create mode 100644 tests/Images/Input/Icon/invalid_all.ico create mode 100644 tests/Images/Input/Icon/invalid_bpp.ico create mode 100644 tests/Images/Input/Icon/invalid_compression.ico create mode 100644 tests/Images/Input/Icon/invalid_png.ico create mode 100644 tests/Images/Input/Icon/mixed_bmp_png_a.ico create mode 100644 tests/Images/Input/Icon/mixed_bmp_png_b.ico create mode 100644 tests/Images/Input/Icon/mixed_bmp_png_c.ico create mode 100644 tests/Images/Input/Icon/multi_size_a.ico create mode 100644 tests/Images/Input/Icon/multi_size_b.ico create mode 100644 tests/Images/Input/Icon/multi_size_c.ico create mode 100644 tests/Images/Input/Icon/multi_size_d.ico create mode 100644 tests/Images/Input/Icon/multi_size_e.ico create mode 100644 tests/Images/Input/Icon/multi_size_f.ico create mode 100644 tests/Images/Input/Icon/multi_size_multi_bits_a.ico create mode 100644 tests/Images/Input/Icon/multi_size_multi_bits_b.ico create mode 100644 tests/Images/Input/Icon/multi_size_multi_bits_c.ico create mode 100644 tests/Images/Input/Icon/multi_size_multi_bits_d.ico diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index 9908786f1..58213397c 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -29,4 +30,12 @@ public class CurEncoderTests memStream.Seek(0, SeekOrigin.Begin); CurDecoder.Instance.Decode(new(), memStream); } + + [Theory] + [WithFile(CurFake, PixelTypes.Rgba32)] + [WithFile(CurReal, PixelTypes.Rgba32)] + public void CurDecoder_Decode2(TestImageProvider provider) + { + Debug.Assert(false); + } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 56378653a..ab4ec0a3d 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics; using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Ico; @@ -21,4 +22,122 @@ public class IcoDecoderTests // TODO: Assert metadata, frame count, etc } + + [Theory] + [WithFile(Bpp1_size_15x15, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_16x16, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_17x17, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_1x1, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_256x256, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_2x2, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_31x31, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_32x32, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_33x33, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_3x3, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_4x4, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_5x5, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_6x6, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_7x7, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_8x8, PixelTypes.Rgba32)] + [WithFile(Bpp1_size_9x9, PixelTypes.Rgba32)] + [WithFile(Bpp1_transp_not_square, PixelTypes.Rgba32)] + [WithFile(Bpp1_transp_partial, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_15x15, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_16x16, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_17x17, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_1x1, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_256x256, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_2x2, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_31x31, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_32x32, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_33x33, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_3x3, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_4x4, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_5x5, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_6x6, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_7x7, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_8x8, PixelTypes.Rgba32)] + [WithFile(Bpp24_size_9x9, PixelTypes.Rgba32)] + [WithFile(Bpp24_transp_not_square, PixelTypes.Rgba32)] + [WithFile(Bpp24_transp_partial, PixelTypes.Rgba32)] + [WithFile(Bpp24_transp, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_15x15, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_16x16, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_17x17, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_1x1, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_256x256, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_2x2, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_31x31, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_32x32, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_33x33, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_3x3, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_4x4, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_5x5, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_6x6, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_7x7, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_8x8, PixelTypes.Rgba32)] + [WithFile(Bpp32_size_9x9, PixelTypes.Rgba32)] + [WithFile(Bpp32_transp_not_square, PixelTypes.Rgba32)] + [WithFile(Bpp32_transp_partial, PixelTypes.Rgba32)] + [WithFile(Bpp32_transp, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_15x15, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_16x16, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_17x17, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_1x1, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_256x256, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_2x2, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_31x31, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_32x32, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_33x33, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_3x3, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_4x4, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_5x5, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_6x6, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_7x7, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_8x8, PixelTypes.Rgba32)] + [WithFile(Bpp4_size_9x9, PixelTypes.Rgba32)] + [WithFile(Bpp4_transp_not_square, PixelTypes.Rgba32)] + [WithFile(Bpp4_transp_partial, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_15x15, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_16x16, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_17x17, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_1x1, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_256x256, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_2x2, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_31x31, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_32x32, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_33x33, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_3x3, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_4x4, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_5x5, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_6x6, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_7x7, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_8x8, PixelTypes.Rgba32)] + [WithFile(Bpp8_size_9x9, PixelTypes.Rgba32)] + [WithFile(Bpp8_transp_not_square, PixelTypes.Rgba32)] + [WithFile(Bpp8_transp_partial, PixelTypes.Rgba32)] + [WithFile(IcoFake, PixelTypes.Rgba32)] + [WithFile(Invalid_all, PixelTypes.Rgba32)] + [WithFile(Invalid_bpp, PixelTypes.Rgba32)] + [WithFile(Invalid_compression, PixelTypes.Rgba32)] + [WithFile(Invalid_png, PixelTypes.Rgba32)] + [WithFile(Invalid_RLE4, PixelTypes.Rgba32)] + [WithFile(Invalid_RLE8, PixelTypes.Rgba32)] + [WithFile(Mixed_bmp_png_a, PixelTypes.Rgba32)] + [WithFile(Mixed_bmp_png_b, PixelTypes.Rgba32)] + [WithFile(Mixed_bmp_png_c, PixelTypes.Rgba32)] + [WithFile(Multi_size_a, PixelTypes.Rgba32)] + [WithFile(Multi_size_b, PixelTypes.Rgba32)] + [WithFile(Multi_size_c, PixelTypes.Rgba32)] + [WithFile(Multi_size_d, PixelTypes.Rgba32)] + [WithFile(Multi_size_e, PixelTypes.Rgba32)] + [WithFile(Multi_size_f, PixelTypes.Rgba32)] + [WithFile(Multi_size_multi_bits_a, PixelTypes.Rgba32)] + [WithFile(Multi_size_multi_bits_b, PixelTypes.Rgba32)] + [WithFile(Multi_size_multi_bits_c, PixelTypes.Rgba32)] + [WithFile(Multi_size_multi_bits_d, PixelTypes.Rgba32)] + public void IcoDecoder_Decode2(TestImageProvider provider) + { + Debug.Assert(false); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8b76abca2..6db11c78f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1126,10 +1126,124 @@ public static class TestImages public static class Ico { public const string Flutter = "Icon/flutter.ico"; + public const string Bpp1_size_15x15 = "Icon/1bpp_size_15x15.ico"; + public const string Bpp1_size_16x16 = "Icon/1bpp_size_16x16.ico"; + public const string Bpp1_size_17x17 = "Icon/1bpp_size_17x17.ico"; + public const string Bpp1_size_1x1 = "Icon/1bpp_size_1x1.ico"; + public const string Bpp1_size_256x256 = "Icon/1bpp_size_256x256.ico"; + public const string Bpp1_size_2x2 = "Icon/1bpp_size_2x2.ico"; + public const string Bpp1_size_31x31 = "Icon/1bpp_size_31x31.ico"; + public const string Bpp1_size_32x32 = "Icon/1bpp_size_32x32.ico"; + public const string Bpp1_size_33x33 = "Icon/1bpp_size_33x33.ico"; + public const string Bpp1_size_3x3 = "Icon/1bpp_size_3x3.ico"; + public const string Bpp1_size_4x4 = "Icon/1bpp_size_4x4.ico"; + public const string Bpp1_size_5x5 = "Icon/1bpp_size_5x5.ico"; + public const string Bpp1_size_6x6 = "Icon/1bpp_size_6x6.ico"; + public const string Bpp1_size_7x7 = "Icon/1bpp_size_7x7.ico"; + public const string Bpp1_size_8x8 = "Icon/1bpp_size_8x8.ico"; + public const string Bpp1_size_9x9 = "Icon/1bpp_size_9x9.ico"; + public const string Bpp1_transp_not_square = "Icon/1bpp_transp_not_square.ico"; + public const string Bpp1_transp_partial = "Icon/1bpp_transp_partial.ico"; + public const string Bpp24_size_15x15 = "Icon/24bpp_size_15x15.ico"; + public const string Bpp24_size_16x16 = "Icon/24bpp_size_16x16.ico"; + public const string Bpp24_size_17x17 = "Icon/24bpp_size_17x17.ico"; + public const string Bpp24_size_1x1 = "Icon/24bpp_size_1x1.ico"; + public const string Bpp24_size_256x256 = "Icon/24bpp_size_256x256.ico"; + public const string Bpp24_size_2x2 = "Icon/24bpp_size_2x2.ico"; + public const string Bpp24_size_31x31 = "Icon/24bpp_size_31x31.ico"; + public const string Bpp24_size_32x32 = "Icon/24bpp_size_32x32.ico"; + public const string Bpp24_size_33x33 = "Icon/24bpp_size_33x33.ico"; + public const string Bpp24_size_3x3 = "Icon/24bpp_size_3x3.ico"; + public const string Bpp24_size_4x4 = "Icon/24bpp_size_4x4.ico"; + public const string Bpp24_size_5x5 = "Icon/24bpp_size_5x5.ico"; + public const string Bpp24_size_6x6 = "Icon/24bpp_size_6x6.ico"; + public const string Bpp24_size_7x7 = "Icon/24bpp_size_7x7.ico"; + public const string Bpp24_size_8x8 = "Icon/24bpp_size_8x8.ico"; + public const string Bpp24_size_9x9 = "Icon/24bpp_size_9x9.ico"; + public const string Bpp24_transp_not_square = "Icon/24bpp_transp_not_square.ico"; + public const string Bpp24_transp_partial = "Icon/24bpp_transp_partial.ico"; + public const string Bpp24_transp = "Icon/24bpp_transp.ico"; + public const string Bpp32_size_15x15 = "Icon/32bpp_size_15x15.ico"; + public const string Bpp32_size_16x16 = "Icon/32bpp_size_16x16.ico"; + public const string Bpp32_size_17x17 = "Icon/32bpp_size_17x17.ico"; + public const string Bpp32_size_1x1 = "Icon/32bpp_size_1x1.ico"; + public const string Bpp32_size_256x256 = "Icon/32bpp_size_256x256.ico"; + public const string Bpp32_size_2x2 = "Icon/32bpp_size_2x2.ico"; + public const string Bpp32_size_31x31 = "Icon/32bpp_size_31x31.ico"; + public const string Bpp32_size_32x32 = "Icon/32bpp_size_32x32.ico"; + public const string Bpp32_size_33x33 = "Icon/32bpp_size_33x33.ico"; + public const string Bpp32_size_3x3 = "Icon/32bpp_size_3x3.ico"; + public const string Bpp32_size_4x4 = "Icon/32bpp_size_4x4.ico"; + public const string Bpp32_size_5x5 = "Icon/32bpp_size_5x5.ico"; + public const string Bpp32_size_6x6 = "Icon/32bpp_size_6x6.ico"; + public const string Bpp32_size_7x7 = "Icon/32bpp_size_7x7.ico"; + public const string Bpp32_size_8x8 = "Icon/32bpp_size_8x8.ico"; + public const string Bpp32_size_9x9 = "Icon/32bpp_size_9x9.ico"; + public const string Bpp32_transp_not_square = "Icon/32bpp_transp_not_square.ico"; + public const string Bpp32_transp_partial = "Icon/32bpp_transp_partial.ico"; + public const string Bpp32_transp = "Icon/32bpp_transp.ico"; + public const string Bpp4_size_15x15 = "Icon/4bpp_size_15x15.ico"; + public const string Bpp4_size_16x16 = "Icon/4bpp_size_16x16.ico"; + public const string Bpp4_size_17x17 = "Icon/4bpp_size_17x17.ico"; + public const string Bpp4_size_1x1 = "Icon/4bpp_size_1x1.ico"; + public const string Bpp4_size_256x256 = "Icon/4bpp_size_256x256.ico"; + public const string Bpp4_size_2x2 = "Icon/4bpp_size_2x2.ico"; + public const string Bpp4_size_31x31 = "Icon/4bpp_size_31x31.ico"; + public const string Bpp4_size_32x32 = "Icon/4bpp_size_32x32.ico"; + public const string Bpp4_size_33x33 = "Icon/4bpp_size_33x33.ico"; + public const string Bpp4_size_3x3 = "Icon/4bpp_size_3x3.ico"; + public const string Bpp4_size_4x4 = "Icon/4bpp_size_4x4.ico"; + public const string Bpp4_size_5x5 = "Icon/4bpp_size_5x5.ico"; + public const string Bpp4_size_6x6 = "Icon/4bpp_size_6x6.ico"; + public const string Bpp4_size_7x7 = "Icon/4bpp_size_7x7.ico"; + public const string Bpp4_size_8x8 = "Icon/4bpp_size_8x8.ico"; + public const string Bpp4_size_9x9 = "Icon/4bpp_size_9x9.ico"; + public const string Bpp4_transp_not_square = "Icon/4bpp_transp_not_square.ico"; + public const string Bpp4_transp_partial = "Icon/4bpp_transp_partial.ico"; + public const string Bpp8_size_15x15 = "Icon/8bpp_size_15x15.ico"; + public const string Bpp8_size_16x16 = "Icon/8bpp_size_16x16.ico"; + public const string Bpp8_size_17x17 = "Icon/8bpp_size_17x17.ico"; + public const string Bpp8_size_1x1 = "Icon/8bpp_size_1x1.ico"; + public const string Bpp8_size_256x256 = "Icon/8bpp_size_256x256.ico"; + public const string Bpp8_size_2x2 = "Icon/8bpp_size_2x2.ico"; + public const string Bpp8_size_31x31 = "Icon/8bpp_size_31x31.ico"; + public const string Bpp8_size_32x32 = "Icon/8bpp_size_32x32.ico"; + public const string Bpp8_size_33x33 = "Icon/8bpp_size_33x33.ico"; + public const string Bpp8_size_3x3 = "Icon/8bpp_size_3x3.ico"; + public const string Bpp8_size_4x4 = "Icon/8bpp_size_4x4.ico"; + public const string Bpp8_size_5x5 = "Icon/8bpp_size_5x5.ico"; + public const string Bpp8_size_6x6 = "Icon/8bpp_size_6x6.ico"; + public const string Bpp8_size_7x7 = "Icon/8bpp_size_7x7.ico"; + public const string Bpp8_size_8x8 = "Icon/8bpp_size_8x8.ico"; + public const string Bpp8_size_9x9 = "Icon/8bpp_size_9x9.ico"; + public const string Bpp8_transp_not_square = "Icon/8bpp_transp_not_square.ico"; + public const string Bpp8_transp_partial = "Icon/8bpp_transp_partial.ico"; + public const string Invalid_all = "Icon/invalid_all.ico"; + public const string IcoFake = "Icon/ico_fake.cur"; + public const string Invalid_bpp = "Icon/invalid_bpp.ico"; + public const string Invalid_compression = "Icon/invalid_compression.ico"; + public const string Invalid_png = "Icon/invalid_png.ico"; + public const string Invalid_RLE4 = "Icon/invalid_RLE4.ico"; + public const string Invalid_RLE8 = "Icon/invalid_RLE8.ico"; + public const string Mixed_bmp_png_a = "Icon/mixed_bmp_png_a.ico"; + public const string Mixed_bmp_png_b = "Icon/mixed_bmp_png_b.ico"; + public const string Mixed_bmp_png_c = "Icon/mixed_bmp_png_c.ico"; + public const string Multi_size_a = "Icon/multi_size_a.ico"; + public const string Multi_size_b = "Icon/multi_size_b.ico"; + public const string Multi_size_c = "Icon/multi_size_c.ico"; + public const string Multi_size_d = "Icon/multi_size_d.ico"; + public const string Multi_size_e = "Icon/multi_size_e.ico"; + public const string Multi_size_f = "Icon/multi_size_f.ico"; + public const string Multi_size_multi_bits_a = "Icon/multi_size_multi_bits_a.ico"; + public const string Multi_size_multi_bits_b = "Icon/multi_size_multi_bits_b.ico"; + public const string Multi_size_multi_bits_c = "Icon/multi_size_multi_bits_c.ico"; + public const string Multi_size_multi_bits_d = "Icon/multi_size_multi_bits_d.ico"; } public static class Cur { public const string WindowsMouse = "Icon/aero_arrow.cur"; + public const string CurReal = "Icon/cur_real.cur"; + public const string CurFake = "Icon/cur_fake.ico"; } } diff --git a/tests/Images/Input/Icon/1bpp_size_15x15.ico b/tests/Images/Input/Icon/1bpp_size_15x15.ico new file mode 100644 index 000000000..39fc9c521 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_15x15.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:846dda605ee23bb641534b272fa57300eacd85038feea5dd1a3f6d4b543a935e +size 190 diff --git a/tests/Images/Input/Icon/1bpp_size_16x16.ico b/tests/Images/Input/Icon/1bpp_size_16x16.ico new file mode 100644 index 000000000..6179678bc --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_16x16.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:029d0438eda83d4d9e087cf79abe2d0234728c37f570de808355c0e79c71be17 +size 198 diff --git a/tests/Images/Input/Icon/1bpp_size_17x17.ico b/tests/Images/Input/Icon/1bpp_size_17x17.ico new file mode 100644 index 000000000..90138a08d --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_17x17.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8db024a49fdd91c9d5d1fc0c1d6f60991518a5776031f58cdafbdd3ed9e4f26b +size 206 diff --git a/tests/Images/Input/Icon/1bpp_size_1x1.ico b/tests/Images/Input/Icon/1bpp_size_1x1.ico new file mode 100644 index 000000000..1161a3f3c --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_1x1.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f13d1bbfa5b29bc386270cb492b705eded0825a9eb3a6341f4ea2b3dbe085cd1 +size 78 diff --git a/tests/Images/Input/Icon/1bpp_size_256x256.ico b/tests/Images/Input/Icon/1bpp_size_256x256.ico new file mode 100644 index 000000000..d6524a31f --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_256x256.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1aa37daf2fd65b424c5e13e94bed165328e624584cc5664d73bb4030a1e1f12 +size 16454 diff --git a/tests/Images/Input/Icon/1bpp_size_2x2.ico b/tests/Images/Input/Icon/1bpp_size_2x2.ico new file mode 100644 index 000000000..73394156a --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_2x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18be2a5384812de1bec70733fb0b283a159edc5d0bc03981de8fb3ccddb8911e +size 86 diff --git a/tests/Images/Input/Icon/1bpp_size_31x31.ico b/tests/Images/Input/Icon/1bpp_size_31x31.ico new file mode 100644 index 000000000..8dffe659f --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_31x31.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a4e7964e3ed5ca2a98929e2f903a6b969961410aab6a935a0c54fbe716d0c3 +size 318 diff --git a/tests/Images/Input/Icon/1bpp_size_32x32.ico b/tests/Images/Input/Icon/1bpp_size_32x32.ico new file mode 100644 index 000000000..e281eb378 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_32x32.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06a55f8219234e43beec6776fe15a7d6d4ad314deaf64df115ea45f2100e5283 +size 326 diff --git a/tests/Images/Input/Icon/1bpp_size_33x33.ico b/tests/Images/Input/Icon/1bpp_size_33x33.ico new file mode 100644 index 000000000..c5e4677d3 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_33x33.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82fa82f6b954515eace3cdd6d4681abd149b37354a7dee4f0d1966f516f27850 +size 598 diff --git a/tests/Images/Input/Icon/1bpp_size_3x3.ico b/tests/Images/Input/Icon/1bpp_size_3x3.ico new file mode 100644 index 000000000..89872b959 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_3x3.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ff0bf1c415925642d99691a9a9a6b92d9995447bc62ed80b9b0c6d4efdcd19b +size 94 diff --git a/tests/Images/Input/Icon/1bpp_size_4x4.ico b/tests/Images/Input/Icon/1bpp_size_4x4.ico new file mode 100644 index 000000000..1e47b4596 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_4x4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5853ba73b06e0f2a0c71850011526419db3bd7c76e5b7b2f6b22f748ce919bf2 +size 102 diff --git a/tests/Images/Input/Icon/1bpp_size_5x5.ico b/tests/Images/Input/Icon/1bpp_size_5x5.ico new file mode 100644 index 000000000..5152c7575 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_5x5.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecd253a6862ec9a9870c8a8a370528b28c22d23247dfc29e09fab65d95b9416d +size 110 diff --git a/tests/Images/Input/Icon/1bpp_size_6x6.ico b/tests/Images/Input/Icon/1bpp_size_6x6.ico new file mode 100644 index 000000000..a1d5c09c0 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_6x6.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a80c7f8d37dc3997bcd674a5af835cae802cacbf5d65020f0aaae67f70cfc31e +size 118 diff --git a/tests/Images/Input/Icon/1bpp_size_7x7.ico b/tests/Images/Input/Icon/1bpp_size_7x7.ico new file mode 100644 index 000000000..9c5a227e3 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_7x7.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00a26274e8563e6378f8bfa4d1aa9695030593a38791ca366cecd3949b0f52af +size 126 diff --git a/tests/Images/Input/Icon/1bpp_size_8x8.ico b/tests/Images/Input/Icon/1bpp_size_8x8.ico new file mode 100644 index 000000000..c019914ee --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_8x8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78150aee2f5d5ccd2a172abfe11f1127efb950cb9173e22e380809afb2a94d3c +size 134 diff --git a/tests/Images/Input/Icon/1bpp_size_9x9.ico b/tests/Images/Input/Icon/1bpp_size_9x9.ico new file mode 100644 index 000000000..2f3fd28eb --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_size_9x9.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8cecf833b31208710c1dde1bed3322a95453468a3f4b39afdad66ac9bc5f86b +size 142 diff --git a/tests/Images/Input/Icon/1bpp_transp_not_square.ico b/tests/Images/Input/Icon/1bpp_transp_not_square.ico new file mode 100644 index 000000000..1c678ec40 --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_transp_not_square.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd9f41711b53d4e1e915ddb992522b97a981fbe3f4536826f0c66e2d6a3677fb +size 182 diff --git a/tests/Images/Input/Icon/1bpp_transp_partial.ico b/tests/Images/Input/Icon/1bpp_transp_partial.ico new file mode 100644 index 000000000..6365a53df --- /dev/null +++ b/tests/Images/Input/Icon/1bpp_transp_partial.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cedcd221abf4b95115f9f8f34f15da456b09e7e56972cced24a99fa56bf8aca9 +size 326 diff --git a/tests/Images/Input/Icon/24bpp_size_15x15.ico b/tests/Images/Input/Icon/24bpp_size_15x15.ico new file mode 100644 index 000000000..f8697e2b5 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_15x15.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b506403852d936d14975ed9aba1c50ab3873cbbd81afcf38381f8e5e841fafd0 +size 842 diff --git a/tests/Images/Input/Icon/24bpp_size_16x16.ico b/tests/Images/Input/Icon/24bpp_size_16x16.ico new file mode 100644 index 000000000..e6de107d7 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_16x16.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bfa9de0f613e9e82e9037193c4124f87d05ac1390459e2f018da45c16200de6 +size 894 diff --git a/tests/Images/Input/Icon/24bpp_size_17x17.ico b/tests/Images/Input/Icon/24bpp_size_17x17.ico new file mode 100644 index 000000000..2c37ffa8b --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_17x17.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4cd2ad22e55000a706d365e0a3395f3e4d9a3933c00880d5f12903ac0aed60e +size 1014 diff --git a/tests/Images/Input/Icon/24bpp_size_1x1.ico b/tests/Images/Input/Icon/24bpp_size_1x1.ico new file mode 100644 index 000000000..f9137f61a --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_1x1.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b20e9a6479c3831b7af75d30bc909ce3046e45384bfd62d7a10ac6816c1c947 +size 70 diff --git a/tests/Images/Input/Icon/24bpp_size_256x256.ico b/tests/Images/Input/Icon/24bpp_size_256x256.ico new file mode 100644 index 000000000..08f928c8e --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_256x256.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1175641f39e68bd6cd38b8f64f02510b22c5a642afc7503d357b064163b3d37b +size 204862 diff --git a/tests/Images/Input/Icon/24bpp_size_2x2.ico b/tests/Images/Input/Icon/24bpp_size_2x2.ico new file mode 100644 index 000000000..d6d472a25 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_2x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72a1380a306b51ae37eabd3edeb0b134b0f8e8e120e6c64b6b08bd833a9c70a4 +size 86 diff --git a/tests/Images/Input/Icon/24bpp_size_31x31.ico b/tests/Images/Input/Icon/24bpp_size_31x31.ico new file mode 100644 index 000000000..5c585c582 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_31x31.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:574b0971908b44334f1f3be79e5b0ff336e74a8b9947b43a295d3a2b86733965 +size 3162 diff --git a/tests/Images/Input/Icon/24bpp_size_32x32.ico b/tests/Images/Input/Icon/24bpp_size_32x32.ico new file mode 100644 index 000000000..3663b8767 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_32x32.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa45880e5611436fc06de0603481d0a61044e52a1a053961fdda1139bb5660a6 +size 3262 diff --git a/tests/Images/Input/Icon/24bpp_size_33x33.ico b/tests/Images/Input/Icon/24bpp_size_33x33.ico new file mode 100644 index 000000000..4834b48c8 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_33x33.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8704a77d6264f6f104e23950099e3d3a4a577787e67c7c39d7925f9d0a347572 +size 3626 diff --git a/tests/Images/Input/Icon/24bpp_size_3x3.ico b/tests/Images/Input/Icon/24bpp_size_3x3.ico new file mode 100644 index 000000000..f2c11ccfe --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_3x3.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81229c27fbc684c0da99b1b04189046d5b9f531ee4111ccc43bde6404dce4f12 +size 110 diff --git a/tests/Images/Input/Icon/24bpp_size_4x4.ico b/tests/Images/Input/Icon/24bpp_size_4x4.ico new file mode 100644 index 000000000..2d7880a03 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_4x4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7cb620eb83acbf20d308f5c6cb8b0246b8b156cdcc43e39f5809754fd4d5ddb +size 126 diff --git a/tests/Images/Input/Icon/24bpp_size_5x5.ico b/tests/Images/Input/Icon/24bpp_size_5x5.ico new file mode 100644 index 000000000..a98c85c19 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_5x5.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bdf1718e3d9695cdf2552e42219d1e3166ad5a94f53cbb44db4a7222e2a32f9a +size 162 diff --git a/tests/Images/Input/Icon/24bpp_size_6x6.ico b/tests/Images/Input/Icon/24bpp_size_6x6.ico new file mode 100644 index 000000000..5dd3c57c2 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_6x6.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e5f3a8f59e297cf67251a89558d4406c4c515c3e3ce7555df4a53ef5420fc38 +size 206 diff --git a/tests/Images/Input/Icon/24bpp_size_7x7.ico b/tests/Images/Input/Icon/24bpp_size_7x7.ico new file mode 100644 index 000000000..d9622629e --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_7x7.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ee1b5836c9f8c89e9b8c8873f1ad92f7555ed9706f4dc76345a727bf3e9f334 +size 258 diff --git a/tests/Images/Input/Icon/24bpp_size_8x8.ico b/tests/Images/Input/Icon/24bpp_size_8x8.ico new file mode 100644 index 000000000..39be58ce4 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_8x8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b46ca32ddb84074d9140224738480eaa0a6c0dce2dbf2074625add1901c27117 +size 286 diff --git a/tests/Images/Input/Icon/24bpp_size_9x9.ico b/tests/Images/Input/Icon/24bpp_size_9x9.ico new file mode 100644 index 000000000..9e7873eaf --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_size_9x9.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7454d6332f4bdba929d610e4a8a232b1443d5a64119de4e69c00e0f03e55e237 +size 350 diff --git a/tests/Images/Input/Icon/24bpp_transp.ico b/tests/Images/Input/Icon/24bpp_transp.ico new file mode 100644 index 000000000..a64157a63 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_transp.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb8ea41822350e5f40bac2aef19ec7a4c40561ce6637948b3fa6db7835c1fded +size 3262 diff --git a/tests/Images/Input/Icon/24bpp_transp_not_square.ico b/tests/Images/Input/Icon/24bpp_transp_not_square.ico new file mode 100644 index 000000000..5abf2ad66 --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_transp_not_square.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44b4c79ff497df0e99f55d68d82f59c0d7c2f4d5e9bb63bcc1b5910f4a2853db +size 1126 diff --git a/tests/Images/Input/Icon/24bpp_transp_partial.ico b/tests/Images/Input/Icon/24bpp_transp_partial.ico new file mode 100644 index 000000000..d1a37498b --- /dev/null +++ b/tests/Images/Input/Icon/24bpp_transp_partial.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8d109413d7f699b92e9d6a5e2c52d2b5c747f2ca9ff31d326f8d4ec2fd5840f +size 3262 diff --git a/tests/Images/Input/Icon/32bpp_size_15x15.ico b/tests/Images/Input/Icon/32bpp_size_15x15.ico new file mode 100644 index 000000000..a7f94e94d --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_15x15.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02b5abd8e15ef2fba1a26cdbdf6e8f66abbbf9aa188404ef911a1d2d02b7b050 +size 1022 diff --git a/tests/Images/Input/Icon/32bpp_size_16x16.ico b/tests/Images/Input/Icon/32bpp_size_16x16.ico new file mode 100644 index 000000000..609a51826 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_16x16.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50bb07bd12f9b388ba6a3abbb815aaf1c800438e35ec48201269fa23342e5622 +size 1150 diff --git a/tests/Images/Input/Icon/32bpp_size_17x17.ico b/tests/Images/Input/Icon/32bpp_size_17x17.ico new file mode 100644 index 000000000..13a71140f --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_17x17.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf4c701d38fc988186e3fcfee6e426ed5a84807b54b91e4ab8c1b12e0794746 +size 1286 diff --git a/tests/Images/Input/Icon/32bpp_size_1x1.ico b/tests/Images/Input/Icon/32bpp_size_1x1.ico new file mode 100644 index 000000000..3f449eefe --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_1x1.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f62be892b36609a57c34eb4784dfb6dc82698ecd15709898a6579ee4f21f668e +size 70 diff --git a/tests/Images/Input/Icon/32bpp_size_256x256.ico b/tests/Images/Input/Icon/32bpp_size_256x256.ico new file mode 100644 index 000000000..2229aee95 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_256x256.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b15f75adc54e70c4751cf9973854f225ef15b12bdaddb5ab00cb9edfd8b386b1 +size 270398 diff --git a/tests/Images/Input/Icon/32bpp_size_2x2.ico b/tests/Images/Input/Icon/32bpp_size_2x2.ico new file mode 100644 index 000000000..cbb64292b --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_2x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa859752136cdf055874ae71589f9585aaaeaef804b13ce192991793d9e57e57 +size 86 diff --git a/tests/Images/Input/Icon/32bpp_size_31x31.ico b/tests/Images/Input/Icon/32bpp_size_31x31.ico new file mode 100644 index 000000000..837e8f512 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_31x31.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5ecf1dc1fdd3dc6cb2fd90b49632b19260e73ad3a6624372d1e9eefc470ed6b +size 4030 diff --git a/tests/Images/Input/Icon/32bpp_size_32x32.ico b/tests/Images/Input/Icon/32bpp_size_32x32.ico new file mode 100644 index 000000000..b359d4d84 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_32x32.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:395ccdc596487ed63db4d893d03b6aa24743b37279d896196d8d38e7028415ea +size 4286 diff --git a/tests/Images/Input/Icon/32bpp_size_33x33.ico b/tests/Images/Input/Icon/32bpp_size_33x33.ico new file mode 100644 index 000000000..01df9ae7d --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_33x33.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e8b89c061abcf959b90149585cc34237aad19e1f67c162d62e5adbad4826970 +size 4682 diff --git a/tests/Images/Input/Icon/32bpp_size_3x3.ico b/tests/Images/Input/Icon/32bpp_size_3x3.ico new file mode 100644 index 000000000..8879d3f1f --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_3x3.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52b7772a9c8fae189abc3cf37d5d24bcdfe94077e6b1cf7be8cc7e0bc6f6bddf +size 110 diff --git a/tests/Images/Input/Icon/32bpp_size_4x4.ico b/tests/Images/Input/Icon/32bpp_size_4x4.ico new file mode 100644 index 000000000..d800b2198 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_4x4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bb0831fab10fb0ff9a0b2b2ea137609a45a6ec3982d594878996eafc32836ad +size 142 diff --git a/tests/Images/Input/Icon/32bpp_size_5x5.ico b/tests/Images/Input/Icon/32bpp_size_5x5.ico new file mode 100644 index 000000000..710c38a23 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_5x5.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10e2ed5cbacc761f2d467c22a8d72dcc4086b9423c5487ba05826804c642730a +size 182 diff --git a/tests/Images/Input/Icon/32bpp_size_6x6.ico b/tests/Images/Input/Icon/32bpp_size_6x6.ico new file mode 100644 index 000000000..4223c1fc2 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_6x6.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ab79a308d24592557b368f00566999d777e38de385eb6ebabc90d53ac723ae9 +size 230 diff --git a/tests/Images/Input/Icon/32bpp_size_7x7.ico b/tests/Images/Input/Icon/32bpp_size_7x7.ico new file mode 100644 index 000000000..1e321acb6 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_7x7.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3799164811b0284535fa90ca60f13e9c0754ca4e8f00d96a83951e91e748d760 +size 286 diff --git a/tests/Images/Input/Icon/32bpp_size_8x8.ico b/tests/Images/Input/Icon/32bpp_size_8x8.ico new file mode 100644 index 000000000..b44fe22d5 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_8x8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a3187750c9313024f983fdfca270f5db2c5d730831adfce0fb19ddac25deecc +size 350 diff --git a/tests/Images/Input/Icon/32bpp_size_9x9.ico b/tests/Images/Input/Icon/32bpp_size_9x9.ico new file mode 100644 index 000000000..682b148ed --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_size_9x9.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ab8bd47cc2d2ce0e9c1a5810b5ccbe3f46e35001114c18f34cbf51ff0566bf6 +size 422 diff --git a/tests/Images/Input/Icon/32bpp_transp.ico b/tests/Images/Input/Icon/32bpp_transp.ico new file mode 100644 index 000000000..592536290 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_transp.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4f94718304fa41041b8cebad7b76762d410b366b46d620f5599b03a2fa7ba00 +size 4286 diff --git a/tests/Images/Input/Icon/32bpp_transp_not_square.ico b/tests/Images/Input/Icon/32bpp_transp_not_square.ico new file mode 100644 index 000000000..3a0bb3dd0 --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_transp_not_square.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e3c2061b64df989e8ae68aee993eba5e11d03e23f282f063cc3929ec3ef2b0c +size 1462 diff --git a/tests/Images/Input/Icon/32bpp_transp_partial.ico b/tests/Images/Input/Icon/32bpp_transp_partial.ico new file mode 100644 index 000000000..334a0b75c --- /dev/null +++ b/tests/Images/Input/Icon/32bpp_transp_partial.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5bd34021ec302f203c39d038854607d9fe8bcd3133ea57b65ebc6da81aa8a4b +size 4286 diff --git a/tests/Images/Input/Icon/4bpp_size_15x15.ico b/tests/Images/Input/Icon/4bpp_size_15x15.ico new file mode 100644 index 000000000..ce67e54cc --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_15x15.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd1fd73648c1b7acc4aef4e0c4e6672ab7241e47cb52345c4459aa86185f4dfd +size 306 diff --git a/tests/Images/Input/Icon/4bpp_size_16x16.ico b/tests/Images/Input/Icon/4bpp_size_16x16.ico new file mode 100644 index 000000000..f26d88b36 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_16x16.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5f8547e5e6aae3a2f662fa266d0f78731d310fb051f99dce5693d6adcbfbb4f +size 318 diff --git a/tests/Images/Input/Icon/4bpp_size_17x17.ico b/tests/Images/Input/Icon/4bpp_size_17x17.ico new file mode 100644 index 000000000..aa44dd4f1 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_17x17.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fd0286a127371d4b40a5c30c6b221753a711231e386b636fcb07d2f1f92967f +size 398 diff --git a/tests/Images/Input/Icon/4bpp_size_1x1.ico b/tests/Images/Input/Icon/4bpp_size_1x1.ico new file mode 100644 index 000000000..7049b0b36 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_1x1.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:313f969c26d12cae6e778563d1c6c9df248c5d28ff657ad5054a280e06573106 +size 134 diff --git a/tests/Images/Input/Icon/4bpp_size_256x256.ico b/tests/Images/Input/Icon/4bpp_size_256x256.ico new file mode 100644 index 000000000..fa0740065 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_256x256.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcad369c1e56dd01b8644a5903ad25af19cc78047b5e0821546d1171a0ab31ff +size 41086 diff --git a/tests/Images/Input/Icon/4bpp_size_2x2.ico b/tests/Images/Input/Icon/4bpp_size_2x2.ico new file mode 100644 index 000000000..2b8d74afa --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_2x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11b1142401678412ce8718dd6e943fab05ec7974c7ae36286316e7f4d168f0f5 +size 142 diff --git a/tests/Images/Input/Icon/4bpp_size_31x31.ico b/tests/Images/Input/Icon/4bpp_size_31x31.ico new file mode 100644 index 000000000..86b71f36c --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_31x31.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b19f60f3441b268a19be0f99078d079c4eb416b882118071ad43ceff41ca40b +size 746 diff --git a/tests/Images/Input/Icon/4bpp_size_32x32.ico b/tests/Images/Input/Icon/4bpp_size_32x32.ico new file mode 100644 index 000000000..aa8fc0932 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_32x32.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f0863b486575dca2d5e3ce07da10cb30e039524f17026d9668d75bad780e833 +size 766 diff --git a/tests/Images/Input/Icon/4bpp_size_33x33.ico b/tests/Images/Input/Icon/4bpp_size_33x33.ico new file mode 100644 index 000000000..ad93685f2 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_33x33.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64b77f80fb48237a0f9cfbd808289e53ed7a70b107f190c93d76d32342829ccb +size 1050 diff --git a/tests/Images/Input/Icon/4bpp_size_3x3.ico b/tests/Images/Input/Icon/4bpp_size_3x3.ico new file mode 100644 index 000000000..781266a67 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_3x3.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8cb358defec66494d3be4eb01fd7e304edfd04435b4552d51769d0858659686 +size 150 diff --git a/tests/Images/Input/Icon/4bpp_size_4x4.ico b/tests/Images/Input/Icon/4bpp_size_4x4.ico new file mode 100644 index 000000000..ffe599149 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_4x4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53083ae340dcc04d88acaf9d75a2dc68b23500c294eb3ed4e15e3fba86c5843f +size 158 diff --git a/tests/Images/Input/Icon/4bpp_size_5x5.ico b/tests/Images/Input/Icon/4bpp_size_5x5.ico new file mode 100644 index 000000000..70f2db036 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_5x5.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4f1401d87e285b18e6f818dfb00a3fb275e72e2fd4bce6652bfdd74bf9565f3 +size 166 diff --git a/tests/Images/Input/Icon/4bpp_size_6x6.ico b/tests/Images/Input/Icon/4bpp_size_6x6.ico new file mode 100644 index 000000000..230d8e85f --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_6x6.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3fa6bdd6125a9de6d3ddaafd5e62e0435b14c52f82239a6d36d44aaf5a49361 +size 174 diff --git a/tests/Images/Input/Icon/4bpp_size_7x7.ico b/tests/Images/Input/Icon/4bpp_size_7x7.ico new file mode 100644 index 000000000..7c4b9834b --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_7x7.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4025a4bd2dde619f950f69d38e042ae38d2a1840d7b00540c372546b5d8ccb0 +size 182 diff --git a/tests/Images/Input/Icon/4bpp_size_8x8.ico b/tests/Images/Input/Icon/4bpp_size_8x8.ico new file mode 100644 index 000000000..b1f3050bf --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_8x8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:332563676808485aac8e2635baacad3f4d1ce5fc64c6be07daa25962f4fa1687 +size 190 diff --git a/tests/Images/Input/Icon/4bpp_size_9x9.ico b/tests/Images/Input/Icon/4bpp_size_9x9.ico new file mode 100644 index 000000000..fc7751086 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_size_9x9.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da5169395108194503853db2f431e0d02b98a191a1bf597c31fe269e7d84206a +size 234 diff --git a/tests/Images/Input/Icon/4bpp_transp_not_square.ico b/tests/Images/Input/Icon/4bpp_transp_not_square.ico new file mode 100644 index 000000000..6b2babe8e --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_transp_not_square.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dede473b7a427029cdb319ecf04aaf71cf8ce1d806efb65d7e3844ebd1703f9 +size 350 diff --git a/tests/Images/Input/Icon/4bpp_transp_partial.ico b/tests/Images/Input/Icon/4bpp_transp_partial.ico new file mode 100644 index 000000000..4394b9e43 --- /dev/null +++ b/tests/Images/Input/Icon/4bpp_transp_partial.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e0617f49eb057ab0346203e0cd04fd4e93de71053bbbfaa3c9655696b10a80d +size 766 diff --git a/tests/Images/Input/Icon/8bpp_size_15x15.ico b/tests/Images/Input/Icon/8bpp_size_15x15.ico new file mode 100644 index 000000000..086edb745 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_15x15.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:577c86aa8b78cca86885ed20b702b260150eacf738beb18da28c25bcb01cfdcd +size 1386 diff --git a/tests/Images/Input/Icon/8bpp_size_16x16.ico b/tests/Images/Input/Icon/8bpp_size_16x16.ico new file mode 100644 index 000000000..e09d74447 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_16x16.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9900b6783aac800836e19890f11fda1bf1d2138dee63403adbc79bf207a71dfd +size 1406 diff --git a/tests/Images/Input/Icon/8bpp_size_17x17.ico b/tests/Images/Input/Icon/8bpp_size_17x17.ico new file mode 100644 index 000000000..9a5684b4d --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_17x17.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b1c214eb0fd9f5b1f1da4ceeb98a3cc0d7b6b02d7ed6aaa5138078e48bf8613 +size 1494 diff --git a/tests/Images/Input/Icon/8bpp_size_1x1.ico b/tests/Images/Input/Icon/8bpp_size_1x1.ico new file mode 100644 index 000000000..20961847a --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_1x1.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:032c11a8e5aaa5f9beb997c83504233b5ad278d3a4e129cd384a4ca62950a998 +size 1094 diff --git a/tests/Images/Input/Icon/8bpp_size_256x256.ico b/tests/Images/Input/Icon/8bpp_size_256x256.ico new file mode 100644 index 000000000..59b0bb63b --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_256x256.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa8f4fc33c541f88318d7c36a5b5e964cb0470c541677790b49b85638c63fd6 +size 74814 diff --git a/tests/Images/Input/Icon/8bpp_size_2x2.ico b/tests/Images/Input/Icon/8bpp_size_2x2.ico new file mode 100644 index 000000000..bcbdf9c07 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_2x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e1c2b72b6ddf0cfedd9e20e5abf3ae3fd19066be811251bb9db404e6dabe279 +size 1102 diff --git a/tests/Images/Input/Icon/8bpp_size_31x31.ico b/tests/Images/Input/Icon/8bpp_size_31x31.ico new file mode 100644 index 000000000..7c320d183 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_31x31.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a588e6f1031c1c8f0d66b9eef770d01b3f79aaea4203d58ee07255c1c6ee258 +size 2238 diff --git a/tests/Images/Input/Icon/8bpp_size_32x32.ico b/tests/Images/Input/Icon/8bpp_size_32x32.ico new file mode 100644 index 000000000..af7581f35 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_32x32.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b00ab33cbb42e9f499844e5add713fcc83854cc2cb104d5c9bd04a456662099 +size 2238 diff --git a/tests/Images/Input/Icon/8bpp_size_33x33.ico b/tests/Images/Input/Icon/8bpp_size_33x33.ico new file mode 100644 index 000000000..3c8043fe6 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_33x33.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e356bb9200375f8fcbc5a0ed70353194a7b1432133e0c61f0b2f03bca3888080 +size 2538 diff --git a/tests/Images/Input/Icon/8bpp_size_3x3.ico b/tests/Images/Input/Icon/8bpp_size_3x3.ico new file mode 100644 index 000000000..075791998 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_3x3.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13cccd62e97bbe6cdd6ca5c4bc765794c7babd3638ff4d09b116a62c3c50be56 +size 1110 diff --git a/tests/Images/Input/Icon/8bpp_size_4x4.ico b/tests/Images/Input/Icon/8bpp_size_4x4.ico new file mode 100644 index 000000000..af95b287d --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_4x4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4343544be11a37083219d99d2dfb4a16b02c309d54223897d6c71503414cc2ef +size 1118 diff --git a/tests/Images/Input/Icon/8bpp_size_5x5.ico b/tests/Images/Input/Icon/8bpp_size_5x5.ico new file mode 100644 index 000000000..2e4f80495 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_5x5.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7cfdf4a0d696573a4cb5291e3165c98444692f1d03a3cab1630df33c765ecd9 +size 1146 diff --git a/tests/Images/Input/Icon/8bpp_size_6x6.ico b/tests/Images/Input/Icon/8bpp_size_6x6.ico new file mode 100644 index 000000000..65ca70be2 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_6x6.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8d36eb593ee34d22e5ff99a08f2b177dbb92355f85210b2a9e53d7ce9e2cc6b +size 1158 diff --git a/tests/Images/Input/Icon/8bpp_size_7x7.ico b/tests/Images/Input/Icon/8bpp_size_7x7.ico new file mode 100644 index 000000000..e02e8fb37 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_7x7.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29050464e324a70bb8a41fb5732b96dbf3bcb36a74ca29775e1f37b07b9ee582 +size 1170 diff --git a/tests/Images/Input/Icon/8bpp_size_8x8.ico b/tests/Images/Input/Icon/8bpp_size_8x8.ico new file mode 100644 index 000000000..39be58ce4 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_8x8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b46ca32ddb84074d9140224738480eaa0a6c0dce2dbf2074625add1901c27117 +size 286 diff --git a/tests/Images/Input/Icon/8bpp_size_9x9.ico b/tests/Images/Input/Icon/8bpp_size_9x9.ico new file mode 100644 index 000000000..8819490ae --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_size_9x9.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ddeaa63b1ec9d389c140499e2b5d2e911a57d8f37467696bb9e9ec3e60ec77b +size 1230 diff --git a/tests/Images/Input/Icon/8bpp_transp_not_square.ico b/tests/Images/Input/Icon/8bpp_transp_not_square.ico new file mode 100644 index 000000000..6b6bbdc9f --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_transp_not_square.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04a255b4cd43ec7005a9a946c2b3dba57eba709b16be85ef77e553943d35f745 +size 1478 diff --git a/tests/Images/Input/Icon/8bpp_transp_partial.ico b/tests/Images/Input/Icon/8bpp_transp_partial.ico new file mode 100644 index 000000000..73cd34c73 --- /dev/null +++ b/tests/Images/Input/Icon/8bpp_transp_partial.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20abca3096b955bc91ed95d194ff3f856473d6d372b5bea0d8c75fd20a231a26 +size 2238 diff --git a/tests/Images/Input/Icon/cur_fake.ico b/tests/Images/Input/Icon/cur_fake.ico new file mode 100644 index 000000000..cad7542c8 --- /dev/null +++ b/tests/Images/Input/Icon/cur_fake.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6679016e7954e335cef537630d122cc3a7a05cb2f3ef32f72811d724b83d4c28 +size 4286 diff --git a/tests/Images/Input/Icon/cur_real.cur b/tests/Images/Input/Icon/cur_real.cur new file mode 100644 index 000000000..cad7542c8 --- /dev/null +++ b/tests/Images/Input/Icon/cur_real.cur @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6679016e7954e335cef537630d122cc3a7a05cb2f3ef32f72811d724b83d4c28 +size 4286 diff --git a/tests/Images/Input/Icon/ico_fake.cur b/tests/Images/Input/Icon/ico_fake.cur new file mode 100644 index 000000000..5ab040538 --- /dev/null +++ b/tests/Images/Input/Icon/ico_fake.cur @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d3a3185dcf0a2c3f5d3fe821474f6787c4de7cffe08db6bd730073ad94e7538 +size 4286 diff --git a/tests/Images/Input/Icon/invalid_RLE4.ico b/tests/Images/Input/Icon/invalid_RLE4.ico new file mode 100644 index 000000000..5d2e25fd3 --- /dev/null +++ b/tests/Images/Input/Icon/invalid_RLE4.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43c543a1c66608a89f0b187afa1526af4be4f7c94265897002b3a150328b964e +size 86 diff --git a/tests/Images/Input/Icon/invalid_RLE8.ico b/tests/Images/Input/Icon/invalid_RLE8.ico new file mode 100644 index 000000000..2ebefbf33 --- /dev/null +++ b/tests/Images/Input/Icon/invalid_RLE8.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b55b3f3f87d9d5801150ffd999c62623528a33d031ca8d6fe665be3328d8c94d +size 86 diff --git a/tests/Images/Input/Icon/invalid_all.ico b/tests/Images/Input/Icon/invalid_all.ico new file mode 100644 index 000000000..ca34a2578 --- /dev/null +++ b/tests/Images/Input/Icon/invalid_all.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93c4f059ced481667315dd7b90e9c1beed0a42a08f3ff8e51e2388919fafa79a +size 283 diff --git a/tests/Images/Input/Icon/invalid_bpp.ico b/tests/Images/Input/Icon/invalid_bpp.ico new file mode 100644 index 000000000..913f780ed --- /dev/null +++ b/tests/Images/Input/Icon/invalid_bpp.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:396bfd08531fc0eae8b5e364ef4c62035e8493a3f59286dedbca6f441d8e9690 +size 86 diff --git a/tests/Images/Input/Icon/invalid_compression.ico b/tests/Images/Input/Icon/invalid_compression.ico new file mode 100644 index 000000000..7e697d3d2 --- /dev/null +++ b/tests/Images/Input/Icon/invalid_compression.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f051ed80db684748d2b2669c77b95068e209b2fe2917fa65f6218beef4dcead5 +size 830 diff --git a/tests/Images/Input/Icon/invalid_png.ico b/tests/Images/Input/Icon/invalid_png.ico new file mode 100644 index 000000000..cbd394fc6 --- /dev/null +++ b/tests/Images/Input/Icon/invalid_png.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61bc9e74d8fd9f72c8ccaf9a3887c517e17c0a39d9d41acabc3699be545b9703 +size 901 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_a.ico b/tests/Images/Input/Icon/mixed_bmp_png_a.ico new file mode 100644 index 000000000..f35027255 --- /dev/null +++ b/tests/Images/Input/Icon/mixed_bmp_png_a.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47adfa4e36adf74ae49cfa481cb54cffe659c09d4b52765e973b6adc8cc31e97 +size 3653 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_b.ico b/tests/Images/Input/Icon/mixed_bmp_png_b.ico new file mode 100644 index 000000000..3efdcab74 --- /dev/null +++ b/tests/Images/Input/Icon/mixed_bmp_png_b.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96338768d8f9c90a6af94268dc55d94809a412c3164c381926b3759ffbf2df79 +size 45693 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_c.ico b/tests/Images/Input/Icon/mixed_bmp_png_c.ico new file mode 100644 index 000000000..65b504eef --- /dev/null +++ b/tests/Images/Input/Icon/mixed_bmp_png_c.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9be0358616581f45eddeaee474c0ce0be8a82279428f5ef1890b2fb6f0c0d27 +size 164189 diff --git a/tests/Images/Input/Icon/multi_size_a.ico b/tests/Images/Input/Icon/multi_size_a.ico new file mode 100644 index 000000000..c34fdc638 --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_a.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dba75ec62f5785ce5d16c2c5c04637b18ccb164917092373b3d470326e7bc0c4 +size 17542 diff --git a/tests/Images/Input/Icon/multi_size_b.ico b/tests/Images/Input/Icon/multi_size_b.ico new file mode 100644 index 000000000..2065bd638 --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_b.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d7ac6313ee263103f4ab6aa5147b1d85bf5ff792c0980189aac9bfab1288011 +size 99678 diff --git a/tests/Images/Input/Icon/multi_size_c.ico b/tests/Images/Input/Icon/multi_size_c.ico new file mode 100644 index 000000000..c6ee58297 --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_c.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85e04b807e084bc9a0e1a65289e21ca1baac1e70c3a37fabbea7d69995945f08 +size 202850 diff --git a/tests/Images/Input/Icon/multi_size_d.ico b/tests/Images/Input/Icon/multi_size_d.ico new file mode 100644 index 000000000..3d9fc96fb --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_d.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a0ce27b63b386c4fdbf7835db738f0e98f274f5a112b7bd45c32dfab93952a0 +size 216804 diff --git a/tests/Images/Input/Icon/multi_size_e.ico b/tests/Images/Input/Icon/multi_size_e.ico new file mode 100644 index 000000000..8f2991acb --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_e.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:363ff3655e978ffe30a8cefec3bd4202a1ae0a22f3ab48e56362f56a31fb349f +size 372526 diff --git a/tests/Images/Input/Icon/multi_size_f.ico b/tests/Images/Input/Icon/multi_size_f.ico new file mode 100644 index 000000000..99948cf1e --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_f.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04701ce87eb82280a4e53d816a0ac3ee91ebc28b1959641bddb90787015ff4a8 +size 3084 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_a.ico b/tests/Images/Input/Icon/multi_size_multi_bits_a.ico new file mode 100644 index 000000000..12b2bf66c --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_multi_bits_a.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1561795509c9e8dbf0633db9b4c242e72e0ebe4ae9a7718328e96b6b273c3ca +size 4710 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_b.ico b/tests/Images/Input/Icon/multi_size_multi_bits_b.ico new file mode 100644 index 000000000..599168aea --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_multi_bits_b.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fe15c1a8ca4bae0ad588863418af960fb62def2db4abfcae594de4fc1b2304a +size 31134 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_c.ico b/tests/Images/Input/Icon/multi_size_multi_bits_c.ico new file mode 100644 index 000000000..701b574dc --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_multi_bits_c.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5afa3da7d278c1d2272581971117408c38ec5fe3aaac17e5234f7a8012fd9e2 +size 72513 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_d.ico b/tests/Images/Input/Icon/multi_size_multi_bits_d.ico new file mode 100644 index 000000000..271ec92a5 --- /dev/null +++ b/tests/Images/Input/Icon/multi_size_multi_bits_d.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0754e570ab3a2ce81759aa206fc6e8780fb1024fb20e60dbdb329ee4c0c1831 +size 293950 From bad83f9a9018f0a2021e5d06b360b8484be16b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 18 Jun 2024 21:29:23 +0800 Subject: [PATCH 24/28] Update identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Ico/IcoDecoderTests.cs | 222 +++++++++--------- tests/ImageSharp.Tests/TestImages.cs | 222 +++++++++--------- 2 files changed, 222 insertions(+), 222 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index ab4ec0a3d..8ea7cf6d4 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -24,118 +24,118 @@ public class IcoDecoderTests } [Theory] - [WithFile(Bpp1_size_15x15, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_16x16, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_17x17, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_1x1, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_256x256, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_2x2, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_31x31, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_32x32, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_33x33, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_3x3, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_4x4, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_5x5, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_6x6, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_7x7, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_8x8, PixelTypes.Rgba32)] - [WithFile(Bpp1_size_9x9, PixelTypes.Rgba32)] - [WithFile(Bpp1_transp_not_square, PixelTypes.Rgba32)] - [WithFile(Bpp1_transp_partial, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_15x15, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_16x16, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_17x17, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_1x1, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_256x256, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_2x2, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_31x31, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_32x32, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_33x33, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_3x3, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_4x4, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_5x5, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_6x6, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_7x7, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_8x8, PixelTypes.Rgba32)] - [WithFile(Bpp24_size_9x9, PixelTypes.Rgba32)] - [WithFile(Bpp24_transp_not_square, PixelTypes.Rgba32)] - [WithFile(Bpp24_transp_partial, PixelTypes.Rgba32)] - [WithFile(Bpp24_transp, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_15x15, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_16x16, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_17x17, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_1x1, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_256x256, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_2x2, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_31x31, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_32x32, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_33x33, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_3x3, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_4x4, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_5x5, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_6x6, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_7x7, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_8x8, PixelTypes.Rgba32)] - [WithFile(Bpp32_size_9x9, PixelTypes.Rgba32)] - [WithFile(Bpp32_transp_not_square, PixelTypes.Rgba32)] - [WithFile(Bpp32_transp_partial, PixelTypes.Rgba32)] - [WithFile(Bpp32_transp, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_15x15, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_16x16, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_17x17, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_1x1, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_256x256, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_2x2, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_31x31, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_32x32, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_33x33, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_3x3, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_4x4, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_5x5, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_6x6, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_7x7, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_8x8, PixelTypes.Rgba32)] - [WithFile(Bpp4_size_9x9, PixelTypes.Rgba32)] - [WithFile(Bpp4_transp_not_square, PixelTypes.Rgba32)] - [WithFile(Bpp4_transp_partial, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_15x15, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_16x16, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_17x17, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_1x1, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_256x256, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_2x2, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_31x31, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_32x32, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_33x33, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_3x3, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_4x4, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_5x5, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_6x6, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_7x7, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_8x8, PixelTypes.Rgba32)] - [WithFile(Bpp8_size_9x9, PixelTypes.Rgba32)] - [WithFile(Bpp8_transp_not_square, PixelTypes.Rgba32)] - [WithFile(Bpp8_transp_partial, PixelTypes.Rgba32)] + [WithFile(Bpp1Size15x15, PixelTypes.Rgba32)] + [WithFile(Bpp1Size16x16, PixelTypes.Rgba32)] + [WithFile(Bpp1Size17x17, PixelTypes.Rgba32)] + [WithFile(Bpp1Size1x1, PixelTypes.Rgba32)] + [WithFile(Bpp1Size256x256, PixelTypes.Rgba32)] + [WithFile(Bpp1Size2x2, PixelTypes.Rgba32)] + [WithFile(Bpp1Size31x31, PixelTypes.Rgba32)] + [WithFile(Bpp1Size32x32, PixelTypes.Rgba32)] + [WithFile(Bpp1Size33x33, PixelTypes.Rgba32)] + [WithFile(Bpp1Size3x3, PixelTypes.Rgba32)] + [WithFile(Bpp1Size4x4, PixelTypes.Rgba32)] + [WithFile(Bpp1Size5x5, PixelTypes.Rgba32)] + [WithFile(Bpp1Size6x6, PixelTypes.Rgba32)] + [WithFile(Bpp1Size7x7, PixelTypes.Rgba32)] + [WithFile(Bpp1Size8x8, PixelTypes.Rgba32)] + [WithFile(Bpp1Size9x9, PixelTypes.Rgba32)] + [WithFile(Bpp1TranspNotSquare, PixelTypes.Rgba32)] + [WithFile(Bpp1TranspPartial, PixelTypes.Rgba32)] + [WithFile(Bpp24Size15x15, PixelTypes.Rgba32)] + [WithFile(Bpp24Size16x16, PixelTypes.Rgba32)] + [WithFile(Bpp24Size17x17, PixelTypes.Rgba32)] + [WithFile(Bpp24Size1x1, PixelTypes.Rgba32)] + [WithFile(Bpp24Size256x256, PixelTypes.Rgba32)] + [WithFile(Bpp24Size2x2, PixelTypes.Rgba32)] + [WithFile(Bpp24Size31x31, PixelTypes.Rgba32)] + [WithFile(Bpp24Size32x32, PixelTypes.Rgba32)] + [WithFile(Bpp24Size33x33, PixelTypes.Rgba32)] + [WithFile(Bpp24Size3x3, PixelTypes.Rgba32)] + [WithFile(Bpp24Size4x4, PixelTypes.Rgba32)] + [WithFile(Bpp24Size5x5, PixelTypes.Rgba32)] + [WithFile(Bpp24Size6x6, PixelTypes.Rgba32)] + [WithFile(Bpp24Size7x7, PixelTypes.Rgba32)] + [WithFile(Bpp24Size8x8, PixelTypes.Rgba32)] + [WithFile(Bpp24Size9x9, PixelTypes.Rgba32)] + [WithFile(Bpp24TranspNotSquare, PixelTypes.Rgba32)] + [WithFile(Bpp24TranspPartial, PixelTypes.Rgba32)] + [WithFile(Bpp24Transp, PixelTypes.Rgba32)] + [WithFile(Bpp32Size15x15, PixelTypes.Rgba32)] + [WithFile(Bpp32Size16x16, PixelTypes.Rgba32)] + [WithFile(Bpp32Size17x17, PixelTypes.Rgba32)] + [WithFile(Bpp32Size1x1, PixelTypes.Rgba32)] + [WithFile(Bpp32Size256x256, PixelTypes.Rgba32)] + [WithFile(Bpp32Size2x2, PixelTypes.Rgba32)] + [WithFile(Bpp32Size31x31, PixelTypes.Rgba32)] + [WithFile(Bpp32Size32x32, PixelTypes.Rgba32)] + [WithFile(Bpp32Size33x33, PixelTypes.Rgba32)] + [WithFile(Bpp32Size3x3, PixelTypes.Rgba32)] + [WithFile(Bpp32Size4x4, PixelTypes.Rgba32)] + [WithFile(Bpp32Size5x5, PixelTypes.Rgba32)] + [WithFile(Bpp32Size6x6, PixelTypes.Rgba32)] + [WithFile(Bpp32Size7x7, PixelTypes.Rgba32)] + [WithFile(Bpp32Size8x8, PixelTypes.Rgba32)] + [WithFile(Bpp32Size9x9, PixelTypes.Rgba32)] + [WithFile(Bpp32TranspNotSquare, PixelTypes.Rgba32)] + [WithFile(Bpp32TranspPartial, PixelTypes.Rgba32)] + [WithFile(Bpp32Transp, PixelTypes.Rgba32)] + [WithFile(Bpp4Size15x15, PixelTypes.Rgba32)] + [WithFile(Bpp4Size16x16, PixelTypes.Rgba32)] + [WithFile(Bpp4Size17x17, PixelTypes.Rgba32)] + [WithFile(Bpp4Size1x1, PixelTypes.Rgba32)] + [WithFile(Bpp4Size256x256, PixelTypes.Rgba32)] + [WithFile(Bpp4Size2x2, PixelTypes.Rgba32)] + [WithFile(Bpp4Size31x31, PixelTypes.Rgba32)] + [WithFile(Bpp4Size32x32, PixelTypes.Rgba32)] + [WithFile(Bpp4Size33x33, PixelTypes.Rgba32)] + [WithFile(Bpp4Size3x3, PixelTypes.Rgba32)] + [WithFile(Bpp4Size4x4, PixelTypes.Rgba32)] + [WithFile(Bpp4Size5x5, PixelTypes.Rgba32)] + [WithFile(Bpp4Size6x6, PixelTypes.Rgba32)] + [WithFile(Bpp4Size7x7, PixelTypes.Rgba32)] + [WithFile(Bpp4Size8x8, PixelTypes.Rgba32)] + [WithFile(Bpp4Size9x9, PixelTypes.Rgba32)] + [WithFile(Bpp4TranspNotSquare, PixelTypes.Rgba32)] + [WithFile(Bpp4TranspPartial, PixelTypes.Rgba32)] + [WithFile(Bpp8Size15x15, PixelTypes.Rgba32)] + [WithFile(Bpp8Size16x16, PixelTypes.Rgba32)] + [WithFile(Bpp8Size17x17, PixelTypes.Rgba32)] + [WithFile(Bpp8Size1x1, PixelTypes.Rgba32)] + [WithFile(Bpp8Size256x256, PixelTypes.Rgba32)] + [WithFile(Bpp8Size2x2, PixelTypes.Rgba32)] + [WithFile(Bpp8Size31x31, PixelTypes.Rgba32)] + [WithFile(Bpp8Size32x32, PixelTypes.Rgba32)] + [WithFile(Bpp8Size33x33, PixelTypes.Rgba32)] + [WithFile(Bpp8Size3x3, PixelTypes.Rgba32)] + [WithFile(Bpp8Size4x4, PixelTypes.Rgba32)] + [WithFile(Bpp8Size5x5, PixelTypes.Rgba32)] + [WithFile(Bpp8Size6x6, PixelTypes.Rgba32)] + [WithFile(Bpp8Size7x7, PixelTypes.Rgba32)] + [WithFile(Bpp8Size8x8, PixelTypes.Rgba32)] + [WithFile(Bpp8Size9x9, PixelTypes.Rgba32)] + [WithFile(Bpp8TranspNotSquare, PixelTypes.Rgba32)] + [WithFile(Bpp8TranspPartial, PixelTypes.Rgba32)] + [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(IcoFake, PixelTypes.Rgba32)] - [WithFile(Invalid_all, PixelTypes.Rgba32)] - [WithFile(Invalid_bpp, PixelTypes.Rgba32)] - [WithFile(Invalid_compression, PixelTypes.Rgba32)] - [WithFile(Invalid_png, PixelTypes.Rgba32)] - [WithFile(Invalid_RLE4, PixelTypes.Rgba32)] - [WithFile(Invalid_RLE8, PixelTypes.Rgba32)] - [WithFile(Mixed_bmp_png_a, PixelTypes.Rgba32)] - [WithFile(Mixed_bmp_png_b, PixelTypes.Rgba32)] - [WithFile(Mixed_bmp_png_c, PixelTypes.Rgba32)] - [WithFile(Multi_size_a, PixelTypes.Rgba32)] - [WithFile(Multi_size_b, PixelTypes.Rgba32)] - [WithFile(Multi_size_c, PixelTypes.Rgba32)] - [WithFile(Multi_size_d, PixelTypes.Rgba32)] - [WithFile(Multi_size_e, PixelTypes.Rgba32)] - [WithFile(Multi_size_f, PixelTypes.Rgba32)] - [WithFile(Multi_size_multi_bits_a, PixelTypes.Rgba32)] - [WithFile(Multi_size_multi_bits_b, PixelTypes.Rgba32)] - [WithFile(Multi_size_multi_bits_c, PixelTypes.Rgba32)] - [WithFile(Multi_size_multi_bits_d, PixelTypes.Rgba32)] + [WithFile(InvalidBpp, PixelTypes.Rgba32)] + [WithFile(InvalidCompression, PixelTypes.Rgba32)] + [WithFile(InvalidPng, PixelTypes.Rgba32)] + [WithFile(InvalidRLE4, PixelTypes.Rgba32)] + [WithFile(InvalidRLE8, PixelTypes.Rgba32)] + [WithFile(MixedBmpPngA, PixelTypes.Rgba32)] + [WithFile(MixedBmpPngB, PixelTypes.Rgba32)] + [WithFile(MixedBmpPngC, PixelTypes.Rgba32)] + [WithFile(MultiSizeA, PixelTypes.Rgba32)] + [WithFile(MultiSizeB, PixelTypes.Rgba32)] + [WithFile(MultiSizeC, PixelTypes.Rgba32)] + [WithFile(MultiSizeD, PixelTypes.Rgba32)] + [WithFile(MultiSizeE, PixelTypes.Rgba32)] + [WithFile(MultiSizeF, PixelTypes.Rgba32)] + [WithFile(MultiSizeMultiBitsA, PixelTypes.Rgba32)] + [WithFile(MultiSizeMultiBitsB, PixelTypes.Rgba32)] + [WithFile(MultiSizeMultiBitsC, PixelTypes.Rgba32)] + [WithFile(MultiSizeMultiBitsD, PixelTypes.Rgba32)] public void IcoDecoder_Decode2(TestImageProvider provider) { Debug.Assert(false); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6db11c78f..fcb32eae6 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1126,118 +1126,118 @@ public static class TestImages public static class Ico { public const string Flutter = "Icon/flutter.ico"; - public const string Bpp1_size_15x15 = "Icon/1bpp_size_15x15.ico"; - public const string Bpp1_size_16x16 = "Icon/1bpp_size_16x16.ico"; - public const string Bpp1_size_17x17 = "Icon/1bpp_size_17x17.ico"; - public const string Bpp1_size_1x1 = "Icon/1bpp_size_1x1.ico"; - public const string Bpp1_size_256x256 = "Icon/1bpp_size_256x256.ico"; - public const string Bpp1_size_2x2 = "Icon/1bpp_size_2x2.ico"; - public const string Bpp1_size_31x31 = "Icon/1bpp_size_31x31.ico"; - public const string Bpp1_size_32x32 = "Icon/1bpp_size_32x32.ico"; - public const string Bpp1_size_33x33 = "Icon/1bpp_size_33x33.ico"; - public const string Bpp1_size_3x3 = "Icon/1bpp_size_3x3.ico"; - public const string Bpp1_size_4x4 = "Icon/1bpp_size_4x4.ico"; - public const string Bpp1_size_5x5 = "Icon/1bpp_size_5x5.ico"; - public const string Bpp1_size_6x6 = "Icon/1bpp_size_6x6.ico"; - public const string Bpp1_size_7x7 = "Icon/1bpp_size_7x7.ico"; - public const string Bpp1_size_8x8 = "Icon/1bpp_size_8x8.ico"; - public const string Bpp1_size_9x9 = "Icon/1bpp_size_9x9.ico"; - public const string Bpp1_transp_not_square = "Icon/1bpp_transp_not_square.ico"; - public const string Bpp1_transp_partial = "Icon/1bpp_transp_partial.ico"; - public const string Bpp24_size_15x15 = "Icon/24bpp_size_15x15.ico"; - public const string Bpp24_size_16x16 = "Icon/24bpp_size_16x16.ico"; - public const string Bpp24_size_17x17 = "Icon/24bpp_size_17x17.ico"; - public const string Bpp24_size_1x1 = "Icon/24bpp_size_1x1.ico"; - public const string Bpp24_size_256x256 = "Icon/24bpp_size_256x256.ico"; - public const string Bpp24_size_2x2 = "Icon/24bpp_size_2x2.ico"; - public const string Bpp24_size_31x31 = "Icon/24bpp_size_31x31.ico"; - public const string Bpp24_size_32x32 = "Icon/24bpp_size_32x32.ico"; - public const string Bpp24_size_33x33 = "Icon/24bpp_size_33x33.ico"; - public const string Bpp24_size_3x3 = "Icon/24bpp_size_3x3.ico"; - public const string Bpp24_size_4x4 = "Icon/24bpp_size_4x4.ico"; - public const string Bpp24_size_5x5 = "Icon/24bpp_size_5x5.ico"; - public const string Bpp24_size_6x6 = "Icon/24bpp_size_6x6.ico"; - public const string Bpp24_size_7x7 = "Icon/24bpp_size_7x7.ico"; - public const string Bpp24_size_8x8 = "Icon/24bpp_size_8x8.ico"; - public const string Bpp24_size_9x9 = "Icon/24bpp_size_9x9.ico"; - public const string Bpp24_transp_not_square = "Icon/24bpp_transp_not_square.ico"; - public const string Bpp24_transp_partial = "Icon/24bpp_transp_partial.ico"; - public const string Bpp24_transp = "Icon/24bpp_transp.ico"; - public const string Bpp32_size_15x15 = "Icon/32bpp_size_15x15.ico"; - public const string Bpp32_size_16x16 = "Icon/32bpp_size_16x16.ico"; - public const string Bpp32_size_17x17 = "Icon/32bpp_size_17x17.ico"; - public const string Bpp32_size_1x1 = "Icon/32bpp_size_1x1.ico"; - public const string Bpp32_size_256x256 = "Icon/32bpp_size_256x256.ico"; - public const string Bpp32_size_2x2 = "Icon/32bpp_size_2x2.ico"; - public const string Bpp32_size_31x31 = "Icon/32bpp_size_31x31.ico"; - public const string Bpp32_size_32x32 = "Icon/32bpp_size_32x32.ico"; - public const string Bpp32_size_33x33 = "Icon/32bpp_size_33x33.ico"; - public const string Bpp32_size_3x3 = "Icon/32bpp_size_3x3.ico"; - public const string Bpp32_size_4x4 = "Icon/32bpp_size_4x4.ico"; - public const string Bpp32_size_5x5 = "Icon/32bpp_size_5x5.ico"; - public const string Bpp32_size_6x6 = "Icon/32bpp_size_6x6.ico"; - public const string Bpp32_size_7x7 = "Icon/32bpp_size_7x7.ico"; - public const string Bpp32_size_8x8 = "Icon/32bpp_size_8x8.ico"; - public const string Bpp32_size_9x9 = "Icon/32bpp_size_9x9.ico"; - public const string Bpp32_transp_not_square = "Icon/32bpp_transp_not_square.ico"; - public const string Bpp32_transp_partial = "Icon/32bpp_transp_partial.ico"; - public const string Bpp32_transp = "Icon/32bpp_transp.ico"; - public const string Bpp4_size_15x15 = "Icon/4bpp_size_15x15.ico"; - public const string Bpp4_size_16x16 = "Icon/4bpp_size_16x16.ico"; - public const string Bpp4_size_17x17 = "Icon/4bpp_size_17x17.ico"; - public const string Bpp4_size_1x1 = "Icon/4bpp_size_1x1.ico"; - public const string Bpp4_size_256x256 = "Icon/4bpp_size_256x256.ico"; - public const string Bpp4_size_2x2 = "Icon/4bpp_size_2x2.ico"; - public const string Bpp4_size_31x31 = "Icon/4bpp_size_31x31.ico"; - public const string Bpp4_size_32x32 = "Icon/4bpp_size_32x32.ico"; - public const string Bpp4_size_33x33 = "Icon/4bpp_size_33x33.ico"; - public const string Bpp4_size_3x3 = "Icon/4bpp_size_3x3.ico"; - public const string Bpp4_size_4x4 = "Icon/4bpp_size_4x4.ico"; - public const string Bpp4_size_5x5 = "Icon/4bpp_size_5x5.ico"; - public const string Bpp4_size_6x6 = "Icon/4bpp_size_6x6.ico"; - public const string Bpp4_size_7x7 = "Icon/4bpp_size_7x7.ico"; - public const string Bpp4_size_8x8 = "Icon/4bpp_size_8x8.ico"; - public const string Bpp4_size_9x9 = "Icon/4bpp_size_9x9.ico"; - public const string Bpp4_transp_not_square = "Icon/4bpp_transp_not_square.ico"; - public const string Bpp4_transp_partial = "Icon/4bpp_transp_partial.ico"; - public const string Bpp8_size_15x15 = "Icon/8bpp_size_15x15.ico"; - public const string Bpp8_size_16x16 = "Icon/8bpp_size_16x16.ico"; - public const string Bpp8_size_17x17 = "Icon/8bpp_size_17x17.ico"; - public const string Bpp8_size_1x1 = "Icon/8bpp_size_1x1.ico"; - public const string Bpp8_size_256x256 = "Icon/8bpp_size_256x256.ico"; - public const string Bpp8_size_2x2 = "Icon/8bpp_size_2x2.ico"; - public const string Bpp8_size_31x31 = "Icon/8bpp_size_31x31.ico"; - public const string Bpp8_size_32x32 = "Icon/8bpp_size_32x32.ico"; - public const string Bpp8_size_33x33 = "Icon/8bpp_size_33x33.ico"; - public const string Bpp8_size_3x3 = "Icon/8bpp_size_3x3.ico"; - public const string Bpp8_size_4x4 = "Icon/8bpp_size_4x4.ico"; - public const string Bpp8_size_5x5 = "Icon/8bpp_size_5x5.ico"; - public const string Bpp8_size_6x6 = "Icon/8bpp_size_6x6.ico"; - public const string Bpp8_size_7x7 = "Icon/8bpp_size_7x7.ico"; - public const string Bpp8_size_8x8 = "Icon/8bpp_size_8x8.ico"; - public const string Bpp8_size_9x9 = "Icon/8bpp_size_9x9.ico"; - public const string Bpp8_transp_not_square = "Icon/8bpp_transp_not_square.ico"; - public const string Bpp8_transp_partial = "Icon/8bpp_transp_partial.ico"; - public const string Invalid_all = "Icon/invalid_all.ico"; + public const string Bpp1Size15x15 = "Icon/1bpp_size_15x15.ico"; + public const string Bpp1Size16x16 = "Icon/1bpp_size_16x16.ico"; + public const string Bpp1Size17x17 = "Icon/1bpp_size_17x17.ico"; + public const string Bpp1Size1x1 = "Icon/1bpp_size_1x1.ico"; + public const string Bpp1Size256x256 = "Icon/1bpp_size_256x256.ico"; + public const string Bpp1Size2x2 = "Icon/1bpp_size_2x2.ico"; + public const string Bpp1Size31x31 = "Icon/1bpp_size_31x31.ico"; + public const string Bpp1Size32x32 = "Icon/1bpp_size_32x32.ico"; + public const string Bpp1Size33x33 = "Icon/1bpp_size_33x33.ico"; + public const string Bpp1Size3x3 = "Icon/1bpp_size_3x3.ico"; + public const string Bpp1Size4x4 = "Icon/1bpp_size_4x4.ico"; + public const string Bpp1Size5x5 = "Icon/1bpp_size_5x5.ico"; + public const string Bpp1Size6x6 = "Icon/1bpp_size_6x6.ico"; + public const string Bpp1Size7x7 = "Icon/1bpp_size_7x7.ico"; + public const string Bpp1Size8x8 = "Icon/1bpp_size_8x8.ico"; + public const string Bpp1Size9x9 = "Icon/1bpp_size_9x9.ico"; + public const string Bpp1TranspNotSquare = "Icon/1bpp_transp_not_square.ico"; + public const string Bpp1TranspPartial = "Icon/1bpp_transp_partial.ico"; + public const string Bpp24Size15x15 = "Icon/24bpp_size_15x15.ico"; + public const string Bpp24Size16x16 = "Icon/24bpp_size_16x16.ico"; + public const string Bpp24Size17x17 = "Icon/24bpp_size_17x17.ico"; + public const string Bpp24Size1x1 = "Icon/24bpp_size_1x1.ico"; + public const string Bpp24Size256x256 = "Icon/24bpp_size_256x256.ico"; + public const string Bpp24Size2x2 = "Icon/24bpp_size_2x2.ico"; + public const string Bpp24Size31x31 = "Icon/24bpp_size_31x31.ico"; + public const string Bpp24Size32x32 = "Icon/24bpp_size_32x32.ico"; + public const string Bpp24Size33x33 = "Icon/24bpp_size_33x33.ico"; + public const string Bpp24Size3x3 = "Icon/24bpp_size_3x3.ico"; + public const string Bpp24Size4x4 = "Icon/24bpp_size_4x4.ico"; + public const string Bpp24Size5x5 = "Icon/24bpp_size_5x5.ico"; + public const string Bpp24Size6x6 = "Icon/24bpp_size_6x6.ico"; + public const string Bpp24Size7x7 = "Icon/24bpp_size_7x7.ico"; + public const string Bpp24Size8x8 = "Icon/24bpp_size_8x8.ico"; + public const string Bpp24Size9x9 = "Icon/24bpp_size_9x9.ico"; + public const string Bpp24TranspNotSquare = "Icon/24bpp_transp_not_square.ico"; + public const string Bpp24TranspPartial = "Icon/24bpp_transp_partial.ico"; + public const string Bpp24Transp = "Icon/24bpp_transp.ico"; + public const string Bpp32Size15x15 = "Icon/32bpp_size_15x15.ico"; + public const string Bpp32Size16x16 = "Icon/32bpp_size_16x16.ico"; + public const string Bpp32Size17x17 = "Icon/32bpp_size_17x17.ico"; + public const string Bpp32Size1x1 = "Icon/32bpp_size_1x1.ico"; + public const string Bpp32Size256x256 = "Icon/32bpp_size_256x256.ico"; + public const string Bpp32Size2x2 = "Icon/32bpp_size_2x2.ico"; + public const string Bpp32Size31x31 = "Icon/32bpp_size_31x31.ico"; + public const string Bpp32Size32x32 = "Icon/32bpp_size_32x32.ico"; + public const string Bpp32Size33x33 = "Icon/32bpp_size_33x33.ico"; + public const string Bpp32Size3x3 = "Icon/32bpp_size_3x3.ico"; + public const string Bpp32Size4x4 = "Icon/32bpp_size_4x4.ico"; + public const string Bpp32Size5x5 = "Icon/32bpp_size_5x5.ico"; + public const string Bpp32Size6x6 = "Icon/32bpp_size_6x6.ico"; + public const string Bpp32Size7x7 = "Icon/32bpp_size_7x7.ico"; + public const string Bpp32Size8x8 = "Icon/32bpp_size_8x8.ico"; + public const string Bpp32Size9x9 = "Icon/32bpp_size_9x9.ico"; + public const string Bpp32TranspNotSquare = "Icon/32bpp_transp_not_square.ico"; + public const string Bpp32TranspPartial = "Icon/32bpp_transp_partial.ico"; + public const string Bpp32Transp = "Icon/32bpp_transp.ico"; + public const string Bpp4Size15x15 = "Icon/4bpp_size_15x15.ico"; + public const string Bpp4Size16x16 = "Icon/4bpp_size_16x16.ico"; + public const string Bpp4Size17x17 = "Icon/4bpp_size_17x17.ico"; + public const string Bpp4Size1x1 = "Icon/4bpp_size_1x1.ico"; + public const string Bpp4Size256x256 = "Icon/4bpp_size_256x256.ico"; + public const string Bpp4Size2x2 = "Icon/4bpp_size_2x2.ico"; + public const string Bpp4Size31x31 = "Icon/4bpp_size_31x31.ico"; + public const string Bpp4Size32x32 = "Icon/4bpp_size_32x32.ico"; + public const string Bpp4Size33x33 = "Icon/4bpp_size_33x33.ico"; + public const string Bpp4Size3x3 = "Icon/4bpp_size_3x3.ico"; + public const string Bpp4Size4x4 = "Icon/4bpp_size_4x4.ico"; + public const string Bpp4Size5x5 = "Icon/4bpp_size_5x5.ico"; + public const string Bpp4Size6x6 = "Icon/4bpp_size_6x6.ico"; + public const string Bpp4Size7x7 = "Icon/4bpp_size_7x7.ico"; + public const string Bpp4Size8x8 = "Icon/4bpp_size_8x8.ico"; + public const string Bpp4Size9x9 = "Icon/4bpp_size_9x9.ico"; + public const string Bpp4TranspNotSquare = "Icon/4bpp_transp_not_square.ico"; + public const string Bpp4TranspPartial = "Icon/4bpp_transp_partial.ico"; + public const string Bpp8Size15x15 = "Icon/8bpp_size_15x15.ico"; + public const string Bpp8Size16x16 = "Icon/8bpp_size_16x16.ico"; + public const string Bpp8Size17x17 = "Icon/8bpp_size_17x17.ico"; + public const string Bpp8Size1x1 = "Icon/8bpp_size_1x1.ico"; + public const string Bpp8Size256x256 = "Icon/8bpp_size_256x256.ico"; + public const string Bpp8Size2x2 = "Icon/8bpp_size_2x2.ico"; + public const string Bpp8Size31x31 = "Icon/8bpp_size_31x31.ico"; + public const string Bpp8Size32x32 = "Icon/8bpp_size_32x32.ico"; + public const string Bpp8Size33x33 = "Icon/8bpp_size_33x33.ico"; + public const string Bpp8Size3x3 = "Icon/8bpp_size_3x3.ico"; + public const string Bpp8Size4x4 = "Icon/8bpp_size_4x4.ico"; + public const string Bpp8Size5x5 = "Icon/8bpp_size_5x5.ico"; + public const string Bpp8Size6x6 = "Icon/8bpp_size_6x6.ico"; + public const string Bpp8Size7x7 = "Icon/8bpp_size_7x7.ico"; + public const string Bpp8Size8x8 = "Icon/8bpp_size_8x8.ico"; + public const string Bpp8Size9x9 = "Icon/8bpp_size_9x9.ico"; + public const string Bpp8TranspNotSquare = "Icon/8bpp_transp_not_square.ico"; + public const string Bpp8TranspPartial = "Icon/8bpp_transp_partial.ico"; + public const string InvalidAll = "Icon/invalid_all.ico"; public const string IcoFake = "Icon/ico_fake.cur"; - public const string Invalid_bpp = "Icon/invalid_bpp.ico"; - public const string Invalid_compression = "Icon/invalid_compression.ico"; - public const string Invalid_png = "Icon/invalid_png.ico"; - public const string Invalid_RLE4 = "Icon/invalid_RLE4.ico"; - public const string Invalid_RLE8 = "Icon/invalid_RLE8.ico"; - public const string Mixed_bmp_png_a = "Icon/mixed_bmp_png_a.ico"; - public const string Mixed_bmp_png_b = "Icon/mixed_bmp_png_b.ico"; - public const string Mixed_bmp_png_c = "Icon/mixed_bmp_png_c.ico"; - public const string Multi_size_a = "Icon/multi_size_a.ico"; - public const string Multi_size_b = "Icon/multi_size_b.ico"; - public const string Multi_size_c = "Icon/multi_size_c.ico"; - public const string Multi_size_d = "Icon/multi_size_d.ico"; - public const string Multi_size_e = "Icon/multi_size_e.ico"; - public const string Multi_size_f = "Icon/multi_size_f.ico"; - public const string Multi_size_multi_bits_a = "Icon/multi_size_multi_bits_a.ico"; - public const string Multi_size_multi_bits_b = "Icon/multi_size_multi_bits_b.ico"; - public const string Multi_size_multi_bits_c = "Icon/multi_size_multi_bits_c.ico"; - public const string Multi_size_multi_bits_d = "Icon/multi_size_multi_bits_d.ico"; + public const string InvalidBpp = "Icon/invalid_bpp.ico"; + public const string InvalidCompression = "Icon/invalid_compression.ico"; + public const string InvalidPng = "Icon/invalid_png.ico"; + public const string InvalidRLE4 = "Icon/invalid_RLE4.ico"; + public const string InvalidRLE8 = "Icon/invalid_RLE8.ico"; + public const string MixedBmpPngA = "Icon/mixed_bmp_png_a.ico"; + public const string MixedBmpPngB = "Icon/mixed_bmp_png_b.ico"; + public const string MixedBmpPngC = "Icon/mixed_bmp_png_c.ico"; + public const string MultiSizeA = "Icon/multi_size_a.ico"; + public const string MultiSizeB = "Icon/multi_size_b.ico"; + public const string MultiSizeC = "Icon/multi_size_c.ico"; + public const string MultiSizeD = "Icon/multi_size_d.ico"; + public const string MultiSizeE = "Icon/multi_size_e.ico"; + public const string MultiSizeF = "Icon/multi_size_f.ico"; + public const string MultiSizeMultiBitsA = "Icon/multi_size_multi_bits_a.ico"; + public const string MultiSizeMultiBitsB = "Icon/multi_size_multi_bits_b.ico"; + public const string MultiSizeMultiBitsC = "Icon/multi_size_multi_bits_c.ico"; + public const string MultiSizeMultiBitsD = "Icon/multi_size_multi_bits_d.ico"; } public static class Cur From 237d129afcf96b1e9876f079cc3d99a199450658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 18 Jun 2024 21:45:45 +0800 Subject: [PATCH 25/28] Update Tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Cur/CurDecoderTests.cs | 12 +++ .../Formats/Icon/Cur/CurEncoderTests.cs | 9 -- .../Formats/Icon/Ico/IcoDecoderTests.cs | 101 +++++++++++++++++- tests/ImageSharp.Tests/TestImages.cs | 2 +- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs index d1587d35b..e6efda4b6 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -21,4 +21,16 @@ public class CurDecoderTests // TODO: Assert metadata, frame count, etc } + + [Theory] + [WithFile(CurFake, PixelTypes.Rgba32)] + [WithFile(CurReal, PixelTypes.Rgba32)] + public void CurDecoder_Decode2(TestImageProvider provider) + { + using Image image = provider.GetImage(CurDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index 58213397c..9908786f1 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -30,12 +29,4 @@ public class CurEncoderTests memStream.Seek(0, SeekOrigin.Begin); CurDecoder.Instance.Decode(new(), memStream); } - - [Theory] - [WithFile(CurFake, PixelTypes.Rgba32)] - [WithFile(CurReal, PixelTypes.Rgba32)] - public void CurDecoder_Decode2(TestImageProvider provider) - { - Debug.Assert(false); - } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 8ea7cf6d4..981a1031d 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics; using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Ico; @@ -42,6 +41,16 @@ public class IcoDecoderTests [WithFile(Bpp1Size9x9, PixelTypes.Rgba32)] [WithFile(Bpp1TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp1TranspPartial, PixelTypes.Rgba32)] + public void Bpp1Test(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(Bpp24Size15x15, PixelTypes.Rgba32)] [WithFile(Bpp24Size16x16, PixelTypes.Rgba32)] [WithFile(Bpp24Size17x17, PixelTypes.Rgba32)] @@ -61,6 +70,16 @@ public class IcoDecoderTests [WithFile(Bpp24TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp24TranspPartial, PixelTypes.Rgba32)] [WithFile(Bpp24Transp, PixelTypes.Rgba32)] + public void Bpp24Test(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(Bpp32Size15x15, PixelTypes.Rgba32)] [WithFile(Bpp32Size16x16, PixelTypes.Rgba32)] [WithFile(Bpp32Size17x17, PixelTypes.Rgba32)] @@ -80,6 +99,16 @@ public class IcoDecoderTests [WithFile(Bpp32TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp32TranspPartial, PixelTypes.Rgba32)] [WithFile(Bpp32Transp, PixelTypes.Rgba32)] + public void Bpp32Test(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(Bpp4Size15x15, PixelTypes.Rgba32)] [WithFile(Bpp4Size16x16, PixelTypes.Rgba32)] [WithFile(Bpp4Size17x17, PixelTypes.Rgba32)] @@ -98,6 +127,16 @@ public class IcoDecoderTests [WithFile(Bpp4Size9x9, PixelTypes.Rgba32)] [WithFile(Bpp4TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp4TranspPartial, PixelTypes.Rgba32)] + public void Bpp4Test(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(Bpp8Size15x15, PixelTypes.Rgba32)] [WithFile(Bpp8Size16x16, PixelTypes.Rgba32)] [WithFile(Bpp8Size17x17, PixelTypes.Rgba32)] @@ -116,28 +155,82 @@ public class IcoDecoderTests [WithFile(Bpp8Size9x9, PixelTypes.Rgba32)] [WithFile(Bpp8TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp8TranspPartial, PixelTypes.Rgba32)] + public void Bpp8Test(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(InvalidAll, PixelTypes.Rgba32)] - [WithFile(IcoFake, PixelTypes.Rgba32)] [WithFile(InvalidBpp, PixelTypes.Rgba32)] [WithFile(InvalidCompression, PixelTypes.Rgba32)] [WithFile(InvalidPng, PixelTypes.Rgba32)] [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] + public void InvalidTest(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(MixedBmpPngA, PixelTypes.Rgba32)] [WithFile(MixedBmpPngB, PixelTypes.Rgba32)] [WithFile(MixedBmpPngC, PixelTypes.Rgba32)] + public void MixedBmpPngTest(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(MultiSizeA, PixelTypes.Rgba32)] [WithFile(MultiSizeB, PixelTypes.Rgba32)] [WithFile(MultiSizeC, PixelTypes.Rgba32)] [WithFile(MultiSizeD, PixelTypes.Rgba32)] [WithFile(MultiSizeE, PixelTypes.Rgba32)] [WithFile(MultiSizeF, PixelTypes.Rgba32)] + public void MultiSizeTest(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] [WithFile(MultiSizeMultiBitsA, PixelTypes.Rgba32)] [WithFile(MultiSizeMultiBitsB, PixelTypes.Rgba32)] [WithFile(MultiSizeMultiBitsC, PixelTypes.Rgba32)] [WithFile(MultiSizeMultiBitsD, PixelTypes.Rgba32)] - public void IcoDecoder_Decode2(TestImageProvider provider) + public void MultiSizeMultiBitsTest(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + + [Theory] + [WithFile(IcoFake, PixelTypes.Rgba32)] + public void IcoFakeTest(TestImageProvider provider) { - Debug.Assert(false); + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fcb32eae6..8937799e1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1219,7 +1219,6 @@ public static class TestImages public const string Bpp8TranspNotSquare = "Icon/8bpp_transp_not_square.ico"; public const string Bpp8TranspPartial = "Icon/8bpp_transp_partial.ico"; public const string InvalidAll = "Icon/invalid_all.ico"; - public const string IcoFake = "Icon/ico_fake.cur"; public const string InvalidBpp = "Icon/invalid_bpp.ico"; public const string InvalidCompression = "Icon/invalid_compression.ico"; public const string InvalidPng = "Icon/invalid_png.ico"; @@ -1238,6 +1237,7 @@ public static class TestImages public const string MultiSizeMultiBitsB = "Icon/multi_size_multi_bits_b.ico"; public const string MultiSizeMultiBitsC = "Icon/multi_size_multi_bits_c.ico"; public const string MultiSizeMultiBitsD = "Icon/multi_size_multi_bits_d.ico"; + public const string IcoFake = "Icon/ico_fake.cur"; } public static class Cur From 1f4c7e3360fc6397be6d3f64a719f3b84b86e56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 18 Jun 2024 21:56:34 +0800 Subject: [PATCH 26/28] Assert Invalid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Ico/IcoDecoderTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 981a1031d..c964733c3 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -172,13 +172,14 @@ public class IcoDecoderTests [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] public void InvalidTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); + => Assert.Throws(() => + { + using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSaveMultiFrame(provider, extension: "png"); - // TODO: Assert metadata, frame count, etc - } + // TODO: Assert metadata, frame count, etc + }); [Theory] [WithFile(MixedBmpPngA, PixelTypes.Rgba32)] From 9a7727b3148bd461726b3cfdeab5592b57a2d273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Tue, 18 Jun 2024 22:03:06 +0800 Subject: [PATCH 27/28] Pass the Test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 舰队的偶像-岛风酱! --- .../Formats/Icon/Ico/IcoDecoderTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index c964733c3..1c724ca35 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -168,7 +168,6 @@ public class IcoDecoderTests [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidBpp, PixelTypes.Rgba32)] [WithFile(InvalidCompression, PixelTypes.Rgba32)] - [WithFile(InvalidPng, PixelTypes.Rgba32)] [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] public void InvalidTest(TestImageProvider provider) @@ -181,6 +180,17 @@ public class IcoDecoderTests // TODO: Assert metadata, frame count, etc }); + [Theory] + [WithFile(InvalidPng, PixelTypes.Rgba32)] + public void InvalidPngTest(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance); + + image.DebugSaveMultiFrame(provider, extension: "png"); + + // TODO: Assert metadata, frame count, etc + } + [Theory] [WithFile(MixedBmpPngA, PixelTypes.Rgba32)] [WithFile(MixedBmpPngB, PixelTypes.Rgba32)] From b31bd2337c57c39a62d4be09c7db68d264b0eea7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 19 Jun 2024 16:24:31 +1000 Subject: [PATCH 28/28] Complete tests and fix issues --- src/ImageSharp/Formats/Cur/CurDecoderCore.cs | 8 +- src/ImageSharp/Formats/Cur/CurEncoder.cs | 2 +- src/ImageSharp/Formats/Cur/CurEncoderCore.cs | 4 +- .../Formats/Cur/CurFrameMetadata.cs | 25 ++-- src/ImageSharp/Formats/Ico/IcoDecoderCore.cs | 8 +- src/ImageSharp/Formats/Ico/IcoEncoder.cs | 4 +- src/ImageSharp/Formats/Ico/IcoEncoderCore.cs | 4 +- .../Formats/Ico/IcoFrameMetadata.cs | 26 +++- .../Formats/Icon/IconDecoderCore.cs | 81 ++++++++-- .../Formats/Icon/IconEncoderCore.cs | 72 ++++++--- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 31 +++- .../Quantization/PaletteQuantizer.cs | 7 +- .../Formats/Icon/Cur/CurDecoderTests.cs | 19 ++- .../Formats/Icon/Cur/CurEncoderTests.cs | 26 ++-- .../Formats/Icon/Ico/IcoDecoderTests.cs | 139 ++++++++++++++---- .../Formats/Icon/Ico/IcoEncoderTests.cs | 26 ++-- 16 files changed, 363 insertions(+), 119 deletions(-) diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs index 18ab8c75a..3018ec6bf 100644 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -14,11 +14,17 @@ internal sealed class CurDecoderCore : IconDecoderCore { } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel) + protected override void SetFrameMetadata( + ImageFrameMetadata metadata, + in IconDirEntry entry, + IconFrameCompression compression, + BmpBitsPerPixel bitsPerPixel, + ReadOnlyMemory? colorTable) { CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata(); curFrameMetadata.FromIconDirEntry(entry); curFrameMetadata.Compression = compression; curFrameMetadata.BmpBitsPerPixel = bitsPerPixel; + curFrameMetadata.ColorTable = colorTable; } } diff --git a/src/ImageSharp/Formats/Cur/CurEncoder.cs b/src/ImageSharp/Formats/Cur/CurEncoder.cs index d237fe7d0..e19a73990 100644 --- a/src/ImageSharp/Formats/Cur/CurEncoder.cs +++ b/src/ImageSharp/Formats/Cur/CurEncoder.cs @@ -11,7 +11,7 @@ public sealed class CurEncoder : QuantizingImageEncoder /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - CurEncoderCore encoderCore = new(); + CurEncoderCore encoderCore = new(this); encoderCore.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs index a6922d431..6435587e2 100644 --- a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs @@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Cur; internal sealed class CurEncoderCore : IconEncoderCore { - public CurEncoderCore() - : base(IconFileType.CUR) + public CurEncoderCore(QuantizingImageEncoder encoder) + : base(encoder, IconFileType.CUR) { } } diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index fc5cc5b2c..014944ba6 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Cur; @@ -18,14 +19,14 @@ public class CurFrameMetadata : IDeepCloneable, IDeepCloneable { } - private CurFrameMetadata(CurFrameMetadata metadata) + private CurFrameMetadata(CurFrameMetadata other) { - this.Compression = metadata.Compression; - this.HotspotX = metadata.HotspotX; - this.HotspotY = metadata.HotspotY; - this.EncodingWidth = metadata.EncodingWidth; - this.EncodingHeight = metadata.EncodingHeight; - this.BmpBitsPerPixel = metadata.BmpBitsPerPixel; + this.Compression = other.Compression; + this.HotspotX = other.HotspotX; + this.HotspotY = other.HotspotY; + this.EncodingWidth = other.EncodingWidth; + this.EncodingHeight = other.EncodingHeight; + this.BmpBitsPerPixel = other.BmpBitsPerPixel; } /// @@ -45,13 +46,13 @@ public class CurFrameMetadata : IDeepCloneable, IDeepCloneable /// /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. + /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. ///
public byte EncodingWidth { get; set; } /// /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. + /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. ///
public byte EncodingHeight { get; set; } @@ -61,6 +62,12 @@ public class CurFrameMetadata : IDeepCloneable, IDeepCloneable ///
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32; + /// + /// Gets or sets the color table, if any. + /// The underlying pixel format is represented by . + /// + public ReadOnlyMemory? ColorTable { get; set; } + /// public CurFrameMetadata DeepClone() => new(this); diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index e8629e35b..f4990c66a 100644 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -14,11 +14,17 @@ internal sealed class IcoDecoderCore : IconDecoderCore { } - protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel) + protected override void SetFrameMetadata( + ImageFrameMetadata metadata, + in IconDirEntry entry, + IconFrameCompression compression, + BmpBitsPerPixel bitsPerPixel, + ReadOnlyMemory? colorTable) { IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata(); icoFrameMetadata.FromIconDirEntry(entry); icoFrameMetadata.Compression = compression; icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel; + icoFrameMetadata.ColorTable = colorTable; } } diff --git a/src/ImageSharp/Formats/Ico/IcoEncoder.cs b/src/ImageSharp/Formats/Ico/IcoEncoder.cs index 298f93dec..e72925156 100644 --- a/src/ImageSharp/Formats/Ico/IcoEncoder.cs +++ b/src/ImageSharp/Formats/Ico/IcoEncoder.cs @@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Formats.Ico; /// /// Image encoder for writing an image to a stream as a Windows Icon. /// -public sealed class IcoEncoder : ImageEncoder +public sealed class IcoEncoder : QuantizingImageEncoder { /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - IcoEncoderCore encoderCore = new(); + IcoEncoderCore encoderCore = new(this); encoderCore.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs index ab3edfbd3..f3cacb1b9 100644 --- a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs @@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Ico; internal sealed class IcoEncoderCore : IconEncoderCore { - public IcoEncoderCore() - : base(IconFileType.ICO) + public IcoEncoderCore(QuantizingImageEncoder encoder) + : base(encoder, IconFileType.ICO) { } } diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index 82e4ce3b2..ea27d13c8 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Icon; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Ico; @@ -18,12 +19,17 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable { } - private IcoFrameMetadata(IcoFrameMetadata metadata) + private IcoFrameMetadata(IcoFrameMetadata other) { - this.Compression = metadata.Compression; - this.EncodingWidth = metadata.EncodingWidth; - this.EncodingHeight = metadata.EncodingHeight; - this.BmpBitsPerPixel = metadata.BmpBitsPerPixel; + this.Compression = other.Compression; + this.EncodingWidth = other.EncodingWidth; + this.EncodingHeight = other.EncodingHeight; + this.BmpBitsPerPixel = other.BmpBitsPerPixel; + + if (other.ColorTable?.Length > 0) + { + this.ColorTable = other.ColorTable.Value.ToArray(); + } } /// @@ -33,13 +39,13 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable /// /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. + /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. ///
public byte EncodingWidth { get; set; } /// /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels. + /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. ///
public byte EncodingHeight { get; set; } @@ -49,6 +55,12 @@ public class IcoFrameMetadata : IDeepCloneable, IDeepCloneable ///
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32; + /// + /// Gets or sets the color table, if any. + /// The underlying pixel format is represented by . + /// + public ReadOnlyMemory? ColorTable { get; set; } + /// public IcoFrameMetadata DeepClone() => new(this); diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index a0849fa61..74fe7b9e5 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -31,10 +31,16 @@ internal abstract class IconDecoderCore : IImageDecoderInternals Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.entries.Length); + List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries + = new((int)Math.Min(this.entries.Length, this.Options.MaxFrames)); for (int i = 0; i < this.entries.Length; i++) { + if (i == this.Options.MaxFrames) + { + break; + } + ref IconDirEntry entry = ref this.entries[i]; // If we hit the end of the stream we should break. @@ -69,6 +75,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals Image result = new(this.Options.Configuration, metadata, decodedEntries.Select(x => { BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32; + ReadOnlyMemory? colorTable = null; ImageFrame target = new(this.Options.Configuration, this.Dimensions); ImageFrame source = x.Image.Frames.RootFrameUnsafe; for (int y = 0; y < source.Height; y++) @@ -88,11 +95,22 @@ internal abstract class IconDecoderCore : IImageDecoderInternals } else { - bmpMetadata = x.Image.Metadata.GetBmpMetadata(); - bitsPerPixel = bmpMetadata.BitsPerPixel; + BmpMetadata meta = x.Image.Metadata.GetBmpMetadata(); + bitsPerPixel = meta.BitsPerPixel; + colorTable = meta.ColorTable; + + if (x.Index == 0) + { + bmpMetadata = meta; + } } - this.SetFrameMetadata(target.Metadata, this.entries[x.Index], x.Compression, bitsPerPixel); + this.SetFrameMetadata( + target.Metadata, + this.entries[x.Index], + x.Compression, + bitsPerPixel, + colorTable); x.Image.Dispose(); @@ -122,11 +140,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; ImageMetadata metadata = new(); - ImageFrameMetadata[] frames = new ImageFrameMetadata[this.fileHeader.Count]; + BmpMetadata? bmpMetadata = null; + PngMetadata? pngMetadata = null; + ImageFrameMetadata[] frames = new ImageFrameMetadata[Math.Min(this.fileHeader.Count, this.Options.MaxFrames)]; int bpp = 0; for (int i = 0; i < frames.Length; i++) { BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32; + ReadOnlyMemory? colorTable = null; ref IconDirEntry entry = ref this.entries[i]; // If we hit the end of the stream we should break. @@ -149,25 +170,65 @@ internal abstract class IconDecoderCore : IImageDecoderInternals // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken); - frames[i] = new(); - if (!isPng) + ImageFrameMetadata frameMetadata = new(); + + if (isPng) + { + if (i == 0) + { + pngMetadata = temp.Metadata.GetPngMetadata(); + } + + frameMetadata.SetFormatMetadata(PngFormat.Instance, temp.FrameMetadataCollection[0].GetPngMetadata()); + } + else { - bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel; + BmpMetadata meta = temp.Metadata.GetBmpMetadata(); + bitsPerPixel = meta.BitsPerPixel; + colorTable = meta.ColorTable; + + if (i == 0) + { + bmpMetadata = meta; + } } bpp = Math.Max(bpp, (int)bitsPerPixel); - this.SetFrameMetadata(frames[i], this.entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel); + frames[i] = frameMetadata; + + this.SetFrameMetadata( + frames[i], + this.entries[i], + isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, + bitsPerPixel, + colorTable); // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height)); } + // Copy the format specific metadata to the image. + if (bmpMetadata != null) + { + metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); + } + + if (pngMetadata != null) + { + metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); + } + return new(new(bpp), this.Dimensions, metadata, frames); } - protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel); + protected abstract void SetFrameMetadata( + ImageFrameMetadata metadata, + in IconDirEntry entry, + IconFrameCompression compression, + BmpBitsPerPixel bitsPerPixel, + ReadOnlyMemory? colorTable); [MemberNotNull(nameof(entries))] protected void ReadHeader(Stream stream) diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs index eb07ab483..243339661 100644 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs @@ -13,12 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Icon; internal abstract class IconEncoderCore : IImageEncoderInternals { + private readonly QuantizingImageEncoder encoder; private readonly IconFileType iconFileType; private IconDir fileHeader; private EncodingFrameMetadata[]? entries; - protected IconEncoderCore(IconFileType iconFileType) - => this.iconFileType = iconFileType; + protected IconEncoderCore(QuantizingImageEncoder encoder, IconFileType iconFileType) + { + this.encoder = encoder; + this.iconFileType = iconFileType; + } public void Encode( Image image, @@ -71,14 +75,7 @@ internal abstract class IconEncoderCore : IImageEncoderInternals { IconFrameCompression.Bmp => new BmpEncoder() { - // We don't have access to the palette in the metadata so we need to quantize the image - // using a new one generated from the pixel data. - Quantizer = encodingMetadata.Entry.BitCount <= 8 - ? new WuQuantizer(new() - { - MaxColors = encodingMetadata.Entry.ColorCount - }) - : null, + Quantizer = this.GetQuantizer(encodingMetadata), ProcessedAlphaMask = true, UseDoubleHeight = true, SkipFileHeader = true, @@ -122,28 +119,65 @@ internal abstract class IconEncoderCore : IImageEncoderInternals image.Frames.Select(i => { IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); - return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry()); + return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry()); }).ToArray(), IconFileType.CUR => image.Frames.Select(i => { CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); - return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ToIconDirEntry()); + return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry()); }).ToArray(), _ => throw new NotSupportedException(), }; } - internal sealed class EncodingFrameMetadata( - IconFrameCompression compression, - BmpBitsPerPixel bmpBitsPerPixel, - IconDirEntry iconDirEntry) + private IQuantizer? GetQuantizer(EncodingFrameMetadata metadata) { - private IconDirEntry iconDirEntry = iconDirEntry; + if (metadata.Entry.BitCount > 8) + { + return null; + } + + if (this.encoder.Quantizer is not null) + { + return this.encoder.Quantizer; + } + + if (metadata.ColorTable is null) + { + return new WuQuantizer(new() + { + MaxColors = metadata.Entry.ColorCount + }); + } + + // Don't dither if we have a palette. We want to preserve as much information as possible. + return new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }); + } + + internal sealed class EncodingFrameMetadata + { + private IconDirEntry iconDirEntry; + + public EncodingFrameMetadata( + IconFrameCompression compression, + BmpBitsPerPixel bmpBitsPerPixel, + ReadOnlyMemory? colorTable, + IconDirEntry iconDirEntry) + { + this.Compression = compression; + this.BmpBitsPerPixel = compression == IconFrameCompression.Png + ? BmpBitsPerPixel.Pixel32 + : bmpBitsPerPixel; + this.ColorTable = colorTable; + this.iconDirEntry = iconDirEntry; + } + + public IconFrameCompression Compression { get; } - public IconFrameCompression Compression { get; set; } = compression; + public BmpBitsPerPixel BmpBitsPerPixel { get; } - public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = bmpBitsPerPixel; + public ReadOnlyMemory? ColorTable { get; set; } public ref IconDirEntry Entry => ref this.iconDirEntry; } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 36a0a8bcb..3e278be14 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -345,9 +345,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { uint frameCount = 0; ImageMetadata metadata = new(); + List framesMetadata = []; PngMetadata pngMetadata = metadata.GetPngMetadata(); this.currentStream = stream; - FrameControl? lastFrameControl = null; + FrameControl? currentFrameControl = null; Span buffer = stackalloc byte[20]; this.currentStream.Skip(8); @@ -400,7 +401,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals break; } - lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); + currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); + break; case PngChunkType.FrameData: if (frameCount == this.maxFrames) @@ -413,22 +415,35 @@ internal sealed class PngDecoderCore : IImageDecoderInternals goto EOF; } - if (lastFrameControl is null) + if (currentFrameControl is null) { PngThrowHelper.ThrowMissingFrameControl(); } + InitializeFrameMetadata(framesMetadata, currentFrameControl.Value); + // Skip sequence number this.currentStream.Skip(4); this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Data: + // Spec says tRNS must be before IDAT so safe to exit. if (this.colorMetadataOnly) { goto EOF; } + pngMetadata.AnimateRootFrame = currentFrameControl != null; + currentFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height); + if (framesMetadata.Count == 0) + { + InitializeFrameMetadata(framesMetadata, currentFrameControl.Value); + + // Both PLTE and tRNS chunks, if present, have been read at this point as per spec. + AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); + } + this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Palette: @@ -515,7 +530,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals // Both PLTE and tRNS chunks, if present, have been read at this point as per spec. AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata); + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata, framesMetadata); } finally { @@ -680,6 +695,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); } + private static void InitializeFrameMetadata(List imageFrameMetadata, FrameControl currentFrameControl) + { + ImageFrameMetadata meta = new(); + PngFrameMetadata frameMetadata = meta.GetPngMetadata(); + frameMetadata.FromChunk(currentFrameControl); + imageFrameMetadata.Add(meta); + } + /// /// Calculates the correct number of bits per pixel for the given color type. /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index acd179ffc..13a59a26d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -62,9 +62,10 @@ public class PaletteQuantizer : IQuantizer { Guard.NotNull(options, nameof(options)); - // Always use the palette length over options since the palette cannot be reduced. - TPixel[] palette = new TPixel[this.colorPalette.Length]; - Color.ToPixel(this.colorPalette.Span, palette.AsSpan()); + // If the palette is larger than the max colors then we need to trim it down. + // treat the buffer as FILO. + TPixel[] palette = new TPixel[Math.Min(options.MaxColors, this.colorPalette.Length)]; + Color.ToPixel(this.colorPalette.Span[..palette.Length], palette.AsSpan()); return new PaletteQuantizer(configuration, options, palette, this.transparentIndex); } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs index e6efda4b6..4efd33648 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; +using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -17,9 +19,11 @@ public class CurDecoderTests { using Image image = provider.GetImage(CurDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); - - // TODO: Assert metadata, frame count, etc + CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); + Assert.Equal(image.Width, meta.EncodingWidth); + Assert.Equal(image.Height, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); } [Theory] @@ -28,9 +32,10 @@ public class CurDecoderTests public void CurDecoder_Decode2(TestImageProvider provider) { using Image image = provider.GetImage(CurDecoder.Instance); - - image.DebugSaveMultiFrame(provider, extension: "png"); - - // TODO: Assert metadata, frame count, etc + CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); + Assert.Equal(image.Width, meta.EncodingWidth); + Assert.Equal(image.Height, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index 9908786f1..b9b66296d 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Cur; namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; @@ -10,23 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; [Trait("Format", "Cur")] public class CurEncoderTests { - private static CurEncoder CurEncoder => new(); - - public static readonly TheoryData Files = new() - { - { WindowsMouse }, - }; + private static CurEncoder Encoder => new(); [Theory] - [MemberData(nameof(Files))] - public void Encode(string imagePath) + [WithFile(CurReal, PixelTypes.Rgba32)] + [WithFile(WindowsMouse, PixelTypes.Rgba32)] + public void CanRoundTripEncoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); + using Image image = provider.GetImage(CurDecoder.Instance); using MemoryStream memStream = new(); - input.Save(memStream, CurEncoder); + image.DebugSaveMultiFrame(provider); + image.Save(memStream, Encoder); memStream.Seek(0, SeekOrigin.Begin); - CurDecoder.Instance.Decode(new(), memStream); + + using Image encoded = Image.Load(memStream); + encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false); + + encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance); } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 1c724ca35..a776a637b 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.PixelFormats; using static SixLabors.ImageSharp.Tests.TestImages.Ico; @@ -17,9 +19,9 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSaveMultiFrame(provider); - // TODO: Assert metadata, frame count, etc + Assert.Equal(10, image.Frames.Count); } [Theory] @@ -45,9 +47,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); - // TODO: Assert metadata, frame count, etc + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; + + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel1, meta.BmpBitsPerPixel); } [Theory] @@ -74,9 +83,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); + + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; - // TODO: Assert metadata, frame count, etc + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel24, meta.BmpBitsPerPixel); } [Theory] @@ -103,9 +119,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); - // TODO: Assert metadata, frame count, etc + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; + + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); } [Theory] @@ -131,9 +154,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); + + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; - // TODO: Assert metadata, frame count, etc + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel4, meta.BmpBitsPerPixel); } [Theory] @@ -151,7 +181,8 @@ public class IcoDecoderTests [WithFile(Bpp8Size5x5, PixelTypes.Rgba32)] [WithFile(Bpp8Size6x6, PixelTypes.Rgba32)] [WithFile(Bpp8Size7x7, PixelTypes.Rgba32)] - [WithFile(Bpp8Size8x8, PixelTypes.Rgba32)] + + // [WithFile(Bpp8Size8x8, PixelTypes.Rgba32)] This is actually 24 bit. [WithFile(Bpp8Size9x9, PixelTypes.Rgba32)] [WithFile(Bpp8TranspNotSquare, PixelTypes.Rgba32)] [WithFile(Bpp8TranspPartial, PixelTypes.Rgba32)] @@ -159,9 +190,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); + + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; - // TODO: Assert metadata, frame count, etc + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel8, meta.BmpBitsPerPixel); } [Theory] @@ -174,10 +212,6 @@ public class IcoDecoderTests => Assert.Throws(() => { using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSaveMultiFrame(provider, extension: "png"); - - // TODO: Assert metadata, frame count, etc }); [Theory] @@ -186,9 +220,16 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); + + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; - // TODO: Assert metadata, frame count, etc + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Png, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); } [Theory] @@ -199,9 +240,9 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + Assert.True(image.Frames.Count > 1); - // TODO: Assert metadata, frame count, etc + image.DebugSaveMultiFrame(provider); } [Theory] @@ -215,9 +256,46 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + Assert.True(image.Frames.Count > 1); + + for (int i = 0; i < image.Frames.Count; i++) + { + ImageFrame frame = image.Frames[i]; + IcoFrameMetadata meta = frame.Metadata.GetIcoMetadata(); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); + } + + image.DebugSaveMultiFrame(provider); + } + + [Theory] + [WithFile(MultiSizeA, PixelTypes.Rgba32)] + [WithFile(MultiSizeB, PixelTypes.Rgba32)] + [WithFile(MultiSizeC, PixelTypes.Rgba32)] + [WithFile(MultiSizeD, PixelTypes.Rgba32)] + [WithFile(MultiSizeE, PixelTypes.Rgba32)] + [WithFile(MultiSizeF, PixelTypes.Rgba32)] + public void MultiSize_CanDecodeSingleFrame(TestImageProvider provider) + { + using Image image = provider.GetImage(IcoDecoder.Instance, new() { MaxFrames = 1 }); + Assert.Single(image.Frames); + } - // TODO: Assert metadata, frame count, etc + [Theory] + [InlineData(MultiSizeA)] + [InlineData(MultiSizeB)] + [InlineData(MultiSizeC)] + [InlineData(MultiSizeD)] + [InlineData(MultiSizeE)] + [InlineData(MultiSizeF)] + public void MultiSize_CanIdentifySingleFrame(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(new() { MaxFrames = 1 }, stream); + + Assert.Single(imageInfo.FrameMetadataCollection); } [Theory] @@ -229,9 +307,9 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + Assert.True(image.Frames.Count > 1); - // TODO: Assert metadata, frame count, etc + image.DebugSaveMultiFrame(provider); } [Theory] @@ -240,8 +318,15 @@ public class IcoDecoderTests { using Image image = provider.GetImage(IcoDecoder.Instance); - image.DebugSaveMultiFrame(provider, extension: "png"); + image.DebugSave(provider); + + IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + int expectedWidth = image.Width >= 256 ? 0 : image.Width; + int expectedHeight = image.Height >= 256 ? 0 : image.Height; - // TODO: Assert metadata, frame count, etc + Assert.Equal(expectedWidth, meta.EncodingWidth); + Assert.Equal(expectedHeight, meta.EncodingHeight); + Assert.Equal(IconFrameCompression.Bmp, meta.Compression); + Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel); } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs index 9a239bdd4..db28f9f70 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Ico; namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; @@ -10,23 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; [Trait("Format", "Icon")] public class IcoEncoderTests { - private static IcoEncoder CurEncoder => new(); - - public static readonly TheoryData Files = new() - { - { Flutter }, - }; + private static IcoEncoder Encoder => new(); [Theory] - [MemberData(nameof(Files))] - public void Encode(string imagePath) + [WithFile(Flutter, PixelTypes.Rgba32)] + public void CanRoundTripEncoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); + using Image image = provider.GetImage(IcoDecoder.Instance); using MemoryStream memStream = new(); - input.Save(memStream, CurEncoder); + image.DebugSaveMultiFrame(provider); + image.Save(memStream, Encoder); memStream.Seek(0, SeekOrigin.Begin); - IcoDecoder.Instance.Decode(new(), memStream); + + using Image encoded = Image.Load(memStream); + encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false); + + // Despite preservation of the palette. The process can still be lossy + encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance); } }