diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs index 0cf8c97a54..3a7288b718 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 2b410a0f95..6394c564b0 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 6c2f3abe4d..12ced58fd6 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 2a275d1a1d..8d7eb17b5c 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 ba21d0abc5..497375f994 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 05a657b51b..b4d563d4b6 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; + } }