diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index f36f3d09b4..0d5faabe18 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -10,10 +10,13 @@
 using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.Formats.Jpeg;
 using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
 using SixLabors.ImageSharp.Formats.Pbm;
 using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
 using SixLabors.ImageSharp.Formats.Tga;
 using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
 using SixLabors.ImageSharp.Formats.Webp;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.PixelFormats;
@@ -129,6 +132,7 @@ private static void Seed<TPixel>()
         AotCompileImageDecoderInternals<TPixel>();
         AotCompileImageEncoders<TPixel>();
         AotCompileImageDecoders<TPixel>();
+        AotCompileSpectralConverter<TPixel>();
         AotCompileImageProcessors<TPixel>();
         AotCompileGenericImageProcessors<TPixel>();
         AotCompileResamplers<TPixel>();
@@ -195,39 +199,41 @@ private static void AotCompileImageProcessingContextFactory<TPixel>()
             => default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext<TPixel>(default, default, default);
 
     /// <summary>
-    /// This method pre-seeds the all <see cref="IImageEncoderInternals"/> in the AoT compiler.
+    /// This method pre-seeds the all core encoders in the AoT compiler.
     /// </summary>
     /// <typeparam name="TPixel">The pixel format.</typeparam>
     [Preserve]
     private static void AotCompileImageEncoderInternals<TPixel>()
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        default(WebpEncoderCore).Encode<TPixel>(default, default, default);
         default(BmpEncoderCore).Encode<TPixel>(default, default, default);
         default(GifEncoderCore).Encode<TPixel>(default, default, default);
         default(JpegEncoderCore).Encode<TPixel>(default, default, default);
         default(PbmEncoderCore).Encode<TPixel>(default, default, default);
         default(PngEncoderCore).Encode<TPixel>(default, default, default);
+        default(QoiEncoderCore).Encode<TPixel>(default, default, default);
         default(TgaEncoderCore).Encode<TPixel>(default, default, default);
         default(TiffEncoderCore).Encode<TPixel>(default, default, default);
+        default(WebpEncoderCore).Encode<TPixel>(default, default, default);
     }
 
     /// <summary>
-    /// This method pre-seeds the all <see cref="IImageDecoderInternals"/> in the AoT compiler.
+    /// This method pre-seeds the all <see cref="ImageDecoderCore"/> in the AoT compiler.
     /// </summary>
     /// <typeparam name="TPixel">The pixel format.</typeparam>
     [Preserve]
     private static void AotCompileImageDecoderInternals<TPixel>()
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        default(WebpDecoderCore).Decode<TPixel>(default, default);
-        default(BmpDecoderCore).Decode<TPixel>(default, default);
-        default(GifDecoderCore).Decode<TPixel>(default, default);
-        default(JpegDecoderCore).Decode<TPixel>(default, default);
-        default(PbmDecoderCore).Decode<TPixel>(default, default);
-        default(PngDecoderCore).Decode<TPixel>(default, default);
-        default(TgaDecoderCore).Decode<TPixel>(default, default);
-        default(TiffDecoderCore).Decode<TPixel>(default, default);
+        default(BmpDecoderCore).Decode<TPixel>(default, default, default);
+        default(GifDecoderCore).Decode<TPixel>(default, default, default);
+        default(JpegDecoderCore).Decode<TPixel>(default, default, default);
+        default(PbmDecoderCore).Decode<TPixel>(default, default, default);
+        default(PngDecoderCore).Decode<TPixel>(default, default, default);
+        default(QoiDecoderCore).Decode<TPixel>(default, default, default);
+        default(TgaDecoderCore).Decode<TPixel>(default, default, default);
+        default(TiffDecoderCore).Decode<TPixel>(default, default, default);
+        default(WebpDecoderCore).Decode<TPixel>(default, default, default);
     }
 
     /// <summary>
@@ -266,6 +272,17 @@ private static void AotCompileImageDecoders<TPixel>()
         AotCompileImageDecoder<TPixel, TiffDecoder>();
     }
 
+    [Preserve]
+    private static void AotCompileSpectralConverter<TPixel>()
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        default(SpectralConverter<TPixel>).GetPixelBuffer(default);
+        default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
+        default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
+        default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
+        default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
+    }
+
     /// <summary>
     /// This method pre-seeds the <see cref="IImageEncoder"/> in the AoT compiler.
     /// </summary>
diff --git a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs
index 75595e1f7d..8f8e187403 100644
--- a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs
+++ b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs
@@ -30,65 +30,3 @@ internal class AnimatedImageFrameMetadata
     /// </summary>
     public FrameDisposalMode DisposalMode { get; set; }
 }
-
-#pragma warning disable SA1201 // Elements should appear in the correct order
-internal enum FrameBlendMode
-#pragma warning restore SA1201 // Elements should appear in the correct order
-{
-    /// <summary>
-    /// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame.
-    /// </summary>
-    Source = 0,
-
-    /// <summary>
-    /// Blend the current frame with the previous frame in the animation sequence within the rectangle covered
-    /// by the current frame.
-    /// If the current has any transparent areas, the corresponding areas of the previous frame will be visible
-    /// through these transparent regions.
-    /// </summary>
-    Over = 1
-}
-
-internal enum FrameDisposalMode
-{
-    /// <summary>
-    /// No disposal specified.
-    /// The decoder is not required to take any action.
-    /// </summary>
-    Unspecified = 0,
-
-    /// <summary>
-    /// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to
-    /// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains
-    /// transparency, the previous frame will be visible through these transparent areas.
-    /// </summary>
-    DoNotDispose = 1,
-
-    /// <summary>
-    /// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is
-    /// filled with the background color specified in the image metadata.
-    /// This effectively erases the current frame by replacing it with the background color before the next frame is displayed.
-    /// </summary>
-    RestoreToBackground = 2,
-
-    /// <summary>
-    /// Restore to previous. This method restores the area affected by the current frame to what it was before the
-    /// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image
-    /// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small
-    /// part of the image changes from frame to frame.
-    /// </summary>
-    RestoreToPrevious = 3
-}
-
-internal enum FrameColorTableMode
-{
-    /// <summary>
-    /// The frame uses the shared color table specified by the image metadata.
-    /// </summary>
-    Global,
-
-    /// <summary>
-    /// The frame uses a color table specified by the frame metadata.
-    /// </summary>
-    Local
-}
diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
index 5700bb444c..3c9e3ce795 100644
--- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
@@ -11,35 +11,35 @@ public enum BmpBitsPerPixel : short
     /// <summary>
     /// 1 bit per pixel.
     /// </summary>
-    Pixel1 = 1,
+    Bit1 = 1,
 
     /// <summary>
     /// 2 bits per pixel.
     /// </summary>
-    Pixel2 = 2,
+    Bit2 = 2,
 
     /// <summary>
     /// 4 bits per pixel.
     /// </summary>
-    Pixel4 = 4,
+    Bit4 = 4,
 
     /// <summary>
     /// 8 bits per pixel. Each pixel consists of 1 byte.
     /// </summary>
-    Pixel8 = 8,
+    Bit8 = 8,
 
     /// <summary>
     /// 16 bits per pixel. Each pixel consists of 2 bytes.
     /// </summary>
-    Pixel16 = 16,
+    Bit16 = 16,
 
     /// <summary>
     /// 24 bits per pixel. Each pixel consists of 3 bytes.
     /// </summary>
-    Pixel24 = 24,
+    Bit24 = 24,
 
     /// <summary>
     /// 32 bits per pixel. Each pixel consists of 4 bytes.
     /// </summary>
-    Pixel32 = 32
+    Bit32 = 32
 }
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index c26536fd11..94257517d2 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
 /// <remarks>
 /// A useful decoding source example can be found at <see href="https://dxr.mozilla.org/mozilla-central/source/image/decoders/nsBMPDecoder.cpp"/>
 /// </remarks>
-internal sealed class BmpDecoderCore : IImageDecoderInternals
+internal sealed class BmpDecoderCore : ImageDecoderCore
 {
     /// <summary>
     /// The default mask for the red part of the color for 16 bit rgb bitmaps.
@@ -114,8 +114,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The options.</param>
     public BmpDecoderCore(BmpDecoderOptions options)
+        : base(options.GeneralOptions)
     {
-        this.Options = options.GeneralOptions;
         this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
         this.configuration = options.GeneralOptions.Configuration;
         this.memoryAllocator = this.configuration.MemoryAllocator;
@@ -125,14 +125,7 @@ public BmpDecoderCore(BmpDecoderOptions options)
     }
 
     /// <inheritdoc />
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc />
-    public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
-
-    /// <inheritdoc />
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         Image<TPixel>? image = null;
         try
@@ -224,10 +217,10 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc />
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ReadImageHeaders(stream, out _, out _);
-        return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
+        return new ImageInfo(new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
     }
 
     /// <summary>
@@ -1463,6 +1456,8 @@ private void ReadInfoHeader(BufferedReadStream stream)
         this.bmpMetadata = this.metadata.GetBmpMetadata();
         this.bmpMetadata.InfoHeaderType = infoHeaderType;
         this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
+
+        this.Dimensions = new(this.infoHeader.Width, this.infoHeader.Height);
     }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index 0be243f9a6..e255568047 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -1,7 +1,6 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
-using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.Processing;
 
 namespace SixLabors.ImageSharp.Formats.Bmp;
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 151da18281..85786949d8 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
 /// <summary>
 /// Image encoder for writing an image to a stream as a Windows bitmap.
 /// </summary>
-internal sealed class BmpEncoderCore : IImageEncoderInternals
+internal sealed class BmpEncoderCore
 {
     /// <summary>
     /// The amount to pad each row by.
@@ -146,10 +146,10 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
         int colorPaletteSize = this.bitsPerPixel switch
         {
-            BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
-            BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
-            BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
-            BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
+            BmpBitsPerPixel.Bit8 => ColorPaletteSize8Bit,
+            BmpBitsPerPixel.Bit4 => ColorPaletteSize4Bit,
+            BmpBitsPerPixel.Bit2 => ColorPaletteSize2Bit,
+            BmpBitsPerPixel.Bit1 => ColorPaletteSize1Bit,
             _ => 0
         };
 
@@ -248,7 +248,7 @@ private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderS
             clrUsed: 0,
             clrImportant: 0);
 
-        if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
+        if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Bit32)
         {
             infoHeader.AlphaMask = Rgba32AlphaMask;
             infoHeader.RedMask = Rgba32RedMask;
@@ -351,31 +351,31 @@ private void WriteImage<TPixel>(Configuration configuration, Stream stream, Imag
         Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
         switch (this.bitsPerPixel)
         {
-            case BmpBitsPerPixel.Pixel32:
+            case BmpBitsPerPixel.Bit32:
                 this.Write32BitPixelData(configuration, stream, pixels);
                 break;
 
-            case BmpBitsPerPixel.Pixel24:
+            case BmpBitsPerPixel.Bit24:
                 this.Write24BitPixelData(configuration, stream, pixels);
                 break;
 
-            case BmpBitsPerPixel.Pixel16:
+            case BmpBitsPerPixel.Bit16:
                 this.Write16BitPixelData(configuration, stream, pixels);
                 break;
 
-            case BmpBitsPerPixel.Pixel8:
+            case BmpBitsPerPixel.Bit8:
                 this.Write8BitPixelData(configuration, stream, image);
                 break;
 
-            case BmpBitsPerPixel.Pixel4:
+            case BmpBitsPerPixel.Bit4:
                 this.Write4BitPixelData(configuration, stream, image);
                 break;
 
-            case BmpBitsPerPixel.Pixel2:
+            case BmpBitsPerPixel.Bit2:
                 this.Write2BitPixelData(configuration, stream, image);
                 break;
 
-            case BmpBitsPerPixel.Pixel1:
+            case BmpBitsPerPixel.Bit1:
                 this.Write1BitPixelData(configuration, stream, image);
                 break;
         }
diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
index a50023b272..68e99bdc5f 100644
--- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
@@ -1,12 +1,15 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.PixelFormats;
+
+// TODO: Add color table information.
 namespace SixLabors.ImageSharp.Formats.Bmp;
 
 /// <summary>
 /// Provides Bmp specific metadata information for the image.
 /// </summary>
-public class BmpMetadata : IDeepCloneable
+public class BmpMetadata : IFormatMetadata<BmpMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="BmpMetadata"/> class.
@@ -38,7 +41,7 @@ private BmpMetadata(BmpMetadata other)
     /// <summary>
     /// Gets or sets the number of bits per pixel.
     /// </summary>
-    public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
+    public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Bit24;
 
     /// <summary>
     /// Gets or sets the color table, if any.
@@ -46,5 +49,109 @@ private BmpMetadata(BmpMetadata other)
     public ReadOnlyMemory<Color>? ColorTable { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new BmpMetadata(this);
+    public static BmpMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        return bpp switch
+        {
+            1 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit1 },
+            2 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit2 },
+            <= 4 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit4 },
+            <= 8 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit8 },
+            <= 16 => new BmpMetadata
+            {
+                BitsPerPixel = BmpBitsPerPixel.Bit16,
+                InfoHeaderType = BmpInfoHeaderType.WinVersion3
+            },
+            <= 24 => new BmpMetadata
+            {
+                BitsPerPixel = BmpBitsPerPixel.Bit24,
+                InfoHeaderType = BmpInfoHeaderType.WinVersion4
+            },
+            _ => new BmpMetadata
+            {
+                BitsPerPixel = BmpBitsPerPixel.Bit32,
+                InfoHeaderType = BmpInfoHeaderType.WinVersion5
+            }
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BitsPerPixel;
+
+        PixelAlphaRepresentation alpha = this.InfoHeaderType switch
+        {
+            BmpInfoHeaderType.WinVersion2 or
+            BmpInfoHeaderType.Os2Version2Short or
+            BmpInfoHeaderType.WinVersion3 or
+            BmpInfoHeaderType.AdobeVersion3 or
+            BmpInfoHeaderType.Os2Version2 => PixelAlphaRepresentation.None,
+            BmpInfoHeaderType.AdobeVersion3WithAlpha or
+            BmpInfoHeaderType.WinVersion4 or
+            BmpInfoHeaderType.WinVersion5 or
+            _ => bpp < 32 ? PixelAlphaRepresentation.None : PixelAlphaRepresentation.Unassociated
+        };
+
+        PixelComponentInfo info;
+        PixelColorType color;
+        switch (this.BitsPerPixel)
+        {
+            case BmpBitsPerPixel.Bit1:
+                info = PixelComponentInfo.Create(1, bpp, 1);
+                color = PixelColorType.Indexed;
+                break;
+            case BmpBitsPerPixel.Bit2:
+                info = PixelComponentInfo.Create(1, bpp, 2);
+                color = PixelColorType.Indexed;
+                break;
+            case BmpBitsPerPixel.Bit4:
+                info = PixelComponentInfo.Create(1, bpp, 4);
+                color = PixelColorType.Indexed;
+                break;
+            case BmpBitsPerPixel.Bit8:
+                info = PixelComponentInfo.Create(1, bpp, 8);
+                color = PixelColorType.Indexed;
+                break;
+
+            // Could be 555 with padding but 565 is more common in newer bitmaps and offers
+            // greater accuracy due to extra green precision.
+            case BmpBitsPerPixel.Bit16:
+                info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
+                color = PixelColorType.RGB;
+                break;
+            case BmpBitsPerPixel.Bit24:
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                color = PixelColorType.RGB;
+                break;
+            case BmpBitsPerPixel.Bit32 or _:
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                color = PixelColorType.RGB | PixelColorType.Alpha;
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            EncodingType = this.BitsPerPixel <= BmpBitsPerPixel.Bit8
+                ? EncodingType.Lossy
+                : EncodingType.Lossless,
+            PixelTypeInfo = this.GetPixelTypeInfo()
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public BmpMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs
deleted file mode 100644
index 5297d0c989..0000000000
--- a/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Formats.Bmp;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the bmp format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="BmpMetadata"/>.</returns>
-    public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs
index 3018ec6bf9..a8a51878e0 100644
--- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs
+++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs
@@ -15,16 +15,30 @@ public CurDecoderCore(DecoderOptions options)
     }
 
     protected override void SetFrameMetadata(
-        ImageFrameMetadata metadata,
+        ImageMetadata imageMetadata,
+        ImageFrameMetadata frameMetadata,
+        int index,
         in IconDirEntry entry,
         IconFrameCompression compression,
         BmpBitsPerPixel bitsPerPixel,
         ReadOnlyMemory<Color>? colorTable)
     {
-        CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata();
+        CurFrameMetadata curFrameMetadata = frameMetadata.GetCurMetadata();
         curFrameMetadata.FromIconDirEntry(entry);
         curFrameMetadata.Compression = compression;
         curFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
         curFrameMetadata.ColorTable = colorTable;
+
+        if (index == 0)
+        {
+            CurMetadata curMetadata = imageMetadata.GetCurMetadata();
+            curMetadata.Compression = compression;
+            curMetadata.BmpBitsPerPixel = bitsPerPixel;
+            curMetadata.ColorTable = colorTable;
+            curMetadata.EncodingWidth = curFrameMetadata.EncodingWidth;
+            curMetadata.EncodingHeight = curFrameMetadata.EncodingHeight;
+            curMetadata.HotspotX = curFrameMetadata.HotspotX;
+            curMetadata.HotspotY = curFrameMetadata.HotspotY;
+        }
     }
 }
diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
index 014944ba69..06cf426dc4 100644
--- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Cur;
 /// <summary>
 /// IcoFrameMetadata.
 /// </summary>
-public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
+public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="CurFrameMetadata"/> class.
@@ -60,7 +60,7 @@ private CurFrameMetadata(CurFrameMetadata other)
     /// Gets or sets the number of bits per pixel.<br/>
     /// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
     /// </summary>
-    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
+    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
 
     /// <summary>
     /// Gets or sets the color table, if any.
@@ -69,11 +69,75 @@ private CurFrameMetadata(CurFrameMetadata other)
     public ReadOnlyMemory<Color>? ColorTable { get; set; }
 
     /// <inheritdoc/>
-    public CurFrameMetadata DeepClone() => new(this);
+    public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
+    {
+        if (!metadata.PixelTypeInfo.HasValue)
+        {
+            return new CurFrameMetadata
+            {
+                BmpBitsPerPixel = BmpBitsPerPixel.Bit32,
+                Compression = IconFrameCompression.Png
+            };
+        }
+
+        byte encodingWidth = metadata.EncodingWidth switch
+        {
+            > 255 => 0,
+            <= 255 and >= 1 => (byte)metadata.EncodingWidth,
+            _ => 0
+        };
+
+        byte encodingHeight = metadata.EncodingHeight switch
+        {
+            > 255 => 0,
+            <= 255 and >= 1 => (byte)metadata.EncodingHeight,
+            _ => 0
+        };
+
+        int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
+        BmpBitsPerPixel bbpp = bpp switch
+        {
+            1 => BmpBitsPerPixel.Bit1,
+            2 => BmpBitsPerPixel.Bit2,
+            <= 4 => BmpBitsPerPixel.Bit4,
+            <= 8 => BmpBitsPerPixel.Bit8,
+            <= 16 => BmpBitsPerPixel.Bit16,
+            <= 24 => BmpBitsPerPixel.Bit24,
+            _ => BmpBitsPerPixel.Bit32
+        };
+
+        IconFrameCompression compression = IconFrameCompression.Bmp;
+        if (bbpp is BmpBitsPerPixel.Bit32)
+        {
+            compression = IconFrameCompression.Png;
+        }
+
+        return new CurFrameMetadata
+        {
+            BmpBitsPerPixel = bbpp,
+            Compression = compression,
+            EncodingWidth = encodingWidth,
+            EncodingHeight = encodingHeight,
+            ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            ColorTable = this.ColorTable,
+            EncodingWidth = this.EncodingWidth,
+            EncodingHeight = this.EncodingHeight
+        };
 
     /// <inheritdoc/>
     IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
 
+    /// <inheritdoc/>
+    public CurFrameMetadata DeepClone() => new(this);
+
     internal void FromIconDirEntry(IconDirEntry entry)
     {
         this.EncodingWidth = entry.Width;
@@ -84,7 +148,7 @@ internal void FromIconDirEntry(IconDirEntry entry)
 
     internal IconDirEntry ToIconDirEntry()
     {
-        byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8
+        byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
             ? (byte)0
             : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel);
 
@@ -97,4 +161,65 @@ internal IconDirEntry ToIconDirEntry()
             ColorCount = colorCount
         };
     }
+
+    private PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BmpBitsPerPixel;
+        PixelComponentInfo info;
+        PixelColorType color;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+
+        if (this.Compression is IconFrameCompression.Png)
+        {
+            bpp = 32;
+            info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+            color = PixelColorType.RGB | PixelColorType.Alpha;
+            alpha = PixelAlphaRepresentation.Unassociated;
+        }
+        else
+        {
+            switch (this.BmpBitsPerPixel)
+            {
+                case BmpBitsPerPixel.Bit1:
+                    info = PixelComponentInfo.Create(1, bpp, 1);
+                    color = PixelColorType.Binary;
+                    break;
+                case BmpBitsPerPixel.Bit2:
+                    info = PixelComponentInfo.Create(1, bpp, 2);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit4:
+                    info = PixelComponentInfo.Create(1, bpp, 4);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit8:
+                    info = PixelComponentInfo.Create(1, bpp, 8);
+                    color = PixelColorType.Indexed;
+                    break;
+
+                // Could be 555 with padding but 565 is more common in newer bitmaps and offers
+                // greater accuracy due to extra green precision.
+                case BmpBitsPerPixel.Bit16:
+                    info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit24:
+                    info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit32 or _:
+                    info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                    color = PixelColorType.RGB | PixelColorType.Alpha;
+                    alpha = PixelAlphaRepresentation.Unassociated;
+                    break;
+            }
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
 }
diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs
index 5c3486d4a2..6e97a8584a 100644
--- a/src/ImageSharp/Formats/Cur/CurMetadata.cs
+++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs
@@ -1,16 +1,183 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Icon;
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Cur;
 
 /// <summary>
-/// Provides Ico specific metadata information for the image.
+/// Provides Cur specific metadata information for the image.
 /// </summary>
-public class CurMetadata : IDeepCloneable<CurMetadata>, IDeepCloneable
+public class CurMetadata : IFormatMetadata<CurMetadata>
 {
+    /// <summary>
+    /// Initializes a new instance of the <see cref="CurMetadata"/> class.
+    /// </summary>
+    public CurMetadata()
+    {
+    }
+
+    private CurMetadata(CurMetadata other)
+    {
+        this.Compression = other.Compression;
+        this.HotspotX = other.HotspotX;
+        this.HotspotY = other.HotspotY;
+        this.EncodingWidth = other.EncodingWidth;
+        this.EncodingHeight = other.EncodingHeight;
+        this.BmpBitsPerPixel = other.BmpBitsPerPixel;
+
+        if (other.ColorTable?.Length > 0)
+        {
+            this.ColorTable = other.ColorTable.Value.ToArray();
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the frame compressions format. Derived from the root frame.
+    /// </summary>
+    public IconFrameCompression Compression { get; set; }
+
+    /// <summary>
+    /// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. Derived from the root frame.
+    /// </summary>
+    public ushort HotspotX { get; set; }
+
+    /// <summary>
+    /// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. Derived from the root frame.
+    /// </summary>
+    public ushort HotspotY { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding width. <br />
+    /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
+    /// </summary>
+    public byte EncodingWidth { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding height. <br />
+    /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
+    /// </summary>
+    public byte EncodingHeight { get; set; }
+
+    /// <summary>
+    /// Gets or sets the number of bits per pixel.<br/>
+    /// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
+    /// </summary>
+    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
+
+    /// <summary>
+    /// Gets or sets the color table, if any. Derived from the root frame.<br/>
+    /// The underlying pixel format is represented by <see cref="Bgr24"/>.
+    /// </summary>
+    public ReadOnlyMemory<Color>? ColorTable { get; set; }
+
     /// <inheritdoc/>
-    public CurMetadata DeepClone() => new();
+    public static CurMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        BmpBitsPerPixel bbpp = bpp switch
+        {
+            1 => BmpBitsPerPixel.Bit1,
+            2 => BmpBitsPerPixel.Bit2,
+            <= 4 => BmpBitsPerPixel.Bit4,
+            <= 8 => BmpBitsPerPixel.Bit8,
+            <= 16 => BmpBitsPerPixel.Bit16,
+            <= 24 => BmpBitsPerPixel.Bit24,
+            _ => BmpBitsPerPixel.Bit32
+        };
+
+        IconFrameCompression compression = IconFrameCompression.Bmp;
+        if (bbpp is BmpBitsPerPixel.Bit32)
+        {
+            compression = IconFrameCompression.Png;
+        }
+
+        return new CurMetadata
+        {
+            BmpBitsPerPixel = bbpp,
+            Compression = compression,
+            ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BmpBitsPerPixel;
+        PixelComponentInfo info;
+        PixelColorType color;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+
+        if (this.Compression is IconFrameCompression.Png)
+        {
+            bpp = 32;
+            info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+            color = PixelColorType.RGB | PixelColorType.Alpha;
+            alpha = PixelAlphaRepresentation.Unassociated;
+        }
+        else
+        {
+            switch (this.BmpBitsPerPixel)
+            {
+                case BmpBitsPerPixel.Bit1:
+                    info = PixelComponentInfo.Create(1, bpp, 1);
+                    color = PixelColorType.Binary;
+                    break;
+                case BmpBitsPerPixel.Bit2:
+                    info = PixelComponentInfo.Create(1, bpp, 2);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit4:
+                    info = PixelComponentInfo.Create(1, bpp, 4);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit8:
+                    info = PixelComponentInfo.Create(1, bpp, 8);
+                    color = PixelColorType.Indexed;
+                    break;
+
+                // Could be 555 with padding but 565 is more common in newer bitmaps and offers
+                // greater accuracy due to extra green precision.
+                case BmpBitsPerPixel.Bit16:
+                    info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit24:
+                    info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit32 or _:
+                    info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                    color = PixelColorType.RGB | PixelColorType.Alpha;
+                    alpha = PixelAlphaRepresentation.Unassociated;
+                    break;
+            }
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8
+                ? EncodingType.Lossy
+                : EncodingType.Lossless,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            ColorTable = this.ColorTable
+        };
 
     /// <inheritdoc/>
     IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public CurMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Cur/MetadataExtensions.cs b/src/ImageSharp/Formats/Cur/MetadataExtensions.cs
deleted file mode 100644
index 6394c564b0..0000000000
--- a/src/ImageSharp/Formats/Cur/MetadataExtensions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-using SixLabors.ImageSharp.Formats.Cur;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the Icon format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="CurMetadata"/>.</returns>
-    public static CurMetadata GetCurMetadata(this ImageMetadata source)
-        => source.GetFormatMetadata(CurFormat.Instance);
-
-    /// <summary>
-    /// Gets the Icon format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="CurFrameMetadata"/>.</returns>
-    public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source)
-        => source.GetFormatMetadata(CurFormat.Instance);
-
-    /// <summary>
-    /// Gets the Icon format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">
-    /// 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.
-    /// </param>
-    /// <returns>
-    /// <see langword="true"/> if the Icon frame metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetCurMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out CurFrameMetadata? metadata)
-        => source.TryGetFormatMetadata(CurFormat.Instance, out metadata);
-}
diff --git a/src/ImageSharp/Formats/EncodingType.cs b/src/ImageSharp/Formats/EncodingType.cs
new file mode 100644
index 0000000000..f4567ca43d
--- /dev/null
+++ b/src/ImageSharp/Formats/EncodingType.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// Provides a way to specify the type of encoding to be used.
+/// </summary>
+public enum EncodingType
+{
+    /// <summary>
+    /// Lossless encoding, which compresses data without any loss of information.
+    /// </summary>
+    Lossless,
+
+    /// <summary>
+    /// Lossy encoding, which compresses data by discarding some of it.
+    /// </summary>
+    Lossy
+}
diff --git a/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs b/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs
new file mode 100644
index 0000000000..ded220c9ad
--- /dev/null
+++ b/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// A metadata format designed to allow conversion between different image format frames.
+/// </summary>
+public class FormatConnectingFrameMetadata
+{
+    /// <summary>
+    /// Gets information about the encoded pixel type if any.
+    /// </summary>
+    public PixelTypeInfo? PixelTypeInfo { get; init; }
+
+    /// <summary>
+    /// Gets the frame color table if any.
+    /// </summary>
+    public ReadOnlyMemory<Color>? ColorTable { get; init; }
+
+    /// <summary>
+    /// Gets the frame color table mode.
+    /// </summary>
+    public FrameColorTableMode ColorTableMode { get; init; }
+
+    /// <summary>
+    /// Gets the duration of the frame.
+    /// </summary>
+    public TimeSpan Duration { get; init; }
+
+    /// <summary>
+    /// Gets the frame alpha blending mode.
+    /// </summary>
+    public FrameBlendMode BlendMode { get; init; }
+
+    /// <summary>
+    /// Gets the frame disposal mode.
+    /// </summary>
+    public FrameDisposalMode DisposalMode { get; init; }
+
+    /// <summary>
+    /// Gets or sets the encoding width. <br />
+    /// Used for formats that require a specific frame size.
+    /// </summary>
+    public int? EncodingWidth { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding height. <br />
+    /// Used for formats that require a specific frame size.
+    /// </summary>
+    public int? EncodingHeight { get; set; }
+}
diff --git a/src/ImageSharp/Formats/FormatConnectingMetadata.cs b/src/ImageSharp/Formats/FormatConnectingMetadata.cs
new file mode 100644
index 0000000000..baf0a35457
--- /dev/null
+++ b/src/ImageSharp/Formats/FormatConnectingMetadata.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// A metadata format designed to allow conversion between different image formats.
+/// </summary>
+public class FormatConnectingMetadata
+{
+    /// <summary>
+    /// Gets the encoding type.
+    /// </summary>
+    public EncodingType EncodingType { get; init; }
+
+    /// <summary>
+    /// Gets the quality to use when <see cref="EncodingType"/> is <see cref="EncodingType.Lossy"/>.
+    /// </summary>
+    /// <remarks>
+    /// The value is usually between 1 and 100. Defaults to 100.
+    /// </remarks>
+    public int Quality { get; init; } = 100;
+
+    /// <summary>
+    /// Gets information about the encoded pixel type.
+    /// </summary>
+    public PixelTypeInfo PixelTypeInfo { get; init; }
+
+    /// <summary>
+    /// Gets the shared color table if any.
+    /// </summary>
+    public ReadOnlyMemory<Color>? ColorTable { get; init; }
+
+    /// <summary>
+    /// Gets the shared color table mode.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <see cref="FrameColorTableMode.Global"/>.
+    /// </remarks>
+    public FrameColorTableMode ColorTableMode { get; init; } = FrameColorTableMode.Global;
+
+    /// <summary>
+    /// Gets the default background color of the canvas when animating.
+    /// This color may be used to fill the unused space on the canvas around the frames,
+    /// as well as the transparent pixels of the first frame.
+    /// The background color is also used when the disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <see cref="Color.Transparent"/>.
+    /// </remarks>
+    public Color BackgroundColor { get; init; } = Color.Transparent;
+
+    /// <summary>
+    /// Gets the number of times any animation is repeated.
+    /// </summary>
+    /// <remarks>
+    /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1.
+    /// </remarks>
+    public ushort RepeatCount { get; init; } = 1;
+
+    /// <summary>
+    /// Gets a value indicating whether the root frame is shown as part of the animated sequence.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <see langword="true"/>.
+    /// </remarks>
+    public bool AnimateRootFrame { get; init; } = true;
+}
diff --git a/src/ImageSharp/Formats/FrameBlendMode.cs b/src/ImageSharp/Formats/FrameBlendMode.cs
new file mode 100644
index 0000000000..5785324e5a
--- /dev/null
+++ b/src/ImageSharp/Formats/FrameBlendMode.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// Provides a way to specify how the current frame should be blended with the previous frame in the animation sequence.
+/// </summary>
+public enum FrameBlendMode
+{
+    /// <summary>
+    /// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame.
+    /// </summary>
+    Source = 0,
+
+    /// <summary>
+    /// Blend the current frame with the previous frame in the animation sequence within the rectangle covered
+    /// by the current frame.
+    /// If the current has any transparent areas, the corresponding areas of the previous frame will be visible
+    /// through these transparent regions.
+    /// </summary>
+    Over = 1
+}
diff --git a/src/ImageSharp/Formats/FrameColorTableMode.cs b/src/ImageSharp/Formats/FrameColorTableMode.cs
new file mode 100644
index 0000000000..a45b6de0e4
--- /dev/null
+++ b/src/ImageSharp/Formats/FrameColorTableMode.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// Provides a way to specify how the color table is used by the frame.
+/// </summary>
+public enum FrameColorTableMode
+{
+    /// <summary>
+    /// The frame uses the shared color table specified by the image metadata.
+    /// </summary>
+    Global,
+
+    /// <summary>
+    /// The frame uses a color table specified by the frame metadata.
+    /// </summary>
+    Local
+}
diff --git a/src/ImageSharp/Formats/FrameDisposalMode.cs b/src/ImageSharp/Formats/FrameDisposalMode.cs
new file mode 100644
index 0000000000..196fdb5ad4
--- /dev/null
+++ b/src/ImageSharp/Formats/FrameDisposalMode.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// Provides a way to specify how the current frame should be disposed of before rendering the next frame.
+/// </summary>
+public enum FrameDisposalMode
+{
+    /// <summary>
+    /// No disposal specified.
+    /// The decoder is not required to take any action.
+    /// </summary>
+    Unspecified = 0,
+
+    /// <summary>
+    /// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to
+    /// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains
+    /// transparency, the previous frame will be visible through these transparent areas.
+    /// </summary>
+    DoNotDispose = 1,
+
+    /// <summary>
+    /// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is
+    /// filled with the background color specified in the image metadata.
+    /// This effectively erases the current frame by replacing it with the background color before the next frame is displayed.
+    /// </summary>
+    RestoreToBackground = 2,
+
+    /// <summary>
+    /// Restore to previous. This method restores the area affected by the current frame to what it was before the
+    /// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image
+    /// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small
+    /// part of the image changes from frame to frame.
+    /// </summary>
+    RestoreToPrevious = 3
+}
diff --git a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs
deleted file mode 100644
index 0f65f46022..0000000000
--- a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-﻿// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Gif;
-
-/// <summary>
-/// Provides enumeration for the available color table modes.
-/// </summary>
-public enum GifColorTableMode
-{
-    /// <summary>
-    /// A single color table is calculated from the first frame and reused for subsequent frames.
-    /// </summary>
-    Global,
-
-    /// <summary>
-    /// A unique color table is calculated for each frame.
-    /// </summary>
-    Local
-}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 982dbdc780..a99b5862dd 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
 /// <summary>
 /// Performs the gif decoding operation.
 /// </summary>
-internal sealed class GifDecoderCore : IImageDecoderInternals
+internal sealed class GifDecoderCore : ImageDecoderCore
 {
     /// <summary>
     /// The temp buffer used to reduce allocations.
@@ -94,8 +94,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public GifDecoderCore(DecoderOptions options)
+        : base(options)
     {
-        this.Options = options;
         this.configuration = options.Configuration;
         this.skipMetadata = options.SkipMetadata;
         this.maxFrames = options.MaxFrames;
@@ -103,14 +103,7 @@ public GifDecoderCore(DecoderOptions options)
     }
 
     /// <inheritdoc />
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc />
-    public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height);
-
-    /// <inheritdoc />
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         uint frameCount = 0;
         Image<TPixel>? image = null;
@@ -181,7 +174,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc />
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         uint frameCount = 0;
         ImageFrameMetadata? previousFrame = null;
@@ -249,7 +242,6 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         }
 
         return new ImageInfo(
-            new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
             new(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height),
             this.metadata,
             framesMetadata);
@@ -287,6 +279,8 @@ private void ReadImageDescriptor(BufferedReadStream stream)
         {
             GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
         }
+
+        this.Dimensions = new(this.imageDescriptor.Width, this.imageDescriptor.Height);
     }
 
     /// <summary>
@@ -498,7 +492,7 @@ private void ReadFrameColors<TPixel>(
         }
         else
         {
-            if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious)
+            if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious)
             {
                 prevFrame = previousFrame;
             }
@@ -615,7 +609,7 @@ private void ReadFrameColors<TPixel>(
 
         previousFrame = currentFrame ?? image.Frames.RootFrame;
 
-        if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground)
+        if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToBackground)
         {
             this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
         }
@@ -690,14 +684,14 @@ private void SetFrameMetadata(ImageFrameMetadata metadata)
             && this.logicalScreenDescriptor.GlobalColorTableSize > 0)
         {
             GifFrameMetadata gifMeta = metadata.GetGifMetadata();
-            gifMeta.ColorTableMode = GifColorTableMode.Global;
+            gifMeta.ColorTableMode = FrameColorTableMode.Global;
         }
 
         if (this.imageDescriptor.LocalColorTableFlag
             && this.imageDescriptor.LocalColorTableSize > 0)
         {
             GifFrameMetadata gifMeta = metadata.GetGifMetadata();
-            gifMeta.ColorTableMode = GifColorTableMode.Local;
+            gifMeta.ColorTableMode = FrameColorTableMode.Local;
 
             Color[] colorTable = new Color[this.imageDescriptor.LocalColorTableSize];
             ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize]);
@@ -713,7 +707,7 @@ private void SetFrameMetadata(ImageFrameMetadata metadata)
             gifMeta.HasTransparency = this.graphicsControlExtension.TransparencyFlag;
             gifMeta.TransparencyIndex = this.graphicsControlExtension.TransparencyIndex;
             gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
-            gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
+            gifMeta.DisposalMode = this.graphicsControlExtension.DisposalMethod;
         }
     }
 
@@ -760,8 +754,8 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
         this.metadata = meta;
         this.gifMetadata = meta.GetGifMetadata();
         this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag
-        ? GifColorTableMode.Global
-        : GifColorTableMode.Local;
+            ? FrameColorTableMode.Global
+            : FrameColorTableMode.Local;
 
         if (this.logicalScreenDescriptor.GlobalColorTableFlag)
         {
diff --git a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
deleted file mode 100644
index 12b4239c4f..0000000000
--- a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-﻿// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Gif;
-
-/// <summary>
-/// Provides enumeration for instructing the decoder what to do with the last image
-/// in an animation sequence.
-/// <see href="http://www.w3.org/Graphics/GIF/spec-gif89a.txt"/> section 23
-/// </summary>
-public enum GifDisposalMethod
-{
-    /// <summary>
-    /// No disposal specified.
-    /// The decoder is not required to take any action.
-    /// </summary>
-    Unspecified = 0,
-
-    /// <summary>
-    /// Do not dispose.
-    /// The graphic is to be left in place.
-    /// </summary>
-    NotDispose = 1,
-
-    /// <summary>
-    /// Restore to background color.
-    /// The area used by the graphic must be restored to the background color.
-    /// </summary>
-    RestoreToBackground = 2,
-
-    /// <summary>
-    /// Restore to previous.
-    /// The decoder is required to restore the area overwritten by the
-    /// graphic with what was there prior to rendering the graphic.
-    /// </summary>
-    RestoreToPrevious = 3
-}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index ab05548ac5..6cb8f9d8ce 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -11,7 +11,7 @@ public sealed class GifEncoder : QuantizingImageEncoder
     /// <summary>
     /// Gets the color table mode: Global or local.
     /// </summary>
-    public GifColorTableMode? ColorTableMode { get; init; }
+    public FrameColorTableMode? ColorTableMode { get; init; }
 
     /// <inheritdoc/>
     protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 95429e6065..11185d90b0 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -5,8 +5,6 @@
 using System.Numerics;
 using System.Runtime.CompilerServices;
 using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Formats.Webp;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -19,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
 /// <summary>
 /// Implements the GIF encoding protocol.
 /// </summary>
-internal sealed class GifEncoderCore : IImageEncoderInternals
+internal sealed class GifEncoderCore
 {
     /// <summary>
     /// Used for allocating memory during processing operations.
@@ -49,7 +47,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
     /// <summary>
     /// The color table mode: Global or local.
     /// </summary>
-    private GifColorTableMode? colorTableMode;
+    private FrameColorTableMode? colorTableMode;
 
     /// <summary>
     /// The pixel sampling strategy for global quantization.
@@ -85,9 +83,9 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         Guard.NotNull(image, nameof(image));
         Guard.NotNull(stream, nameof(stream));
 
-        GifMetadata gifMetadata = GetGifMetadata(image);
+        GifMetadata gifMetadata = image.Metadata.CloneGifMetadata();
         this.colorTableMode ??= gifMetadata.ColorTableMode;
-        bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
+        bool useGlobalTable = this.colorTableMode == FrameColorTableMode.Global;
 
         // Quantize the first image frame returning a palette.
         IndexedImageFrame<TPixel>? quantized = null;
@@ -99,7 +97,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         if (this.quantizer is null)
         {
             // Is this a gif with color information. If so use that, otherwise use octree.
-            if (gifMetadata.ColorTableMode == GifColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0)
+            if (gifMetadata.ColorTableMode == FrameColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0)
             {
                 // We avoid dithering by default to preserve the original colors.
                 int transparencyIndex = GetTransparentIndex(quantized, frameMetadata);
@@ -171,63 +169,24 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
         TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
 
-        this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMethod);
+        this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMode);
 
         stream.WriteByte(GifConstants.EndIntroducer);
 
         quantized?.Dispose();
     }
 
-    private static GifMetadata GetGifMetadata<TPixel>(Image<TPixel> image)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
-        {
-            return (GifMetadata)gif.DeepClone();
-        }
-
-        if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
-        {
-            AnimatedImageMetadata ani = png.ToAnimatedImageMetadata();
-            return GifMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
-        {
-            AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata();
-            return GifMetadata.FromAnimatedMetadata(ani);
-        }
-
-        // Return explicit new instance so we do not mutate the original metadata.
-        return new();
-    }
-
     private static GifFrameMetadata GetGifFrameMetadata<TPixel>(ImageFrame<TPixel> frame, int transparencyIndex)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        GifFrameMetadata? metadata = null;
-        if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
-        {
-            metadata = (GifFrameMetadata)gif.DeepClone();
-        }
-        else if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
-        {
-            AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata();
-            metadata = GifFrameMetadata.FromAnimatedMetadata(ani);
-        }
-        else if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
-        {
-            AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata();
-            metadata = GifFrameMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (metadata?.ColorTableMode == GifColorTableMode.Global && transparencyIndex > -1)
+        GifFrameMetadata metadata = frame.Metadata.CloneGifMetadata();
+        if (metadata.ColorTableMode == FrameColorTableMode.Global && transparencyIndex > -1)
         {
             metadata.HasTransparency = true;
             metadata.TransparencyIndex = ClampIndex(transparencyIndex);
         }
 
-        return metadata ?? new();
+        return metadata;
     }
 
     private void EncodeAdditionalFrames<TPixel>(
@@ -235,7 +194,7 @@ private void EncodeAdditionalFrames<TPixel>(
         Image<TPixel> image,
         ReadOnlyMemory<TPixel> globalPalette,
         int globalTransparencyIndex,
-        GifDisposalMethod previousDisposalMethod)
+        FrameDisposalMode previousDisposalMode)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         if (image.Frames.Count == 1)
@@ -258,7 +217,7 @@ private void EncodeAdditionalFrames<TPixel>(
             ImageFrame<TPixel> currentFrame = image.Frames[i];
             ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
             GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex);
-            bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata.ColorTableMode == GifColorTableMode.Local);
+            bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (gifMetadata.ColorTableMode == FrameColorTableMode.Local);
 
             if (!useLocal && !hasPaletteQuantizer && i > 0)
             {
@@ -279,10 +238,10 @@ private void EncodeAdditionalFrames<TPixel>(
                 useLocal,
                 gifMetadata,
                 paletteQuantizer,
-                previousDisposalMethod);
+                previousDisposalMode);
 
             previousFrame = currentFrame;
-            previousDisposalMethod = gifMetadata.DisposalMethod;
+            previousDisposalMode = gifMetadata.DisposalMode;
         }
 
         if (hasPaletteQuantizer)
@@ -301,7 +260,7 @@ private void EncodeFirstFrame<TPixel>(
 
         Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer;
         Rectangle interest = indices.FullRectangle();
-        bool useLocal = this.colorTableMode == GifColorTableMode.Local || (metadata.ColorTableMode == GifColorTableMode.Local);
+        bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local);
         int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
 
         this.WriteImageDescriptor(interest, useLocal, bitDepth, stream);
@@ -323,14 +282,14 @@ private void EncodeAdditionalFrame<TPixel>(
         bool useLocal,
         GifFrameMetadata metadata,
         PaletteQuantizer<TPixel> globalPaletteQuantizer,
-        GifDisposalMethod previousDisposal)
+        FrameDisposalMode previousDisposalMode)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         // Capture any explicit transparency index from the metadata.
         // We use it to determine the value to use to replace duplicate pixels.
         int transparencyIndex = metadata.HasTransparency ? metadata.TransparencyIndex : -1;
 
-        ImageFrame<TPixel>? previous = previousDisposal == GifDisposalMethod.RestoreToBackground ? null : previousFrame;
+        ImageFrame<TPixel>? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
 
         // Deduplicate and quantize the frame capturing only required parts.
         (bool difference, Rectangle bounds) =
@@ -664,7 +623,7 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, Stream st
         bool hasTransparency = metadata.HasTransparency;
 
         byte packedValue = GifGraphicControlExtension.GetPackedValue(
-            disposalMethod: metadata.DisposalMethod,
+            disposalMode: metadata.DisposalMode,
             transparencyFlag: hasTransparency);
 
         GifGraphicControlExtension extension = new(
diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
index 6598def2a5..f81329e973 100644
--- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
 /// <summary>
 /// Provides Gif specific metadata information for the image frame.
 /// </summary>
-public class GifFrameMetadata : IDeepCloneable
+public class GifFrameMetadata : IFormatFrameMetadata<GifFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="GifFrameMetadata"/> class.
@@ -26,7 +26,7 @@ private GifFrameMetadata(GifFrameMetadata other)
     {
         this.ColorTableMode = other.ColorTableMode;
         this.FrameDelay = other.FrameDelay;
-        this.DisposalMethod = other.DisposalMethod;
+        this.DisposalMode = other.DisposalMode;
 
         if (other.LocalColorTable?.Length > 0)
         {
@@ -40,7 +40,7 @@ private GifFrameMetadata(GifFrameMetadata other)
     /// <summary>
     /// Gets or sets the color table mode.
     /// </summary>
-    public GifColorTableMode ColorTableMode { get; set; }
+    public FrameColorTableMode ColorTableMode { get; set; }
 
     /// <summary>
     /// Gets or sets the local color table, if any.
@@ -73,10 +73,64 @@ private GifFrameMetadata(GifFrameMetadata other)
     /// Primarily used in Gif animation, this field indicates the way in which the graphic is to
     /// be treated after being displayed.
     /// </summary>
-    public GifDisposalMethod DisposalMethod { get; set; }
+    public FrameDisposalMode DisposalMode { get; set; }
+
+    /// <inheritdoc />
+    public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
+    {
+        int index = -1;
+        const float background = 1f;
+        if (metadata.ColorTable.HasValue)
+        {
+            ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
+            for (int i = 0; i < colorTable.Length; i++)
+            {
+                Vector4 vector = colorTable[i].ToScaledVector4();
+                if (vector.W < background)
+                {
+                    index = i;
+                }
+            }
+        }
+
+        bool hasTransparency = index >= 0;
+
+        return new()
+        {
+            LocalColorTable = metadata.ColorTable,
+            ColorTableMode = metadata.ColorTableMode,
+            FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10),
+            DisposalMode = metadata.DisposalMode,
+            HasTransparency = hasTransparency,
+            TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue,
+        };
+    }
+
+    /// <inheritdoc />
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+    {
+        // For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or
+        // has a local palette with 256 colors and is not transparent we should use 'Source'.
+        bool blendSource = this.DisposalMode == FrameDisposalMode.RestoreToBackground || (this.LocalColorTable?.Length == 256 && !this.HasTransparency);
+
+        // If the color table is global and frame has no transparency. Consider it 'Source' also.
+        blendSource |= this.ColorTableMode == FrameColorTableMode.Global && !this.HasTransparency;
+
+        return new()
+        {
+            ColorTable = this.LocalColorTable,
+            ColorTableMode = this.ColorTableMode,
+            Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10),
+            DisposalMode = this.DisposalMode,
+            BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over,
+        };
+    }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new GifFrameMetadata(this);
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public GifFrameMetadata DeepClone() => new(this);
 
     internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata)
     {
@@ -101,19 +155,11 @@ internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata
         return new()
         {
             LocalColorTable = metadata.ColorTable,
-            ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local,
+            ColorTableMode = metadata.ColorTableMode,
             FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10),
-            DisposalMethod = GetMode(metadata.DisposalMode),
+            DisposalMode = metadata.DisposalMode,
             HasTransparency = hasTransparency,
             TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue,
         };
     }
-
-    private static GifDisposalMethod GetMode(FrameDisposalMode mode) => mode switch
-    {
-        FrameDisposalMode.DoNotDispose => GifDisposalMethod.NotDispose,
-        FrameDisposalMode.RestoreToBackground => GifDisposalMethod.RestoreToBackground,
-        FrameDisposalMode.RestoreToPrevious => GifDisposalMethod.RestoreToPrevious,
-        _ => GifDisposalMethod.Unspecified,
-    };
 }
diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs
index 1331edee89..565038b55a 100644
--- a/src/ImageSharp/Formats/Gif/GifMetadata.cs
+++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
 /// <summary>
 /// Provides Gif specific metadata information for the image.
 /// </summary>
-public class GifMetadata : IDeepCloneable
+public class GifMetadata : IFormatMetadata<GifMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="GifMetadata"/> class.
@@ -49,7 +49,7 @@ private GifMetadata(GifMetadata other)
     /// <summary>
     /// Gets or sets the color table mode.
     /// </summary>
-    public GifColorTableMode ColorTableMode { get; set; }
+    public FrameColorTableMode ColorTableMode { get; set; }
 
     /// <summary>
     /// Gets or sets the global color table, if any.
@@ -67,12 +67,10 @@ private GifMetadata(GifMetadata other)
     /// Gets or sets the collection of comments about the graphics, credits, descriptions or any
     /// other type of non-control and non-graphic data.
     /// </summary>
-    public IList<string> Comments { get; set; } = new List<string>();
+    public IList<string> Comments { get; set; } = [];
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new GifMetadata(this);
-
-    internal static GifMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
+    public static GifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
     {
         int index = 0;
         Color background = metadata.BackgroundColor;
@@ -81,20 +79,60 @@ internal static GifMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
             ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
             for (int i = 0; i < colorTable.Length; i++)
             {
-                if (background == colorTable[i])
+                if (background != colorTable[i])
                 {
-                    index = i;
-                    break;
+                    continue;
                 }
+
+                index = i;
+                break;
             }
         }
 
         return new()
         {
             GlobalColorTable = metadata.ColorTable,
-            ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local,
+            ColorTableMode = metadata.ColorTableMode,
             RepeatCount = metadata.RepeatCount,
             BackgroundColorIndex = (byte)Numerics.Clamp(index, 0, 255),
         };
     }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = this.GlobalColorTable.HasValue
+            ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8)
+            : 8;
+
+        return new PixelTypeInfo(bpp)
+        {
+            ColorType = PixelColorType.Indexed,
+            ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp),
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+    {
+        Color color = this.GlobalColorTable.HasValue && this.GlobalColorTable.Value.Span.Length > this.BackgroundColorIndex
+            ? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex]
+            : Color.Transparent;
+
+        return new()
+        {
+            AnimateRootFrame = true,
+            BackgroundColor = color,
+            ColorTable = this.GlobalColorTable,
+            ColorTableMode = this.ColorTableMode,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            RepeatCount = this.RepeatCount,
+        };
+    }
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public GifMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs
deleted file mode 100644
index ad06462e77..0000000000
--- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.Formats.Gif;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the gif format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="GifMetadata"/>.</returns>
-    public static GifMetadata GetGifMetadata(this ImageMetadata source)
-        => source.GetFormatMetadata(GifFormat.Instance);
-
-    /// <summary>
-    /// Gets the gif format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">
-    /// When this method returns, contains the metadata associated with the specified image,
-    /// if found; otherwise, the default value for the type of the metadata parameter.
-    /// This parameter is passed uninitialized.
-    /// </param>
-    /// <returns>
-    /// <see langword="true"/> if the gif metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetGifMetadata(this ImageMetadata source, [NotNullWhen(true)] out GifMetadata? metadata)
-        => source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
-
-    /// <summary>
-    /// Gets the gif format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="GifFrameMetadata"/>.</returns>
-    public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata source)
-        => source.GetFormatMetadata(GifFormat.Instance);
-
-    /// <summary>
-    /// Gets the gif format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">
-    /// 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.
-    /// </param>
-    /// <returns>
-    /// <see langword="true"/> if the gif frame metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetGifMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata)
-        => source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
-
-    internal static AnimatedImageMetadata ToAnimatedImageMetadata(this GifMetadata source)
-    {
-        Color background = Color.Transparent;
-        if (source.GlobalColorTable != null)
-        {
-            background = source.GlobalColorTable.Value.Span[source.BackgroundColorIndex];
-        }
-
-        return new()
-        {
-            ColorTable = source.GlobalColorTable,
-            ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
-            RepeatCount = source.RepeatCount,
-            BackgroundColor = background,
-        };
-    }
-
-    internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this GifFrameMetadata source)
-    {
-        // For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or
-        // has a local palette with 256 colors and is not transparent we should use 'Source'.
-        bool blendSource = source.DisposalMethod == GifDisposalMethod.RestoreToBackground || (source.LocalColorTable?.Length == 256 && !source.HasTransparency);
-
-        // If the color table is global and frame has no transparency. Consider it 'Source' also.
-        blendSource |= source.ColorTableMode == GifColorTableMode.Global && !source.HasTransparency;
-
-        return new()
-        {
-            ColorTable = source.LocalColorTable,
-            ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
-            Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10),
-            DisposalMode = GetMode(source.DisposalMethod),
-            BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over,
-        };
-    }
-
-    private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch
-    {
-        GifDisposalMethod.NotDispose => FrameDisposalMode.DoNotDispose,
-        GifDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
-        GifDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
-        _ => FrameDisposalMode.Unspecified,
-    };
-}
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
index 52052021fc..ad99ac0f42 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
@@ -52,7 +52,7 @@ public GifGraphicControlExtension(
     /// Gets the disposal method which indicates the way in which the
     /// graphic is to be treated after being displayed.
     /// </summary>
-    public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2);
+    public FrameDisposalMode DisposalMethod => (FrameDisposalMode)((this.Packed & 0x1C) >> 2);
 
     /// <summary>
     /// Gets a value indicating whether transparency flag is to be set.
@@ -80,7 +80,7 @@ public int WriteTo(Span<byte> buffer)
     public static GifGraphicControlExtension Parse(ReadOnlySpan<byte> buffer)
         => MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0];
 
-    public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
+    public static byte GetPackedValue(FrameDisposalMode disposalMode, bool userInputFlag = false, bool transparencyFlag = false)
     {
         /*
         Reserved               | 3 Bits
@@ -91,7 +91,7 @@ Transparent Color Flag | 1 Bit
 
         byte value = 0;
 
-        value |= (byte)((int)disposalMethod << 2);
+        value |= (byte)((int)disposalMode << 2);
 
         if (userInputFlag)
         {
diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs
new file mode 100644
index 0000000000..4eef93ad34
--- /dev/null
+++ b/src/ImageSharp/Formats/IFormatFrameMetadata.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// An interface that provides metadata for a specific image format frames.
+/// </summary>
+public interface IFormatFrameMetadata : IDeepCloneable
+{
+    /// <summary>
+    /// Converts the metadata to a <see cref="FormatConnectingFrameMetadata"/> instance.
+    /// </summary>
+    /// <returns>The <see cref="FormatConnectingFrameMetadata"/>.</returns>
+    FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata();
+}
+
+/// <summary>
+/// An interface that provides metadata for a specific image format frames.
+/// </summary>
+/// <typeparam name="TSelf">The metadata type implementing this interface.</typeparam>
+public interface IFormatFrameMetadata<TSelf> : IFormatFrameMetadata, IDeepCloneable<TSelf>
+    where TSelf : class, IFormatFrameMetadata
+{
+    /// <summary>
+    /// Creates a new instance of the <typeparamref name="TSelf"/> class from the given <see cref="FormatConnectingFrameMetadata"/>.
+    /// </summary>
+    /// <param name="metadata">The <see cref="FormatConnectingFrameMetadata"/>.</param>
+    /// <returns>The <typeparamref name="TSelf"/>.</returns>
+#pragma warning disable CA1000 // Do not declare static members on generic types
+    static abstract TSelf FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata);
+#pragma warning restore CA1000 // Do not declare static members on generic types
+}
diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs
new file mode 100644
index 0000000000..8d695306e4
--- /dev/null
+++ b/src/ImageSharp/Formats/IFormatMetadata.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// An interface that provides metadata for a specific image format.
+/// </summary>
+public interface IFormatMetadata : IDeepCloneable
+{
+    /// <summary>
+    /// Converts the metadata to a <see cref="PixelTypeInfo"/> instance.
+    /// </summary>
+    /// <returns>The pixel type info.</returns>
+    PixelTypeInfo GetPixelTypeInfo();
+
+    /// <summary>
+    /// Converts the metadata to a <see cref="FormatConnectingMetadata"/> instance.
+    /// </summary>
+    /// <returns>The <see cref="FormatConnectingMetadata"/>.</returns>
+    FormatConnectingMetadata ToFormatConnectingMetadata();
+}
+
+/// <summary>
+/// An interface that provides metadata for a specific image format.
+/// </summary>
+/// <typeparam name="TSelf">The metadata type implementing this interface.</typeparam>
+public interface IFormatMetadata<TSelf> : IFormatMetadata, IDeepCloneable<TSelf>
+    where TSelf : class, IFormatMetadata
+{
+    /// <summary>
+    /// Creates a new instance of the <typeparamref name="TSelf"/> class from the given <see cref="FormatConnectingMetadata"/>.
+    /// </summary>
+    /// <param name="metadata">The <see cref="FormatConnectingMetadata"/>.</param>
+    /// <returns>The <typeparamref name="TSelf"/>.</returns>
+#pragma warning disable CA1000 // Do not declare static members on generic types
+    static abstract TSelf FromFormatConnectingMetadata(FormatConnectingMetadata metadata);
+#pragma warning restore CA1000 // Do not declare static members on generic types
+}
diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs
deleted file mode 100644
index 06fb597640..0000000000
--- a/src/ImageSharp/Formats/IImageDecoderInternals.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-﻿// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats;
-
-/// <summary>
-/// Abstraction for shared internals for XXXDecoderCore implementations to be used with <see cref="ImageDecoderUtilities"/>.
-/// </summary>
-internal interface IImageDecoderInternals
-{
-    /// <summary>
-    /// Gets the general decoder options.
-    /// </summary>
-    DecoderOptions Options { get; }
-
-    /// <summary>
-    /// Gets the dimensions of the image being decoded.
-    /// </summary>
-    Size Dimensions { get; }
-
-    /// <summary>
-    /// Decodes the image from the specified stream.
-    /// </summary>
-    /// <typeparam name="TPixel">The pixel format.</typeparam>
-    /// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
-    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-    /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
-    /// <returns>The decoded image.</returns>
-    /// <remarks>
-    /// Cancellable synchronous method. In case of cancellation,
-    /// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
-    /// </remarks>
-    Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>;
-
-    /// <summary>
-    /// Reads the raw image information from the specified stream.
-    /// </summary>
-    /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
-    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-    /// <returns>The <see cref="ImageInfo"/>.</returns>
-    /// <remarks>
-    /// Cancellable synchronous method. In case of cancellation,
-    /// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
-    /// </remarks>
-    ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
-}
diff --git a/src/ImageSharp/Formats/IImageEncoderInternals.cs b/src/ImageSharp/Formats/IImageEncoderInternals.cs
deleted file mode 100644
index 131949c968..0000000000
--- a/src/ImageSharp/Formats/IImageEncoderInternals.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats;
-
-/// <summary>
-/// Abstraction for shared internals for ***DecoderCore implementations.
-/// </summary>
-internal interface IImageEncoderInternals
-{
-    /// <summary>
-    /// Encodes the image.
-    /// </summary>
-    /// <param name="image">The image.</param>
-    /// <param name="stream">The stream.</param>
-    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-    /// <typeparam name="TPixel">The pixel type.</typeparam>
-    void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>;
-}
diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs
index b983219777..5c579eb4f6 100644
--- a/src/ImageSharp/Formats/IImageFormat.cs
+++ b/src/ImageSharp/Formats/IImageFormat.cs
@@ -1,4 +1,4 @@
-﻿// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
 namespace SixLabors.ImageSharp.Formats;
@@ -14,12 +14,12 @@ public interface IImageFormat
     string Name { get; }
 
     /// <summary>
-    /// Gets the default mimetype that the image format uses
+    /// Gets the default mime type that the image format uses
     /// </summary>
     string DefaultMimeType { get; }
 
     /// <summary>
-    /// Gets all the mimetypes that have been used by this image format.
+    /// Gets all the mime types that have been used by this image format.
     /// </summary>
     IEnumerable<string> MimeTypes { get; }
 
diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs
index f4990c66af..8b59974eb3 100644
--- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs
+++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs
@@ -15,16 +15,28 @@ public IcoDecoderCore(DecoderOptions options)
     }
 
     protected override void SetFrameMetadata(
-        ImageFrameMetadata metadata,
+        ImageMetadata imageMetadata,
+        ImageFrameMetadata frameMetadata,
+        int index,
         in IconDirEntry entry,
         IconFrameCompression compression,
         BmpBitsPerPixel bitsPerPixel,
         ReadOnlyMemory<Color>? colorTable)
     {
-        IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata();
+        IcoFrameMetadata icoFrameMetadata = frameMetadata.GetIcoMetadata();
         icoFrameMetadata.FromIconDirEntry(entry);
         icoFrameMetadata.Compression = compression;
         icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
         icoFrameMetadata.ColorTable = colorTable;
+
+        if (index == 0)
+        {
+            IcoMetadata curMetadata = imageMetadata.GetIcoMetadata();
+            curMetadata.Compression = compression;
+            curMetadata.BmpBitsPerPixel = bitsPerPixel;
+            curMetadata.ColorTable = colorTable;
+            curMetadata.EncodingWidth = icoFrameMetadata.EncodingWidth;
+            curMetadata.EncodingHeight = icoFrameMetadata.EncodingHeight;
+        }
     }
 }
diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
index ea27d13c8d..c244e38981 100644
--- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Ico;
 /// <summary>
 /// Provides Ico specific metadata information for the image frame.
 /// </summary>
-public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
+public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="IcoFrameMetadata"/> class.
@@ -53,7 +53,7 @@ private IcoFrameMetadata(IcoFrameMetadata other)
     /// Gets or sets the number of bits per pixel.<br/>
     /// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
     /// </summary>
-    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
+    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
 
     /// <summary>
     /// Gets or sets the color table, if any.
@@ -62,11 +62,75 @@ private IcoFrameMetadata(IcoFrameMetadata other)
     public ReadOnlyMemory<Color>? ColorTable { get; set; }
 
     /// <inheritdoc/>
-    public IcoFrameMetadata DeepClone() => new(this);
+    public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
+    {
+        if (!metadata.PixelTypeInfo.HasValue)
+        {
+            return new IcoFrameMetadata
+            {
+                BmpBitsPerPixel = BmpBitsPerPixel.Bit32,
+                Compression = IconFrameCompression.Png
+            };
+        }
+
+        byte encodingWidth = metadata.EncodingWidth switch
+        {
+            > 255 => 0,
+            <= 255 and >= 1 => (byte)metadata.EncodingWidth,
+            _ => 0
+        };
+
+        byte encodingHeight = metadata.EncodingHeight switch
+        {
+            > 255 => 0,
+            <= 255 and >= 1 => (byte)metadata.EncodingHeight,
+            _ => 0
+        };
+
+        int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
+        BmpBitsPerPixel bbpp = bpp switch
+        {
+            1 => BmpBitsPerPixel.Bit1,
+            2 => BmpBitsPerPixel.Bit2,
+            <= 4 => BmpBitsPerPixel.Bit4,
+            <= 8 => BmpBitsPerPixel.Bit8,
+            <= 16 => BmpBitsPerPixel.Bit16,
+            <= 24 => BmpBitsPerPixel.Bit24,
+            _ => BmpBitsPerPixel.Bit32
+        };
+
+        IconFrameCompression compression = IconFrameCompression.Bmp;
+        if (bbpp is BmpBitsPerPixel.Bit32)
+        {
+            compression = IconFrameCompression.Png;
+        }
+
+        return new IcoFrameMetadata
+        {
+            BmpBitsPerPixel = bbpp,
+            Compression = compression,
+            EncodingWidth = encodingWidth,
+            EncodingHeight = encodingHeight,
+            ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            ColorTable = this.ColorTable,
+            EncodingWidth = this.EncodingWidth,
+            EncodingHeight = this.EncodingHeight
+        };
 
     /// <inheritdoc/>
     IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
 
+    /// <inheritdoc/>
+    public IcoFrameMetadata DeepClone() => new(this);
+
     internal void FromIconDirEntry(IconDirEntry entry)
     {
         this.EncodingWidth = entry.Width;
@@ -75,7 +139,7 @@ internal void FromIconDirEntry(IconDirEntry entry)
 
     internal IconDirEntry ToIconDirEntry()
     {
-        byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8
+        byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
             ? (byte)0
             : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel);
 
@@ -92,4 +156,65 @@ internal IconDirEntry ToIconDirEntry()
             },
         };
     }
+
+    private PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BmpBitsPerPixel;
+        PixelComponentInfo info;
+        PixelColorType color;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+
+        if (this.Compression is IconFrameCompression.Png)
+        {
+            bpp = 32;
+            info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+            color = PixelColorType.RGB | PixelColorType.Alpha;
+            alpha = PixelAlphaRepresentation.Unassociated;
+        }
+        else
+        {
+            switch (this.BmpBitsPerPixel)
+            {
+                case BmpBitsPerPixel.Bit1:
+                    info = PixelComponentInfo.Create(1, bpp, 1);
+                    color = PixelColorType.Binary;
+                    break;
+                case BmpBitsPerPixel.Bit2:
+                    info = PixelComponentInfo.Create(1, bpp, 2);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit4:
+                    info = PixelComponentInfo.Create(1, bpp, 4);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit8:
+                    info = PixelComponentInfo.Create(1, bpp, 8);
+                    color = PixelColorType.Indexed;
+                    break;
+
+                // Could be 555 with padding but 565 is more common in newer bitmaps and offers
+                // greater accuracy due to extra green precision.
+                case BmpBitsPerPixel.Bit16:
+                    info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit24:
+                    info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit32 or _:
+                    info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                    color = PixelColorType.RGB | PixelColorType.Alpha;
+                    alpha = PixelAlphaRepresentation.Unassociated;
+                    break;
+            }
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
 }
diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs
index f165bf9164..7e31468ecc 100644
--- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs
+++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs
@@ -1,16 +1,171 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Icon;
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Ico;
 
 /// <summary>
 /// Provides Ico specific metadata information for the image.
 /// </summary>
-public class IcoMetadata : IDeepCloneable<IcoMetadata>, IDeepCloneable
+public class IcoMetadata : IFormatMetadata<IcoMetadata>
 {
+    /// <summary>
+    /// Initializes a new instance of the <see cref="IcoMetadata"/> class.
+    /// </summary>
+    public IcoMetadata()
+    {
+    }
+
+    private IcoMetadata(IcoMetadata other)
+    {
+        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();
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the frame compressions format. Derived from the root frame.
+    /// </summary>
+    public IconFrameCompression Compression { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding width. <br />
+    /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
+    /// </summary>
+    public byte EncodingWidth { get; set; }
+
+    /// <summary>
+    /// Gets or sets the encoding height. <br />
+    /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
+    /// </summary>
+    public byte EncodingHeight { get; set; }
+
+    /// <summary>
+    /// Gets or sets the number of bits per pixel.<br/>
+    /// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
+    /// </summary>
+    public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
+
+    /// <summary>
+    /// Gets or sets the color table, if any. Derived from the root frame.<br/>
+    /// The underlying pixel format is represented by <see cref="Bgr24"/>.
+    /// </summary>
+    public ReadOnlyMemory<Color>? ColorTable { get; set; }
+
+    /// <inheritdoc/>
+    public static IcoMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        BmpBitsPerPixel bbpp = bpp switch
+        {
+            1 => BmpBitsPerPixel.Bit1,
+            2 => BmpBitsPerPixel.Bit2,
+            <= 4 => BmpBitsPerPixel.Bit4,
+            <= 8 => BmpBitsPerPixel.Bit8,
+            <= 16 => BmpBitsPerPixel.Bit16,
+            <= 24 => BmpBitsPerPixel.Bit24,
+            _ => BmpBitsPerPixel.Bit32
+        };
+
+        IconFrameCompression compression = IconFrameCompression.Bmp;
+        if (bbpp is BmpBitsPerPixel.Bit32)
+        {
+            compression = IconFrameCompression.Png;
+        }
+
+        return new IcoMetadata
+        {
+            BmpBitsPerPixel = bbpp,
+            Compression = compression,
+            ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BmpBitsPerPixel;
+        PixelComponentInfo info;
+        PixelColorType color;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+
+        if (this.Compression is IconFrameCompression.Png)
+        {
+            bpp = 32;
+            info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+            color = PixelColorType.RGB | PixelColorType.Alpha;
+            alpha = PixelAlphaRepresentation.Unassociated;
+        }
+        else
+        {
+            switch (this.BmpBitsPerPixel)
+            {
+                case BmpBitsPerPixel.Bit1:
+                    info = PixelComponentInfo.Create(1, bpp, 1);
+                    color = PixelColorType.Binary;
+                    break;
+                case BmpBitsPerPixel.Bit2:
+                    info = PixelComponentInfo.Create(1, bpp, 2);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit4:
+                    info = PixelComponentInfo.Create(1, bpp, 4);
+                    color = PixelColorType.Indexed;
+                    break;
+                case BmpBitsPerPixel.Bit8:
+                    info = PixelComponentInfo.Create(1, bpp, 8);
+                    color = PixelColorType.Indexed;
+                    break;
+
+                // Could be 555 with padding but 565 is more common in newer bitmaps and offers
+                // greater accuracy due to extra green precision.
+                case BmpBitsPerPixel.Bit16:
+                    info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit24:
+                    info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                    color = PixelColorType.RGB;
+                    break;
+                case BmpBitsPerPixel.Bit32 or _:
+                    info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                    color = PixelColorType.RGB | PixelColorType.Alpha;
+                    alpha = PixelAlphaRepresentation.Unassociated;
+                    break;
+            }
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
+
     /// <inheritdoc/>
-    public IcoMetadata DeepClone() => new();
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8
+                ? EncodingType.Lossy
+                : EncodingType.Lossless,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            ColorTable = this.ColorTable
+        };
 
     /// <inheritdoc/>
     IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public IcoMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Ico/MetadataExtensions.cs b/src/ImageSharp/Formats/Ico/MetadataExtensions.cs
deleted file mode 100644
index 497375f994..0000000000
--- a/src/ImageSharp/Formats/Ico/MetadataExtensions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-using SixLabors.ImageSharp.Formats.Ico;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the Ico format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="IcoMetadata"/>.</returns>
-    public static IcoMetadata GetIcoMetadata(this ImageMetadata source)
-        => source.GetFormatMetadata(IcoFormat.Instance);
-
-    /// <summary>
-    /// Gets the Ico format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="IcoFrameMetadata"/>.</returns>
-    public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source)
-        => source.GetFormatMetadata(IcoFormat.Instance);
-
-    /// <summary>
-    /// Gets the Ico format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">
-    /// 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.
-    /// </param>
-    /// <returns>
-    /// <see langword="true"/> if the Ico frame metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    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/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs
index 74fe7b9e50..caed2dd902 100644
--- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs
+++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs
@@ -6,24 +6,21 @@
 using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.IO;
 using SixLabors.ImageSharp.Metadata;
-using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Icon;
 
-internal abstract class IconDecoderCore : IImageDecoderInternals
+internal abstract class IconDecoderCore : ImageDecoderCore
 {
     private IconDir fileHeader;
     private IconDirEntry[]? entries;
 
     protected IconDecoderCore(DecoderOptions options)
-        => this.Options = options;
-
-    public DecoderOptions Options { get; }
-
-    public Size Dimensions { get; private set; }
+        : base(options)
+    {
+    }
 
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    /// <inheritdoc />
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         // Stream may not at 0.
         long basePosition = stream.Position;
@@ -61,7 +58,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
             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.
-            Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
+            Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(this.Options.Configuration, stream, cancellationToken);
             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
@@ -74,7 +71,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
         PngMetadata? pngMetadata = null;
         Image<TPixel> result = new(this.Options.Configuration, metadata, decodedEntries.Select(x =>
         {
-            BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
+            BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32;
             ReadOnlyMemory<Color>? colorTable = null;
             ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
             ImageFrame<TPixel> source = x.Image.Frames.RootFrameUnsafe;
@@ -106,7 +103,9 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
             }
 
             this.SetFrameMetadata(
+                metadata,
                 target.Metadata,
+                x.Index,
                 this.entries[x.Index],
                 x.Compression,
                 bitsPerPixel,
@@ -131,7 +130,8 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
         return result;
     }
 
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    /// <inheritdoc />
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         // Stream may not at 0.
         long basePosition = stream.Position;
@@ -146,7 +146,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         int bpp = 0;
         for (int i = 0; i < frames.Length; i++)
         {
-            BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
+            BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32;
             ReadOnlyMemory<Color>? colorTable = null;
             ref IconDirEntry entry = ref this.entries[i];
 
@@ -168,7 +168,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
             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);
+            ImageInfo frameInfo = this.GetDecoder(isPng).Identify(this.Options.Configuration, stream, cancellationToken);
 
             ImageFrameMetadata frameMetadata = new();
 
@@ -176,14 +176,14 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
             {
                 if (i == 0)
                 {
-                    pngMetadata = temp.Metadata.GetPngMetadata();
+                    pngMetadata = frameInfo.Metadata.GetPngMetadata();
                 }
 
-                frameMetadata.SetFormatMetadata(PngFormat.Instance, temp.FrameMetadataCollection[0].GetPngMetadata());
+                frameMetadata.SetFormatMetadata(PngFormat.Instance, frameInfo.FrameMetadataCollection[0].GetPngMetadata());
             }
             else
             {
-                BmpMetadata meta = temp.Metadata.GetBmpMetadata();
+                BmpMetadata meta = frameInfo.Metadata.GetBmpMetadata();
                 bitsPerPixel = meta.BitsPerPixel;
                 colorTable = meta.ColorTable;
 
@@ -198,7 +198,9 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
             frames[i] = frameMetadata;
 
             this.SetFrameMetadata(
+                metadata,
                 frames[i],
+                i,
                 this.entries[i],
                 isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp,
                 bitsPerPixel,
@@ -206,7 +208,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
 
             // 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));
+            this.Dimensions = new(Math.Max(this.Dimensions.Width, frameInfo.Size.Width), Math.Max(this.Dimensions.Height, frameInfo.Size.Height));
         }
 
         // Copy the format specific metadata to the image.
@@ -220,11 +222,13 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
             metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata);
         }
 
-        return new(new(bpp), this.Dimensions, metadata, frames);
+        return new(this.Dimensions, metadata, frames);
     }
 
     protected abstract void SetFrameMetadata(
-        ImageFrameMetadata metadata,
+        ImageMetadata imageMetadata,
+        ImageFrameMetadata frameMetadata,
+        int index,
         in IconDirEntry entry,
         IconFrameCompression compression,
         BmpBitsPerPixel bitsPerPixel,
@@ -275,7 +279,7 @@ protected void ReadHeader(Stream stream)
         this.Dimensions = new(width, height);
     }
 
-    private IImageDecoderInternals GetDecoder(bool isPng)
+    private ImageDecoderCore GetDecoder(bool isPng)
     {
         if (isPng)
         {
diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
index 2433396612..4b973d5115 100644
--- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
+++ b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs
@@ -11,7 +11,7 @@
 
 namespace SixLabors.ImageSharp.Formats.Icon;
 
-internal abstract class IconEncoderCore : IImageEncoderInternals
+internal abstract class IconEncoderCore
 {
     private readonly QuantizingImageEncoder encoder;
     private readonly IconFileType iconFileType;
@@ -43,6 +43,8 @@ public void Encode<TPixel>(
 
         for (int i = 0; i < image.Frames.Count; i++)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             // 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<TPixel> frame = image.Frames[i];
@@ -167,7 +169,7 @@ public EncodingFrameMetadata(
         {
             this.Compression = compression;
             this.BmpBitsPerPixel = compression == IconFrameCompression.Png
-                ? BmpBitsPerPixel.Pixel32
+                ? BmpBitsPerPixel.Bit32
                 : bmpBitsPerPixel;
             this.ColorTable = colorTable;
             this.iconDirEntry = iconDirEntry;
diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs
index ebb45d7013..e58cd6a6de 100644
--- a/src/ImageSharp/Formats/ImageDecoder.cs
+++ b/src/ImageSharp/Formats/ImageDecoder.cs
@@ -2,6 +2,7 @@
 // Licensed under the Six Labors Split License.
 
 using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 
@@ -189,7 +190,7 @@ internal static T WithSeekableStream<T>(
             throw new NotSupportedException("Cannot read from the stream.");
         }
 
-        T PeformActionAndResetPosition(Stream s, long position)
+        T PerformActionAndResetPosition(Stream s, long position)
         {
             T result = action(s);
 
@@ -206,7 +207,7 @@ T PeformActionAndResetPosition(Stream s, long position)
 
         if (stream.CanSeek)
         {
-            return PeformActionAndResetPosition(stream, stream.Position);
+            return PerformActionAndResetPosition(stream, stream.Position);
         }
 
         Configuration configuration = options.Configuration;
@@ -231,7 +232,7 @@ internal static Task<T> WithSeekableMemoryStreamAsync<T>(
             throw new NotSupportedException("Cannot read from the stream.");
         }
 
-        Task<T> PeformActionAndResetPosition(Stream s, long position, CancellationToken ct)
+        Task<T> PerformActionAndResetPosition(Stream s, long position, CancellationToken ct)
         {
             try
             {
@@ -263,15 +264,15 @@ Task<T> PeformActionAndResetPosition(Stream s, long position, CancellationToken
         // code below to copy the stream to an in-memory buffer before invoking the action.
         if (stream is MemoryStream ms)
         {
-            return PeformActionAndResetPosition(ms, ms.Position, cancellationToken);
+            return PerformActionAndResetPosition(ms, ms.Position, cancellationToken);
         }
 
         if (stream is ChunkedMemoryStream cms)
         {
-            return PeformActionAndResetPosition(cms, cms.Position, cancellationToken);
+            return PerformActionAndResetPosition(cms, cms.Position, cancellationToken);
         }
 
-        return CopyToMemoryStreamAndActionAsync(options, stream, PeformActionAndResetPosition, cancellationToken);
+        return CopyToMemoryStreamAndActionAsync(options, stream, PerformActionAndResetPosition, cancellationToken);
     }
 
     private static async Task<T> CopyToMemoryStreamAndActionAsync<T>(
@@ -282,7 +283,7 @@ private static async Task<T> CopyToMemoryStreamAndActionAsync<T>(
     {
         long position = stream.CanSeek ? stream.Position : 0;
         Configuration configuration = options.Configuration;
-        using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
+        await using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
         await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
         memoryStream.Position = 0;
         return await action(memoryStream, position, cancellationToken).ConfigureAwait(false);
@@ -293,6 +294,11 @@ internal void SetDecoderFormat(Configuration configuration, Image image)
         if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
         {
             image.Metadata.DecodedImageFormat = format;
+
+            foreach (ImageFrame frame in image.Frames)
+            {
+                frame.Metadata.DecodedImageFormat = format;
+            }
         }
     }
 
@@ -301,6 +307,12 @@ internal void SetDecoderFormat(Configuration configuration, ImageInfo info)
         if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
         {
             info.Metadata.DecodedImageFormat = format;
+            info.PixelType = info.Metadata.GetDecodedPixelTypeInfo();
+
+            foreach (ImageFrameMetadata frame in info.FrameMetadataCollection)
+            {
+                frame.DecodedImageFormat = format;
+            }
         }
     }
 }
diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs
new file mode 100644
index 0000000000..adf0107da0
--- /dev/null
+++ b/src/ImageSharp/Formats/ImageDecoderCore.cs
@@ -0,0 +1,127 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats;
+
+/// <summary>
+/// The base class for all stateful image decoders.
+/// </summary>
+internal abstract class ImageDecoderCore
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="ImageDecoderCore"/> class.
+    /// </summary>
+    /// <param name="options">The general decoder options.</param>
+    protected ImageDecoderCore(DecoderOptions options)
+        => this.Options = options;
+
+    /// <summary>
+    /// Gets the general decoder options.
+    /// </summary>
+    public DecoderOptions Options { get; }
+
+    /// <summary>
+    /// Gets or sets the dimensions of the image being decoded.
+    /// </summary>
+    public Size Dimensions { get; protected internal set; }
+
+    /// <summary>
+    /// Reads the raw image information from the specified stream.
+    /// </summary>
+    /// <param name="configuration">The shared configuration.</param>
+    /// <param name="stream">The <see cref="Stream" /> containing image data.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>The <see cref="ImageInfo" />.</returns>
+    /// <exception cref="InvalidImageContentException">Thrown if the encoded image contains errors.</exception>
+    public ImageInfo Identify(
+        Configuration configuration,
+        Stream stream,
+        CancellationToken cancellationToken)
+    {
+        using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken);
+
+        try
+        {
+            return this.Identify(bufferedReadStream, cancellationToken);
+        }
+        catch (InvalidMemoryOperationException ex)
+        {
+            throw new InvalidImageContentException(this.Dimensions, ex);
+        }
+        catch (Exception)
+        {
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
+    /// </summary>
+    /// <typeparam name="TPixel">The pixel format.</typeparam>
+    /// <param name="configuration">The shared configuration.</param>
+    /// <param name="stream">The <see cref="Stream" /> containing image data.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>The <see cref="Image{TPixel}" />.</returns>
+    /// <exception cref="InvalidImageContentException">Thrown if the encoded image contains errors.</exception>
+    public Image<TPixel> Decode<TPixel>(
+        Configuration configuration,
+        Stream stream,
+        CancellationToken cancellationToken)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        // Test may pass a BufferedReadStream in order to monitor EOF hits, if so, use the existing instance.
+        BufferedReadStream bufferedReadStream =
+            stream as BufferedReadStream ?? new BufferedReadStream(configuration, stream, cancellationToken);
+
+        try
+        {
+            return this.Decode<TPixel>(bufferedReadStream, cancellationToken);
+        }
+        catch (InvalidMemoryOperationException ex)
+        {
+            throw new InvalidImageContentException(this.Dimensions, ex);
+        }
+        catch (Exception)
+        {
+            throw;
+        }
+        finally
+        {
+            if (bufferedReadStream != stream)
+            {
+                bufferedReadStream.Dispose();
+            }
+        }
+    }
+
+    /// <summary>
+    /// Reads the raw image information from the specified stream.
+    /// </summary>
+    /// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>The <see cref="ImageInfo"/>.</returns>
+    /// <remarks>
+    /// Cancellable synchronous method. In case of cancellation,
+    /// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
+    /// </remarks>
+    protected abstract ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// Decodes the image from the specified stream.
+    /// </summary>
+    /// <typeparam name="TPixel">The pixel format.</typeparam>
+    /// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
+    /// <returns>The decoded image.</returns>
+    /// <remarks>
+    /// Cancellable synchronous method. In case of cancellation, an <see cref="OperationCanceledException"/> shall
+    /// be thrown which will be handled on the call site.
+    /// </remarks>
+    protected abstract Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
+        where TPixel : unmanaged, IPixel<TPixel>;
+}
diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs
deleted file mode 100644
index a1abd7dc30..0000000000
--- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats;
-
-/// <summary>
-/// Utility methods for <see cref="IImageDecoderInternals"/>.
-/// </summary>
-internal static class ImageDecoderUtilities
-{
-    internal static ImageInfo Identify(
-        this IImageDecoderInternals decoder,
-        Configuration configuration,
-        Stream stream,
-        CancellationToken cancellationToken)
-    {
-        using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken);
-
-        try
-        {
-            return decoder.Identify(bufferedReadStream, cancellationToken);
-        }
-        catch (InvalidMemoryOperationException ex)
-        {
-            throw new InvalidImageContentException(decoder.Dimensions, ex);
-        }
-        catch (Exception)
-        {
-            throw;
-        }
-    }
-
-    internal static Image<TPixel> Decode<TPixel>(
-        this IImageDecoderInternals decoder,
-        Configuration configuration,
-        Stream stream,
-        CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
-        => decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
-
-    internal static Image<TPixel> Decode<TPixel>(
-        this IImageDecoderInternals decoder,
-        Configuration configuration,
-        Stream stream,
-        Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
-        CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        // Test may pass a BufferedReadStream in order to monitor EOF hits, if so, use the existing instance.
-        BufferedReadStream bufferedReadStream = stream as BufferedReadStream ?? new BufferedReadStream(configuration, stream, cancellationToken);
-
-        try
-        {
-            return decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
-        }
-        catch (InvalidMemoryOperationException ex)
-        {
-            throw largeImageExceptionFactory(ex, decoder.Dimensions);
-        }
-        catch (Exception)
-        {
-            throw;
-        }
-        finally
-        {
-            if (bufferedReadStream != stream)
-            {
-                bufferedReadStream.Dispose();
-            }
-        }
-    }
-
-    private static InvalidImageContentException DefaultLargeImageExceptionFactory(
-        InvalidMemoryOperationException memoryOperationException,
-        Size dimensions) =>
-        new(dimensions, memoryOperationException);
-}
diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs
index 4acd29e81c..fdaa5c35dc 100644
--- a/src/ImageSharp/Formats/ImageEncoder.cs
+++ b/src/ImageSharp/Formats/ImageEncoder.cs
@@ -1,7 +1,6 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
-using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.IO;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -42,6 +41,8 @@ protected abstract void Encode<TPixel>(Image<TPixel> image, Stream stream, Cance
     private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
+        image.SynchronizeMetadata();
+
         Configuration configuration = image.Configuration;
         if (stream.CanSeek)
         {
@@ -59,6 +60,8 @@ private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream
     private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
+        image.SynchronizeMetadata();
+
         Configuration configuration = image.Configuration;
         if (stream.CanSeek)
         {
@@ -66,7 +69,7 @@ private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, St
         }
         else
         {
-            using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
+            await using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
             await DoEncodeAsync(ms);
             ms.Position = 0;
             await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
index 5a59837e58..0b7b21f90b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
 
 internal class JpegFrameConfig
 {
-    public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables)
+    public JpegFrameConfig(JpegColorSpace colorType, JpegColorType encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables)
     {
         this.ColorType = colorType;
         this.EncodingColor = encodingColor;
@@ -25,7 +25,7 @@ public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor
 
     public JpegColorSpace ColorType { get; }
 
-    public JpegEncodingColor EncodingColor { get; }
+    public JpegColorType EncodingColor { get; }
 
     public JpegComponentConfig[] Components { get; }
 
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
index d74494f9e5..ac527ff312 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
@@ -139,13 +139,13 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig)
     /// <param name="frame">Frame to encode.</param>
     /// <param name="converter">Converter from color to spectral.</param>
     /// <param name="cancellationToken">The token to request cancellation.</param>
-    public void EncodeScanBaselineInterleaved<TPixel>(JpegEncodingColor color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
+    public void EncodeScanBaselineInterleaved<TPixel>(JpegColorType color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         switch (color)
         {
-            case JpegEncodingColor.YCbCrRatio444:
-            case JpegEncodingColor.Rgb:
+            case JpegColorType.YCbCrRatio444:
+            case JpegColorType.Rgb:
                 this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken);
                 break;
             default:
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
index faba3d5afc..f5666f7799 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
 /// <summary>
 ///     The Huffman encoding specifications.
 /// </summary>
-public readonly struct HuffmanSpec
+internal readonly struct HuffmanSpec
 {
     /// <summary>
     /// Huffman talbe specification for luminance DC.
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs
similarity index 98%
rename from src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs
rename to src/ImageSharp/Formats/Jpeg/JpegColorType.cs
index 779ccf61e1..a8429273fe 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
 /// <summary>
 /// Provides enumeration of available JPEG color types.
 /// </summary>
-public enum JpegEncodingColor : byte
+public enum JpegColorType : byte
 {
     /// <summary>
     /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 906505b76a..2320fe1791 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
 /// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
 /// with additional fixes for both performance and common encoding errors.
 /// </summary>
-internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
+internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
 {
     /// <summary>
     /// Whether the image has an EXIF marker.
@@ -117,8 +117,8 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public JpegDecoderCore(JpegDecoderOptions options)
+        : base(options.GeneralOptions)
     {
-        this.Options = options.GeneralOptions;
         this.resizeMode = options.ResizeMode;
         this.configuration = options.GeneralOptions.Configuration;
         this.skipMetadata = options.GeneralOptions.SkipMetadata;
@@ -130,12 +130,6 @@ public JpegDecoderCore(JpegDecoderOptions options)
     // Refers to assembly's static data segment, no allocation occurs.
     private static ReadOnlySpan<byte> SupportedPrecisions => new byte[] { 8, 12 };
 
-    /// <inheritdoc />
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc/>
-    public Size Dimensions => this.Frame.PixelSize;
-
     /// <summary>
     /// Gets the frame
     /// </summary>
@@ -198,8 +192,7 @@ public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream)
     }
 
     /// <inheritdoc/>
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         using SpectralConverter<TPixel> spectralConverter = new(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize);
         this.ParseStream(stream, spectralConverter, cancellationToken);
@@ -216,7 +209,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc/>
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ParseStream(stream, spectralConverter: null, cancellationToken);
         this.InitExifProfile();
@@ -226,7 +219,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         this.InitDerivedMetadataProperties();
 
         Size pixelSize = this.Frame.PixelSize;
-        return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), new(pixelSize.Width, pixelSize.Height), this.Metadata);
+        return new ImageInfo(new(pixelSize.Width, pixelSize.Height), this.Metadata);
     }
 
     /// <summary>
@@ -603,58 +596,58 @@ internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount)
     /// Returns the jpeg color type based on the colorspace and subsampling used.
     /// </summary>
     /// <returns>Jpeg color type.</returns>
-    private JpegEncodingColor DeduceJpegColorType()
+    private JpegColorType DeduceJpegColorType()
     {
         switch (this.ColorSpace)
         {
             case JpegColorSpace.Grayscale:
-                return JpegEncodingColor.Luminance;
+                return JpegColorType.Luminance;
 
             case JpegColorSpace.RGB:
-                return JpegEncodingColor.Rgb;
+                return JpegColorType.Rgb;
 
             case JpegColorSpace.YCbCr:
                 if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
                     this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
                     this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
                 {
-                    return JpegEncodingColor.YCbCrRatio444;
+                    return JpegColorType.YCbCrRatio444;
                 }
                 else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
                     this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
                     this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
                 {
-                    return JpegEncodingColor.YCbCrRatio422;
+                    return JpegColorType.YCbCrRatio422;
                 }
                 else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
                     this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
                     this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
                 {
-                    return JpegEncodingColor.YCbCrRatio420;
+                    return JpegColorType.YCbCrRatio420;
                 }
                 else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
                          this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
                          this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
                 {
-                    return JpegEncodingColor.YCbCrRatio411;
+                    return JpegColorType.YCbCrRatio411;
                 }
                 else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
                          this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
                          this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
                 {
-                    return JpegEncodingColor.YCbCrRatio410;
+                    return JpegColorType.YCbCrRatio410;
                 }
                 else
                 {
-                    return JpegEncodingColor.YCbCrRatio420;
+                    return JpegColorType.YCbCrRatio420;
                 }
 
             case JpegColorSpace.Cmyk:
-                return JpegEncodingColor.Cmyk;
+                return JpegColorType.Cmyk;
             case JpegColorSpace.Ycck:
-                return JpegEncodingColor.Ycck;
+                return JpegColorType.Ycck;
             default:
-                return JpegEncodingColor.YCbCrRatio420;
+                return JpegColorType.YCbCrRatio420;
         }
     }
 
@@ -1237,6 +1230,7 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
         }
 
         this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
+        this.Dimensions = new(frameWidth, frameHeight);
         this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive;
 
         remaining -= length;
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 5ff4b1694d..0daaae112c 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
@@ -45,7 +45,7 @@ public int? Quality
     /// <summary>
     /// Gets the jpeg color for encoding.
     /// </summary>
-    public JpegEncodingColor? ColorType { get; init; }
+    public JpegColorType? ColorType { get; init; }
 
     /// <inheritdoc/>
     protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs
index 4aed795825..71f852a092 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs
@@ -40,7 +40,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YCbCr 4:4:4
             new JpegFrameConfig(
                 JpegColorSpace.YCbCr,
-                JpegEncodingColor.YCbCrRatio444,
+                JpegColorType.YCbCrRatio444,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -53,7 +53,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YCbCr 4:2:2
             new JpegFrameConfig(
                 JpegColorSpace.YCbCr,
-                JpegEncodingColor.YCbCrRatio422,
+                JpegColorType.YCbCrRatio422,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -66,7 +66,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YCbCr 4:2:0
             new JpegFrameConfig(
                 JpegColorSpace.YCbCr,
-                JpegEncodingColor.YCbCrRatio420,
+                JpegColorType.YCbCrRatio420,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -79,7 +79,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YCbCr 4:1:1
             new JpegFrameConfig(
                 JpegColorSpace.YCbCr,
-                JpegEncodingColor.YCbCrRatio411,
+                JpegColorType.YCbCrRatio411,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -92,7 +92,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YCbCr 4:1:0
             new JpegFrameConfig(
                 JpegColorSpace.YCbCr,
-                JpegEncodingColor.YCbCrRatio410,
+                JpegColorType.YCbCrRatio410,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -105,7 +105,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // Luminance
             new JpegFrameConfig(
                 JpegColorSpace.Grayscale,
-                JpegEncodingColor.Luminance,
+                JpegColorType.Luminance,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -123,7 +123,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // Rgb
             new JpegFrameConfig(
                 JpegColorSpace.RGB,
-                JpegEncodingColor.Rgb,
+                JpegColorType.Rgb,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -146,7 +146,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // Cmyk
             new JpegFrameConfig(
                 JpegColorSpace.Cmyk,
-                JpegEncodingColor.Cmyk,
+                JpegColorType.Cmyk,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@@ -170,7 +170,7 @@ private static JpegFrameConfig[] CreateFrameConfigs()
             // YccK
             new JpegFrameConfig(
                 JpegColorSpace.Ycck,
-                JpegEncodingColor.Ycck,
+                JpegColorType.Ycck,
                 new JpegComponentConfig[]
                 {
                     new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 243bbe051d..a6ff623660 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
 /// <summary>
 /// Image encoder for writing an image to a stream as a jpeg.
 /// </summary>
-internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
+internal sealed unsafe partial class JpegEncoderCore
 {
     /// <summary>
     /// The available encodable frame configs.
@@ -72,7 +72,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
         JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata);
 
-        bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved ?? true;
+        bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved;
         using JpegFrame frame = new(image, frameConfig, interleaved);
 
         // Write the Start Of Image marker.
@@ -539,17 +539,11 @@ private void WriteIccProfile(IccProfile iccProfile, Span<byte> buffer)
     /// <param name="buffer">Temporary buffer.</param>
     private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer)
     {
-        if (metadata is null)
-        {
-            return;
-        }
-
         // For compatibility, place the profiles in the following order:
         // - APP1 EXIF
         // - APP1 XMP
         // - APP2 ICC
         // - APP13 IPTC
-        metadata.SyncProfiles();
         this.WriteExifProfile(metadata.ExifProfile, buffer);
         this.WriteXmpProfile(metadata.XmpProfile, buffer);
         this.WriteIccProfile(metadata.IccProfile, buffer);
@@ -780,7 +774,7 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs
 
     private JpegFrameConfig GetFrameConfig(JpegMetadata metadata)
     {
-        JpegEncodingColor color = this.encoder.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
+        JpegColorType color = this.encoder.ColorType ?? metadata.ColorType;
         JpegFrameConfig frameConfig = Array.Find(
             FrameConfigs,
             cfg => cfg.EncodingColor == color);
diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
index fe1324a862..f2f34ec496 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
@@ -2,20 +2,20 @@
 // Licensed under the Six Labors Split License.
 
 using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Jpeg;
 
 /// <summary>
 /// Provides Jpeg specific metadata information for the image.
 /// </summary>
-public class JpegMetadata : IDeepCloneable
+public class JpegMetadata : IFormatMetadata<JpegMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="JpegMetadata"/> class.
     /// </summary>
     public JpegMetadata()
     {
-        this.Comments = new List<JpegComData>();
     }
 
     /// <summary>
@@ -36,7 +36,7 @@ private JpegMetadata(JpegMetadata other)
     /// </summary>
     /// <remarks>
     /// This value might not be accurate if it was calculated during jpeg decoding
-    /// with non-complient ITU quantization tables.
+    /// with non-compliant ITU quantization tables.
     /// </remarks>
     internal int? LuminanceQuality { get; set; }
 
@@ -45,16 +45,17 @@ private JpegMetadata(JpegMetadata other)
     /// </summary>
     /// <remarks>
     /// This value might not be accurate if it was calculated during jpeg decoding
-    /// with non-complient ITU quantization tables.
+    /// with non-compliant ITU quantization tables.
     /// </remarks>
     internal int? ChrominanceQuality { get; set; }
 
     /// <summary>
-    /// Gets the encoded quality.
+    /// Gets or sets the encoded quality.
     /// </summary>
     /// <remarks>
     /// Note that jpeg image can have different quality for luminance and chrominance components.
     /// This property returns maximum value of luma/chroma qualities if both are present.
+    /// Setting the quality will update both values.
     /// </remarks>
     public int Quality
     {
@@ -69,45 +70,138 @@ public int Quality
 
                 return this.LuminanceQuality.Value;
             }
-            else
-            {
-                if (this.ChrominanceQuality.HasValue)
-                {
-                    return this.ChrominanceQuality.Value;
-                }
 
-                return Quantization.DefaultQualityFactor;
-            }
+            return this.ChrominanceQuality ?? Quantization.DefaultQualityFactor;
+        }
+
+        set
+        {
+            this.LuminanceQuality = value;
+            this.ChrominanceQuality = value;
         }
     }
 
     /// <summary>
-    /// Gets the color type.
+    /// Gets or sets the color type.
     /// </summary>
-    public JpegEncodingColor? ColorType { get; internal set; }
+    public JpegColorType ColorType { get; set; } = JpegColorType.YCbCrRatio420;
 
     /// <summary>
-    /// Gets the component encoding mode.
+    /// Gets or sets a value indicating whether the component encoding mode should be interleaved.
     /// </summary>
     /// <remarks>
     /// Interleaved encoding mode encodes all color components in a single scan.
     /// Non-interleaved encoding mode encodes each color component in a separate scan.
     /// </remarks>
-    public bool? Interleaved { get; internal set; }
+    public bool Interleaved { get; set; } = true;
 
     /// <summary>
-    /// Gets the scan encoding mode.
+    /// Gets or sets a value indicating whether the scan encoding mode is progressive.
     /// </summary>
     /// <remarks>
     /// Progressive jpeg images encode component data across multiple scans.
     /// </remarks>
-    public bool? Progressive { get; internal set; }
+    public bool Progressive { get; set; }
 
     /// <summary>
-    /// Gets the comments.
+    /// Gets or sets collection of comments.
     /// </summary>
-    public IList<JpegComData> Comments { get; }
+    public IList<JpegComData> Comments { get; set; } = [];
+
+    /// <inheritdoc/>
+    public static JpegMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        JpegColorType color;
+        PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
+        switch (colorType)
+        {
+            case PixelColorType.Luminance:
+                color = JpegColorType.Luminance;
+                break;
+            case PixelColorType.CMYK:
+                color = JpegColorType.Cmyk;
+                break;
+            case PixelColorType.YCCK:
+                color = JpegColorType.Ycck;
+                break;
+            default:
+                if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR))
+                {
+                    color = JpegColorType.Rgb;
+                }
+                else
+                {
+                    color = metadata.Quality <= Quantization.DefaultQualityFactor
+                        ? JpegColorType.YCbCrRatio420
+                        : JpegColorType.YCbCrRatio444;
+                }
+
+                break;
+        }
+
+        return new JpegMetadata
+        {
+            ColorType = color,
+            ChrominanceQuality = metadata.Quality,
+            LuminanceQuality = metadata.Quality,
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp;
+        PixelColorType colorType;
+        PixelComponentInfo info;
+        switch (this.ColorType)
+        {
+            case JpegColorType.Luminance:
+                bpp = 8;
+                colorType = PixelColorType.Luminance;
+                info = PixelComponentInfo.Create(1, bpp, 8);
+                break;
+            case JpegColorType.Cmyk:
+                bpp = 32;
+                colorType = PixelColorType.CMYK;
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                break;
+            case JpegColorType.Ycck:
+                bpp = 32;
+                colorType = PixelColorType.YCCK;
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                break;
+            case JpegColorType.Rgb:
+                bpp = 24;
+                colorType = PixelColorType.RGB;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+            default:
+                bpp = 24;
+                colorType = PixelColorType.YCbCr;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = PixelAlphaRepresentation.None,
+            ColorType = colorType,
+            ComponentInfo = info,
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            EncodingType = EncodingType.Lossy,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            Quality = this.Quality,
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new JpegMetadata(this);
+    public JpegMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs
deleted file mode 100644
index 7330e74b79..0000000000
--- a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Text;
-using SixLabors.ImageSharp.Formats.Jpeg;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the jpeg format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="JpegMetadata"/>.</returns>
-    public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
deleted file mode 100644
index 7039ef2620..0000000000
--- a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Pbm;
-
-/// <summary>
-/// Configuration options for use during PBM encoding.
-/// </summary>
-internal interface IPbmEncoderOptions
-{
-    /// <summary>
-    /// Gets the encoding of the pixels.
-    /// </summary>
-    PbmEncoding? Encoding { get; }
-
-    /// <summary>
-    /// Gets the Color type of the resulting image.
-    /// </summary>
-    PbmColorType? ColorType { get; }
-
-    /// <summary>
-    /// Gets the Data Type of the pixel components.
-    /// </summary>
-    PbmComponentType? ComponentType { get; }
-}
diff --git a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
deleted file mode 100644
index 6d44e91a50..0000000000
--- a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Formats.Pbm;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the pbm format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="PbmMetadata"/>.</returns>
-    public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
index 3fe339865b..9d3dd3ea4c 100644
--- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
 /// <summary>
 /// Performs the PBM decoding operation.
 /// </summary>
-internal sealed class PbmDecoderCore : IImageDecoderInternals
+internal sealed class PbmDecoderCore : ImageDecoderCore
 {
     private int maxPixelValue;
 
@@ -52,24 +52,17 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public PbmDecoderCore(DecoderOptions options)
+        : base(options)
     {
-        this.Options = options;
         this.configuration = options.Configuration;
     }
 
     /// <inheritdoc/>
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc/>
-    public Size Dimensions => this.pixelSize;
-
-    /// <inheritdoc/>
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ProcessHeader(stream);
 
-        var image = new Image<TPixel>(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata);
+        Image<TPixel> image = new(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata);
 
         Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
 
@@ -83,13 +76,12 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc/>
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ProcessHeader(stream);
-
-        // BlackAndWhite pixels are encoded into a byte.
-        int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
-        return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
+        return new ImageInfo(
+            new(this.pixelSize.Width, this.pixelSize.Height),
+            this.metadata);
     }
 
     /// <summary>
@@ -97,6 +89,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
     /// </summary>
     /// <param name="stream">The input stream.</param>
     /// <exception cref="InvalidImageContentException">An EOF marker has been read before the image has been decoded.</exception>
+    [MemberNotNull(nameof(metadata))]
     private void ProcessHeader(BufferedReadStream stream)
     {
         Span<byte> buffer = stackalloc byte[2];
@@ -179,6 +172,7 @@ private void ProcessHeader(BufferedReadStream stream)
         }
 
         this.pixelSize = new Size(width, height);
+        this.Dimensions = this.pixelSize;
         this.metadata = new ImageMetadata();
         PbmMetadata meta = this.metadata.GetPbmMetadata();
         meta.Encoding = this.encoding;
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
index 8258c91655..f7a9d79366 100644
--- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs
@@ -1,8 +1,6 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
-using SixLabors.ImageSharp.Advanced;
-
 namespace SixLabors.ImageSharp.Formats.Pbm;
 
 /// <summary>
diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
index b6e31a3c28..843f1880e6 100644
--- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
@@ -2,7 +2,6 @@
 // Licensed under the Six Labors Split License.
 
 using System.Buffers.Text;
-using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Pbm;
@@ -10,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
 /// <summary>
 /// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
 /// </summary>
-internal sealed class PbmEncoderCore : IImageEncoderInternals
+internal sealed class PbmEncoderCore
 {
     private const byte NewLine = (byte)'\n';
     private const byte Space = (byte)' ';
diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
index 938e11db16..fec4beda7c 100644
--- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
@@ -1,12 +1,14 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Pbm;
 
 /// <summary>
 /// Provides PBM specific metadata information for the image.
 /// </summary>
-public class PbmMetadata : IDeepCloneable
+public class PbmMetadata : IFormatMetadata<PbmMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="PbmMetadata"/> class.
@@ -41,5 +43,95 @@ private PbmMetadata(PbmMetadata other)
     public PbmComponentType ComponentType { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new PbmMetadata(this);
+    public static PbmMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        PbmColorType color;
+        PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
+
+        switch (colorType)
+        {
+            case PixelColorType.Binary:
+                color = PbmColorType.BlackAndWhite;
+                break;
+            case PixelColorType.Luminance:
+                color = PbmColorType.Grayscale;
+                break;
+            default:
+                if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR))
+                {
+                    color = PbmColorType.Rgb;
+                }
+                else
+                {
+                    color = PbmColorType.Grayscale;
+                }
+
+                break;
+        }
+
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        PbmComponentType componentType = bpp switch
+        {
+            1 => PbmComponentType.Bit,
+            <= 8 => PbmComponentType.Byte,
+            _ => PbmComponentType.Short
+        };
+
+        return new PbmMetadata
+        {
+            ColorType = color,
+            ComponentType = componentType
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp;
+        PixelColorType colorType;
+        PixelComponentInfo info;
+        switch (this.ColorType)
+        {
+            case PbmColorType.BlackAndWhite:
+                bpp = 1;
+                colorType = PixelColorType.Binary;
+                info = PixelComponentInfo.Create(1, bpp, 1);
+                break;
+            case PbmColorType.Rgb:
+                bpp = this.ComponentType == PbmComponentType.Short ? 48 : 24;
+                colorType = PixelColorType.RGB;
+                info = this.ComponentType == PbmComponentType.Short
+                    ? PixelComponentInfo.Create(3, bpp, 16, 16, 16)
+                    : PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+            case PbmColorType.Grayscale:
+            default:
+                bpp = this.ComponentType == PbmComponentType.Short ? 16 : 8;
+                colorType = PixelColorType.Luminance;
+                info = this.ComponentType == PbmComponentType.Short
+                    ? PixelComponentInfo.Create(1, bpp, bpp)
+                    : PixelComponentInfo.Create(1, bpp, bpp);
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = PixelAlphaRepresentation.None,
+            ColorType = colorType,
+            ComponentInfo = info,
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public PbmMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
index fb2ca473c2..91f79c8154 100644
--- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
@@ -22,8 +22,8 @@ public FrameControl(
         uint yOffset,
         ushort delayNumerator,
         ushort delayDenominator,
-        PngDisposalMethod disposeOperation,
-        PngBlendMethod blendOperation)
+        FrameDisposalMode disposalMode,
+        FrameBlendMode blendMode)
     {
         this.SequenceNumber = sequenceNumber;
         this.Width = width;
@@ -32,8 +32,8 @@ public FrameControl(
         this.YOffset = yOffset;
         this.DelayNumerator = delayNumerator;
         this.DelayDenominator = delayDenominator;
-        this.DisposeOperation = disposeOperation;
-        this.BlendOperation = blendOperation;
+        this.DisposalMode = disposalMode;
+        this.BlendMode = blendMode;
     }
 
     /// <summary>
@@ -84,12 +84,12 @@ public FrameControl(
     /// <summary>
     /// Gets the type of frame area disposal to be done after rendering this frame
     /// </summary>
-    public PngDisposalMethod DisposeOperation { get; }
+    public FrameDisposalMode DisposalMode { get; }
 
     /// <summary>
     /// Gets the type of frame area rendering for this frame
     /// </summary>
-    public PngBlendMethod BlendOperation { get; }
+    public FrameBlendMode BlendMode { get; }
 
     public Rectangle Bounds => new((int)this.XOffset, (int)this.YOffset, (int)this.Width, (int)this.Height);
 
@@ -137,8 +137,8 @@ public void WriteTo(Span<byte> buffer)
         BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator);
         BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator);
 
-        buffer[24] = (byte)this.DisposeOperation;
-        buffer[25] = (byte)this.BlendOperation;
+        buffer[24] = (byte)(this.DisposalMode - 1);
+        buffer[25] = (byte)this.BlendMode;
     }
 
     /// <summary>
@@ -155,6 +155,6 @@ public static FrameControl Parse(ReadOnlySpan<byte> data)
             yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]),
             delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]),
             delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]),
-            disposeOperation: (PngDisposalMethod)data[24],
-            blendOperation: (PngBlendMethod)data[25]);
+            disposalMode: (FrameDisposalMode)(data[24] + 1),
+            blendMode: (FrameBlendMode)data[25]);
 }
diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs
deleted file mode 100644
index b6313bffe0..0000000000
--- a/src/ImageSharp/Formats/Png/MetadataExtensions.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the png format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="PngMetadata"/>.</returns>
-    public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
-
-    /// <summary>
-    /// Gets the png format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">The metadata.</param>
-    /// <returns>
-    /// <see langword="true"/> if the png metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetPngMetadata(this ImageMetadata source, [NotNullWhen(true)] out PngMetadata? metadata)
-        => source.TryGetFormatMetadata(PngFormat.Instance, out metadata);
-
-    /// <summary>
-    /// Gets the png format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <returns>The <see cref="PngFrameMetadata"/>.</returns>
-    public static PngFrameMetadata GetPngMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
-
-    /// <summary>
-    /// Gets the png format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">The metadata.</param>
-    /// <returns>
-    /// <see langword="true"/> if the png frame metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetPngMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata)
-        => source.TryGetFormatMetadata(PngFormat.Instance, out metadata);
-
-    internal static AnimatedImageMetadata ToAnimatedImageMetadata(this PngMetadata source)
-        => new()
-        {
-            ColorTable = source.ColorTable,
-            ColorTableMode = FrameColorTableMode.Global,
-            RepeatCount = (ushort)Numerics.Clamp(source.RepeatCount, 0, ushort.MaxValue),
-        };
-
-    internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this PngFrameMetadata source)
-    {
-        double delay = source.FrameDelay.ToDouble();
-        if (double.IsNaN(delay))
-        {
-            delay = 0;
-        }
-
-        return new()
-        {
-            ColorTableMode = FrameColorTableMode.Global,
-            Duration = TimeSpan.FromMilliseconds(delay * 1000),
-            DisposalMode = GetMode(source.DisposalMethod),
-            BlendMode = source.BlendMethod == PngBlendMethod.Source ? FrameBlendMode.Source : FrameBlendMode.Over,
-        };
-    }
-
-    private static FrameDisposalMode GetMode(PngDisposalMethod method) => method switch
-    {
-        PngDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose,
-        PngDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
-        PngDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
-        _ => FrameDisposalMode.Unspecified,
-    };
-}
diff --git a/src/ImageSharp/Formats/Png/PngBlendMethod.cs b/src/ImageSharp/Formats/Png/PngBlendMethod.cs
deleted file mode 100644
index f71dce8325..0000000000
--- a/src/ImageSharp/Formats/Png/PngBlendMethod.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Png;
-
-/// <summary>
-/// Specifies whether the frame is to be alpha blended into the current output buffer content,
-/// or whether it should completely replace its region in the output buffer.
-/// </summary>
-public enum PngBlendMethod
-{
-    /// <summary>
-    /// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
-    /// </summary>
-    Source,
-
-    /// <summary>
-    /// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as
-    /// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2].
-    /// </summary>
-    Over
-}
diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index 4a7ba3f961..cfea0e6020 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -53,8 +53,8 @@ protected override Image Decode(PngDecoderOptions options, Stream stream, Cancel
         stream.Position = 0;
 
         PngMetadata meta = info.Metadata.GetPngMetadata();
-        PngColorType color = meta.ColorType.GetValueOrDefault();
-        PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
+        PngColorType color = meta.ColorType;
+        PngBitDepth bits = meta.BitDepth;
 
         switch (color)
         {
@@ -101,5 +101,5 @@ protected override Image Decode(PngDecoderOptions options, Stream stream, Cancel
     }
 
     /// <inheritdoc/>
-    protected override PngDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new PngDecoderOptions() { GeneralOptions = options };
+    protected override PngDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new() { GeneralOptions = options };
 }
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index f2cefb42b4..01f038141a 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
 /// <summary>
 /// Performs the png decoding operation.
 /// </summary>
-internal sealed class PngDecoderCore : IImageDecoderInternals
+internal sealed class PngDecoderCore : ImageDecoderCore
 {
     /// <summary>
     /// The general decoder options.
@@ -136,8 +136,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public PngDecoderCore(PngDecoderOptions options)
+        : base(options.GeneralOptions)
     {
-        this.Options = options.GeneralOptions;
         this.configuration = options.GeneralOptions.Configuration;
         this.maxFrames = options.GeneralOptions.MaxFrames;
         this.skipMetadata = options.GeneralOptions.SkipMetadata;
@@ -147,8 +147,8 @@ public PngDecoderCore(PngDecoderOptions options)
     }
 
     internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
+        : base(options.GeneralOptions)
     {
-        this.Options = options.GeneralOptions;
         this.colorMetadataOnly = colorMetadataOnly;
         this.maxFrames = options.GeneralOptions.MaxFrames;
         this.skipMetadata = true;
@@ -159,14 +159,7 @@ internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
     }
 
     /// <inheritdoc/>
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc/>
-    public Size Dimensions => new(this.header.Width, this.header.Height);
-
-    /// <inheritdoc/>
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         uint frameCount = 0;
         ImageMetadata metadata = new();
@@ -241,7 +234,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
                                 cancellationToken);
 
                             // if current frame dispose is restore to previous, then from future frame's perspective, it never happened
-                            if (currentFrameControl.Value.DisposeOperation != PngDisposalMethod.RestoreToPrevious)
+                            if (currentFrameControl.Value.DisposalMode != FrameDisposalMode.RestoreToPrevious)
                             {
                                 previousFrame = currentFrame;
                                 previousFrameControl = currentFrameControl;
@@ -341,7 +334,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc/>
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         uint frameCount = 0;
         ImageMetadata metadata = new();
@@ -527,10 +520,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
                 PngThrowHelper.ThrowInvalidHeader();
             }
 
-            // 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, framesMetadata);
+            return new ImageInfo(new(this.header.Width, this.header.Height), metadata, framesMetadata);
         }
         finally
         {
@@ -674,12 +664,12 @@ private void InitializeFrame<TPixel>(
 
         // If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
         // So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null).
-        if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
+        if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToPrevious))
         {
             Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion();
             pixelRegion.Clear();
         }
-        else if (previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToBackground)
+        else if (previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToBackground)
         {
             Rectangle restoreArea = previousFrameControl.Value.Bounds;
             Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(restoreArea);
@@ -703,29 +693,6 @@ private static void InitializeFrameMetadata(List<ImageFrameMetadata> imageFrameM
         imageFrameMetadata.Add(meta);
     }
 
-    /// <summary>
-    /// Calculates the correct number of bits per pixel for the given color type.
-    /// </summary>
-    /// <returns>The <see cref="int"/></returns>
-    private int CalculateBitsPerPixel()
-    {
-        switch (this.pngColorType)
-        {
-            case PngColorType.Grayscale:
-            case PngColorType.Palette:
-                return this.header.BitDepth;
-            case PngColorType.GrayscaleWithAlpha:
-                return this.header.BitDepth * 2;
-            case PngColorType.Rgb:
-                return this.header.BitDepth * 3;
-            case PngColorType.RgbWithAlpha:
-                return this.header.BitDepth * 4;
-            default:
-                PngThrowHelper.ThrowNotSupportedColor();
-                return -1;
-        }
-    }
-
     /// <summary>
     /// Calculates the correct number of bytes per pixel for the given color type.
     /// </summary>
@@ -817,8 +784,8 @@ private void DecodePixelData<TPixel>(
         int height = (int)frameControl.YMax;
 
         IMemoryOwner<TPixel>? blendMemory = null;
-        Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
-        if (frameControl.BlendOperation == PngBlendMethod.Over)
+        Span<TPixel> blendRowBuffer = [];
+        if (frameControl.BlendMode == FrameBlendMode.Over)
         {
             blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
             blendRowBuffer = blendMemory.Memory.Span;
@@ -910,8 +877,8 @@ private void DecodeInterlacedPixelData<TPixel>(
         Buffer2D<TPixel> imageBuffer = imageFrame.PixelBuffer;
 
         IMemoryOwner<TPixel>? blendMemory = null;
-        Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
-        if (frameControl.BlendOperation == PngBlendMethod.Over)
+        Span<TPixel> blendRowBuffer = [];
+        if (frameControl.BlendMode == FrameBlendMode.Over)
         {
             blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
             blendRowBuffer = blendMemory.Memory.Span;
@@ -1036,7 +1003,7 @@ private void ProcessDefilteredScanline<TPixel>(
     {
         Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
 
-        bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
+        bool blend = frameControl.BlendMode == FrameBlendMode.Over;
         Span<TPixel> rowSpan = blend
             ? blendRowBuffer
             : destination;
@@ -1149,7 +1116,7 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
         int increment = 1)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
+        bool blend = frameControl.BlendMode == FrameBlendMode.Over;
         Span<TPixel> rowSpan = blend
             ? blendRowBuffer
             : destination;
@@ -1365,6 +1332,7 @@ private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> data)
         pngMetadata.InterlaceMethod = this.header.InterlaceMethod;
 
         this.pngColorType = this.header.ColorType;
+        this.Dimensions = new(this.header.Width, this.header.Height);
     }
 
     /// <summary>
@@ -1483,7 +1451,20 @@ private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan<byte> dat
         byte colorPrimaries = data[0];
         byte transferFunction = data[1];
         byte matrixCoefficients = data[2];
-        bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null;
+        bool? fullRange;
+        if (data[3] == 1)
+        {
+            fullRange = true;
+        }
+        else if (data[3] == 0)
+        {
+            fullRange = false;
+        }
+        else
+        {
+            fullRange = null;
+        }
+
         metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange);
     }
 
@@ -1515,7 +1496,7 @@ private static bool TryReadLegacyExifTextChunk(ImageMetadata metadata, string da
 
         // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes).
         // This doesn't actually allocate.
-        ReadOnlySpan<byte> exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
+        ReadOnlySpan<byte> exifHeader = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
 
         if (dataLength < exifHeader.Length)
         {
@@ -1626,7 +1607,7 @@ private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int
             Span<byte> destUncompressedData = destBuffer.GetSpan();
             if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
             {
-                uncompressedBytesArray = Array.Empty<byte>();
+                uncompressedBytesArray = [];
                 return false;
             }
 
@@ -1635,7 +1616,7 @@ private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int
             {
                 if (memoryStreamOutput.Length > maxLength)
                 {
-                    uncompressedBytesArray = Array.Empty<byte>();
+                    uncompressedBytesArray = [];
                     return false;
                 }
 
@@ -2002,7 +1983,7 @@ private IMemoryOwner<byte> ReadChunkData(int length)
     {
         if (length == 0)
         {
-            return new BasicArrayBuffer<byte>(Array.Empty<byte>());
+            return new BasicArrayBuffer<byte>([]);
         }
 
         // We rent the buffer here to return it afterwards in Decode()
diff --git a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs
deleted file mode 100644
index 1537c5cedf..0000000000
--- a/src/ImageSharp/Formats/Png/PngDisposalMethod.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Png;
-
-/// <summary>
-/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
-/// </summary>
-public enum PngDisposalMethod
-{
-    /// <summary>
-    /// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
-    /// </summary>
-    DoNotDispose,
-
-    /// <summary>
-    /// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
-    /// </summary>
-    RestoreToBackground,
-
-    /// <summary>
-    /// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
-    /// </summary>
-    RestoreToPrevious
-}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index e3dc0e93cc..4bbb68358f 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -9,10 +9,8 @@
 using System.Runtime.InteropServices;
 using SixLabors.ImageSharp.Common.Helpers;
 using SixLabors.ImageSharp.Compression.Zlib;
-using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.Formats.Png.Chunks;
 using SixLabors.ImageSharp.Formats.Png.Filters;
-using SixLabors.ImageSharp.Formats.Webp;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
@@ -23,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
 /// <summary>
 /// Performs the png encoding operation.
 /// </summary>
-internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
+internal sealed class PngEncoderCore : IDisposable
 {
     /// <summary>
     /// The maximum block size, defaults at 64k for uncompressed blocks.
@@ -160,7 +158,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         this.height = image.Height;
 
         ImageMetadata metadata = image.Metadata;
-        PngMetadata pngMetadata = GetPngMetadata(image);
+        PngMetadata pngMetadata = metadata.ClonePngMetadata();
         this.SanitizeAndSetEncoderOptions<TPixel>(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
 
         stream.Write(PngConstants.HeaderBytes);
@@ -211,8 +209,8 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         {
             // Write the first animated frame.
             currentFrame = image.Frames[currentFrameIndex];
-            PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame);
-            PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
+            PngFrameMetadata frameMetadata = currentFrame.Metadata.GetPngMetadata();
+            FrameDisposalMode previousDisposal = frameMetadata.DisposalMode;
             FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0);
             uint sequenceNumber = 1;
             if (pngMetadata.AnimateRootFrame)
@@ -237,12 +235,12 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
             for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
             {
-                ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
+                ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
                 currentFrame = image.Frames[currentFrameIndex];
                 ImageFrame<TPixel>? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
 
-                frameMetadata = GetPngFrameMetadata(currentFrame);
-                bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over;
+                frameMetadata = currentFrame.Metadata.GetPngMetadata();
+                bool blend = frameMetadata.BlendMode == FrameBlendMode.Over;
 
                 (bool difference, Rectangle bounds) =
                     AnimationUtilities.DeDuplicatePixels(
@@ -268,7 +266,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
                 sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1;
 
                 previousFrame = currentFrame;
-                previousDisposal = frameMetadata.DisposalMethod;
+                previousDisposal = frameMetadata.DisposalMode;
             }
         }
 
@@ -288,54 +286,6 @@ public void Dispose()
         this.currentScanline?.Dispose();
     }
 
-    private static PngMetadata GetPngMetadata<TPixel>(Image<TPixel> image)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
-        {
-            return (PngMetadata)png.DeepClone();
-        }
-
-        if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
-        {
-            AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata();
-            return PngMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
-        {
-            AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata();
-            return PngMetadata.FromAnimatedMetadata(ani);
-        }
-
-        // Return explicit new instance so we do not mutate the original metadata.
-        return new();
-    }
-
-    private static PngFrameMetadata GetPngFrameMetadata<TPixel>(ImageFrame<TPixel> frame)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
-        {
-            return (PngFrameMetadata)png.DeepClone();
-        }
-
-        if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
-        {
-            AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata();
-            return PngFrameMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
-        {
-            AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata();
-            return PngFrameMetadata.FromAnimatedMetadata(ani);
-        }
-
-        // Return explicit new instance so we do not mutate the original metadata.
-        return new();
-    }
-
     /// <summary>
     /// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases.
     /// </summary>
@@ -826,7 +776,6 @@ private void WriteExifChunk(Stream stream, ImageMetadata meta)
             return;
         }
 
-        meta.SyncProfiles();
         this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray());
     }
 
@@ -881,6 +830,7 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
     /// </summary>
     /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
     /// <param name="metaData">The image meta data.</param>
+    /// <exception cref="NotSupportedException">CICP matrix coefficients other than Identity are not supported in PNG.</exception>
     private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
     {
         if (metaData.CicpProfile is null)
@@ -1125,8 +1075,8 @@ private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata fram
             yOffset: (uint)bounds.Top,
             delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator,
             delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator,
-            disposeOperation: frameMetadata.DisposalMethod,
-            blendOperation: frameMetadata.BlendMethod);
+            disposalMode: frameMetadata.DisposalMode,
+            blendMode: frameMetadata.BlendMode);
 
         fcTL.WriteTo(this.chunkDataBuffer.Span);
 
@@ -1483,41 +1433,19 @@ private void SanitizeAndSetEncoderOptions<TPixel>(
 
         // Use options, then check metadata, if nothing set there then we suggest
         // a sensible default based upon the pixel format.
-        PngColorType? colorType = encoder.ColorType ?? pngMetadata.ColorType;
-        byte? bits = (byte?)(encoder.BitDepth ?? pngMetadata.BitDepth);
-
-        if (colorType is null || bits is null)
-        {
-            PixelTypeInfo info = TPixel.GetPixelTypeInfo();
-            PixelComponentInfo? componentInfo = info.ComponentInfo;
-
-            colorType ??= SuggestColorType<TPixel>(in info);
-
-            if (bits is null)
-            {
-                // TODO: Update once we stop abusing PixelTypeInfo in decoders.
-                if (componentInfo.HasValue)
-                {
-                    PixelComponentInfo c = componentInfo.Value;
-                    bits = (byte)SuggestBitDepth<TPixel>(in c);
-                }
-                else
-                {
-                    bits = (byte)PngBitDepth.Bit8;
-                }
-            }
-        }
+        PngColorType color = encoder.ColorType ?? pngMetadata.ColorType;
+        byte bits = (byte)(encoder.BitDepth ?? pngMetadata.BitDepth);
 
-        // Ensure bit depth and color type are a supported combination.
+        // Ensure the bit depth and color type are a supported combination.
         // Bit8 is the only bit depth supported by all color types.
-        byte[] validBitDepths = PngConstants.ColorTypes[colorType.Value];
+        byte[] validBitDepths = PngConstants.ColorTypes[color];
         if (Array.IndexOf(validBitDepths, bits) == -1)
         {
             bits = (byte)PngBitDepth.Bit8;
         }
 
-        this.colorType = colorType.Value;
-        this.bitDepth = bits.Value;
+        this.colorType = color;
+        this.bitDepth = bits;
 
         if (encoder.FilterMethod.HasValue)
         {
@@ -1532,7 +1460,7 @@ private void SanitizeAndSetEncoderOptions<TPixel>(
         use16Bit = bits == (byte)PngBitDepth.Bit16;
         bytesPerPixel = CalculateBytesPerPixel(this.colorType, use16Bit);
 
-        this.interlaceMode = (encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod)!.Value;
+        this.interlaceMode = encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod;
         this.chunkFilter = encoder.SkipMetadata ? PngChunkFilter.ExcludeAll : encoder.ChunkFilter ?? PngChunkFilter.None;
     }
 
@@ -1672,47 +1600,6 @@ private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16
             _ => use16Bit ? 8 : 4,
         };
 
-    /// <summary>
-    /// Returns a suggested <see cref="PngColorType"/> for the given <typeparamref name="TPixel"/>
-    /// </summary>
-    /// <param name="info">The pixel type info.</param>
-    /// <typeparam name="TPixel">The type of pixel format.</typeparam>
-    private static PngColorType SuggestColorType<TPixel>(in PixelTypeInfo info)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (info.AlphaRepresentation == PixelAlphaRepresentation.None)
-        {
-            return info.ColorType switch
-            {
-                PixelColorType.Grayscale => PngColorType.Grayscale,
-                _ => PngColorType.Rgb,
-            };
-        }
-
-        return info.ColorType switch
-        {
-            PixelColorType.Grayscale | PixelColorType.Alpha or PixelColorType.Alpha => PngColorType.GrayscaleWithAlpha,
-            _ => PngColorType.RgbWithAlpha,
-        };
-    }
-
-    /// <summary>
-    /// Returns a suggested <see cref="PngBitDepth"/> for the given <typeparamref name="TPixel"/>
-    /// </summary>
-    /// <param name="info">The pixel type info.</param>
-    /// <typeparam name="TPixel">The type of pixel format.</typeparam>
-    private static PngBitDepth SuggestBitDepth<TPixel>(in PixelComponentInfo info)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        int bits = info.GetMaximumComponentPrecision();
-        if (bits > (int)PixelComponentBitDepth.Bit8)
-        {
-            return PngBitDepth.Bit16;
-        }
-
-        return PngBitDepth.Bit8;
-    }
-
     private unsafe struct ScratchBuffer
     {
         private const int Size = 26;
diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
index dbda4d73c9..c142a1c8e0 100644
--- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
 /// <summary>
 /// Provides APng specific metadata information for the image frame.
 /// </summary>
-public class PngFrameMetadata : IDeepCloneable
+public class PngFrameMetadata : IFormatFrameMetadata<PngFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="PngFrameMetadata"/> class.
@@ -24,8 +24,8 @@ public PngFrameMetadata()
     private PngFrameMetadata(PngFrameMetadata other)
     {
         this.FrameDelay = other.FrameDelay;
-        this.DisposalMethod = other.DisposalMethod;
-        this.BlendMethod = other.BlendMethod;
+        this.DisposalMode = other.DisposalMode;
+        this.BlendMode = other.BlendMode;
     }
 
     /// <summary>
@@ -39,12 +39,12 @@ private PngFrameMetadata(PngFrameMetadata other)
     /// <summary>
     /// Gets or sets the type of frame area disposal to be done after rendering this frame
     /// </summary>
-    public PngDisposalMethod DisposalMethod { get; set; }
+    public FrameDisposalMode DisposalMode { get; set; }
 
     /// <summary>
     /// Gets or sets the type of frame area rendering for this frame
     /// </summary>
-    public PngBlendMethod BlendMethod { get; set; }
+    public FrameBlendMode BlendMode { get; set; }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="PngFrameMetadata"/> class.
@@ -53,26 +53,48 @@ private PngFrameMetadata(PngFrameMetadata other)
     internal void FromChunk(in FrameControl frameControl)
     {
         this.FrameDelay = new Rational(frameControl.DelayNumerator, frameControl.DelayDenominator);
-        this.DisposalMethod = frameControl.DisposeOperation;
-        this.BlendMethod = frameControl.BlendOperation;
+        this.DisposalMode = frameControl.DisposalMode;
+        this.BlendMode = frameControl.BlendMode;
     }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new PngFrameMetadata(this);
-
-    internal static PngFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata)
+    public static PngFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
         => new()
         {
             FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000),
-            DisposalMethod = GetMode(metadata.DisposalMode),
-            BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? PngBlendMethod.Source : PngBlendMethod.Over,
+            DisposalMode = GetMode(metadata.DisposalMode),
+            BlendMode = metadata.BlendMode,
         };
 
-    private static PngDisposalMethod GetMode(FrameDisposalMode mode) => mode switch
+    /// <inheritdoc/>
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+    {
+        double delay = this.FrameDelay.ToDouble();
+        if (double.IsNaN(delay))
+        {
+            delay = 0;
+        }
+
+        return new()
+        {
+            ColorTableMode = FrameColorTableMode.Global,
+            Duration = TimeSpan.FromMilliseconds(delay * 1000),
+            DisposalMode = this.DisposalMode,
+            BlendMode = this.BlendMode,
+        };
+    }
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public PngFrameMetadata DeepClone() => new(this);
+
+    private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch
     {
-        FrameDisposalMode.RestoreToBackground => PngDisposalMethod.RestoreToBackground,
-        FrameDisposalMode.RestoreToPrevious => PngDisposalMethod.RestoreToPrevious,
-        FrameDisposalMode.DoNotDispose => PngDisposalMethod.DoNotDispose,
-        _ => PngDisposalMethod.DoNotDispose,
+        FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
+        FrameDisposalMode.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
+        FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose,
+        _ => FrameDisposalMode.DoNotDispose,
     };
 }
diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs
index d9028dd807..a7b3672ef5 100644
--- a/src/ImageSharp/Formats/Png/PngMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngMetadata.cs
@@ -2,13 +2,14 @@
 // Licensed under the Six Labors Split License.
 
 using SixLabors.ImageSharp.Formats.Png.Chunks;
+using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Png;
 
 /// <summary>
 /// Provides Png specific metadata information for the image.
 /// </summary>
-public class PngMetadata : IDeepCloneable
+public class PngMetadata : IFormatMetadata<PngMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="PngMetadata"/> class.
@@ -46,17 +47,17 @@ private PngMetadata(PngMetadata other)
     /// Gets or sets the number of bits per sample or per palette index (not per pixel).
     /// Not all values are allowed for all <see cref="ColorType"/> values.
     /// </summary>
-    public PngBitDepth? BitDepth { get; set; }
+    public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
 
     /// <summary>
     /// Gets or sets the color type.
     /// </summary>
-    public PngColorType? ColorType { get; set; }
+    public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
 
     /// <summary>
     /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image.
     /// </summary>
-    public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None;
+    public PngInterlaceMode InterlaceMethod { get; set; } = PngInterlaceMode.None;
 
     /// <summary>
     /// Gets or sets the gamma value for the image.
@@ -77,7 +78,7 @@ private PngMetadata(PngMetadata other)
     /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks.
     /// Used for conveying textual information associated with the image.
     /// </summary>
-    public IList<PngTextData> TextData { get; set; } = new List<PngTextData>();
+    public IList<PngTextData> TextData { get; set; } = [];
 
     /// <summary>
     /// Gets or sets the number of times to loop this APNG.  0 indicates infinite looping.
@@ -90,35 +91,165 @@ private PngMetadata(PngMetadata other)
     public bool AnimateRootFrame { get; set; } = true;
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new PngMetadata(this);
-
-    internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
+    public static PngMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
     {
         // Should the conversion be from a format that uses a 24bit palette entries (gif)
         // we need to clone and adjust the color table to allow for transparency.
-        Color[]? colorTable = metadata.ColorTable.HasValue ? metadata.ColorTable.Value.ToArray() : null;
+        Color[]? colorTable = metadata.ColorTable?.ToArray();
         if (colorTable != null)
         {
             for (int i = 0; i < colorTable.Length; i++)
             {
                 ref Color c = ref colorTable[i];
-                if (c == metadata.BackgroundColor)
+                if (c != metadata.BackgroundColor)
                 {
-                    // Png treats background as fully empty
-                    c = Color.Transparent;
-                    break;
+                    continue;
                 }
+
+                // Png treats background as fully empty
+                c = Color.Transparent;
+                break;
             }
         }
 
+        PngColorType color;
+        PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
+
+        switch (colorType)
+        {
+            case PixelColorType.Binary:
+            case PixelColorType.Indexed:
+                color = PngColorType.Palette;
+                break;
+            case PixelColorType.Luminance:
+                color = PngColorType.Grayscale;
+                break;
+            case PixelColorType.RGB:
+            case PixelColorType.BGR:
+                color = PngColorType.Rgb;
+                break;
+            default:
+                if (colorType.HasFlag(PixelColorType.Luminance))
+                {
+                    color = PngColorType.GrayscaleWithAlpha;
+                    break;
+                }
+
+                color = PngColorType.RgbWithAlpha;
+                break;
+        }
+
+        // PNG uses bits per component not per pixel.
+        int bpc = metadata.PixelTypeInfo.ComponentInfo?.GetMaximumComponentPrecision() ?? 8;
+        PngBitDepth bitDepth = bpc switch
+        {
+            1 => PngBitDepth.Bit1,
+            2 => PngBitDepth.Bit2,
+            4 => PngBitDepth.Bit4,
+            _ => (bpc <= 8) ? PngBitDepth.Bit8 : PngBitDepth.Bit16,
+        };
         return new()
         {
-            ColorType = colorTable != null ? PngColorType.Palette : null,
-            BitDepth = colorTable != null
-                        ? (PngBitDepth)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(colorTable.Length), 1, 8)
-                        : null,
+            ColorType = color,
+            BitDepth = bitDepth,
             ColorTable = colorTable,
             RepeatCount = metadata.RepeatCount,
         };
     }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp;
+        PixelColorType colorType;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+        PixelComponentInfo info;
+        switch (this.ColorType)
+        {
+            case PngColorType.Palette:
+                bpp = this.ColorTable.HasValue
+                    ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.ColorTable.Value.Length), 1, 8)
+                    : 8;
+
+                colorType = PixelColorType.Indexed;
+                info = PixelComponentInfo.Create(1, bpp, bpp);
+                break;
+
+            case PngColorType.Grayscale:
+                bpp = (int)this.BitDepth;
+                colorType = PixelColorType.Luminance;
+                info = PixelComponentInfo.Create(1, bpp, bpp);
+                break;
+
+            case PngColorType.GrayscaleWithAlpha:
+
+                alpha = PixelAlphaRepresentation.Unassociated;
+                if (this.BitDepth == PngBitDepth.Bit16)
+                {
+                    bpp = 32;
+                    colorType = PixelColorType.Luminance | PixelColorType.Alpha;
+                    info = PixelComponentInfo.Create(2, bpp, 16, 16);
+                    break;
+                }
+
+                bpp = 16;
+                colorType = PixelColorType.Luminance | PixelColorType.Alpha;
+                info = PixelComponentInfo.Create(2, bpp, 8, 8);
+                break;
+
+            case PngColorType.Rgb:
+                if (this.BitDepth == PngBitDepth.Bit16)
+                {
+                    bpp = 48;
+                    colorType = PixelColorType.RGB;
+                    info = PixelComponentInfo.Create(3, bpp, 16, 16, 16);
+                    break;
+                }
+
+                bpp = 24;
+                colorType = PixelColorType.RGB;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+
+            case PngColorType.RgbWithAlpha:
+            default:
+
+                alpha = PixelAlphaRepresentation.Unassociated;
+                if (this.BitDepth == PngBitDepth.Bit16)
+                {
+                    bpp = 64;
+                    colorType = PixelColorType.RGB | PixelColorType.Alpha;
+                    info = PixelComponentInfo.Create(4, bpp, 16, 16, 16, 16);
+                    break;
+                }
+
+                bpp = 32;
+                colorType = PixelColorType.RGB | PixelColorType.Alpha;
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ColorType = colorType,
+            ComponentInfo = info,
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            ColorTable = this.ColorTable,
+            ColorTableMode = FrameColorTableMode.Global,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue),
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public PngMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs b/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
deleted file mode 100644
index 1e0fa88997..0000000000
--- a/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Formats.Qoi;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the qoi format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="QoiMetadata"/>.</returns>
-    public static QoiMetadata GetQoiMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(QoiFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
index 86d81d8345..85fac7ea26 100644
--- a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
@@ -13,7 +13,7 @@
 
 namespace SixLabors.ImageSharp.Formats.Qoi;
 
-internal class QoiDecoderCore : IImageDecoderInternals
+internal class QoiDecoderCore : ImageDecoderCore
 {
     /// <summary>
     ///     The global configuration.
@@ -31,31 +31,20 @@ internal class QoiDecoderCore : IImageDecoderInternals
     private QoiHeader header;
 
     public QoiDecoderCore(DecoderOptions options)
+        : base(options)
     {
-        this.Options = options;
         this.configuration = options.Configuration;
         this.memoryAllocator = this.configuration.MemoryAllocator;
     }
 
-    public DecoderOptions Options { get; }
-
-    public Size Dimensions { get; }
-
     /// <inheritdoc />
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         // Process the header to get metadata
         this.ProcessHeader(stream);
 
         // Create Image object
-        ImageMetadata metadata = new()
-        {
-            DecodedImageFormat = QoiFormat.Instance,
-            HorizontalResolution = this.header.Width,
-            VerticalResolution = this.header.Height,
-            ResolutionUnits = PixelResolutionUnit.AspectRatio
-        };
+        ImageMetadata metadata = new();
         QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
         qoiMetadata.Channels = this.header.Channels;
         qoiMetadata.ColorSpace = this.header.ColorSpace;
@@ -68,7 +57,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc />
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ProcessHeader(stream);
         PixelTypeInfo pixelType = new(8 * (int)this.header.Channels);
@@ -79,7 +68,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         qoiMetadata.Channels = this.header.Channels;
         qoiMetadata.ColorSpace = this.header.ColorSpace;
 
-        return new ImageInfo(pixelType, size, metadata);
+        return new ImageInfo(size, metadata);
     }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
index 53f67e765d..88d87a3825 100644
--- a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
 /// <summary>
 /// Image encoder for writing an image to a stream as a QOi image
 /// </summary>
-internal class QoiEncoderCore : IImageEncoderInternals
+internal class QoiEncoderCore
 {
     /// <summary>
     /// The encoder with options
@@ -41,7 +41,13 @@ public QoiEncoderCore(QoiEncoder encoder, Configuration configuration)
         this.memoryAllocator = configuration.MemoryAllocator;
     }
 
-    /// <inheritdoc />
+    /// <summary>
+    /// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
+    /// </summary>
+    /// <typeparam name="TPixel">The pixel format.</typeparam>
+    /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
+    /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
+    /// <param name="cancellationToken">The token to request cancellation.</param>
     public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
index 610c6c15b8..e2062014d7 100644
--- a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
@@ -1,12 +1,14 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Qoi;
 
 /// <summary>
 /// Provides Qoi specific metadata information for the image.
 /// </summary>
-public class QoiMetadata : IDeepCloneable
+public class QoiMetadata : IFormatMetadata<QoiMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="QoiMetadata"/> class.
@@ -36,5 +38,59 @@ private QoiMetadata(QoiMetadata other)
     public QoiColorSpace ColorSpace { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new QoiMetadata(this);
+    public static QoiMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        PixelColorType color = metadata.PixelTypeInfo.ColorType;
+
+        if (color.HasFlag(PixelColorType.Alpha))
+        {
+            return new QoiMetadata { Channels = QoiChannels.Rgba };
+        }
+
+        return new QoiMetadata { Channels = QoiChannels.Rgb };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp;
+        PixelColorType colorType;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+        PixelComponentInfo info;
+
+        switch (this.Channels)
+        {
+            case QoiChannels.Rgb:
+                bpp = 24;
+                colorType = PixelColorType.RGB;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+            default:
+                bpp = 32;
+                colorType = PixelColorType.RGB | PixelColorType.Alpha;
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                alpha = PixelAlphaRepresentation.Unassociated;
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ColorType = colorType,
+            ComponentInfo = info,
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo()
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public QoiMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs
deleted file mode 100644
index 8d5e357641..0000000000
--- a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Formats.Tga;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the tga format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="TgaMetadata"/>.</returns>
-    public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
index da34e62f7e..af537ddc21 100644
--- a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
@@ -11,20 +11,20 @@ public enum TgaBitsPerPixel : byte
     /// <summary>
     /// 8 bits per pixel. Each pixel consists of 1 byte.
     /// </summary>
-    Pixel8 = 8,
+    Bit8 = 8,
 
     /// <summary>
     /// 16 bits per pixel. Each pixel consists of 2 bytes.
     /// </summary>
-    Pixel16 = 16,
+    Bit16 = 16,
 
     /// <summary>
     /// 24 bits per pixel. Each pixel consists of 3 bytes.
     /// </summary>
-    Pixel24 = 24,
+    Bit24 = 24,
 
     /// <summary>
     /// 32 bits per pixel. Each pixel consists of 4 bytes.
     /// </summary>
-    Pixel32 = 32
+    Bit32 = 32
 }
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index b454c677bc..dc6b33422f 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
 /// <summary>
 /// Performs the tga decoding operation.
 /// </summary>
-internal sealed class TgaDecoderCore : IImageDecoderInternals
+internal sealed class TgaDecoderCore : ImageDecoderCore
 {
     /// <summary>
     /// General configuration options.
@@ -52,21 +52,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The options.</param>
     public TgaDecoderCore(DecoderOptions options)
+        : base(options)
     {
-        this.Options = options;
         this.configuration = options.Configuration;
         this.memoryAllocator = this.configuration.MemoryAllocator;
     }
 
     /// <inheritdoc />
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc />
-    public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
-
-    /// <inheritdoc />
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         try
         {
@@ -642,11 +635,10 @@ private void ReadRle<TPixel>(BufferedReadStream stream, int width, int height, B
     }
 
     /// <inheritdoc />
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.ReadFileHeader(stream);
         return new ImageInfo(
-            new PixelTypeInfo(this.fileHeader.PixelDepth),
             new(this.fileHeader.Width, this.fileHeader.Height),
             this.metadata);
     }
@@ -915,6 +907,8 @@ private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
 
         stream.Read(buffer, 0, TgaFileHeader.Size);
         this.fileHeader = TgaFileHeader.Parse(buffer);
+        this.Dimensions = new Size(this.fileHeader.Width, this.fileHeader.Height);
+
         this.metadata = new ImageMetadata();
         this.tgaMetadata = this.metadata.GetTgaMetadata();
         this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
@@ -934,7 +928,7 @@ private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
         return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
     }
 
-    private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Pixel32 &&
+    private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Bit32 &&
                                                                            (this.fileHeader.ImageType == TgaImageType.TrueColor ||
                                                                             this.fileHeader.ImageType == TgaImageType.RleTrueColor);
 }
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
index bb13798c50..1e05a9f716 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -3,8 +3,6 @@
 
 using System.Buffers;
 using System.Buffers.Binary;
-using System.Numerics;
-using System.Runtime.CompilerServices;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
@@ -14,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
 /// <summary>
 /// Image encoder for writing an image to a stream as a truevision targa image.
 /// </summary>
-internal sealed class TgaEncoderCore : IImageEncoderInternals
+internal sealed class TgaEncoderCore
 {
     /// <summary>
     /// Used for allocating memory during processing operations.
@@ -61,7 +59,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         this.bitsPerPixel ??= tgaMetadata.BitsPerPixel;
 
         TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor;
-        if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8)
+        if (this.bitsPerPixel == TgaBitsPerPixel.Bit8)
         {
             imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite;
         }
@@ -73,13 +71,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
             imageDescriptor |= 0x20;
         }
 
-        if (this.bitsPerPixel is TgaBitsPerPixel.Pixel32)
+        if (this.bitsPerPixel is TgaBitsPerPixel.Bit32)
         {
             // Indicate, that 8 bit are used for the alpha channel.
             imageDescriptor |= 0x8;
         }
 
-        if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16)
+        if (this.bitsPerPixel is TgaBitsPerPixel.Bit16)
         {
             // Indicate, that 1 bit is used for the alpha channel.
             imageDescriptor |= 0x1;
@@ -107,11 +105,11 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         stream.Write(buffer, 0, TgaFileHeader.Size);
         if (this.compression is TgaCompression.RunLength)
         {
-            this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
+            this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame, cancellationToken);
         }
         else
         {
-            this.WriteImage(image.Configuration, stream, image.Frames.RootFrame);
+            this.WriteImage(image.Configuration, stream, image.Frames.RootFrame, cancellationToken);
         }
 
         stream.Flush();
@@ -123,29 +121,28 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
     /// <typeparam name="TPixel">The pixel format.</typeparam>
     /// <param name="configuration">The global configuration.</param>
     /// <param name="stream">The <see cref="Stream"/> to write to.</param>
-    /// <param name="image">
-    /// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
-    /// </param>
-    private void WriteImage<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
+    /// <param name="image">    /// The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void WriteImage<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         Buffer2D<TPixel> pixels = image.PixelBuffer;
         switch (this.bitsPerPixel)
         {
-            case TgaBitsPerPixel.Pixel8:
-                this.Write8Bit(configuration, stream, pixels);
+            case TgaBitsPerPixel.Bit8:
+                this.Write8Bit(configuration, stream, pixels, cancellationToken);
                 break;
 
-            case TgaBitsPerPixel.Pixel16:
-                this.Write16Bit(configuration, stream, pixels);
+            case TgaBitsPerPixel.Bit16:
+                this.Write16Bit(configuration, stream, pixels, cancellationToken);
                 break;
 
-            case TgaBitsPerPixel.Pixel24:
-                this.Write24Bit(configuration, stream, pixels);
+            case TgaBitsPerPixel.Bit24:
+                this.Write24Bit(configuration, stream, pixels, cancellationToken);
                 break;
 
-            case TgaBitsPerPixel.Pixel32:
-                this.Write32Bit(configuration, stream, pixels);
+            case TgaBitsPerPixel.Bit32:
+                this.Write32Bit(configuration, stream, pixels, cancellationToken);
                 break;
         }
     }
@@ -156,23 +153,33 @@ private void WriteImage<TPixel>(Configuration configuration, Stream stream, Imag
     /// <typeparam name="TPixel">The pixel type.</typeparam>
     /// <param name="stream">The stream to write the image to.</param>
     /// <param name="image">The image to encode.</param>
-    private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         Buffer2D<TPixel> pixels = image.PixelBuffer;
+
+        using IMemoryOwner<Rgba32> rgbaOwner = this.memoryAllocator.Allocate<Rgba32>(image.Width);
+        Span<Rgba32> rgbaRow = rgbaOwner.GetSpan();
+
         for (int y = 0; y < image.Height; y++)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y);
+            PixelOperations<TPixel>.Instance.ToRgba32(image.Configuration, pixelRow, rgbaRow);
+
             for (int x = 0; x < image.Width;)
             {
                 TPixel currentPixel = pixelRow[x];
+                Rgba32 rgba = rgbaRow[x];
                 byte equalPixelCount = FindEqualPixels(pixelRow, x);
 
                 if (equalPixelCount > 0)
                 {
-                    // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
+                    // Write the number of equal pixels, with the high bit set, indicating it's a compressed pixel run.
                     stream.WriteByte((byte)(equalPixelCount | 128));
-                    this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
+                    this.WritePixel(stream, rgba);
                     x += equalPixelCount + 1;
                 }
                 else
@@ -180,12 +187,13 @@ private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel
                     // Write Raw Packet (i.e., Non-Run-Length Encoded):
                     byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x);
                     stream.WriteByte(unEqualPixelCount);
-                    this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
+                    this.WritePixel(stream, rgba);
                     x++;
                     for (int i = 0; i < unEqualPixelCount; i++)
                     {
                         currentPixel = pixelRow[x];
-                        this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
+                        rgba = rgbaRow[x];
+                        this.WritePixel(stream, rgba);
                         x++;
                     }
                 }
@@ -196,22 +204,19 @@ private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel
     /// <summary>
     /// Writes a the pixel to the stream.
     /// </summary>
-    /// <typeparam name="TPixel">The type of the pixel.</typeparam>
     /// <param name="stream">The stream to write to.</param>
-    /// <param name="currentPixel">The current pixel.</param>
     /// <param name="color">The color of the pixel to write.</param>
-    private void WritePixel<TPixel>(Stream stream, TPixel currentPixel, Rgba32 color)
-        where TPixel : unmanaged, IPixel<TPixel>
+    private void WritePixel(Stream stream, Rgba32 color)
     {
         switch (this.bitsPerPixel)
         {
-            case TgaBitsPerPixel.Pixel8:
-                int luminance = GetLuminance(currentPixel);
-                stream.WriteByte((byte)luminance);
+            case TgaBitsPerPixel.Bit8:
+                L8 l8 = L8.FromRgba32(color);
+                stream.WriteByte(l8.PackedValue);
                 break;
 
-            case TgaBitsPerPixel.Pixel16:
-                Bgra5551 bgra5551 = new(color.ToVector4());
+            case TgaBitsPerPixel.Bit16:
+                Bgra5551 bgra5551 = Bgra5551.FromRgba32(color);
                 Span<byte> buffer = stackalloc byte[2];
                 BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)bgra5551.PackedValue);
                 stream.WriteByte(buffer[0]);
@@ -219,13 +224,13 @@ private void WritePixel<TPixel>(Stream stream, TPixel currentPixel, Rgba32 color
 
                 break;
 
-            case TgaBitsPerPixel.Pixel24:
+            case TgaBitsPerPixel.Bit24:
                 stream.WriteByte(color.B);
                 stream.WriteByte(color.G);
                 stream.WriteByte(color.R);
                 break;
 
-            case TgaBitsPerPixel.Pixel32:
+            case TgaBitsPerPixel.Bit32:
                 stream.WriteByte(color.B);
                 stream.WriteByte(color.G);
                 stream.WriteByte(color.R);
@@ -310,7 +315,8 @@ private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
     /// <param name="configuration">The global configuration.</param>
     /// <param name="stream">The <see cref="Stream"/> to write to.</param>
     /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
-    private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 1);
@@ -318,6 +324,8 @@ private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffe
 
         for (int y = pixels.Height - 1; y >= 0; y--)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
             PixelOperations<TPixel>.Instance.ToL8Bytes(
                 configuration,
@@ -335,7 +343,8 @@ private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffe
     /// <param name="configuration">The global configuration.</param>
     /// <param name="stream">The <see cref="Stream"/> to write to.</param>
     /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
-    private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 2);
@@ -343,6 +352,8 @@ private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buff
 
         for (int y = pixels.Height - 1; y >= 0; y--)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
             PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
                 configuration,
@@ -360,7 +371,8 @@ private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buff
     /// <param name="configuration">The global configuration.</param>
     /// <param name="stream">The <see cref="Stream"/> to write to.</param>
     /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
-    private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 3);
@@ -368,6 +380,8 @@ private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buff
 
         for (int y = pixels.Height - 1; y >= 0; y--)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
             PixelOperations<TPixel>.Instance.ToBgr24Bytes(
                 configuration,
@@ -385,7 +399,8 @@ private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buff
     /// <param name="configuration">The global configuration.</param>
     /// <param name="stream">The <see cref="Stream"/> to write to.</param>
     /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
-    private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
+    /// <param name="cancellationToken">The token to request cancellation.</param>
+    private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@@ -393,6 +408,8 @@ private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buff
 
         for (int y = pixels.Height - 1; y >= 0; y--)
         {
+            cancellationToken.ThrowIfCancellationRequested();
+
             Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
             PixelOperations<TPixel>.Instance.ToBgra32Bytes(
                 configuration,
@@ -402,17 +419,4 @@ private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buff
             stream.Write(rowSpan);
         }
     }
-
-    /// <summary>
-    /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
-    /// </summary>
-    /// <typeparam name="TPixel">The type of pixel format.</typeparam>
-    /// <param name="sourcePixel">The pixel to get the luminance from.</param>
-    [MethodImpl(InliningOptions.ShortMethod)]
-    public static int GetLuminance<TPixel>(TPixel sourcePixel)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        Vector4 vector = sourcePixel.ToVector4();
-        return ColorNumerics.GetBT709Luminance(ref vector, 256);
-    }
 }
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
index 1fb3ab5c5d..58b5119523 100644
--- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -1,12 +1,14 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Tga;
 
 /// <summary>
 /// Provides TGA specific metadata information for the image.
 /// </summary>
-public class TgaMetadata : IDeepCloneable
+public class TgaMetadata : IFormatMetadata<TgaMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="TgaMetadata"/> class.
@@ -25,7 +27,7 @@ private TgaMetadata(TgaMetadata other)
     /// <summary>
     /// Gets or sets the number of bits per pixel.
     /// </summary>
-    public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
+    public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Bit24;
 
     /// <summary>
     /// Gets or sets the number of alpha bits per pixel.
@@ -33,5 +35,68 @@ private TgaMetadata(TgaMetadata other)
     public byte AlphaChannelBits { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new TgaMetadata(this);
+    public static TgaMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        // TODO: AlphaChannelBits is not used during encoding.
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        return bpp switch
+        {
+            <= 8 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit8 },
+            <= 16 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit16 },
+            <= 24 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit24 },
+            _ => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit32 }
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BitsPerPixel;
+        PixelComponentInfo info;
+        PixelColorType color;
+        PixelAlphaRepresentation alpha;
+        switch (this.BitsPerPixel)
+        {
+            case TgaBitsPerPixel.Bit8:
+                info = PixelComponentInfo.Create(1, bpp, 8);
+                color = PixelColorType.Luminance;
+                alpha = PixelAlphaRepresentation.None;
+                break;
+            case TgaBitsPerPixel.Bit16:
+                info = PixelComponentInfo.Create(1, bpp, 5, 5, 5, 1);
+                color = PixelColorType.BGR | PixelColorType.Alpha;
+                alpha = PixelAlphaRepresentation.Unassociated;
+                break;
+            case TgaBitsPerPixel.Bit24:
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                color = PixelColorType.RGB;
+                alpha = PixelAlphaRepresentation.None;
+                break;
+            case TgaBitsPerPixel.Bit32 or _:
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                color = PixelColorType.RGB | PixelColorType.Alpha;
+                alpha = PixelAlphaRepresentation.Unassociated;
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ComponentInfo = info,
+            ColorType = color
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo()
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public TgaMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
index 9096271fe5..08faa539a8 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
@@ -33,7 +33,7 @@ public override void CompressStrip(Span<byte> rows, int height)
         var image = Image.LoadPixelData<Rgb24>(rows, width, height);
         image.Save(memoryStream, new JpegEncoder()
         {
-            ColorType = JpegEncodingColor.Rgb
+            ColorType = JpegColorType.Rgb
         });
         memoryStream.Position = 0;
         memoryStream.WriteTo(this.Output);
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
index 416472e830..76d0bb6418 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
@@ -32,8 +32,8 @@ public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocat
     /// <inheritdoc/>
     protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
     {
-        using WebpDecoderCore decoder = new(new WebpDecoderOptions());
-        using Image<Rgb24> image = decoder.Decode<Rgb24>(stream, cancellationToken);
+        using WebpDecoderCore decoder = new(new WebpDecoderOptions() { GeneralOptions = this.options });
+        using Image<Rgb24> image = decoder.Decode<Rgb24>(this.options.Configuration, stream, cancellationToken);
         CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
     }
 
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
index 978860910c..c24eee484b 100644
--- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
@@ -39,9 +39,9 @@ internal static class TiffConstants
     public const ushort BigTiffHeaderMagicNumber = 43;
 
     /// <summary>
-    /// The big tiff bytesize of offsets value.
+    /// The big tiff byte size of offsets value.
     /// </summary>
-    public const ushort BigTiffBytesize = 8;
+    public const ushort BigTiffByteSize = 8;
 
     /// <summary>
     /// RowsPerStrip default value, which is effectively infinity.
@@ -58,38 +58,63 @@ internal static class TiffConstants
     /// </summary>
     public const int DefaultStripSize = 8 * 1024;
 
+    /// <summary>
+    /// The default predictor is None.
+    /// </summary>
+    public const TiffPredictor DefaultPredictor = TiffPredictor.None;
+
+    /// <summary>
+    /// The default bits per pixel is Bit24.
+    /// </summary>
+    public const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
+
+    /// <summary>
+    /// The default bits per sample for color images with 8 bits for each color channel.
+    /// </summary>
+    public static readonly TiffBitsPerSample DefaultBitsPerSample = BitsPerSampleRgb8Bit;
+
+    /// <summary>
+    /// The default compression is None.
+    /// </summary>
+    public const TiffCompression DefaultCompression = TiffCompression.None;
+
+    /// <summary>
+    /// The default photometric interpretation is Rgb.
+    /// </summary>
+    public const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
+
     /// <summary>
     /// The bits per sample for 1 bit bicolor images.
     /// </summary>
-    public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0);
+    public static readonly TiffBitsPerSample BitsPerSample1Bit = new(1, 0, 0);
 
     /// <summary>
     /// The bits per sample for images with a 4 color palette.
     /// </summary>
-    public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0);
+    public static readonly TiffBitsPerSample BitsPerSample4Bit = new(4, 0, 0);
 
     /// <summary>
     /// The bits per sample for 8 bit images.
     /// </summary>
-    public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0);
+    public static readonly TiffBitsPerSample BitsPerSample8Bit = new(8, 0, 0);
 
     /// <summary>
     /// The bits per sample for 16-bit grayscale images.
     /// </summary>
-    public static readonly TiffBitsPerSample BitsPerSample16Bit = new TiffBitsPerSample(16, 0, 0);
+    public static readonly TiffBitsPerSample BitsPerSample16Bit = new(16, 0, 0);
 
     /// <summary>
     /// The bits per sample for color images with 8 bits for each color channel.
     /// </summary>
-    public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8);
+    public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new(8, 8, 8);
 
     /// <summary>
-    /// The list of mimetypes that equate to a tiff.
+    /// The list of mime types that equate to a tiff.
     /// </summary>
-    public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
+    public static readonly IEnumerable<string> MimeTypes = ["image/tiff", "image/tiff-fx"];
 
     /// <summary>
     /// The list of file extensions that equate to a tiff.
     /// </summary>
-    public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
+    public static readonly IEnumerable<string> FileExtensions = ["tiff", "tif"];
 }
diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
index 2673700092..4496de6fb5 100644
--- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
@@ -64,7 +64,7 @@ public void ReadFileHeader()
 
             ushort bytesize = this.ReadUInt16();
             ushort reserve = this.ReadUInt16();
-            if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0)
+            if (bytesize == TiffConstants.BigTiffByteSize && reserve == 0)
             {
                 this.FirstIfdOffset = this.ReadUInt64();
                 return;
diff --git a/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs b/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
deleted file mode 100644
index b06f5dd470..0000000000
--- a/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Formats.Tiff;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the tiff format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="TiffMetadata"/>.</returns>
-    public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
-
-    /// <summary>
-    /// Gets the tiff format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
-    public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
-}
diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
index 382faa3874..2bfd9a626f 100644
--- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
@@ -147,20 +147,20 @@ public ushort[] ToArray()
     {
         if (this.Channel1 == 0)
         {
-            return new[] { this.Channel0 };
+            return [this.Channel0];
         }
 
         if (this.Channel2 == 0)
         {
-            return new[] { this.Channel0, this.Channel1 };
+            return [this.Channel0, this.Channel1];
         }
 
         if (this.Channel3 == 0)
         {
-            return new[] { this.Channel0, this.Channel1, this.Channel2 };
+            return [this.Channel0, this.Channel1, this.Channel2];
         }
 
-        return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 };
+        return [this.Channel0, this.Channel1, this.Channel2, this.Channel3];
     }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 63dc623995..2356d45e47 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
 /// <summary>
 /// Performs the tiff decoding operation.
 /// </summary>
-internal class TiffDecoderCore : IImageDecoderInternals
+internal class TiffDecoderCore : ImageDecoderCore
 {
     /// <summary>
     /// General configuration options.
@@ -60,8 +60,8 @@ internal class TiffDecoderCore : IImageDecoderInternals
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public TiffDecoderCore(DecoderOptions options)
+        : base(options)
     {
-        this.Options = options;
         this.configuration = options.Configuration;
         this.skipMetadata = options.SkipMetadata;
         this.maxFrames = options.MaxFrames;
@@ -154,14 +154,7 @@ public TiffDecoderCore(DecoderOptions options)
     public TiffPredictor Predictor { get; set; }
 
     /// <inheritdoc/>
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc/>
-    public Size Dimensions { get; private set; }
-
-    /// <inheritdoc/>
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         List<ImageFrame<TPixel>> frames = [];
         List<ImageFrameMetadata> framesMetadata = [];
@@ -215,7 +208,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc/>
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         this.inputStream = stream;
         DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);
@@ -234,7 +227,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         int width = GetImageWidth(rootFrameExifProfile);
         int height = GetImageHeight(rootFrameExifProfile);
 
-        return new ImageInfo(new PixelTypeInfo((int)framesMetadata[0].GetTiffMetadata().BitsPerPixel), new(width, height), metadata, framesMetadata);
+        return new ImageInfo(new(width, height), metadata, framesMetadata);
     }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
index 1ef2478e3d..28565cac47 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
@@ -23,7 +23,7 @@ public static ImageMetadata Create(List<ImageFrameMetadata> frames, bool ignoreM
             TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
         }
 
-        ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].ExifProfile);
+        ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]);
 
         if (!ignoreMetadata)
         {
@@ -50,14 +50,22 @@ public static ImageMetadata Create(List<ImageFrameMetadata> frames, bool ignoreM
         return imageMetaData;
     }
 
-    private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile)
+    private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata)
     {
         ImageMetadata imageMetaData = new();
-        SetResolution(imageMetaData, exifProfile);
+        SetResolution(imageMetaData, rootFrameMetadata.ExifProfile);
 
         TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
         tiffMetadata.ByteOrder = byteOrder;
         tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default;
+
+        TiffFrameMetadata tiffFrameMetadata = rootFrameMetadata.GetTiffMetadata();
+        tiffMetadata.BitsPerPixel = tiffFrameMetadata.BitsPerPixel;
+        tiffMetadata.BitsPerSample = tiffFrameMetadata.BitsPerSample;
+        tiffMetadata.Compression = tiffFrameMetadata.Compression;
+        tiffMetadata.PhotometricInterpretation = tiffFrameMetadata.PhotometricInterpretation;
+        tiffMetadata.Predictor = tiffFrameMetadata.Predictor;
+
         return imageMetaData;
     }
 
@@ -109,7 +117,7 @@ private static bool TryGetIptc(IReadOnlyList<IExifValue> exifValues, out byte[]
                     return false;
                 }
 
-                // Probably wrong endianess, swap byte order.
+                // Probably wrong endianness, swap byte order.
                 Span<byte> iptcBytesSpan = iptcBytes.AsSpan();
                 Span<byte> buffer = stackalloc byte[4];
                 for (int i = 0; i < iptcBytes.Length; i += 4)
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index 5a5c2b3e51..186a4bd314 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -63,7 +63,7 @@ public static bool VerifyAndParse(this TiffDecoderCore options, ExifProfile exif
         }
 
         TiffSampleFormat? sampleFormat = null;
-        if (exifProfile.TryGetValue(ExifTag.SampleFormat, out var formatValue))
+        if (exifProfile.TryGetValue(ExifTag.SampleFormat, out IExifValue<ushort[]> formatValue))
         {
             TiffSampleFormat[] sampleFormats = formatValue.Value.Select(a => (TiffSampleFormat)a).ToArray();
             sampleFormat = sampleFormats[0];
@@ -106,11 +106,11 @@ public static bool VerifyAndParse(this TiffDecoderCore options, ExifProfile exif
             options.PlanarConfiguration = DefaultPlanarConfiguration;
         }
 
-        options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
-        options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
+        options.Predictor = frameMetadata.Predictor;
+        options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation;
         options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
-        options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
-        options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
+        options.BitsPerPixel = (int)frameMetadata.BitsPerPixel;
+        options.BitsPerSample = frameMetadata.BitsPerSample;
 
         if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue<Rational[]> blackWhiteValue))
         {
@@ -142,9 +142,7 @@ public static bool VerifyAndParse(this TiffDecoderCore options, ExifProfile exif
         options.ParseCompression(frameMetadata.Compression, exifProfile);
         options.ParseColorType(exifProfile);
 
-        bool isTiled = VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
-
-        return isTiled;
+        return VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
     }
 
     /// <summary>
@@ -194,13 +192,6 @@ private static bool VerifyRequiredFieldsArePresent(ExifProfile exifProfile, Tiff
             }
         }
 
-        // For BiColor compressed images, the BitsPerPixel value will be set explicitly to 1, so we don't throw in those cases.
-        // See: https://github.com/SixLabors/ImageSharp/issues/2587
-        if (frameMetadata.BitsPerPixel == null && !IsBiColorCompression(frameMetadata.Compression))
-        {
-            TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!");
-        }
-
         return isTiled;
     }
 
@@ -224,7 +215,6 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
                 switch (bitsPerChannel)
                 {
                     case 32:
-                    {
                         if (options.SampleFormat == TiffSampleFormat.Float)
                         {
                             options.ColorType = TiffColorType.WhiteIsZero32Float;
@@ -233,43 +223,30 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
 
                         options.ColorType = TiffColorType.WhiteIsZero32;
                         break;
-                    }
 
                     case 24:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero24;
                         break;
-                    }
 
                     case 16:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero16;
                         break;
-                    }
 
                     case 8:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero8;
                         break;
-                    }
 
                     case 4:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero4;
                         break;
-                    }
 
                     case 1:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero1;
                         break;
-                    }
 
                     default:
-                    {
                         options.ColorType = TiffColorType.WhiteIsZero;
                         break;
-                    }
                 }
 
                 break;
@@ -291,7 +268,6 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
                 switch (bitsPerChannel)
                 {
                     case 32:
-                    {
                         if (options.SampleFormat == TiffSampleFormat.Float)
                         {
                             options.ColorType = TiffColorType.BlackIsZero32Float;
@@ -300,43 +276,30 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
 
                         options.ColorType = TiffColorType.BlackIsZero32;
                         break;
-                    }
 
                     case 24:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero24;
                         break;
-                    }
 
                     case 16:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero16;
                         break;
-                    }
 
                     case 8:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero8;
                         break;
-                    }
 
                     case 4:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero4;
                         break;
-                    }
 
                     case 1:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero1;
                         break;
-                    }
 
                     default:
-                    {
                         options.ColorType = TiffColorType.BlackIsZero;
                         break;
-                    }
                 }
 
                 break;
@@ -535,29 +498,21 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi
         switch (compression ?? TiffCompression.None)
         {
             case TiffCompression.None:
-            {
                 options.CompressionType = TiffDecoderCompressionType.None;
                 break;
-            }
 
             case TiffCompression.PackBits:
-            {
                 options.CompressionType = TiffDecoderCompressionType.PackBits;
                 break;
-            }
 
             case TiffCompression.Deflate:
             case TiffCompression.OldDeflate:
-            {
                 options.CompressionType = TiffDecoderCompressionType.Deflate;
                 break;
-            }
 
             case TiffCompression.Lzw:
-            {
                 options.CompressionType = TiffDecoderCompressionType.Lzw;
                 break;
-            }
 
             case TiffCompression.CcittGroup3Fax:
             {
@@ -599,16 +554,13 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi
             }
 
             case TiffCompression.Ccitt1D:
-            {
                 options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
                 options.BitsPerSample = new TiffBitsPerSample(1, 0, 0);
                 options.BitsPerPixel = 1;
 
                 break;
-            }
 
             case TiffCompression.OldJpeg:
-            {
                 if (!options.OldJpegCompressionStartOfImageMarker.HasValue)
                 {
                     TiffThrowHelper.ThrowNotSupported("Missing SOI marker offset for tiff with old jpeg compression");
@@ -629,10 +581,8 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi
                 }
 
                 break;
-            }
 
             case TiffCompression.Jpeg:
-            {
                 options.CompressionType = TiffDecoderCompressionType.Jpeg;
 
                 if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
@@ -643,30 +593,14 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi
                 }
 
                 break;
-            }
 
             case TiffCompression.Webp:
-            {
                 options.CompressionType = TiffDecoderCompressionType.Webp;
                 break;
-            }
 
             default:
-            {
                 TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
                 break;
-            }
         }
     }
-
-    private static bool IsBiColorCompression(TiffCompression? compression)
-    {
-        if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or
-            TiffCompression.CcittGroup4Fax)
-        {
-            return true;
-        }
-
-        return false;
-    }
 }
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
index ea64e82899..a068613bf4 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -47,7 +47,7 @@ public class TiffEncoder : QuantizingImageEncoder
     /// <inheritdoc/>
     protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
     {
-        TiffEncoderCore encode = new(this, image.Configuration.MemoryAllocator);
+        TiffEncoderCore encode = new(this, image.Configuration);
         encode.Encode(image, stream, cancellationToken);
     }
 }
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 149f23f1bf..5f91fd7393 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -1,8 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
-#nullable disable
 
-using SixLabors.ImageSharp.Advanced;
+using System.Diagnostics.CodeAnalysis;
 using SixLabors.ImageSharp.Compression.Zlib;
 using SixLabors.ImageSharp.Formats.Tiff.Compression;
 using SixLabors.ImageSharp.Formats.Tiff.Constants;
@@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
 /// <summary>
 /// Performs the TIFF encoding operation.
 /// </summary>
-internal sealed class TiffEncoderCore : IImageEncoderInternals
+internal sealed class TiffEncoderCore
 {
     private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian
             ? TiffConstants.ByteOrderLittleEndianShort
@@ -50,41 +49,22 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
     /// </summary>
     private readonly DeflateCompressionLevel compressionLevel;
 
-    /// <summary>
-    /// The default predictor is None.
-    /// </summary>
-    private const TiffPredictor DefaultPredictor = TiffPredictor.None;
-
-    /// <summary>
-    /// The default bits per pixel is Bit24.
-    /// </summary>
-    private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
-
-    /// <summary>
-    /// The default compression is None.
-    /// </summary>
-    private const TiffCompression DefaultCompression = TiffCompression.None;
-
-    /// <summary>
-    /// The default photometric interpretation is Rgb.
-    /// </summary>
-    private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
-
     /// <summary>
     /// Whether to skip metadata during encoding.
     /// </summary>
     private readonly bool skipMetadata;
 
-    private readonly List<(long, uint)> frameMarkers = new();
+    private readonly List<(long, uint)> frameMarkers = [];
 
     /// <summary>
     /// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
     /// </summary>
     /// <param name="options">The options for the encoder.</param>
-    /// <param name="memoryAllocator">The memory allocator.</param>
-    public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator)
+    /// <param name="configuration">The global configuration.</param>
+    public TiffEncoderCore(TiffEncoder options, Configuration configuration)
     {
-        this.memoryAllocator = memoryAllocator;
+        this.configuration = configuration;
+        this.memoryAllocator = configuration.MemoryAllocator;
         this.PhotometricInterpretation = options.PhotometricInterpretation;
         this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
         this.pixelSamplingStrategy = options.PixelSamplingStrategy;
@@ -135,35 +115,29 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
         // Determine the correct values to encode with.
         // EncoderOptions > Metadata > Default.
-        TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
+        TiffBitsPerPixel bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
 
-        TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
+        TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
 
-        TiffPredictor predictor =
-            this.HorizontalPredictor
-            ?? rootFrameTiffMetaData.Predictor
-            ?? DefaultPredictor;
+        TiffPredictor predictor = this.HorizontalPredictor ?? rootFrameTiffMetaData.Predictor;
 
-        TiffCompression compression =
-            this.CompressionType
-            ?? rootFrameTiffMetaData.Compression
-            ?? DefaultCompression;
+        TiffCompression compression = this.CompressionType ?? rootFrameTiffMetaData.Compression;
 
-        // Make sure, the Encoder options makes sense in combination with each other.
-        this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
+        // Make sure the Encoder options makes sense in combination with each other.
+        this.SanitizeAndSetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
 
         using TiffStreamWriter writer = new(stream);
         Span<byte> buffer = stackalloc byte[4];
 
         long ifdMarker = WriteHeader(writer, buffer);
 
-        Image<TPixel> metadataImage = image;
+        Image<TPixel>? metadataImage = image;
 
         foreach (ImageFrame<TPixel> frame in image.Frames)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
+            ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
             metadataImage = null;
         }
 
@@ -199,6 +173,8 @@ public static long WriteHeader(TiffStreamWriter writer, Span<byte> buffer)
     /// <param name="frame">The tiff frame.</param>
     /// <param name="imageMetadata">The image metadata (resolution values for each frame).</param>
     /// <param name="image">The image (common metadata for root frame).</param>
+    /// <param name="bitsPerPixel">The bits per pixel.</param>
+    /// <param name="compression">The compression type.</param>
     /// <param name="ifdOffset">The marker to write this IFD offset.</param>
     /// <returns>
     /// The next IFD offset value.
@@ -207,16 +183,18 @@ private long WriteFrame<TPixel>(
         TiffStreamWriter writer,
         ImageFrame<TPixel> frame,
         ImageMetadata imageMetadata,
-        Image<TPixel> image,
+        Image<TPixel>? image,
+        TiffBitsPerPixel bitsPerPixel,
+        TiffCompression compression,
         long ifdOffset)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
-            this.CompressionType ?? TiffCompression.None,
+            compression,
             writer.BaseStream,
             this.memoryAllocator,
             frame.Width,
-            (int)this.BitsPerPixel,
+            (int)bitsPerPixel,
             this.compressionLevel,
             this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
 
@@ -229,7 +207,7 @@ private long WriteFrame<TPixel>(
             this.memoryAllocator,
             this.configuration,
             entriesCollector,
-            (int)this.BitsPerPixel);
+            (int)bitsPerPixel);
 
         int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType);
 
@@ -307,7 +285,7 @@ private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
         }
 
         uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
-        List<byte[]> largeDataBlocks = new();
+        List<byte[]> largeDataBlocks = [];
 
         entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
 
@@ -354,135 +332,87 @@ private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
         return nextIfdMarker;
     }
 
+    [MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
     private void SanitizeAndSetEncoderOptions(
-        TiffBitsPerPixel? bitsPerPixel,
-        int inputBitsPerPixel,
-        TiffPhotometricInterpretation? photometricInterpretation,
+        TiffBitsPerPixel bitsPerPixel,
+        TiffPhotometricInterpretation photometricInterpretation,
         TiffCompression compression,
         TiffPredictor predictor)
     {
-        // BitsPerPixel should be the primary source of truth for the encoder options.
-        if (bitsPerPixel.HasValue)
+        // Ensure 1 Bit compression is only used with 1 bit pixel type.
+        // Choose a sensible default based on the bits per pixel.
+        if (IsOneBitCompression(compression) && bitsPerPixel != TiffBitsPerPixel.Bit1)
         {
-            switch (bitsPerPixel)
+            compression = bitsPerPixel switch
             {
-                case TiffBitsPerPixel.Bit1:
-                    if (IsOneBitCompression(compression))
-                    {
-                        // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
-                        this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
-                        break;
-                    }
-
-                    this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None);
-                    break;
-                case TiffBitsPerPixel.Bit4:
-                    this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None);
-                    break;
-                case TiffBitsPerPixel.Bit8:
-                    this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
-                    break;
-                case TiffBitsPerPixel.Bit16:
-                    // Assume desire to encode as L16 grayscale
-                    this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
-                    break;
-                case TiffBitsPerPixel.Bit6:
-                case TiffBitsPerPixel.Bit10:
-                case TiffBitsPerPixel.Bit12:
-                case TiffBitsPerPixel.Bit14:
-                case TiffBitsPerPixel.Bit30:
-                case TiffBitsPerPixel.Bit36:
-                case TiffBitsPerPixel.Bit42:
-                case TiffBitsPerPixel.Bit48:
-                    // Encoding not yet supported bits per pixel will default to 24 bits.
-                    this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
-                    break;
-                case TiffBitsPerPixel.Bit64:
-                    // Encoding not yet supported bits per pixel will default to 32 bits.
-                    this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
-                    break;
-                default:
-                    this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
-                    break;
-            }
-
-            // Make sure 1 Bit compression is only used with 1 bit pixel type.
-            if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
-            {
-                // Invalid compression / bits per pixel combination, fallback to no compression.
-                this.CompressionType = DefaultCompression;
-            }
-
-            return;
+                < TiffBitsPerPixel.Bit8 => TiffCompression.None,
+                _ => TiffCompression.Deflate,
+            };
         }
 
-        // If no photometric interpretation was chosen, the input image bit per pixel should be preserved.
-        if (!photometricInterpretation.HasValue)
-        {
-            if (IsOneBitCompression(this.CompressionType))
-            {
-                // We need to make sure bits per pixel is set to Bit1 now. WhiteIsZero is set because its the default for bilevel compressed data.
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
-                return;
-            }
+        // Ensure predictor is only used with compression that supports it.
+        predictor = HasPredictor(compression) ? predictor : TiffPredictor.None;
 
-            // At the moment only 8, 16 and 32 bits per pixel can be preserved by the tiff encoder.
-            if (inputBitsPerPixel == 8)
-            {
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
-                return;
-            }
-
-            if (inputBitsPerPixel == 16)
-            {
-                // Assume desire to encode as L16 grayscale
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
-                return;
-            }
-
-            this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
-            return;
-        }
-
-        switch (photometricInterpretation)
+        // BitsPerPixel should be the primary source of truth for the encoder options.
+        switch (bitsPerPixel)
         {
-            case TiffPhotometricInterpretation.BlackIsZero:
-            case TiffPhotometricInterpretation.WhiteIsZero:
-                if (IsOneBitCompression(this.CompressionType))
+            case TiffBitsPerPixel.Bit1:
+                if (IsOneBitCompression(compression))
                 {
-                    this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
-                    return;
-                }
-
-                if (inputBitsPerPixel == 16)
-                {
-                    this.SetEncoderOptions(TiffBitsPerPixel.Bit16, photometricInterpretation, compression, predictor);
-                    return;
+                    // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
+                    this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, predictor);
+                    break;
                 }
 
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
-                return;
-
-            case TiffPhotometricInterpretation.PaletteColor:
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
-                return;
-
-            case TiffPhotometricInterpretation.Rgb:
-                // Make sure 1 Bit compression is only used with 1 bit pixel type.
-                if (IsOneBitCompression(this.CompressionType))
+                this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
+                break;
+            case TiffBitsPerPixel.Bit4:
+                this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
+                break;
+            case TiffBitsPerPixel.Bit8:
+
+                // Allow any combination of the below for 8 bit images.
+                if (photometricInterpretation is TiffPhotometricInterpretation.BlackIsZero
+                    or TiffPhotometricInterpretation.WhiteIsZero
+                    or TiffPhotometricInterpretation.PaletteColor)
                 {
-                    // Invalid compression / bits per pixel combination, fallback to no compression.
-                    compression = DefaultCompression;
+                    this.SetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
+                    break;
                 }
 
-                this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor);
-                return;
+                this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
+                break;
+            case TiffBitsPerPixel.Bit16:
+                // Assume desire to encode as L16 grayscale
+                this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
+                break;
+            case TiffBitsPerPixel.Bit6:
+            case TiffBitsPerPixel.Bit10:
+            case TiffBitsPerPixel.Bit12:
+            case TiffBitsPerPixel.Bit14:
+            case TiffBitsPerPixel.Bit30:
+            case TiffBitsPerPixel.Bit36:
+            case TiffBitsPerPixel.Bit42:
+            case TiffBitsPerPixel.Bit48:
+                // Encoding not yet supported bits per pixel will default to 24 bits.
+                this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
+                break;
+            case TiffBitsPerPixel.Bit64:
+                // Encoding not yet supported bits per pixel will default to 32 bits.
+                this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, predictor);
+                break;
+            default:
+                this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
+                break;
         }
-
-        this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor);
     }
 
-    private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor)
+    [MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
+    private void SetEncoderOptions(
+        TiffBitsPerPixel bitsPerPixel,
+        TiffPhotometricInterpretation photometricInterpretation,
+        TiffCompression compression,
+        TiffPredictor predictor)
     {
         this.BitsPerPixel = bitsPerPixel;
         this.PhotometricInterpretation = photometricInterpretation;
@@ -492,4 +422,7 @@ private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricIn
 
     public static bool IsOneBitCompression(TiffCompression? compression)
         => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
+
+    public static bool HasPredictor(TiffCompression? compression)
+        => compression is TiffCompression.Deflate or TiffCompression.Lzw;
 }
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
index e309830984..bb5da37411 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
 /// <summary>
 /// Provides Tiff specific metadata information for the frame.
 /// </summary>
-public class TiffFrameMetadata : IDeepCloneable
+public class TiffFrameMetadata : IFormatFrameMetadata<TiffFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
@@ -34,33 +34,47 @@ private TiffFrameMetadata(TiffFrameMetadata other)
     /// <summary>
     /// Gets or sets the bits per pixel.
     /// </summary>
-    public TiffBitsPerPixel? BitsPerPixel { get; set; }
+    public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
 
     /// <summary>
     /// Gets or sets number of bits per component.
     /// </summary>
-    public TiffBitsPerSample? BitsPerSample { get; set; }
+    public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
 
     /// <summary>
     /// Gets or sets the compression scheme used on the image data.
     /// </summary>
-    public TiffCompression? Compression { get; set; }
+    public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
 
     /// <summary>
     /// Gets or sets the color space of the image data.
     /// </summary>
-    public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
+    public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
 
     /// <summary>
     /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
     /// </summary>
-    public TiffPredictor? Predictor { get; set; }
+    public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
 
     /// <summary>
     /// Gets or sets the set of inks used in a separated (<see cref="TiffPhotometricInterpretation.Separated"/>) image.
     /// </summary>
     public TiffInkSet? InkSet { get; set; }
 
+    /// <inheritdoc/>
+    public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
+        => new();
+
+    /// <inheritdoc/>
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+        => new();
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public TiffFrameMetadata DeepClone() => new(this);
+
     /// <summary>
     /// Returns a new <see cref="TiffFrameMetadata"/> instance parsed from the given Exif profile.
     /// </summary>
@@ -89,7 +103,7 @@ internal static void Parse(TiffFrameMetadata meta, ExifProfile profile)
                 meta.BitsPerSample = bitsPerSample;
             }
 
-            meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();
+            meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel();
 
             if (profile.TryGetValue(ExifTag.Compression, out IExifValue<ushort>? compressionValue))
             {
@@ -111,13 +125,11 @@ internal static void Parse(TiffFrameMetadata meta, ExifProfile profile)
                 meta.InkSet = (TiffInkSet)inkSetValue.Value;
             }
 
+            // TODO: Why do we remove this? Encoding should overwrite.
             profile.RemoveValue(ExifTag.BitsPerSample);
             profile.RemoveValue(ExifTag.Compression);
             profile.RemoveValue(ExifTag.PhotometricInterpretation);
             profile.RemoveValue(ExifTag.Predictor);
         }
     }
-
-    /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new TiffFrameMetadata(this);
 }
diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
index 2759d0130c..cc70941d51 100644
--- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
@@ -1,12 +1,15 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Tiff;
 
 /// <summary>
 /// Provides Tiff specific metadata information for the image.
 /// </summary>
-public class TiffMetadata : IDeepCloneable
+public class TiffMetadata : IFormatMetadata<TiffMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="TiffMetadata"/> class.
@@ -23,6 +26,11 @@ private TiffMetadata(TiffMetadata other)
     {
         this.ByteOrder = other.ByteOrder;
         this.FormatType = other.FormatType;
+        this.BitsPerPixel = other.BitsPerPixel;
+        this.BitsPerSample = other.BitsPerSample;
+        this.Compression = other.Compression;
+        this.PhotometricInterpretation = other.PhotometricInterpretation;
+        this.Predictor = other.Predictor;
     }
 
     /// <summary>
@@ -35,6 +43,146 @@ private TiffMetadata(TiffMetadata other)
     /// </summary>
     public TiffFormatType FormatType { get; set; }
 
+    /// <summary>
+    /// Gets or sets the bits per pixel. Derived from the root frame.
+    /// </summary>
+    public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
+
+    /// <summary>
+    /// Gets or sets number of bits per component. Derived from the root frame.
+    /// </summary>
+    public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
+
+    /// <summary>
+    /// Gets or sets the compression scheme used on the image data. Derived from the root frame.
+    /// </summary>
+    public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
+
+    /// <summary>
+    /// Gets or sets the color space of the image data. Derived from the root frame.
+    /// </summary>
+    public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
+
+    /// <summary>
+    /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
+    /// Derived from the root frame.
+    /// </summary>
+    public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
+
+    /// <inheritdoc/>
+    public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        int bpp = metadata.PixelTypeInfo.BitsPerPixel;
+        return bpp switch
+        {
+            1 => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit1,
+                BitsPerSample = TiffConstants.BitsPerSample1Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero,
+                Compression = TiffCompression.CcittGroup4Fax,
+                Predictor = TiffPredictor.None
+            },
+            <= 4 => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit4,
+                BitsPerSample = TiffConstants.BitsPerSample4Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
+                Compression = TiffCompression.Deflate,
+                Predictor = TiffPredictor.None // Best match for low bit depth
+            },
+            8 => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit8,
+                BitsPerSample = TiffConstants.BitsPerSample8Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
+                Compression = TiffCompression.Deflate,
+                Predictor = TiffPredictor.Horizontal
+            },
+            16 => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit16,
+                BitsPerSample = TiffConstants.BitsPerSample16Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero,
+                Compression = TiffCompression.Deflate,
+                Predictor = TiffPredictor.Horizontal
+            },
+            32 or 64 => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit32,
+                BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
+                Compression = TiffCompression.Deflate,
+                Predictor = TiffPredictor.Horizontal
+            },
+            _ => new TiffMetadata
+            {
+                BitsPerPixel = TiffBitsPerPixel.Bit24,
+                BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
+                PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
+                Compression = TiffCompression.Deflate,
+                Predictor = TiffPredictor.Horizontal
+            }
+        };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp = (int)this.BitsPerPixel;
+
+        TiffBitsPerSample samples = this.BitsPerSample;
+        PixelComponentInfo info = samples.Channels switch
+        {
+            1 => PixelComponentInfo.Create(1, bpp, bpp),
+            2 => PixelComponentInfo.Create(2, bpp, bpp, samples.Channel0, samples.Channel1),
+            3 => PixelComponentInfo.Create(3, bpp, samples.Channel0, samples.Channel1, samples.Channel2),
+            _ => PixelComponentInfo.Create(4, bpp, samples.Channel0, samples.Channel1, samples.Channel2, samples.Channel3)
+        };
+
+        PixelColorType colorType;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+        switch (this.BitsPerPixel)
+        {
+            case TiffBitsPerPixel.Bit1:
+                colorType = PixelColorType.Binary;
+                break;
+            case TiffBitsPerPixel.Bit4:
+            case TiffBitsPerPixel.Bit6:
+            case TiffBitsPerPixel.Bit8:
+                colorType = PixelColorType.Indexed;
+                break;
+            case TiffBitsPerPixel.Bit16:
+                colorType = PixelColorType.Luminance;
+                break;
+            case TiffBitsPerPixel.Bit32:
+            case TiffBitsPerPixel.Bit64:
+                colorType = PixelColorType.RGB | PixelColorType.Alpha;
+                alpha = PixelAlphaRepresentation.Unassociated;
+                break;
+            default:
+                colorType = PixelColorType.RGB;
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            ColorType = colorType,
+            ComponentInfo = info,
+            AlphaRepresentation = alpha
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            PixelTypeInfo = this.GetPixelTypeInfo()
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new TiffMetadata(this);
+    public TiffMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
index c8ff579a89..66662569c3 100644
--- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
+++ b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
@@ -10,7 +10,7 @@ internal readonly struct WebpFrameData
     /// </summary>
     public const uint HeaderSize = 16;
 
-    public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
+    public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod)
     {
         this.DataSize = dataSize;
         this.X = x;
@@ -30,12 +30,12 @@ public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uin
             width,
             height,
             duration,
-            (flags & 2) == 0 ? WebpBlendMethod.Over : WebpBlendMethod.Source,
-            (flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose)
+            (flags & 2) == 0 ? FrameBlendMode.Over : FrameBlendMode.Source,
+            (flags & 1) == 1 ? FrameDisposalMode.RestoreToBackground : FrameDisposalMode.DoNotDispose)
     {
     }
 
-    public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
+    public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod)
         : this(0, x, y, width, height, duration, blendingMethod, disposalMethod)
     {
     }
@@ -74,12 +74,12 @@ public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, Web
     /// <summary>
     /// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
     /// </summary>
-    public WebpBlendMethod BlendingMethod { get; }
+    public FrameBlendMode BlendingMethod { get; }
 
     /// <summary>
     /// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
     /// </summary>
-    public WebpDisposalMethod DisposalMethod { get; }
+    public FrameDisposalMode DisposalMethod { get; }
 
     public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
 
@@ -91,13 +91,13 @@ public long WriteHeaderTo(Stream stream)
     {
         byte flags = 0;
 
-        if (this.BlendingMethod is WebpBlendMethod.Source)
+        if (this.BlendingMethod is FrameBlendMode.Source)
         {
             // Set blending flag.
             flags |= 2;
         }
 
-        if (this.DisposalMethod is WebpDisposalMethod.RestoreToBackground)
+        if (this.DisposalMethod is FrameDisposalMode.RestoreToBackground)
         {
             // Set disposal flag.
             flags |= 1;
@@ -124,7 +124,7 @@ public static WebpFrameData Parse(Stream stream)
     {
         Span<byte> buffer = stackalloc byte[4];
 
-        WebpFrameData data = new(
+        return new(
             dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
             x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
             y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
@@ -132,7 +132,5 @@ public static WebpFrameData Parse(Stream stream)
             height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
             duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
             flags: stream.ReadByte());
-
-        return data;
     }
 }
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 3f09f94e3a..244691e77e 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -241,8 +241,6 @@ public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool ha
     {
         // Write bytes from the bit-writer buffer to the stream.
         ImageMetadata metadata = image.Metadata;
-        metadata.SyncProfiles();
-
         ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
         XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
 
@@ -259,7 +257,7 @@ public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool ha
 
         if (hasAnimation)
         {
-            WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
+            WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
             BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
         }
 
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 40d91ecf1a..3ad72f7d00 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -315,8 +315,6 @@ public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool ha
     {
         // Write bytes from the bitwriter buffer to the stream.
         ImageMetadata metadata = image.Metadata;
-        metadata.SyncProfiles();
-
         ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
         XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
 
@@ -332,7 +330,7 @@ public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool ha
 
         if (hasAnimation)
         {
-            WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
+            WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
             BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
         }
 
@@ -376,7 +374,7 @@ public void EncodeStatic<TPixel>(Stream stream, Image<TPixel> image)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         ImageFrame<TPixel> frame = image.Frames.RootFrame;
-        this.Encode(stream, frame, image.Bounds, WebpCommonUtils.GetWebpFrameMetadata(frame), false, image);
+        this.Encode(stream, frame, image.Bounds, frame.Metadata.GetWebpMetadata(), false, image);
     }
 
     /// <summary>
@@ -462,7 +460,7 @@ private bool Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle b
         // Extract and encode alpha channel data, if present.
         int alphaDataSize = 0;
         bool alphaCompressionSucceeded = false;
-        Span<byte> alphaData = Span<byte>.Empty;
+        Span<byte> alphaData = [];
         IMemoryOwner<byte> encodedAlphaData = null;
         try
         {
diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs
deleted file mode 100644
index 731d3f1ff2..0000000000
--- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.Formats.Webp;
-using SixLabors.ImageSharp.Metadata;
-
-namespace SixLabors.ImageSharp;
-
-/// <summary>
-/// Extension methods for the <see cref="ImageMetadata"/> type.
-/// </summary>
-public static partial class MetadataExtensions
-{
-    /// <summary>
-    /// Gets the webp format specific metadata for the image.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="WebpMetadata"/>.</returns>
-    public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance);
-
-    /// <summary>
-    /// Gets the webp format specific metadata for the image.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">The metadata.</param>
-    /// <returns>
-    /// <see langword="true"/> if the webp metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetWebpMetadata(this ImageMetadata source, [NotNullWhen(true)] out WebpMetadata? metadata)
-        => source.TryGetFormatMetadata(WebpFormat.Instance, out metadata);
-
-    /// <summary>
-    /// Gets the webp format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="metadata">The metadata this method extends.</param>
-    /// <returns>The <see cref="WebpFrameMetadata"/>.</returns>
-    public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance);
-
-    /// <summary>
-    /// Gets the webp format specific metadata for the image frame.
-    /// </summary>
-    /// <param name="source">The metadata this method extends.</param>
-    /// <param name="metadata">The metadata.</param>
-    /// <returns>
-    /// <see langword="true"/> if the webp frame metadata exists; otherwise, <see langword="false"/>.
-    /// </returns>
-    public static bool TryGetWebpFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out WebpFrameMetadata? metadata)
-        => source.TryGetFormatMetadata(WebpFormat.Instance, out metadata);
-
-    internal static AnimatedImageMetadata ToAnimatedImageMetadata(this WebpMetadata source)
-        => new()
-        {
-            ColorTableMode = FrameColorTableMode.Global,
-            RepeatCount = source.RepeatCount,
-            BackgroundColor = source.BackgroundColor
-        };
-
-    internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this WebpFrameMetadata source)
-        => new()
-        {
-            ColorTableMode = FrameColorTableMode.Global,
-            Duration = TimeSpan.FromMilliseconds(source.FrameDelay),
-            DisposalMode = GetMode(source.DisposalMethod),
-            BlendMode = source.BlendMethod == WebpBlendMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
-        };
-
-    private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch
-    {
-        WebpDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
-        WebpDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose,
-        _ => FrameDisposalMode.DoNotDispose,
-    };
-}
diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
index 70372fe985..72405e480e 100644
--- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
@@ -195,14 +195,14 @@ private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
 
         Rectangle regionRectangle = frameData.Bounds;
 
-        if (frameData.DisposalMethod is WebpDisposalMethod.RestoreToBackground)
+        if (frameData.DisposalMethod is FrameDisposalMode.RestoreToBackground)
         {
             this.RestoreToBackground(imageFrame, backgroundColor);
         }
 
         using Buffer2D<TPixel> decodedImageFrame = this.DecodeImageFrameData<TPixel>(frameData, webpInfo);
 
-        bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendMethod.Over;
+        bool blend = previousFrame != null && frameData.BlendingMethod == FrameBlendMode.Over;
         DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend);
 
         previousFrame = currentFrame ?? image.Frames.RootFrame;
diff --git a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs
index 529c4bafb9..03717d852a 100644
--- a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs
@@ -11,10 +11,10 @@ public enum WebpBitsPerPixel : short
     /// <summary>
     /// 24 bits per pixel. Each pixel consists of 3 bytes.
     /// </summary>
-    Pixel24 = 24,
+    Bit24 = 24,
 
     /// <summary>
     /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present).
     /// </summary>
-    Pixel32 = 32
+    Bit32 = 32
 }
diff --git a/src/ImageSharp/Formats/Webp/WebpBlendMethod.cs b/src/ImageSharp/Formats/Webp/WebpBlendMethod.cs
deleted file mode 100644
index f16f7650c7..0000000000
--- a/src/ImageSharp/Formats/Webp/WebpBlendMethod.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-﻿// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Webp;
-
-/// <summary>
-/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
-/// </summary>
-public enum WebpBlendMethod
-{
-    /// <summary>
-    /// Do not blend. After disposing of the previous frame,
-    /// render the current frame on the canvas by overwriting the rectangle covered by the current frame.
-    /// </summary>
-    Source = 0,
-
-    /// <summary>
-    /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending.
-    /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle.
-    /// </summary>
-    Over = 1,
-}
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index 839798b4d7..4ccaf65031 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -17,6 +17,10 @@ internal static class WebpChunkParsingUtils
     /// <summary>
     /// Reads the header of a lossy webp image.
     /// </summary>
+    /// <param name="memoryAllocator">The memory allocator.</param>
+    /// <param name="stream">The buffered read stream.</param>
+    /// <param name="buffer">The scratch buffer to use while reading.</param>
+    /// <param name="features">The webp features to parse.</param>
     /// <returns>Information about this webp image.</returns>
     public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
     {
@@ -114,13 +118,15 @@ public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, Buffe
 
         Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
 
-        return new WebpImageInfo
+        return new()
         {
             Width = width,
             Height = height,
             XScale = xScale,
             YScale = yScale,
-            BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Pixel32 : WebpBitsPerPixel.Pixel24,
+
+            // Vp8 header can be parsed during the processing of the Vp8X header.
+            BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24,
             IsLossless = false,
             Features = features,
             Vp8Profile = (sbyte)version,
@@ -132,7 +138,10 @@ public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, Buffe
     /// <summary>
     /// Reads the header of a lossless webp image.
     /// </summary>
-    /// <returns>Information about this image.</returns>
+    /// <param name="memoryAllocator">The memory allocator.</param>
+    /// <param name="stream">The buffered read stream.</param>
+    /// <param name="buffer">The scratch buffer to use while reading.</param>
+    /// <param name="features">The webp features to parse.</param>
     public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
     {
         // VP8 data size.
@@ -156,8 +165,8 @@ public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, Buff
         }
 
         // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise.
-        // TODO: this flag value is not used yet
-        bool alphaIsUsed = bitReader.ReadBit();
+        // Alpha may have already been set by the VP8X chunk.
+        features.Alpha |= bitReader.ReadBit();
 
         // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0.
         // Any other value should be treated as an error.
@@ -167,11 +176,11 @@ public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, Buff
             WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header");
         }
 
-        return new WebpImageInfo
+        return new()
         {
             Width = width,
             Height = height,
-            BitsPerPixel = WebpBitsPerPixel.Pixel32,
+            BitsPerPixel = features.Alpha ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24,
             IsLossless = true,
             Features = features,
             Vp8LBitReader = bitReader
@@ -187,6 +196,9 @@ public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, Buff
     /// - An optional 'ALPH' chunk with alpha channel data.
     /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow.
     /// </summary>
+    /// <param name="stream">The buffered read stream.</param>
+    /// <param name="buffer">The scratch buffer to use while reading.</param>
+    /// <param name="features">The webp features to parse.</param>
     /// <returns>Information about this webp image.</returns>
     public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
     {
@@ -217,6 +229,7 @@ public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte>
         features.Animation = (imageFeatures & (1 << 1)) != 0;
 
         // 3 reserved bytes should follow which are supposed to be zero.
+        // No other decoder actually checks this though.
         stream.Read(buffer, 0, 3);
 
         // 3 bytes for the width.
@@ -226,14 +239,14 @@ public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte>
         uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
 
         // Read all the chunks in the order they occur.
-        WebpImageInfo info = new()
+        return new()
         {
             Width = width,
             Height = height,
             Features = features
-        };
 
-        return info;
+            // Additional properties are set during the parsing of the VP8 or VP8L headers.
+        };
     }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Webp/WebpColorType.cs b/src/ImageSharp/Formats/Webp/WebpColorType.cs
new file mode 100644
index 0000000000..64d9143b1f
--- /dev/null
+++ b/src/ImageSharp/Formats/Webp/WebpColorType.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Webp;
+
+/// <summary>
+/// Provides enumeration of the various webp color types.
+/// </summary>
+public enum WebpColorType
+{
+    /// <summary>
+    /// Yuv (luminance, blue chroma, red chroma) as defined in the ITU-R Rec. BT.709 specification.
+    /// </summary>
+    Yuv,
+
+    /// <summary>
+    /// Rgb color space.
+    /// </summary>
+    Rgb,
+
+    /// <summary>
+    /// Rgba color space.
+    /// </summary>
+    Rgba
+}
diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
index 49482260bb..a1e9821c09 100644
--- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
@@ -4,8 +4,6 @@
 using System.Runtime.InteropServices;
 using System.Runtime.Intrinsics;
 using System.Runtime.Intrinsics.X86;
-using SixLabors.ImageSharp.Formats.Gif;
-using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Webp;
@@ -15,54 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
 /// </summary>
 internal static class WebpCommonUtils
 {
-    public static WebpMetadata GetWebpMetadata<TPixel>(Image<TPixel> image)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
-        {
-            return (WebpMetadata)webp.DeepClone();
-        }
-
-        if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
-        {
-            AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata();
-            return WebpMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
-        {
-            AnimatedImageMetadata ani = png.ToAnimatedImageMetadata();
-            return WebpMetadata.FromAnimatedMetadata(ani);
-        }
-
-        // Return explicit new instance so we do not mutate the original metadata.
-        return new();
-    }
-
-    public static WebpFrameMetadata GetWebpFrameMetadata<TPixel>(ImageFrame<TPixel> frame)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
-        {
-            return (WebpFrameMetadata)webp.DeepClone();
-        }
-
-        if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
-        {
-            AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata();
-            return WebpFrameMetadata.FromAnimatedMetadata(ani);
-        }
-
-        if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
-        {
-            AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata();
-            return WebpFrameMetadata.FromAnimatedMetadata(ani);
-        }
-
-        // Return explicit new instance so we do not mutate the original metadata.
-        return new();
-    }
-
     /// <summary>
     /// Checks if the pixel row is not opaque.
     /// </summary>
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 21f0f4946b..9ca6e2bee3 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
 /// <summary>
 /// Performs the webp decoding operation.
 /// </summary>
-internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
+internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
 {
     /// <summary>
     /// General configuration options.
@@ -61,8 +61,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
     /// </summary>
     /// <param name="options">The decoder options.</param>
     public WebpDecoderCore(WebpDecoderOptions options)
+        : base(options.GeneralOptions)
     {
-        this.Options = options.GeneralOptions;
         this.backgroundColorHandling = options.BackgroundColorHandling;
         this.configuration = options.GeneralOptions.Configuration;
         this.skipMetadata = options.GeneralOptions.SkipMetadata;
@@ -70,15 +70,8 @@ public WebpDecoderCore(WebpDecoderOptions options)
         this.memoryAllocator = this.configuration.MemoryAllocator;
     }
 
-    /// <inheritdoc/>
-    public DecoderOptions Options { get; }
-
-    /// <inheritdoc/>
-    public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
-
     /// <inheritdoc />
-    public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
-        where TPixel : unmanaged, IPixel<TPixel>
+    protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         Image<TPixel>? image = null;
         try
@@ -136,7 +129,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
     }
 
     /// <inheritdoc />
-    public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+    protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
     {
         ReadImageHeader(stream, stackalloc byte[4]);
 
@@ -144,7 +137,6 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
         using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
         {
             return new ImageInfo(
-                new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel),
                 new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height),
                 metadata);
         }
@@ -186,36 +178,43 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metad
         Span<byte> buffer = stackalloc byte[4];
         WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
 
+        WebpImageInfo? info = null;
         WebpFeatures features = new();
         switch (chunkType)
         {
             case WebpChunkType.Vp8:
+                info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
                 webpMetadata.FileFormat = WebpFileFormatType.Lossy;
-                return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
+                webpMetadata.ColorType = WebpColorType.Yuv;
+                return info;
             case WebpChunkType.Vp8L:
+                info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
                 webpMetadata.FileFormat = WebpFileFormatType.Lossless;
-                return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
+                webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
+                return info;
             case WebpChunkType.Vp8X:
-                WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features);
+                info = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features);
                 while (stream.Position < stream.Length)
                 {
                     chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
                     if (chunkType == WebpChunkType.Vp8)
                     {
+                        info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
                         webpMetadata.FileFormat = WebpFileFormatType.Lossy;
-                        webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
+                        webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
                     }
                     else if (chunkType == WebpChunkType.Vp8L)
                     {
+                        info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
                         webpMetadata.FileFormat = WebpFileFormatType.Lossless;
-                        webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
+                        webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
                     }
                     else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
                     {
                         bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer);
                         if (isAnimationChunk)
                         {
-                            return webpInfos;
+                            return info;
                         }
                     }
                     else
@@ -226,7 +225,7 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metad
                     }
                 }
 
-                return webpInfos;
+                return info;
             default:
                 WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header");
                 return
diff --git a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs b/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs
deleted file mode 100644
index 47cc83951d..0000000000
--- a/src/ImageSharp/Formats/Webp/WebpDisposalMethod.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-namespace SixLabors.ImageSharp.Formats.Webp;
-
-/// <summary>
-/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
-/// </summary>
-public enum WebpDisposalMethod
-{
-    /// <summary>
-    /// Do not dispose. Leave the canvas as is.
-    /// </summary>
-    DoNotDispose = 0,
-
-    /// <summary>
-    /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame with background color specified in the ANIM chunk.
-    /// </summary>
-    RestoreToBackground = 1
-}
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
index d29759f9a1..e37462fda4 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
@@ -5,6 +5,7 @@
 using SixLabors.ImageSharp.Formats.Webp.Lossless;
 using SixLabors.ImageSharp.Formats.Webp.Lossy;
 using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Webp;
@@ -12,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
 /// <summary>
 /// Image encoder for writing an image to a stream in the Webp format.
 /// </summary>
-internal sealed class WebpEncoderCore : IImageEncoderInternals
+internal sealed class WebpEncoderCore
 {
     /// <summary>
     /// Used for allocating memory during processing operations.
@@ -124,7 +125,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
         }
         else
         {
-            WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
+            WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
             lossless = webpMetadata.FileFormat == WebpFileFormatType.Lossless;
         }
 
@@ -150,12 +151,12 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
             // Encode the first frame.
             ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
-            WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame);
+            WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata();
             hasAlpha |= encoder.Encode(previousFrame, previousFrame.Bounds(), frameMetadata, stream, hasAnimation);
 
             if (hasAnimation)
             {
-                WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
+                FrameDisposalMode previousDisposal = frameMetadata.DisposalMethod;
 
                 // Encode additional frames
                 // This frame is reused to store de-duplicated pixel buffers.
@@ -163,12 +164,12 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
                 for (int i = 1; i < image.Frames.Count; i++)
                 {
-                    ImageFrame<TPixel>? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
+                    ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
                     ImageFrame<TPixel> currentFrame = image.Frames[i];
                     ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
 
-                    frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
-                    bool blend = frameMetadata.BlendMethod == WebpBlendMethod.Over;
+                    frameMetadata = currentFrame.Metadata.GetWebpMetadata();
+                    bool blend = frameMetadata.BlendMethod == FrameBlendMode.Over;
 
                     (bool difference, Rectangle bounds) =
                         AnimationUtilities.DeDuplicatePixels(
@@ -227,8 +228,8 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
                 // Encode the first frame.
                 ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
-                WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(previousFrame);
-                WebpDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
+                WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata();
+                FrameDisposalMode previousDisposal = frameMetadata.DisposalMethod;
 
                 hasAlpha |= encoder.EncodeAnimation(previousFrame, stream, previousFrame.Bounds(), frameMetadata);
 
@@ -238,12 +239,12 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
 
                 for (int i = 1; i < image.Frames.Count; i++)
                 {
-                    ImageFrame<TPixel>? prev = previousDisposal == WebpDisposalMethod.RestoreToBackground ? null : previousFrame;
+                    ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
                     ImageFrame<TPixel> currentFrame = image.Frames[i];
                     ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
 
-                    frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(currentFrame);
-                    bool blend = frameMetadata.BlendMethod == WebpBlendMethod.Over;
+                    frameMetadata = currentFrame.Metadata.GetWebpMetadata();
+                    bool blend = frameMetadata.BlendMethod == FrameBlendMode.Over;
 
                     (bool difference, Rectangle bounds) =
                         AnimationUtilities.DeDuplicatePixels(
diff --git a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs
index 1ed9bbb431..6f606cdf46 100644
--- a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs
+++ b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs
@@ -9,12 +9,12 @@ namespace SixLabors.ImageSharp.Formats.Webp;
 public enum WebpFileFormatType
 {
     /// <summary>
-    /// The lossless webp format.
+    /// The lossless Webp format, which compresses data without any loss of information.
     /// </summary>
     Lossless,
 
     /// <summary>
-    /// The lossy webp format.
+    /// The lossy Webp format, which compresses data by discarding some of it.
     /// </summary>
     Lossy,
 }
diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
index cd1b5d5905..45e182d223 100644
--- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
 /// <summary>
 /// Provides webp specific metadata information for the image frame.
 /// </summary>
-public class WebpFrameMetadata : IDeepCloneable
+public class WebpFrameMetadata : IFormatFrameMetadata<WebpFrameMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="WebpFrameMetadata"/> class.
@@ -29,12 +29,12 @@ private WebpFrameMetadata(WebpFrameMetadata other)
     /// <summary>
     /// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
     /// </summary>
-    public WebpBlendMethod BlendMethod { get; set; }
+    public FrameBlendMode BlendMethod { get; set; }
 
     /// <summary>
     /// Gets or sets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
     /// </summary>
-    public WebpDisposalMethod DisposalMethod { get; set; }
+    public FrameDisposalMode DisposalMethod { get; set; }
 
     /// <summary>
     /// Gets or sets the frame duration. The time to wait before displaying the next frame,
@@ -43,13 +43,34 @@ private WebpFrameMetadata(WebpFrameMetadata other)
     public uint FrameDelay { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new WebpFrameMetadata(this);
-
-    internal static WebpFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata)
+    public static WebpFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
         => new()
         {
             FrameDelay = (uint)metadata.Duration.TotalMilliseconds,
-            BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? WebpBlendMethod.Source : WebpBlendMethod.Over,
-            DisposalMethod = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose
+            BlendMethod = metadata.BlendMode,
+            DisposalMethod = GetMode(metadata.DisposalMode)
+        };
+
+    /// <inheritdoc/>
+    public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
+        => new()
+        {
+            ColorTableMode = FrameColorTableMode.Global,
+            Duration = TimeSpan.FromMilliseconds(this.FrameDelay),
+            DisposalMode = this.DisposalMethod,
+            BlendMode = this.BlendMethod,
         };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public WebpFrameMetadata DeepClone() => new(this);
+
+    private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch
+    {
+        FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
+        FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose,
+        _ => FrameDisposalMode.DoNotDispose,
+    };
 }
diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
index 5f7301b262..3428ce199a 100644
--- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
+++ b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs
@@ -18,8 +18,14 @@ internal class WebpImageInfo : IDisposable
     /// </summary>
     public uint Height { get; set; }
 
+    /// <summary>
+    /// Gets or sets the horizontal scale.
+    /// </summary>
     public sbyte XScale { get; set; }
 
+    /// <summary>
+    /// Gets or sets the vertical scale.
+    /// </summary>
     public sbyte YScale { get; set; }
 
     /// <summary>
diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs
index 536ea09294..33ebbbf6dc 100644
--- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs
+++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs
@@ -1,12 +1,14 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.PixelFormats;
+
 namespace SixLabors.ImageSharp.Formats.Webp;
 
 /// <summary>
 /// Provides Webp specific metadata information for the image.
 /// </summary>
-public class WebpMetadata : IDeepCloneable
+public class WebpMetadata : IFormatMetadata<WebpMetadata>
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="WebpMetadata"/> class.
@@ -21,15 +23,27 @@ public WebpMetadata()
     /// <param name="other">The metadata to create an instance from.</param>
     private WebpMetadata(WebpMetadata other)
     {
+        this.BitsPerPixel = other.BitsPerPixel;
+        this.ColorType = other.ColorType;
         this.FileFormat = other.FileFormat;
         this.RepeatCount = other.RepeatCount;
         this.BackgroundColor = other.BackgroundColor;
     }
 
+    /// <summary>
+    /// Gets or sets the number of bits per pixel.
+    /// </summary>
+    public WebpBitsPerPixel BitsPerPixel { get; set; } = WebpBitsPerPixel.Bit32;
+
+    /// <summary>
+    /// Gets or sets the color type.
+    /// </summary>
+    public WebpColorType ColorType { get; set; } = WebpColorType.Rgba;
+
     /// <summary>
     /// Gets or sets the webp file format used. Either lossless or lossy.
     /// </summary>
-    public WebpFileFormatType? FileFormat { get; set; }
+    public WebpFileFormatType FileFormat { get; set; } = WebpFileFormatType.Lossy;
 
     /// <summary>
     /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely.
@@ -40,18 +54,100 @@ private WebpMetadata(WebpMetadata other)
     /// Gets or sets the default background color of the canvas when animating.
     /// This color may be used to fill the unused space on the canvas around the frames,
     /// as well as the transparent pixels of the first frame.
-    /// The background color is also used when the Disposal method is <see cref="WebpDisposalMethod.RestoreToBackground"/>.
+    /// The background color is also used when the Disposal method is <see cref="FrameDisposalMode.RestoreToBackground"/>.
     /// </summary>
     public Color BackgroundColor { get; set; }
 
     /// <inheritdoc/>
-    public IDeepCloneable DeepClone() => new WebpMetadata(this);
+    public static WebpMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
+    {
+        WebpBitsPerPixel bitsPerPixel;
+        WebpColorType color;
+        PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
+        switch (colorType)
+        {
+            case PixelColorType.RGB:
+            case PixelColorType.BGR:
+                color = WebpColorType.Rgb;
+                bitsPerPixel = WebpBitsPerPixel.Bit24;
+                break;
+            case PixelColorType.YCbCr:
+                color = WebpColorType.Yuv;
+                bitsPerPixel = WebpBitsPerPixel.Bit24;
+                break;
+            default:
+                if (colorType.HasFlag(PixelColorType.Alpha))
+                {
+                    color = WebpColorType.Rgba;
+                    bitsPerPixel = WebpBitsPerPixel.Bit32;
+                    break;
+                }
 
-    internal static WebpMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
-        => new()
+                color = WebpColorType.Rgb;
+                bitsPerPixel = WebpBitsPerPixel.Bit24;
+                break;
+        }
+
+        return new()
         {
-            FileFormat = WebpFileFormatType.Lossless,
+            BitsPerPixel = bitsPerPixel,
+            ColorType = color,
             BackgroundColor = metadata.BackgroundColor,
-            RepeatCount = metadata.RepeatCount
+            RepeatCount = metadata.RepeatCount,
+            FileFormat = metadata.EncodingType == EncodingType.Lossless ? WebpFileFormatType.Lossless : WebpFileFormatType.Lossy
         };
+    }
+
+    /// <inheritdoc/>
+    public PixelTypeInfo GetPixelTypeInfo()
+    {
+        int bpp;
+        PixelColorType colorType;
+        PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
+        PixelComponentInfo info;
+        switch (this.ColorType)
+        {
+            case WebpColorType.Yuv:
+                bpp = 24;
+                colorType = PixelColorType.YCbCr;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+            case WebpColorType.Rgb:
+                bpp = 24;
+                colorType = PixelColorType.RGB;
+                info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
+                break;
+            case WebpColorType.Rgba:
+            default:
+                bpp = 32;
+                colorType = PixelColorType.RGB | PixelColorType.Alpha;
+                info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
+                alpha = PixelAlphaRepresentation.Unassociated;
+                break;
+        }
+
+        return new PixelTypeInfo(bpp)
+        {
+            AlphaRepresentation = alpha,
+            ColorType = colorType,
+            ComponentInfo = info,
+        };
+    }
+
+    /// <inheritdoc/>
+    public FormatConnectingMetadata ToFormatConnectingMetadata()
+        => new()
+        {
+            EncodingType = this.FileFormat == WebpFileFormatType.Lossless ? EncodingType.Lossless : EncodingType.Lossy,
+            PixelTypeInfo = this.GetPixelTypeInfo(),
+            ColorTableMode = FrameColorTableMode.Global,
+            RepeatCount = this.RepeatCount,
+            BackgroundColor = this.BackgroundColor
+        };
+
+    /// <inheritdoc/>
+    IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
+
+    /// <inheritdoc/>
+    public WebpMetadata DeepClone() => new(this);
 }
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs
similarity index 81%
rename from src/ImageSharp/Formats/ImageExtensions.Save.cs
rename to src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs
index 7e5989d6fc..73d1145883 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs
@@ -2,10 +2,10 @@
 // Licensed under the Six Labors Split License.
 
 // <auto-generated />
-using SixLabors.ImageSharp.Advanced;
-
 using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Cur;
 using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Formats.Ico;
 using SixLabors.ImageSharp.Formats.Jpeg;
 using SixLabors.ImageSharp.Formats.Pbm;
 using SixLabors.ImageSharp.Formats.Png;
@@ -123,6 +123,108 @@ public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder e
               encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance),
               cancellationToken);
 
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    public static void SaveAsCur(this Image source, string path) => SaveAsCur(source, path, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsCurAsync(this Image source, string path) => SaveAsCurAsync(source, path, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsCurAsync(this Image source, string path, CancellationToken cancellationToken)
+        => SaveAsCurAsync(source, path, default, cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    public static void SaveAsCur(this Image source, string path, CurEncoder encoder) =>
+        source.Save(
+            path,
+            encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance));
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsCurAsync(this Image source, string path, CurEncoder encoder, CancellationToken cancellationToken = default)
+        => source.SaveAsync(
+              path,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance),
+              cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    public static void SaveAsCur(this Image source, Stream stream)
+        => SaveAsCur(source, stream, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsCurAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+        => SaveAsCurAsync(source, stream, default, cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    public static void SaveAsCur(this Image source, Stream stream, CurEncoder encoder)
+        => source.Save(
+              stream,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance));
+
+    /// <summary>
+    /// Saves the image to the given stream with the Cur format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsCurAsync(this Image source, Stream stream, CurEncoder encoder, CancellationToken cancellationToken = default)
+        => source.SaveAsync(
+              stream,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance),
+              cancellationToken);
+
     /// <summary>
     /// Saves the image to the given stream with the Gif format.
     /// </summary>
@@ -225,6 +327,108 @@ public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder e
               encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance),
               cancellationToken);
 
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    public static void SaveAsIco(this Image source, string path) => SaveAsIco(source, path, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsIcoAsync(this Image source, string path) => SaveAsIcoAsync(source, path, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsIcoAsync(this Image source, string path, CancellationToken cancellationToken)
+        => SaveAsIcoAsync(source, path, default, cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    public static void SaveAsIco(this Image source, string path, IcoEncoder encoder) =>
+        source.Save(
+            path,
+            encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance));
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="path">The file path to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsIcoAsync(this Image source, string path, IcoEncoder encoder, CancellationToken cancellationToken = default)
+        => source.SaveAsync(
+              path,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance),
+              cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    public static void SaveAsIco(this Image source, Stream stream)
+        => SaveAsIco(source, stream, default);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsIcoAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+        => SaveAsIcoAsync(source, stream, default, cancellationToken);
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    public static void SaveAsIco(this Image source, Stream stream, IcoEncoder encoder)
+        => source.Save(
+              stream,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance));
+
+    /// <summary>
+    /// Saves the image to the given stream with the Ico format.
+    /// </summary>
+    /// <param name="source">The image this method extends.</param>
+    /// <param name="stream">The stream to save the image to.</param>
+    /// <param name="encoder">The encoder to save the image with.</param>
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
+    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+    public static Task SaveAsIcoAsync(this Image source, Stream stream, IcoEncoder encoder, CancellationToken cancellationToken = default)
+        => source.SaveAsync(
+              stream,
+              encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance),
+              cancellationToken);
+
     /// <summary>
     /// Saves the image to the given stream with the Jpeg format.
     /// </summary>
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt
similarity index 95%
rename from src/ImageSharp/Formats/ImageExtensions.Save.tt
rename to src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt
index d4f1ed233b..144dd83625 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.tt
+++ b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt
@@ -1,25 +1,8 @@
-<#@ template language="C#" #>
+<#@include file="_Formats.ttinclude" #>
 <#@ import namespace="System.Text" #>
 <#@ import namespace="System.Collections.Generic" #>
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
 // <auto-generated />
-using SixLabors.ImageSharp.Advanced;
-
 <#
-    var formats = new []{
-        "Bmp",
-        "Gif",
-        "Jpeg",
-        "Pbm",
-        "Png",
-        "Qoi",
-        "Tga",
-        "Tiff",
-        "Webp",
-    };
-
     foreach (string fmt in formats)
     {
 #>
diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs
new file mode 100644
index 0000000000..e35d00ed39
--- /dev/null
+++ b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs
@@ -0,0 +1,365 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+// <auto-generated />
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Cur;
+using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Formats.Ico;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Pbm;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Webp;
+
+namespace SixLabors.ImageSharp;
+
+/// <summary>
+/// Extension methods for the <see cref="ImageMetadata"/> and <see cref="ImageFrameMetadata"/> types.
+/// </summary>
+public static class ImageMetadataExtensions
+{
+    /// <summary>
+    /// Gets the <see cref="BmpMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="BmpMetadata"/>
+    /// </returns>
+    public static BmpMetadata GetBmpMetadata(this ImageMetadata source) => source.GetFormatMetadata(BmpFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="BmpMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetBmpMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="BmpMetadata"/></returns>
+    public static BmpMetadata CloneBmpMetadata(this ImageMetadata source) => source.CloneFormatMetadata(BmpFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="CurMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="CurMetadata"/>
+    /// </returns>
+    public static CurMetadata GetCurMetadata(this ImageMetadata source) => source.GetFormatMetadata(CurFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="CurMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetCurMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="CurMetadata"/></returns>
+    public static CurMetadata CloneCurMetadata(this ImageMetadata source) => source.CloneFormatMetadata(CurFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="GifMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="GifMetadata"/>
+    /// </returns>
+    public static GifMetadata GetGifMetadata(this ImageMetadata source) => source.GetFormatMetadata(GifFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="GifMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetGifMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="GifMetadata"/></returns>
+    public static GifMetadata CloneGifMetadata(this ImageMetadata source) => source.CloneFormatMetadata(GifFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="IcoMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="IcoMetadata"/>
+    /// </returns>
+    public static IcoMetadata GetIcoMetadata(this ImageMetadata source) => source.GetFormatMetadata(IcoFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="IcoMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetIcoMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="IcoMetadata"/></returns>
+    public static IcoMetadata CloneIcoMetadata(this ImageMetadata source) => source.CloneFormatMetadata(IcoFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="JpegMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="JpegMetadata"/>
+    /// </returns>
+    public static JpegMetadata GetJpegMetadata(this ImageMetadata source) => source.GetFormatMetadata(JpegFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="JpegMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetJpegMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="JpegMetadata"/></returns>
+    public static JpegMetadata CloneJpegMetadata(this ImageMetadata source) => source.CloneFormatMetadata(JpegFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="PbmMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="PbmMetadata"/>
+    /// </returns>
+    public static PbmMetadata GetPbmMetadata(this ImageMetadata source) => source.GetFormatMetadata(PbmFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="PbmMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetPbmMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="PbmMetadata"/></returns>
+    public static PbmMetadata ClonePbmMetadata(this ImageMetadata source) => source.CloneFormatMetadata(PbmFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="PngMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="PngMetadata"/>
+    /// </returns>
+    public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="PngMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetPngMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="PngMetadata"/></returns>
+    public static PngMetadata ClonePngMetadata(this ImageMetadata source) => source.CloneFormatMetadata(PngFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="QoiMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="QoiMetadata"/>
+    /// </returns>
+    public static QoiMetadata GetQoiMetadata(this ImageMetadata source) => source.GetFormatMetadata(QoiFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="QoiMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetQoiMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="QoiMetadata"/></returns>
+    public static QoiMetadata CloneQoiMetadata(this ImageMetadata source) => source.CloneFormatMetadata(QoiFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="TgaMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="TgaMetadata"/>
+    /// </returns>
+    public static TgaMetadata GetTgaMetadata(this ImageMetadata source) => source.GetFormatMetadata(TgaFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="TgaMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetTgaMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="TgaMetadata"/></returns>
+    public static TgaMetadata CloneTgaMetadata(this ImageMetadata source) => source.CloneFormatMetadata(TgaFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="TiffMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="TiffMetadata"/>
+    /// </returns>
+    public static TiffMetadata GetTiffMetadata(this ImageMetadata source) => source.GetFormatMetadata(TiffFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="TiffMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetTiffMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="TiffMetadata"/></returns>
+    public static TiffMetadata CloneTiffMetadata(this ImageMetadata source) => source.CloneFormatMetadata(TiffFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="WebpMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="WebpMetadata"/>
+    /// </returns>
+    public static WebpMetadata GetWebpMetadata(this ImageMetadata source) => source.GetFormatMetadata(WebpFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="WebpMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetWebpMetadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="WebpMetadata"/></returns>
+    public static WebpMetadata CloneWebpMetadata(this ImageMetadata source) => source.CloneFormatMetadata(WebpFormat.Instance);
+
+
+    /// <summary>
+    /// Gets the <see cref="CurFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="CurFrameMetadata"/>
+    /// </returns>
+    public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(CurFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="CurMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetCurMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="CurFrameMetadata"/></returns>
+    public static CurFrameMetadata CloneCurMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(CurFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="IcoFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="IcoFrameMetadata"/>
+    /// </returns>
+    public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(IcoFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="IcoMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetIcoMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="IcoFrameMetadata"/></returns>
+    public static IcoFrameMetadata CloneIcoMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(IcoFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="GifFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="GifFrameMetadata"/>
+    /// </returns>
+    public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(GifFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="GifMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetGifMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="GifFrameMetadata"/></returns>
+    public static GifFrameMetadata CloneGifMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(GifFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="PngFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="PngFrameMetadata"/>
+    /// </returns>
+    public static PngFrameMetadata GetPngMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="PngMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetPngMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="PngFrameMetadata"/></returns>
+    public static PngFrameMetadata ClonePngMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(PngFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="TiffFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="TiffFrameMetadata"/>
+    /// </returns>
+    public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(TiffFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="TiffMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetTiffMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="TiffFrameMetadata"/></returns>
+    public static TiffFrameMetadata CloneTiffMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(TiffFormat.Instance);
+
+    /// <summary>
+    /// Gets the <see cref="WebpFrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="WebpFrameMetadata"/>
+    /// </returns>
+    public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(WebpFormat.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="WebpMetadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="GetWebpMetadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="WebpFrameMetadata"/></returns>
+    public static WebpFrameMetadata CloneWebpMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(WebpFormat.Instance);
+}
diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt
new file mode 100644
index 0000000000..e4db85ed59
--- /dev/null
+++ b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt
@@ -0,0 +1,77 @@
+<#@include file="_Formats.ttinclude" #>
+<#@ import namespace="System.Text" #>
+<#@ import namespace="System.Collections.Generic" #>
+// <auto-generated />
+using SixLabors.ImageSharp.Metadata;
+<#
+    foreach (string fmt in formats)
+    {
+#>
+using SixLabors.ImageSharp.Formats.<#= fmt #>;
+<#
+
+    }
+#>
+
+namespace SixLabors.ImageSharp;
+
+/// <summary>
+/// Extension methods for the <see cref="ImageMetadata"/> and <see cref="ImageFrameMetadata"/> types.
+/// </summary>
+public static class ImageMetadataExtensions
+{
+<#
+    foreach (string fmt in formats)
+    {
+#>
+    /// <summary>
+    /// Gets the <see cref="<#= fmt #>Metadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>
+    /// The <see cref="<#= fmt #>Metadata"/>
+    /// </returns>
+    public static <#= fmt #>Metadata Get<#= fmt #>Metadata(this ImageMetadata source) => source.GetFormatMetadata(<#= fmt #>Format.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="<#= fmt #>Metadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="Get<#= fmt #>Metadata(ImageMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image metadata.</param>
+    /// <returns>The new <see cref="<#= fmt #>Metadata"/></returns>
+    public static <#= fmt #>Metadata Clone<#= fmt #>Metadata(this ImageMetadata source) => source.CloneFormatMetadata(<#= fmt #>Format.Instance);
+
+<#
+    }
+#>
+<#
+    foreach (string fmt in frameFormats)
+    {
+#>
+
+    /// <summary>
+    /// Gets the <see cref="<#= fmt #>FrameMetadata"/> from <paramref name="source"/>.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>
+    /// The <see cref="<#= fmt #>FrameMetadata"/>
+    /// </returns>
+    public static <#= fmt #>FrameMetadata Get<#= fmt #>Metadata(this ImageFrameMetadata source) => source.GetFormatMetadata(<#= fmt #>Format.Instance);
+
+    /// <summary>
+    /// Creates a new cloned instance of <see cref="<#= fmt #>Metadata"/> from the <paramref name="source"/>.
+    /// The instance is created via <see cref="Get<#= fmt #>Metadata(ImageFrameMetadata)"/>
+    /// </summary>
+    /// <param name="source">The image frame metadata.</param>
+    /// <returns>The new <see cref="<#= fmt #>FrameMetadata"/></returns>
+    public static <#= fmt #>FrameMetadata Clone<#= fmt #>Metadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(<#= fmt #>Format.Instance);
+<#
+    }
+#>
+}
diff --git a/src/ImageSharp/Formats/_Generated/_Formats.ttinclude b/src/ImageSharp/Formats/_Generated/_Formats.ttinclude
new file mode 100644
index 0000000000..89940d406e
--- /dev/null
+++ b/src/ImageSharp/Formats/_Generated/_Formats.ttinclude
@@ -0,0 +1,28 @@
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core" #>
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+<#+
+    private static readonly string[] formats = new []{
+        "Bmp",
+        "Cur",
+        "Gif",
+        "Ico",
+        "Jpeg",
+        "Pbm",
+        "Png",
+        "Qoi",
+        "Tga",
+        "Tiff",
+        "Webp",
+    };
+
+    private static readonly string[] frameFormats = new []{
+        "Cur",
+        "Ico",
+        "Gif",
+        "Png",
+        "Tiff",
+        "Webp",
+    };
+#>
diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs
index 03a19a4be8..d4f773abe1 100644
--- a/src/ImageSharp/Image.cs
+++ b/src/ImageSharp/Image.cs
@@ -157,6 +157,28 @@ public Image<TPixel2> CloneAs<TPixel2>()
     public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
         where TPixel2 : unmanaged, IPixel<TPixel2>;
 
+    /// <summary>
+    /// Synchronizes any embedded metadata profiles with the current image properties.
+    /// </summary>
+    public void SynchronizeMetadata()
+    {
+        this.Metadata.SynchronizeProfiles();
+        foreach (ImageFrame frame in this.Frames)
+        {
+            frame.Metadata.SynchronizeProfiles();
+        }
+    }
+
+    /// <summary>
+    /// Synchronizes any embedded metadata profiles with the current image properties.
+    /// </summary>
+    /// <param name="action">A synchronization action to run in addition to the default process.</param>
+    public void SynchronizeMetadata(Action<Image> action)
+    {
+        this.SynchronizeMetadata();
+        action(this);
+    }
+
     /// <summary>
     /// Update the size of the image after mutation.
     /// </summary>
diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs
index c0d1f27ca4..0bbd73b63a 100644
--- a/src/ImageSharp/ImageInfo.cs
+++ b/src/ImageSharp/ImageInfo.cs
@@ -14,40 +14,43 @@ public class ImageInfo
     /// <summary>
     /// Initializes a new instance of the <see cref="ImageInfo"/> class.
     /// </summary>
-    /// <param name="pixelType">The pixel type information.</param>
     /// <param name="size">The size of the image in px units.</param>
     /// <param name="metadata">The image metadata.</param>
     public ImageInfo(
-        PixelTypeInfo pixelType,
         Size size,
-        ImageMetadata? metadata)
-        : this(pixelType, size, metadata, null)
+        ImageMetadata metadata)
+        : this(size, metadata, null)
     {
     }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="ImageInfo"/> class.
     /// </summary>
-    /// <param name="pixelType">The pixel type information.</param>
     /// <param name="size">The size of the image in px units.</param>
     /// <param name="metadata">The image metadata.</param>
     /// <param name="frameMetadataCollection">The collection of image frame metadata.</param>
     public ImageInfo(
-        PixelTypeInfo pixelType,
         Size size,
-        ImageMetadata? metadata,
+        ImageMetadata metadata,
         IReadOnlyList<ImageFrameMetadata>? frameMetadataCollection)
     {
-        this.PixelType = pixelType;
         this.Size = size;
-        this.Metadata = metadata ?? new ImageMetadata();
-        this.FrameMetadataCollection = frameMetadataCollection ?? Array.Empty<ImageFrameMetadata>();
+        this.Metadata = metadata;
+
+        // PixelTpe is normally set following decoding
+        // See ImageDecoder.SetDecoderFormat(Configuration configuration, ImageInfo info).
+        if (metadata.DecodedImageFormat is not null)
+        {
+            this.PixelType = metadata.GetDecodedPixelTypeInfo();
+        }
+
+        this.FrameMetadataCollection = frameMetadataCollection ?? [];
     }
 
     /// <summary>
     /// Gets information about the image pixels.
     /// </summary>
-    public PixelTypeInfo PixelType { get; }
+    public PixelTypeInfo PixelType { get; internal set; }
 
     /// <summary>
     /// Gets the image width in px units.
@@ -59,6 +62,11 @@ public ImageInfo(
     /// </summary>
     public int Height => this.Size.Height;
 
+    /// <summary>
+    /// Gets the number of frames in the image.
+    /// </summary>
+    public int FrameCount => this.FrameMetadataCollection.Count;
+
     /// <summary>
     /// Gets any metadata associated with the image.
     /// </summary>
@@ -72,10 +80,10 @@ public ImageInfo(
     /// <summary>
     /// Gets the size of the image in px units.
     /// </summary>
-    public Size Size { get; internal set; }
+    public Size Size { get; }
 
     /// <summary>
     /// Gets the bounds of the image.
     /// </summary>
-    public Rectangle Bounds => new(0, 0, this.Width, this.Height);
+    public Rectangle Bounds => new(Point.Empty, this.Size);
 }
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index 6096bd33e3..d3c4034717 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -52,6 +52,11 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Compile Update="Formats\_Generated\ImageMetadataExtensions.cs">
+      <DesignTime>True</DesignTime>
+      <AutoGen>True</AutoGen>
+      <DependentUpon>ImageMetadataExtensions.tt</DependentUpon>
+    </Compile>
     <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
       <DesignTime>True</DesignTime>
       <AutoGen>True</AutoGen>
@@ -142,7 +147,7 @@
       <AutoGen>True</AutoGen>
       <DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon>
     </Compile>
-    <Compile Update="Formats\ImageExtensions.Save.cs">
+    <Compile Update="Formats\_Generated\ImageExtensions.Save.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
       <DependentUpon>ImageExtensions.Save.tt</DependentUpon>
@@ -150,6 +155,10 @@
   </ItemGroup>
 
   <ItemGroup>
+    <None Update="Formats\_Generated\ImageMetadataExtensions.tt">
+      <LastGenOutput>ImageMetadataExtensions.cs</LastGenOutput>
+      <Generator>TextTemplatingFileGenerator</Generator>
+    </None>
     <None Update="Formats\Jpeg\Components\Block8x8F.Generated.tt">
       <Generator>TextTemplatingFileGenerator</Generator>
       <LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
@@ -222,7 +231,7 @@
       <LastGenOutput>DefaultPixelBlenders.Generated.cs</LastGenOutput>
       <Generator>TextTemplatingFileGenerator</Generator>
     </None>
-    <None Update="Formats\ImageExtensions.Save.tt">
+    <None Update="Formats\_Generated\ImageExtensions.Save.tt">
       <Generator>TextTemplatingFileGenerator</Generator>
       <LastGenOutput>ImageExtensions.Save.cs</LastGenOutput>
     </None>
diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs
index 562e478031..9c0de1edbe 100644
--- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Metadata;
 /// </summary>
 public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
 {
-    private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetadata = new();
+    private readonly Dictionary<IImageFormat, IFormatFrameMetadata> formatMetadata = [];
 
     /// <summary>
     /// Initializes a new instance of the <see cref="ImageFrameMetadata"/> class.
@@ -35,9 +35,9 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
     {
         DebugGuard.NotNull(other, nameof(other));
 
-        foreach (KeyValuePair<IImageFormat, IDeepCloneable> meta in other.formatMetadata)
+        foreach (KeyValuePair<IImageFormat, IFormatFrameMetadata> meta in other.formatMetadata)
         {
-            this.formatMetadata.Add(meta.Key, meta.Value.DeepClone());
+            this.formatMetadata.Add(meta.Key, (IFormatFrameMetadata)meta.Value.DeepClone());
         }
 
         this.ExifProfile = other.ExifProfile?.DeepClone();
@@ -45,6 +45,10 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
         this.IptcProfile = other.IptcProfile?.DeepClone();
         this.XmpProfile = other.XmpProfile?.DeepClone();
         this.CicpProfile = other.CicpProfile?.DeepClone();
+
+        // NOTE: This clone is actually shallow but we share the same format
+        // instances for all images in the configuration.
+        this.DecodedImageFormat = other.DecodedImageFormat;
     }
 
     /// <summary>
@@ -72,12 +76,19 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
     /// </summary>
     public CicpProfile? CicpProfile { get; set; }
 
+    /// <summary>
+    /// Gets the original format, if any, the image was decode from.
+    /// </summary>
+    public IImageFormat? DecodedImageFormat { get; internal set; }
+
     /// <inheritdoc/>
     public ImageFrameMetadata DeepClone() => new(this);
 
     /// <summary>
-    /// Gets the metadata value associated with the specified key. This method will always return a result creating
-    /// a new instance and binding it to the frame metadata if none is found.
+    /// Gets the metadata value associated with the specified key.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
     /// </summary>
     /// <typeparam name="TFormatMetadata">The type of format metadata.</typeparam>
     /// <typeparam name="TFormatFrameMetadata">The type of format frame metadata.</typeparam>
@@ -87,13 +98,22 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
     /// </returns>
     public TFormatFrameMetadata GetFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key)
         where TFormatMetadata : class
-        where TFormatFrameMetadata : class, IDeepCloneable
+        where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata>
     {
-        if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
+        if (this.formatMetadata.TryGetValue(key, out IFormatFrameMetadata? meta))
         {
             return (TFormatFrameMetadata)meta;
         }
 
+        // None found. Check if we have a decoded format to convert from.
+        if (this.DecodedImageFormat is not null
+            && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatFrameMetadata? decodedMetadata))
+        {
+            TFormatFrameMetadata derivedMeta = TFormatFrameMetadata.FromFormatConnectingFrameMetadata(decodedMetadata.ToFormatConnectingFrameMetadata());
+            this.formatMetadata[key] = derivedMeta;
+            return derivedMeta;
+        }
+
         TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata();
         this.formatMetadata[key] = newMeta;
         return newMeta;
@@ -101,34 +121,26 @@ public TFormatFrameMetadata GetFormatMetadata<TFormatMetadata, TFormatFrameMetad
 
     internal void SetFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key, TFormatFrameMetadata value)
         where TFormatMetadata : class
-        where TFormatFrameMetadata : class, IDeepCloneable
+        where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata>
         => this.formatMetadata[key] = value;
 
     /// <summary>
-    /// Gets the metadata value associated with the specified key.
+    /// Creates a new instance the metadata value associated with the specified key.
+    /// The instance is created from a clone generated via <see cref="GetFormatMetadata{TFormatMetadata, TFormatFrameMetadata}(IImageFormat{TFormatMetadata, TFormatFrameMetadata})"/>.
     /// </summary>
-    /// <typeparam name="TFormatMetadata">The type of format metadata.</typeparam>
+    /// <typeparam name="TFormatMetadata">The type of metadata.</typeparam>
     /// <typeparam name="TFormatFrameMetadata">The type of format frame metadata.</typeparam>
     /// <param name="key">The key of the value to get.</param>
-    /// <param name="metadata">
-    /// When this method returns, contains the metadata associated with the specified key,
-    /// if the key is found; otherwise, the default value for the type of the metadata parameter.
-    /// This parameter is passed uninitialized.
-    /// </param>
     /// <returns>
-    /// <see langword="true"/> if the frame metadata exists for the specified key; otherwise, <see langword="false"/>.
+    /// The <typeparamref name="TFormatMetadata"/>.
     /// </returns>
-    public bool TryGetFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key, out TFormatFrameMetadata? metadata)
+    public TFormatFrameMetadata CloneFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key)
         where TFormatMetadata : class
-        where TFormatFrameMetadata : class, IDeepCloneable
-    {
-        if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
-        {
-            metadata = (TFormatFrameMetadata)meta;
-            return true;
-        }
+        where TFormatFrameMetadata : class, IFormatFrameMetadata<TFormatFrameMetadata>
+        => ((IDeepCloneable<TFormatFrameMetadata>)this.GetFormatMetadata(key)).DeepClone();
 
-        metadata = default;
-        return false;
-    }
+    /// <summary>
+    /// Synchronizes the profiles with the current metadata.
+    /// </summary>
+    internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this);
 }
diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index 0c1d05d50e..37557ba1dc 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -7,6 +7,7 @@
 using SixLabors.ImageSharp.Metadata.Profiles.Icc;
 using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
 using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
+using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Metadata;
 
@@ -33,7 +34,7 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
     /// </summary>
     public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch;
 
-    private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetadata = new();
+    private readonly Dictionary<IImageFormat, IFormatMetadata> formatMetadata = [];
     private double horizontalResolution;
     private double verticalResolution;
 
@@ -60,9 +61,9 @@ private ImageMetadata(ImageMetadata other)
         this.VerticalResolution = other.VerticalResolution;
         this.ResolutionUnits = other.ResolutionUnits;
 
-        foreach (KeyValuePair<IImageFormat, IDeepCloneable> meta in other.formatMetadata)
+        foreach (KeyValuePair<IImageFormat, IFormatMetadata> meta in other.formatMetadata)
         {
-            this.formatMetadata.Add(meta.Key, meta.Value.DeepClone());
+            this.formatMetadata.Add(meta.Key, (IFormatMetadata)meta.Value.DeepClone());
         }
 
         this.ExifProfile = other.ExifProfile?.DeepClone();
@@ -170,7 +171,10 @@ public double VerticalResolution
     public IImageFormat? DecodedImageFormat { get; internal set; }
 
     /// <summary>
-    /// Gets the metadata value associated with the specified key.
+    /// Gets the metadata value associated with the specified key.<br/>
+    /// If none is found, an instance is created either by conversion from the decoded image format metadata
+    /// or the requested format default constructor.
+    /// This instance will be added to the metadata for future requests.
     /// </summary>
     /// <typeparam name="TFormatMetadata">The type of metadata.</typeparam>
     /// <param name="key">The key of the value to get.</param>
@@ -178,46 +182,44 @@ public double VerticalResolution
     /// The <typeparamref name="TFormatMetadata"/>.
     /// </returns>
     public TFormatMetadata GetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key)
-         where TFormatMetadata : class, IDeepCloneable
+         where TFormatMetadata : class, IFormatMetadata<TFormatMetadata>
     {
-        if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
+        // Check for existing metadata.
+        if (this.formatMetadata.TryGetValue(key, out IFormatMetadata? meta))
         {
             return (TFormatMetadata)meta;
         }
 
+        // None found. Check if we have a decoded format to convert from.
+        if (this.DecodedImageFormat is not null
+            && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatMetadata? decodedMetadata))
+        {
+            TFormatMetadata derivedMeta = TFormatMetadata.FromFormatConnectingMetadata(decodedMetadata.ToFormatConnectingMetadata());
+            this.formatMetadata[key] = derivedMeta;
+            return derivedMeta;
+        }
+
+        // Fall back to a default instance.
         TFormatMetadata newMeta = key.CreateDefaultFormatMetadata();
         this.formatMetadata[key] = newMeta;
         return newMeta;
     }
 
     /// <summary>
-    /// Gets the metadata value associated with the specified key.
+    /// Creates a new instance the metadata value associated with the specified key.
+    /// The instance is created from a clone generated via <see cref="GetFormatMetadata{TFormatMetadata}(IImageFormat{TFormatMetadata})"/>.
     /// </summary>
-    /// <typeparam name="TFormatMetadata">The type of format metadata.</typeparam>
+    /// <typeparam name="TFormatMetadata">The type of metadata.</typeparam>
     /// <param name="key">The key of the value to get.</param>
-    /// <param name="metadata">
-    /// When this method returns, contains the metadata associated with the specified key,
-    /// if the key is found; otherwise, the default value for the type of the metadata parameter.
-    /// This parameter is passed uninitialized.
-    /// </param>
     /// <returns>
-    /// <see langword="true"/> if the frame metadata exists for the specified key; otherwise, <see langword="false"/>.
+    /// The <typeparamref name="TFormatMetadata"/>.
     /// </returns>
-    public bool TryGetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key, out TFormatMetadata? metadata)
-        where TFormatMetadata : class, IDeepCloneable
-    {
-        if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
-        {
-            metadata = (TFormatMetadata)meta;
-            return true;
-        }
-
-        metadata = default;
-        return false;
-    }
+    public TFormatMetadata CloneFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key)
+        where TFormatMetadata : class, IFormatMetadata<TFormatMetadata>
+        => ((IDeepCloneable<TFormatMetadata>)this.GetFormatMetadata(key)).DeepClone();
 
     internal void SetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key, TFormatMetadata value)
-        where TFormatMetadata : class, IDeepCloneable
+        where TFormatMetadata : class, IFormatMetadata<TFormatMetadata>
         => this.formatMetadata[key] = value;
 
     /// <inheritdoc/>
@@ -226,5 +228,18 @@ internal void SetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> k
     /// <summary>
     /// Synchronizes the profiles with the current metadata.
     /// </summary>
-    internal void SyncProfiles() => this.ExifProfile?.Sync(this);
+    internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this);
+
+    internal PixelTypeInfo GetDecodedPixelTypeInfo()
+    {
+        // None found. Check if we have a decoded format to convert from.
+        if (this.DecodedImageFormat is not null
+            && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatMetadata? decodedMetadata))
+        {
+            return decodedMetadata.GetPixelTypeInfo();
+        }
+
+        // This should never happen.
+        return default;
+    }
 }
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index dd5792ae79..41d3c293b6 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -298,6 +298,17 @@ internal void Sync(ImageMetadata metadata)
         this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution);
     }
 
+    /// <summary>
+    /// Synchronizes the profiles with the specified metadata.
+    /// </summary>
+    /// <param name="metadata">The metadata.</param>
+#pragma warning disable CA1822, RCS1163, IDE0060
+    internal void Sync(ImageFrameMetadata metadata)
+#pragma warning restore IDE0060, RCS1163, CA1822
+    {
+        // Nothing to do ....YET.
+    }
+
     private void SyncResolution(ExifTag<Rational> tag, double resolution)
     {
         if (!this.TryGetValue(tag, out IExifValue<Rational>? value))
diff --git a/src/ImageSharp/PixelFormats/PixelColorType.cs b/src/ImageSharp/PixelFormats/PixelColorType.cs
index 9ac2c308c2..315c0f6584 100644
--- a/src/ImageSharp/PixelFormats/PixelColorType.cs
+++ b/src/ImageSharp/PixelFormats/PixelColorType.cs
@@ -9,6 +9,11 @@ namespace SixLabors.ImageSharp.PixelFormats;
 [Flags]
 public enum PixelColorType
 {
+    /// <summary>
+    /// No color type.
+    /// </summary>
+    None = 0,
+
     /// <summary>
     /// Represents the Red component of the color.
     /// </summary>
@@ -30,17 +35,82 @@ public enum PixelColorType
     Alpha = 1 << 3,
 
     /// <summary>
-    /// Indicates that the color is in grayscale.
+    /// Represents the Exponent component used in formats like R9G9B9E5.
+    /// </summary>
+    Exponent = 1 << 4,
+
+    /// <summary>
+    /// Indicates that the color is in luminance (grayscale) format.
     /// </summary>
-    Grayscale = 1 << 4,
+    Luminance = 1 << 5,
+
+    /// <summary>
+    /// Indicates that the color is in binary (black and white) format.
+    /// </summary>
+    Binary = 1 << 6,
+
+    /// <summary>
+    /// Indicates that the color is indexed using a palette.
+    /// </summary>
+    Indexed = 1 << 7,
 
     /// <summary>
     /// Indicates that the color is in RGB (Red, Green, Blue) format.
     /// </summary>
-    RGB = Red | Green | Blue | (1 << 5),
+    RGB = Red | Green | Blue | (1 << 8),
 
     /// <summary>
     /// Indicates that the color is in BGR (Blue, Green, Red) format.
     /// </summary>
-    BGR = Blue | Green | Red | (1 << 6)
+    BGR = Blue | Green | Red | (1 << 9),
+
+    /// <summary>
+    /// Represents the Chrominance Blue component.
+    /// </summary>
+    ChrominanceBlue = 1 << 10,
+
+    /// <summary>
+    /// Represents the Chrominance Red component.
+    /// </summary>
+    ChrominanceRed = 1 << 11,
+
+    /// <summary>
+    /// Indicates that the color is in YCbCr (Luminance, Chrominance Blue, Chrominance Red) format.
+    /// </summary>
+    YCbCr = Luminance | ChrominanceBlue | ChrominanceRed | (1 << 12),
+
+    /// <summary>
+    /// Represents the Cyan component in CMYK.
+    /// </summary>
+    Cyan = 1 << 13,
+
+    /// <summary>
+    /// Represents the Magenta component in CMYK.
+    /// </summary>
+    Magenta = 1 << 14,
+
+    /// <summary>
+    /// Represents the Yellow component in CMYK.
+    /// </summary>
+    Yellow = 1 << 15,
+
+    /// <summary>
+    /// Represents the Key (black) component in CMYK and YCCK.
+    /// </summary>
+    Key = 1 << 16,
+
+    /// <summary>
+    /// Indicates that the color is in CMYK (Cyan, Magenta, Yellow, Key) format.
+    /// </summary>
+    CMYK = Cyan | Magenta | Yellow | Key,
+
+    /// <summary>
+    /// Indicates that the color is in YCCK (Luminance, Chrominance Blue, Chrominance Red, Key) format.
+    /// </summary>
+    YCCK = Luminance | ChrominanceBlue | ChrominanceRed | Key,
+
+    /// <summary>
+    /// Indicates that the color is of a type not specified in this enum.
+    /// </summary>
+    Other = 1 << 17
 }
diff --git a/src/ImageSharp/PixelFormats/PixelComponentInfo.cs b/src/ImageSharp/PixelFormats/PixelComponentInfo.cs
index a761054144..1444b344b6 100644
--- a/src/ImageSharp/PixelFormats/PixelComponentInfo.cs
+++ b/src/ImageSharp/PixelFormats/PixelComponentInfo.cs
@@ -41,10 +41,21 @@ private PixelComponentInfo(int count, int padding, long precisionData1, long pre
     /// <exception cref="ArgumentOutOfRangeException">The component precision and index cannot exceed the component range.</exception>
     public static PixelComponentInfo Create<TPixel>(int count, params int[] precision)
         where TPixel : unmanaged, IPixel<TPixel>
+        => Create(count, Unsafe.SizeOf<TPixel>() * 8, precision);
+
+    /// <summary>
+    /// Creates a new <see cref="PixelComponentInfo"/> instance.
+    /// </summary>
+    /// <param name="count">The number of components within the pixel format.</param>
+    /// <param name="bitsPerPixel">The number of bits per pixel.</param>
+    /// <param name="precision">The precision in bits of each component.</param>
+    /// <returns>The <see cref="PixelComponentInfo"/>.</returns>
+    /// <exception cref="ArgumentOutOfRangeException">The component precision and index cannot exceed the component range.</exception>
+    public static PixelComponentInfo Create(int count, int bitsPerPixel, params int[] precision)
     {
-        if (precision.Length != count || precision.Length > 16)
+        if (precision.Length < count || precision.Length > 16)
         {
-            throw new ArgumentException($"Count must match the length of precision array and cannot exceed 16.");
+            throw new ArgumentOutOfRangeException(nameof(count), $"Count {count} must match the length of precision array and cannot exceed 16.");
         }
 
         long precisionData1 = 0;
@@ -55,7 +66,7 @@ public static PixelComponentInfo Create<TPixel>(int count, params int[] precisio
             int p = precision[i];
             if (p is < 0 or > 255)
             {
-                throw new ArgumentException("Precision must be between 0 and 255.");
+                throw new ArgumentOutOfRangeException(nameof(precision), $"Precision {precision.Length} must be between 0 and 255.");
             }
 
             if (i < 8)
@@ -70,7 +81,7 @@ public static PixelComponentInfo Create<TPixel>(int count, params int[] precisio
             sum += p;
         }
 
-        return new PixelComponentInfo(count, (Unsafe.SizeOf<TPixel>() * 8) - sum, precisionData1, precisionData2);
+        return new PixelComponentInfo(count, bitsPerPixel - sum, precisionData1, precisionData2);
     }
 
     /// <summary>
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs
index 2b5241b0bc..64a22060c0 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs
@@ -71,7 +71,7 @@ public readonly Vector4 ToVector4()
     public static PixelTypeInfo GetPixelTypeInfo()
         => PixelTypeInfo.Create<L16>(
             PixelComponentInfo.Create<L16>(1, 16),
-            PixelColorType.Grayscale,
+            PixelColorType.Luminance,
             PixelAlphaRepresentation.None);
 
     /// <inheritdoc />
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs
index 5d733bdbb3..cf8646cfa0 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs
@@ -73,7 +73,7 @@ public readonly Vector4 ToVector4()
     public static PixelTypeInfo GetPixelTypeInfo()
         => PixelTypeInfo.Create<L8>(
             PixelComponentInfo.Create<L8>(1, 8),
-            PixelColorType.Grayscale,
+            PixelColorType.Luminance,
             PixelAlphaRepresentation.None);
 
     /// <inheritdoc />
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs
index 69ca662187..026d0c299d 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs
@@ -100,7 +100,7 @@ public readonly Vector4 ToVector4()
     public static PixelTypeInfo GetPixelTypeInfo()
         => PixelTypeInfo.Create<La16>(
             PixelComponentInfo.Create<La16>(2, 8, 8),
-            PixelColorType.Grayscale | PixelColorType.Alpha,
+            PixelColorType.Luminance | PixelColorType.Alpha,
             PixelAlphaRepresentation.Unassociated);
 
     /// <inheritdoc/>
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs
index 1886ef39a1..0ddcf16a1f 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs
@@ -97,7 +97,7 @@ public readonly Vector4 ToVector4()
     public static PixelTypeInfo GetPixelTypeInfo()
         => PixelTypeInfo.Create<La32>(
             PixelComponentInfo.Create<La32>(2, 16, 16),
-            PixelColorType.Grayscale | PixelColorType.Alpha,
+            PixelColorType.Luminance | PixelColorType.Alpha,
             PixelAlphaRepresentation.Unassociated);
 
     /// <inheritdoc/>
diff --git a/src/ImageSharp/PixelFormats/PixelTypeInfo.cs b/src/ImageSharp/PixelFormats/PixelTypeInfo.cs
index 7cd1284f48..7865b9900e 100644
--- a/src/ImageSharp/PixelFormats/PixelTypeInfo.cs
+++ b/src/ImageSharp/PixelFormats/PixelTypeInfo.cs
@@ -31,13 +31,13 @@ public readonly struct PixelTypeInfo(int bitsPerPixel)
     /// <summary>
     /// Gets the pixel color type.
     /// </summary>
-    public PixelColorType? ColorType { get; init; }
+    public PixelColorType ColorType { get; init; }
 
     /// <summary>
     /// Gets the pixel alpha transparency behavior.
     /// <see langword="null"/> means unknown, unspecified.
     /// </summary>
-    public PixelAlphaRepresentation? AlphaRepresentation { get; init; }
+    public PixelAlphaRepresentation AlphaRepresentation { get; init; }
 
     /// <summary>
     /// Creates a new <see cref="PixelTypeInfo"/> instance.
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
index d762e8e95e..deb3125b30 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
@@ -39,7 +39,7 @@ public void SetupImageSharp()
         using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
 
         this.imageImageSharp = Image.Load<Rgba32>(imageBinaryStream);
-        this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 };
+        this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
 
         this.destinationStream = new MemoryStream();
     }
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
index 98eb0b54dc..0692c5a3b5 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
@@ -20,19 +20,19 @@ public class EncodeJpegFeatures
     // No metadata
     private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
 
-    public static IEnumerable<JpegEncodingColor> ColorSpaceValues => new[]
+    public static IEnumerable<JpegColorType> ColorSpaceValues => new[]
     {
-        JpegEncodingColor.Luminance,
-        JpegEncodingColor.Rgb,
-        JpegEncodingColor.YCbCrRatio420,
-        JpegEncodingColor.YCbCrRatio444,
+        JpegColorType.Luminance,
+        JpegColorType.Rgb,
+        JpegColorType.YCbCrRatio420,
+        JpegColorType.YCbCrRatio444,
     };
 
     [Params(75, 90, 100)]
     public int Quality;
 
     [ParamsSource(nameof(ColorSpaceValues), Priority = -100)]
-    public JpegEncodingColor TargetColorSpace;
+    public JpegColorType TargetColorSpace;
 
     private Image<Rgb24> bmpCore;
     private JpegEncoder encoder;
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index ffc2a7aefd..94cfe85ee5 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -4,6 +4,7 @@
 using Microsoft.DotNet.RemoteExecutor;
 using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
@@ -112,7 +113,7 @@ public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider
     {
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
+        image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance));
     }
 
     [Theory]
@@ -219,7 +220,7 @@ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRe
         image.DebugSave(provider);
         if (TestEnvironment.IsWindows)
         {
-            image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
+            image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance));
         }
     }
 
@@ -232,7 +233,7 @@ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecode
         BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance, options);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -251,7 +252,7 @@ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit<TPixel>(TestImageProvider
         BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette };
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance, options);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -298,7 +299,7 @@ public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvide
     {
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -314,7 +315,7 @@ public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageP
         // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
         // The total difference without the alpha channel is still: 0.0204%
         // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation.
-        image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -327,7 +328,7 @@ public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider
         image.DebugSave(provider);
 
         // Do not validate. Reference files will fail validation.
-        image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
+        image.CompareToOriginal(provider, new MagickReferenceDecoder(PngFormat.Instance, false));
     }
 
     [Theory]
@@ -347,7 +348,7 @@ public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TP
     {
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -394,7 +395,7 @@ public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> pro
     {
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
@@ -404,7 +405,7 @@ public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<T
     {
         using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
         image.DebugSave(provider);
-        image.CompareToOriginal(provider, new MagickReferenceDecoder());
+        image.CompareToOriginal(provider, MagickReferenceDecoder.Png);
     }
 
     [Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index 42cbd90f3b..d68ec47557 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -20,11 +20,11 @@ public class BmpEncoderTests
     private static BmpEncoder BmpEncoder => new();
 
     public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
-    new()
-    {
-        BmpBitsPerPixel.Pixel24,
-        BmpBitsPerPixel.Pixel32
-    };
+        new()
+        {
+            BmpBitsPerPixel.Bit24,
+            BmpBitsPerPixel.Bit32
+        };
 
     public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
     new()
@@ -37,13 +37,13 @@ public class BmpEncoderTests
     public static readonly TheoryData<string, BmpBitsPerPixel> BmpBitsPerPixelFiles =
     new()
     {
-        { Bit1, BmpBitsPerPixel.Pixel1 },
-        { Bit2, BmpBitsPerPixel.Pixel2 },
-        { Bit4, BmpBitsPerPixel.Pixel4 },
-        { Bit8, BmpBitsPerPixel.Pixel8 },
-        { Rgb16, BmpBitsPerPixel.Pixel16 },
-        { Car, BmpBitsPerPixel.Pixel24 },
-        { Bit32Rgb, BmpBitsPerPixel.Pixel32 }
+        { Bit1, BmpBitsPerPixel.Bit1 },
+        { Bit2, BmpBitsPerPixel.Bit2 },
+        { Bit4, BmpBitsPerPixel.Bit4 },
+        { Bit8, BmpBitsPerPixel.Bit8 },
+        { Rgb16, BmpBitsPerPixel.Bit16 },
+        { Car, BmpBitsPerPixel.Bit24 },
+        { Bit32Rgb, BmpBitsPerPixel.Bit32 }
     };
 
     [Fact]
@@ -97,61 +97,61 @@ public void Encode_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> pro
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel);
 
     [Theory]
-    [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
+    [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
     public void Encode_32Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
 
         // If supportTransparency is false, a v3 bitmap header will be written.
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
 
     [Theory]
-    [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
-    [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
+    [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
+    [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)]
     public void Encode_32Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
     [Theory]
-    [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] // WinBmpv3 is a 24 bits per pixel image.
-    [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
+    [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)] // WinBmpv3 is a 24 bits per pixel image.
+    [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)]
     public void Encode_24Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
 
     [Theory]
-    [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
-    [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
+    [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)]
+    [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)]
     public void Encode_24Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
     [Theory]
-    [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
-    [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
+    [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)]
+    [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)]
     public void Encode_16Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
 
     [Theory]
-    [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
-    [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
+    [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)]
+    [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)]
     public void Encode_16Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
     [Theory]
-    [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
-    [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
+    [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)]
+    [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)]
     public void Encode_8Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
 
     [Theory]
-    [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
-    [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
+    [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)]
+    [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)]
     public void Encode_8Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
     [Theory]
-    [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)]
+    [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Bit8)]
     public void Encode_8BitGray_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> =>
         TestBmpEncoderCore(
@@ -160,7 +160,7 @@ public void Encode_8BitGray_WithV3Header_Works<TPixel>(TestImageProvider<TPixel>
             supportTransparency: false);
 
     [Theory]
-    [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)]
+    [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit4)]
     public void Encode_4Bit_WithV3Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
@@ -176,7 +176,7 @@ public void Encode_4Bit_WithV3Header_Works<TPixel>(
     }
 
     [Theory]
-    [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)]
+    [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit4)]
     public void Encode_4Bit_WithV4Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
@@ -192,7 +192,7 @@ public void Encode_4Bit_WithV4Header_Works<TPixel>(
     }
 
     [Theory]
-    [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
+    [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Bit2)]
     public void Encode_2Bit_WithV3Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
@@ -214,7 +214,7 @@ public void Encode_2Bit_WithV3Header_Works<TPixel>(
     }
 
     [Theory]
-    [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
+    [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Bit2)]
     public void Encode_2Bit_WithV4Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
@@ -236,21 +236,21 @@ public void Encode_2Bit_WithV4Header_Works<TPixel>(
     }
 
     [Theory]
-    [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
+    [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Bit1)]
     public void Encode_1Bit_WithV3Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
 
     [Theory]
-    [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
+    [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Bit1)]
     public void Encode_1Bit_WithV4Header_Works<TPixel>(
         TestImageProvider<TPixel> provider,
         BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
     [Theory]
-    [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)]
+    [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Bit8)]
     public void Encode_8BitGray_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> =>
         TestBmpEncoderCore(
@@ -271,7 +271,7 @@ public void Encode_8BitColor_WithWuQuantizer<TPixel>(TestImageProvider<TPixel> p
         using Image<TPixel> image = provider.GetImage();
         BmpEncoder encoder = new()
         {
-            BitsPerPixel = BmpBitsPerPixel.Pixel8,
+            BitsPerPixel = BmpBitsPerPixel.Bit8,
             Quantizer = new WuQuantizer()
         };
 
@@ -287,7 +287,7 @@ public void Encode_8BitColor_WithWuQuantizer<TPixel>(TestImageProvider<TPixel> p
             provider,
             extension: "bmp",
             appendPixelTypeToFileName: false,
-            decoder: new MagickReferenceDecoder(false));
+            decoder: new MagickReferenceDecoder(BmpFormat.Instance, false));
     }
 
     [Theory]
@@ -303,7 +303,7 @@ public void Encode_8BitColor_WithOctreeQuantizer<TPixel>(TestImageProvider<TPixe
         using Image<TPixel> image = provider.GetImage();
         BmpEncoder encoder = new()
         {
-            BitsPerPixel = BmpBitsPerPixel.Pixel8,
+            BitsPerPixel = BmpBitsPerPixel.Bit8,
             Quantizer = new OctreeQuantizer()
         };
         string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
@@ -318,12 +318,12 @@ public void Encode_8BitColor_WithOctreeQuantizer<TPixel>(TestImageProvider<TPixe
             provider,
             extension: "bmp",
             appendPixelTypeToFileName: false,
-            decoder: new MagickReferenceDecoder(false));
+            decoder: new MagickReferenceDecoder(BmpFormat.Instance, false));
     }
 
     [Theory]
-    [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
-    [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
+    [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)]
+    [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)]
     public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
 
@@ -364,8 +364,8 @@ public void Encode_WorksWithSizeGreaterThen65k(int width, int height)
     }
 
     [Theory]
-    [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
-    [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
+    [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)]
+    [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)]
     public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel>
     {
@@ -374,14 +374,14 @@ public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixe
     }
 
     [Theory]
-    [WithFile(BlackWhitePalletDataMatrix, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel1)]
+    [WithFile(BlackWhitePalletDataMatrix, PixelTypes.Rgb24, BmpBitsPerPixel.Bit1)]
     public void Encode_Issue2467<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage();
 
-        using var reencodedStream = new MemoryStream();
-        var encoder = new BmpEncoder
+        using MemoryStream reencodedStream = new();
+        BmpEncoder encoder = new()
         {
             BitsPerPixel = bitsPerPixel,
             SupportTransparency = false,
@@ -409,7 +409,7 @@ private static void TestBmpEncoderCore<TPixel>(
         using Image<TPixel> image = provider.GetImage();
 
         // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
-        if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
+        if (bitsPerPixel != BmpBitsPerPixel.Bit32)
         {
             image.Mutate(c => c.MakeOpaque());
         }
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
index 1d84356713..64564ae1d8 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
@@ -15,10 +15,10 @@ public class BmpMetadataTests
     public void CloneIsDeep()
     {
         BmpMetadata meta = new()
-        { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 };
-        BmpMetadata clone = (BmpMetadata)meta.DeepClone();
+        { BitsPerPixel = BmpBitsPerPixel.Bit24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 };
+        BmpMetadata clone = meta.DeepClone();
 
-        clone.BitsPerPixel = BmpBitsPerPixel.Pixel32;
+        clone.BitsPerPixel = BmpBitsPerPixel.Bit32;
         clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2;
 
         Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel));
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index a7e16f7737..77ac51e8a1 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Formats.Webp;
@@ -113,7 +114,7 @@ public void EncodeGlobalPaletteReturnsSmallerFile<TPixel>(TestImageProvider<TPix
         using Image<TPixel> image = provider.GetImage();
         GifEncoder encoder = new()
         {
-            ColorTableMode = GifColorTableMode.Global,
+            ColorTableMode = FrameColorTableMode.Global,
             Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null })
         };
 
@@ -122,7 +123,7 @@ public void EncodeGlobalPaletteReturnsSmallerFile<TPixel>(TestImageProvider<TPix
 
         encoder = new()
         {
-            ColorTableMode = GifColorTableMode.Local,
+            ColorTableMode = FrameColorTableMode.Local,
             Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }),
         };
 
@@ -148,7 +149,7 @@ public void Encode_GlobalPalette_DefaultPixelSamplingStrategy<TPixel>(TestImageP
 
         GifEncoder encoder = new()
         {
-            ColorTableMode = GifColorTableMode.Global,
+            ColorTableMode = FrameColorTableMode.Global,
             PixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio)
         };
 
@@ -175,10 +176,10 @@ public void NonMutatingEncodePreservesPaletteCount()
         Image<Rgba32> image = Image.Load<Rgba32>(inStream);
         GifMetadata metaData = image.Metadata.GetGifMetadata();
         GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata();
-        GifColorTableMode colorMode = metaData.ColorTableMode;
+        FrameColorTableMode colorMode = metaData.ColorTableMode;
 
         int maxColors;
-        if (colorMode == GifColorTableMode.Global)
+        if (colorMode == FrameColorTableMode.Global)
         {
             maxColors = metaData.GlobalColorTable.Value.Length;
         }
@@ -204,7 +205,7 @@ public void NonMutatingEncodePreservesPaletteCount()
 
         // Gifiddle and Cyotek GifInfo say this image has 64 colors.
         colorMode = cloneMetadata.ColorTableMode;
-        if (colorMode == GifColorTableMode.Global)
+        if (colorMode == FrameColorTableMode.Global)
         {
             maxColors = metaData.GlobalColorTable.Value.Length;
         }
@@ -220,7 +221,7 @@ public void NonMutatingEncodePreservesPaletteCount()
             GifFrameMetadata iMeta = image.Frames[i].Metadata.GetGifMetadata();
             GifFrameMetadata cMeta = clone.Frames[i].Metadata.GetGifMetadata();
 
-            if (iMeta.ColorTableMode == GifColorTableMode.Local)
+            if (iMeta.ColorTableMode == FrameColorTableMode.Local)
             {
                 Assert.Equal(iMeta.LocalColorTable.Value.Length, cMeta.LocalColorTable.Value.Length);
             }
@@ -241,33 +242,11 @@ public void OptionalExtensionsShouldBeHandledProperly<TPixel>(TestImageProvider<
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage();
-
-        int count = 0;
-        foreach (ImageFrame<TPixel> frame in image.Frames)
-        {
-            if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _))
-            {
-                count++;
-            }
-        }
-
         provider.Utility.SaveTestOutputFile(image, extension: "gif");
 
         using FileStream fs = File.OpenRead(provider.Utility.GetTestOutputFileName("gif"));
         using Image<TPixel> image2 = Image.Load<TPixel>(fs);
-
         Assert.Equal(image.Frames.Count, image2.Frames.Count);
-
-        count = 0;
-        foreach (ImageFrame<TPixel> frame in image2.Frames)
-        {
-            if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata _))
-            {
-                count++;
-            }
-        }
-
-        Assert.Equal(image2.Frames.Count, count);
     }
 
     [Theory]
@@ -305,14 +284,14 @@ public void Encode_AnimatedFormatTransform_FromPng<TPixel>(TestImageProvider<TPi
 
             Assert.Equal((int)(pngF.FrameDelay.ToDouble() * 100), gifF.FrameDelay);
 
-            switch (pngF.DisposalMethod)
+            switch (pngF.DisposalMode)
             {
-                case PngDisposalMethod.RestoreToBackground:
-                    Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode);
                     break;
-                case PngDisposalMethod.DoNotDispose:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode);
                     break;
             }
         }
@@ -357,12 +336,12 @@ public void Encode_AnimatedFormatTransform_FromWebp<TPixel>(TestImageProvider<TP
 
             switch (webpF.DisposalMethod)
             {
-                case WebpDisposalMethod.RestoreToBackground:
-                    Assert.Equal(GifDisposalMethod.RestoreToBackground, gifF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode);
                     break;
-                case WebpDisposalMethod.DoNotDispose:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(GifDisposalMethod.NotDispose, gifF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode);
                     break;
             }
         }
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs
index 774638311d..f12e6e7e50 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 
 namespace SixLabors.ImageSharp.Tests.Formats.Gif;
@@ -14,18 +15,18 @@ public void CloneIsDeep()
         GifFrameMetadata meta = new()
         {
             FrameDelay = 1,
-            DisposalMethod = GifDisposalMethod.RestoreToBackground,
+            DisposalMode = FrameDisposalMode.RestoreToBackground,
             LocalColorTable = new[] { Color.Black, Color.White }
         };
 
         GifFrameMetadata clone = (GifFrameMetadata)meta.DeepClone();
 
         clone.FrameDelay = 2;
-        clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious;
+        clone.DisposalMode = FrameDisposalMode.RestoreToPrevious;
         clone.LocalColorTable = new[] { Color.Black };
 
         Assert.False(meta.FrameDelay.Equals(clone.FrameDelay));
-        Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod));
+        Assert.False(meta.DisposalMode.Equals(clone.DisposalMode));
         Assert.False(meta.LocalColorTable.Value.Length == clone.LocalColorTable.Value.Length);
         Assert.Equal(1, clone.LocalColorTable.Value.Length);
     }
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
index f9c56e05e5..dd3879703b 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
@@ -33,7 +33,7 @@ public void CloneIsDeep()
         GifMetadata meta = new()
         {
             RepeatCount = 1,
-            ColorTableMode = GifColorTableMode.Global,
+            ColorTableMode = FrameColorTableMode.Global,
             GlobalColorTable = new[] { Color.Black, Color.White },
             Comments = ["Foo"]
         };
@@ -41,7 +41,7 @@ public void CloneIsDeep()
         GifMetadata clone = (GifMetadata)meta.DeepClone();
 
         clone.RepeatCount = 2;
-        clone.ColorTableMode = GifColorTableMode.Local;
+        clone.ColorTableMode = FrameColorTableMode.Local;
         clone.GlobalColorTable = new[] { Color.Black };
 
         Assert.False(meta.RepeatCount.Equals(clone.RepeatCount));
@@ -183,14 +183,14 @@ public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount)
     }
 
     [Theory]
-    [InlineData(TestImages.Gif.Cheers, 93, GifColorTableMode.Global, 256, 4, GifDisposalMethod.NotDispose)]
+    [InlineData(TestImages.Gif.Cheers, 93, FrameColorTableMode.Global, 256, 4, FrameDisposalMode.DoNotDispose)]
     public void Identify_Frames(
         string imagePath,
         int framesCount,
-        GifColorTableMode colorTableMode,
+        FrameColorTableMode colorTableMode,
         int globalColorTableLength,
         int frameDelay,
-        GifDisposalMethod disposalMethod)
+        FrameDisposalMode disposalMethod)
     {
         TestFile testFile = TestFile.Create(imagePath);
         using MemoryStream stream = new(testFile.Bytes, false);
@@ -206,13 +206,13 @@ public void Identify_Frames(
 
         Assert.Equal(colorTableMode, gifFrameMetadata.ColorTableMode);
 
-        if (colorTableMode == GifColorTableMode.Global)
+        if (colorTableMode == FrameColorTableMode.Global)
         {
             Assert.Equal(globalColorTableLength, gifMetadata.GlobalColorTable.Value.Length);
         }
 
         Assert.Equal(frameDelay, gifFrameMetadata.FrameDelay);
-        Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMethod);
+        Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMode);
     }
 
     [Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
index c602bc91bb..05dc5bc52a 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 
 namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections;
@@ -10,9 +11,9 @@ public class GifGraphicControlExtensionTests
     [Fact]
     public void TestPackedValue()
     {
-        Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false));
-        Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true));
-        Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false));
-        Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false));
+        Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.Unspecified, false, false));
+        Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToBackground, true, true));
+        Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.DoNotDispose, false, false));
+        Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToPrevious, true, false));
     }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
index 4efd336482..f7ee7614af 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs
@@ -23,7 +23,7 @@ public void CurDecoder_Decode(TestImageProvider<Rgba32> provider)
         Assert.Equal(image.Width, meta.EncodingWidth);
         Assert.Equal(image.Height, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -36,6 +36,6 @@ public void CurDecoder_Decode2(TestImageProvider<Rgba32> provider)
         Assert.Equal(image.Width, meta.EncodingWidth);
         Assert.Equal(image.Height, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
     }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
index b9b66296df..59c40c9245 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs
@@ -2,9 +2,11 @@
 // Licensed under the Six Labors Split License.
 
 using SixLabors.ImageSharp.Formats.Cur;
+using SixLabors.ImageSharp.Formats.Ico;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
 using static SixLabors.ImageSharp.Tests.TestImages.Cur;
+using static SixLabors.ImageSharp.Tests.TestImages.Ico;
 
 namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur;
 
@@ -17,7 +19,7 @@ public class CurEncoderTests
     [WithFile(CurReal, PixelTypes.Rgba32)]
     [WithFile(WindowsMouse, PixelTypes.Rgba32)]
     public void CanRoundTripEncoder<TPixel>(TestImageProvider<TPixel> provider)
-    where TPixel : unmanaged, IPixel<TPixel>
+        where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage(CurDecoder.Instance);
         using MemoryStream memStream = new();
@@ -31,4 +33,34 @@ public void CanRoundTripEncoder<TPixel>(TestImageProvider<TPixel> provider)
 
         encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance);
     }
+
+    [Theory]
+    [WithFile(Flutter, PixelTypes.Rgba32)]
+    public void CanConvertFromIco<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage(IcoDecoder.Instance);
+        using MemoryStream memStream = new();
+
+        image.Save(memStream, Encoder);
+        memStream.Seek(0, SeekOrigin.Begin);
+
+        using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
+        encoded.DebugSaveMultiFrame(provider);
+
+        // Despite preservation of the palette. The process can still be lossy
+        encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance);
+
+        for (int i = 0; i < image.Frames.Count; i++)
+        {
+            IcoFrameMetadata icoFrame = image.Frames[i].Metadata.GetIcoMetadata();
+            CurFrameMetadata curFrame = encoded.Frames[i].Metadata.GetCurMetadata();
+
+            // Compression may differ as we cannot convert that.
+            // Color table may differ.
+            Assert.Equal(icoFrame.BmpBitsPerPixel, curFrame.BmpBitsPerPixel);
+            Assert.Equal(icoFrame.EncodingWidth, curFrame.EncodingWidth);
+            Assert.Equal(icoFrame.EncodingHeight, curFrame.EncodingHeight);
+        }
+    }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
index a776a637b8..bc46df0955 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs
@@ -56,7 +56,7 @@ public void Bpp1Test(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel1, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit1, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -92,7 +92,7 @@ public void Bpp24Test(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel24, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit24, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -128,7 +128,7 @@ public void Bpp32Test(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -163,7 +163,7 @@ public void Bpp4Test(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel4, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit4, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -199,7 +199,7 @@ public void Bpp8Test(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel8, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit8, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -229,7 +229,7 @@ public void InvalidPngTest(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Png, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
     }
 
     [Theory]
@@ -262,7 +262,7 @@ public void MultiSizeTest(TestImageProvider<Rgba32> provider)
         {
             ImageFrame<Rgba32> frame = image.Frames[i];
             IcoFrameMetadata meta = frame.Metadata.GetIcoMetadata();
-            Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+            Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
         }
 
         image.DebugSaveMultiFrame(provider);
@@ -327,6 +327,6 @@ public void IcoFakeTest(TestImageProvider<Rgba32> provider)
         Assert.Equal(expectedWidth, meta.EncodingWidth);
         Assert.Equal(expectedHeight, meta.EncodingHeight);
         Assert.Equal(IconFrameCompression.Bmp, meta.Compression);
-        Assert.Equal(BmpBitsPerPixel.Pixel32, meta.BmpBitsPerPixel);
+        Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel);
     }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
index db28f9f703..751db384d7 100644
--- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs
@@ -1,9 +1,11 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats.Cur;
 using SixLabors.ImageSharp.Formats.Ico;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+using static SixLabors.ImageSharp.Tests.TestImages.Cur;
 using static SixLabors.ImageSharp.Tests.TestImages.Ico;
 
 namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico;
@@ -26,9 +28,38 @@ public void CanRoundTripEncoder<TPixel>(TestImageProvider<TPixel> provider)
         memStream.Seek(0, SeekOrigin.Begin);
 
         using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
-        encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false);
+        encoded.DebugSaveMultiFrame(provider);
 
         // Despite preservation of the palette. The process can still be lossy
         encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance);
     }
+
+    [Theory]
+    [WithFile(WindowsMouse, PixelTypes.Rgba32)]
+    public void CanConvertFromCur<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage(CurDecoder.Instance);
+        using MemoryStream memStream = new();
+
+        image.Save(memStream, Encoder);
+        memStream.Seek(0, SeekOrigin.Begin);
+
+        using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
+        encoded.DebugSaveMultiFrame(provider);
+
+        encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance);
+
+        for (int i = 0; i < image.Frames.Count; i++)
+        {
+            CurFrameMetadata curFrame = image.Frames[i].Metadata.GetCurMetadata();
+            IcoFrameMetadata icoFrame = encoded.Frames[i].Metadata.GetIcoMetadata();
+
+            // Compression may differ as we cannot convert that.
+            Assert.Equal(curFrame.BmpBitsPerPixel, icoFrame.BmpBitsPerPixel);
+            Assert.Equal(curFrame.EncodingWidth, icoFrame.EncodingWidth);
+            Assert.Equal(curFrame.EncodingHeight, icoFrame.EncodingHeight);
+            Assert.Equal(curFrame.ColorTable, icoFrame.ColorTable);
+        }
+    }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
index 3c88d8cbe9..a803372537 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
@@ -146,14 +146,14 @@ public async Task Decode_VerifyQualityAsync(string imagePath, int quality)
     }
 
     [Theory]
-    [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)]
-    [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)]
-    [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)]
-    [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)]
-    [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)]
-    [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)]
-    [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)]
-    public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType)
+    [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)]
+    [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)]
+    [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)]
+    [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)]
+    [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)]
+    [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)]
+    [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)]
+    public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType)
     {
         TestFile testFile = TestFile.Create(imagePath);
         using MemoryStream stream = new(testFile.Bytes, false);
@@ -163,12 +163,12 @@ public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor
     }
 
     [Theory]
-    [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)]
-    [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
-    [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
-    [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)]
-    [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)]
-    public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
+    [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)]
+    [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)]
+    [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)]
+    [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)]
+    [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegColorType.Cmyk)]
+    public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage(JpegDecoder.Instance);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 2fe4282607..950265bd56 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg;
 [ValidateDisposedMemoryAllocations]
 public partial class JpegDecoderTests
 {
-    private static MagickReferenceDecoder ReferenceDecoder => new();
+    private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Jpeg;
 
     public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector;
 
@@ -67,11 +67,11 @@ private static bool SkipTest(ITestImageProvider provider)
     public void ParseStream_BasicPropertiesAreCorrect()
     {
         JpegDecoderOptions options = new();
+        Configuration configuration = options.GeneralOptions.Configuration;
         byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
         using MemoryStream ms = new(bytes);
-        using BufferedReadStream bufferedStream = new(Configuration.Default, ms);
         using JpegDecoderCore decoder = new(options);
-        using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default);
+        using Image<Rgba32> image = decoder.Decode<Rgba32>(configuration, ms, cancellationToken: default);
 
         // I don't know why these numbers are different. All I know is that the decoder works
         // and spectral data is exactly correct also.
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
index f06fbe9635..039215bbc5 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
@@ -208,11 +208,11 @@ public void Encode_SaveTooLongComment()
     }
 
     [Theory]
-    [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)]
-    [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
-    [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
-    [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)]
-    public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor expectedColorType)
+    [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)]
+    [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)]
+    [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)]
+    [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)]
+    public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         // arrange
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index 5842c8e1a0..1f4b3e4656 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
@@ -21,51 +21,51 @@ public partial class JpegEncoderTests
         100,
     };
 
-    public static readonly TheoryData<JpegEncodingColor, int, float> NonSubsampledEncodingSetups = new()
+    public static readonly TheoryData<JpegColorType, int, float> NonSubsampledEncodingSetups = new()
     {
-        { JpegEncodingColor.Rgb, 100, 0.0238f / 100 },
-        { JpegEncodingColor.Rgb, 80,  1.3044f / 100 },
-        { JpegEncodingColor.Rgb, 40,  2.9879f / 100 },
-        { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 },
-        { JpegEncodingColor.YCbCrRatio444, 80,  1.4585f / 100 },
-        { JpegEncodingColor.YCbCrRatio444, 40,  3.1413f / 100 },
+        { JpegColorType.Rgb, 100, 0.0238f / 100 },
+        { JpegColorType.Rgb, 80,  1.3044f / 100 },
+        { JpegColorType.Rgb, 40,  2.9879f / 100 },
+        { JpegColorType.YCbCrRatio444, 100, 0.0780f / 100 },
+        { JpegColorType.YCbCrRatio444, 80,  1.4585f / 100 },
+        { JpegColorType.YCbCrRatio444, 40,  3.1413f / 100 },
     };
 
-    public static readonly TheoryData<JpegEncodingColor, int, float> SubsampledEncodingSetups = new()
+    public static readonly TheoryData<JpegColorType, int, float> SubsampledEncodingSetups = new()
     {
-        { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 },
-        { JpegEncodingColor.YCbCrRatio422, 80,  1.6043f / 100 },
-        { JpegEncodingColor.YCbCrRatio422, 40,  3.1996f / 100 },
-        { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 },
-        { JpegEncodingColor.YCbCrRatio420, 80,  1.6692f / 100 },
-        { JpegEncodingColor.YCbCrRatio420, 40,  3.2324f / 100 },
-        { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 },
-        { JpegEncodingColor.YCbCrRatio411, 80,  1.7139f / 100 },
-        { JpegEncodingColor.YCbCrRatio411, 40,  3.2634f / 100 },
-        { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 },
-        { JpegEncodingColor.YCbCrRatio410, 80,  1.7495f / 100 },
-        { JpegEncodingColor.YCbCrRatio410, 40,  3.2911f / 100 },
+        { JpegColorType.YCbCrRatio422, 100, 0.4895f / 100 },
+        { JpegColorType.YCbCrRatio422, 80,  1.6043f / 100 },
+        { JpegColorType.YCbCrRatio422, 40,  3.1996f / 100 },
+        { JpegColorType.YCbCrRatio420, 100, 0.5790f / 100 },
+        { JpegColorType.YCbCrRatio420, 80,  1.6692f / 100 },
+        { JpegColorType.YCbCrRatio420, 40,  3.2324f / 100 },
+        { JpegColorType.YCbCrRatio411, 100, 0.6868f / 100 },
+        { JpegColorType.YCbCrRatio411, 80,  1.7139f / 100 },
+        { JpegColorType.YCbCrRatio411, 40,  3.2634f / 100 },
+        { JpegColorType.YCbCrRatio410, 100, 0.7357f / 100 },
+        { JpegColorType.YCbCrRatio410, 80,  1.7495f / 100 },
+        { JpegColorType.YCbCrRatio410, 40,  3.2911f / 100 },
     };
 
-    public static readonly TheoryData<JpegEncodingColor, int, float> CmykEncodingSetups = new()
+    public static readonly TheoryData<JpegColorType, int, float> CmykEncodingSetups = new()
     {
-        { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 },
-        { JpegEncodingColor.Cmyk, 80,  0.3922f / 100 },
-        { JpegEncodingColor.Cmyk, 40,  0.6488f / 100 },
+        { JpegColorType.Cmyk, 100, 0.0159f / 100 },
+        { JpegColorType.Cmyk, 80,  0.3922f / 100 },
+        { JpegColorType.Cmyk, 40,  0.6488f / 100 },
     };
 
-    public static readonly TheoryData<JpegEncodingColor, int, float> YcckEncodingSetups = new()
+    public static readonly TheoryData<JpegColorType, int, float> YcckEncodingSetups = new()
     {
-        { JpegEncodingColor.Ycck, 100, 0.0356f / 100 },
-        { JpegEncodingColor.Ycck, 80,  0.1245f / 100 },
-        { JpegEncodingColor.Ycck, 40,  0.2663f / 100 },
+        { JpegColorType.Ycck, 100, 0.0356f / 100 },
+        { JpegColorType.Ycck, 80,  0.1245f / 100 },
+        { JpegColorType.Ycck, 40,  0.2663f / 100 },
     };
 
-    public static readonly TheoryData<JpegEncodingColor, int, float> LuminanceEncodingSetups = new()
+    public static readonly TheoryData<JpegColorType, int, float> LuminanceEncodingSetups = new()
     {
-        { JpegEncodingColor.Luminance, 100, 0.0175f / 100 },
-        { JpegEncodingColor.Luminance, 80,  0.6730f / 100 },
-        { JpegEncodingColor.Luminance, 40, 0.9943f / 100 },
+        { JpegColorType.Luminance, 100, 0.0175f / 100 },
+        { JpegColorType.Luminance, 80,  0.6730f / 100 },
+        { JpegColorType.Luminance, 40, 0.9943f / 100 },
     };
 
     [Theory]
@@ -74,7 +74,7 @@ public partial class JpegEncoderTests
     [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)]
     [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)]
     [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)]
-    public void EncodeBaseline_Interleaved<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_Interleaved<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, tolerance);
 
     [Theory]
@@ -83,7 +83,7 @@ public void EncodeBaseline_Interleaved<TPixel>(TestImageProvider<TPixel> provide
     [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)]
     [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)]
     [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)]
-    public void EncodeBaseline_NonInterleavedMode<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_NonInterleavedMode<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage();
@@ -108,7 +108,7 @@ public void EncodeBaseline_NonInterleavedMode<TPixel>(TestImageProvider<TPixel>
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)]
-    public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
 
     [Theory]
@@ -121,7 +121,7 @@ public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPi
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)]
-    public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f));
 
     [Theory]
@@ -131,28 +131,28 @@ public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestI
     [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)]
     [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)]
     public void EncodeBaseline_Grayscale<TPixel>(TestImageProvider<TPixel> provider, int quality)
-        where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality);
+        where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality);
 
     [Theory]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
-    public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
 
     [Theory]
     [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)]
-    public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f));
 
     [Theory]
-    [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
-    [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)]
-    [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
-    [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)]
-    public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType)
+    [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)]
+    [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)]
+    [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)]
+    [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)]
+    public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444
+        ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444
             ? ImageComparer.TolerantPercentage(0.1f)
             : ImageComparer.TolerantPercentage(5f);
 
@@ -161,9 +161,9 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvid
     }
 
     [Theory]
-    [InlineData(JpegEncodingColor.YCbCrRatio420)]
-    [InlineData(JpegEncodingColor.YCbCrRatio444)]
-    public async Task Encode_IsCancellable(JpegEncodingColor colorType)
+    [InlineData(JpegColorType.YCbCrRatio420)]
+    [InlineData(JpegColorType.YCbCrRatio444)]
+    public async Task Encode_IsCancellable(JpegColorType colorType)
     {
         CancellationTokenSource cts = new();
         using PausedStream pausedStream = new(new MemoryStream());
@@ -201,13 +201,13 @@ public static void Issue2595<TPixel>(TestImageProvider<TPixel> provider)
         image.Mutate(x => x.Crop(132, 1606));
 
         int[] quality = new int[] { 100, 50 };
-        JpegEncodingColor[] colors = new[] { JpegEncodingColor.YCbCrRatio444, JpegEncodingColor.YCbCrRatio420 };
+        JpegColorType[] colors = new[] { JpegColorType.YCbCrRatio444, JpegColorType.YCbCrRatio420 };
         for (int i = 0; i < quality.Length; i++)
         {
             int q = quality[i];
             for (int j = 0; j < colors.Length; j++)
             {
-                JpegEncodingColor c = colors[j];
+                JpegColorType c = colors[j];
                 image.VerifyEncoder(provider, "jpeg", $"{q}-{c}", new JpegEncoder() { Quality = q, ColorType = c }, GetComparer(q, c));
             }
         }
@@ -216,7 +216,7 @@ public static void Issue2595<TPixel>(TestImageProvider<TPixel> provider)
     /// <summary>
     /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
     /// </summary>
-    private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType)
+    private static ImageComparer GetComparer(int quality, JpegColorType? colorType)
     {
         float tolerance = 0.015f; // ~1.5%
 
@@ -224,10 +224,10 @@ private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorTy
         {
             tolerance *= 4.5f;
         }
-        else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420)
+        else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
         {
             tolerance *= 2.0f;
-            if (colorType == JpegEncodingColor.YCbCrRatio420)
+            if (colorType == JpegColorType.YCbCrRatio420)
             {
                 tolerance *= 2.0f;
             }
@@ -236,15 +236,15 @@ private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorTy
         return ImageComparer.Tolerant(tolerance);
     }
 
-    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality)
+    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
         where TPixel : unmanaged, IPixel<TPixel>
         => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType));
 
-    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, float tolerance)
+    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, float tolerance)
         where TPixel : unmanaged, IPixel<TPixel>
         => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance));
 
-    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegEncodingColor colorType, int quality, ImageComparer comparer)
+    private static void TestJpegEncoderCore<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality, ImageComparer comparer)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         using Image<TPixel> image = provider.GetImage();
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
index e07c42f898..19b5265a17 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
@@ -12,10 +12,10 @@ public class JpegMetadataTests
     [Fact]
     public void CloneIsDeep()
     {
-        var meta = new JpegMetadata { ColorType = JpegEncodingColor.Luminance };
+        var meta = new JpegMetadata { ColorType = JpegColorType.Luminance };
         var clone = (JpegMetadata)meta.DeepClone();
 
-        clone.ColorType = JpegEncodingColor.YCbCrRatio420;
+        clone.ColorType = JpegColorType.YCbCrRatio420;
 
         Assert.False(meta.ColorType.Equals(clone.ColorType));
     }
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
index 978978989e..a3fbe4018e 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
@@ -5,7 +5,6 @@
 using System.Text;
 using SixLabors.ImageSharp.Formats.Jpeg;
 using SixLabors.ImageSharp.Formats.Jpeg.Components;
-using SixLabors.ImageSharp.IO;
 using SixLabors.ImageSharp.PixelFormats;
 using Xunit.Abstractions;
 
@@ -216,18 +215,17 @@ internal static bool CompareBlocks(Span<float> a, Span<float> b, float tolerance
     internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false)
     {
         byte[] bytes = TestFile.Create(testFileName).Bytes;
-        using var ms = new MemoryStream(bytes);
-        using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
-
-        JpegDecoderOptions options = new();
-        var decoder = new JpegDecoderCore(options);
+        using MemoryStream ms = new(bytes);
+        JpegDecoderOptions decoderOptions = new();
+        Configuration configuration = decoderOptions.GeneralOptions.Configuration;
+        JpegDecoderCore decoder = new(decoderOptions);
         if (metaDataOnly)
         {
-            decoder.Identify(bufferedStream, cancellationToken: default);
+            decoder.Identify(configuration, ms, default);
         }
         else
         {
-            using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default);
+            using Image<Rgba32> image = decoder.Decode<Rgba32>(configuration, ms, default);
         }
 
         return decoder;
diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
index a69d9d9ba7..243a62d592 100644
--- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
@@ -1,7 +1,6 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
-using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Pbm;
 using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
 
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 61ee8d2692..8492d78f86 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -485,7 +485,7 @@ public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors<TPixel>(TestImageProvider
         if (compare)
         {
             // Magick cannot actually decode this image to compare.
-            image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
+            image.CompareToOriginal(provider, new MagickReferenceDecoder(PngFormat.Instance, false));
         }
     }
 
@@ -552,7 +552,7 @@ public void Issue1047<TPixel>(TestImageProvider<TPixel> provider)
                 // We don't have another x-plat reference decoder that can be compared for this image.
                 if (TestEnvironment.IsWindows)
                 {
-                    image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance);
+                    image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png);
                 }
             });
         Assert.Null(ex);
@@ -614,7 +614,7 @@ public void Issue410_MalformedApplePng<TPixel>(TestImageProvider<TPixel> provide
                 // We don't have another x-plat reference decoder that can be compared for this image.
                 if (TestEnvironment.IsWindows)
                 {
-                    image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance);
+                    image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png);
                 }
             });
         Assert.NotNull(ex);
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 35c446c704..009327c17f 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -254,49 +254,6 @@ public void WorksWithAllBitDepthsAndExcludeAllFilter<TPixel>(TestImageProvider<T
         }
     }
 
-    [Theory]
-    [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.Rgb, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.Rgb, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.Rgb, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.Rgb, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.Rgb, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.Rgb, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)]
-    [WithBlankImages(1, 1, PixelTypes.La16, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
-    [WithBlankImages(1, 1, PixelTypes.La32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
-    public void InfersColorTypeAndBitDepth<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
-        where TPixel : unmanaged, IPixel<TPixel>
-    {
-        using Stream stream = new MemoryStream();
-        PngEncoder.Encode(provider.GetImage(), stream);
-
-        stream.Seek(0, SeekOrigin.Begin);
-
-        using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, stream);
-
-        PngMetadata metadata = image.Metadata.GetPngMetadata();
-        Assert.Equal(pngColorType, metadata.ColorType);
-        Assert.Equal(pngBitDepth, metadata.BitDepth);
-    }
-
     [Theory]
     [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)]
     public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
@@ -479,8 +436,8 @@ public void Encode_APng<TPixel>(TestImageProvider<TPixel> provider)
             PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngMetadata();
 
             Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay);
-            Assert.Equal(originalFrameMetadata.BlendMethod, outputFrameMetadata.BlendMethod);
-            Assert.Equal(originalFrameMetadata.DisposalMethod, outputFrameMetadata.DisposalMethod);
+            Assert.Equal(originalFrameMetadata.BlendMode, outputFrameMetadata.BlendMode);
+            Assert.Equal(originalFrameMetadata.DisposalMode, outputFrameMetadata.DisposalMode);
         }
     }
 
@@ -519,18 +476,18 @@ public void Encode_AnimatedFormatTransform_FromGif<TPixel>(TestImageProvider<TPi
 
             Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100));
 
-            switch (gifF.DisposalMethod)
+            switch (gifF.DisposalMode)
             {
-                case GifDisposalMethod.RestoreToBackground:
-                    Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode);
                     break;
-                case GifDisposalMethod.RestoreToPrevious:
-                    Assert.Equal(PngDisposalMethod.RestoreToPrevious, pngF.DisposalMethod);
+                case FrameDisposalMode.RestoreToPrevious:
+                    Assert.Equal(FrameDisposalMode.RestoreToPrevious, pngF.DisposalMode);
                     break;
-                case GifDisposalMethod.Unspecified:
-                case GifDisposalMethod.NotDispose:
+                case FrameDisposalMode.Unspecified:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode);
                     break;
             }
         }
@@ -569,23 +526,23 @@ public void Encode_AnimatedFormatTransform_FromWebp<TPixel>(TestImageProvider<TP
 
             switch (webpF.BlendMethod)
             {
-                case WebpBlendMethod.Source:
-                    Assert.Equal(PngBlendMethod.Source, pngF.BlendMethod);
+                case FrameBlendMode.Source:
+                    Assert.Equal(FrameBlendMode.Source, pngF.BlendMode);
                     break;
-                case WebpBlendMethod.Over:
+                case FrameBlendMode.Over:
                 default:
-                    Assert.Equal(PngBlendMethod.Over, pngF.BlendMethod);
+                    Assert.Equal(FrameBlendMode.Over, pngF.BlendMode);
                     break;
             }
 
             switch (webpF.DisposalMethod)
             {
-                case WebpDisposalMethod.RestoreToBackground:
-                    Assert.Equal(PngDisposalMethod.RestoreToBackground, pngF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode);
                     break;
-                case WebpDisposalMethod.DoNotDispose:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(PngDisposalMethod.DoNotDispose, pngF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode);
                     break;
             }
         }
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs
index 9ba2617281..8fde216545 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Png;
 
 namespace SixLabors.ImageSharp.Tests.Formats.Png;
@@ -14,22 +15,22 @@ public void CloneIsDeep()
         PngFrameMetadata meta = new()
         {
             FrameDelay = new(1, 0),
-            DisposalMethod = PngDisposalMethod.RestoreToBackground,
-            BlendMethod = PngBlendMethod.Over,
+            DisposalMode = FrameDisposalMode.RestoreToBackground,
+            BlendMode = FrameBlendMode.Over,
         };
 
-        PngFrameMetadata clone = (PngFrameMetadata)meta.DeepClone();
+        PngFrameMetadata clone = meta.DeepClone();
 
         Assert.True(meta.FrameDelay.Equals(clone.FrameDelay));
-        Assert.True(meta.DisposalMethod.Equals(clone.DisposalMethod));
-        Assert.True(meta.BlendMethod.Equals(clone.BlendMethod));
+        Assert.True(meta.DisposalMode.Equals(clone.DisposalMode));
+        Assert.True(meta.BlendMode.Equals(clone.BlendMode));
 
         clone.FrameDelay = new(2, 1);
-        clone.DisposalMethod = PngDisposalMethod.RestoreToPrevious;
-        clone.BlendMethod = PngBlendMethod.Source;
+        clone.DisposalMode = FrameDisposalMode.RestoreToPrevious;
+        clone.BlendMode = FrameBlendMode.Source;
 
         Assert.False(meta.FrameDelay.Equals(clone.FrameDelay));
-        Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod));
-        Assert.False(meta.BlendMethod.Equals(clone.BlendMethod));
+        Assert.False(meta.DisposalMode.Equals(clone.DisposalMode));
+        Assert.False(meta.BlendMode.Equals(clone.BlendMode));
     }
 }
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
index a4288a3d84..3ca7730eb1 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
@@ -17,17 +17,13 @@ public class PngSmokeTests
     public void GeneralTest<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        // does saving a file then reopening mean both files are identical???
         using Image<TPixel> image = provider.GetImage();
-        using var ms = new MemoryStream();
+        using MemoryStream ms = new();
 
-        // image.Save(provider.Utility.GetTestOutputFileName("bmp"));
         image.Save(ms, new PngEncoder());
         ms.Position = 0;
         using Image<Rgba32> img2 = PngDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, ms);
-        ImageComparer.Tolerant().VerifySimilarity(image, img2);
-
-        // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
+        ImageComparer.Exact.VerifySimilarity(image, img2);
     }
 
     [Theory]
@@ -37,12 +33,10 @@ public void Resize<TPixel>(TestImageProvider<TPixel> provider)
     {
         // does saving a file then reopening mean both files are identical???
         using Image<TPixel> image = provider.GetImage();
-        using var ms = new MemoryStream();
+        using MemoryStream ms = new();
 
-        // image.Save(provider.Utility.GetTestOutputFileName("png"));
         image.Mutate(x => x.Resize(100, 100));
 
-        // image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
         image.Save(ms, new PngEncoder());
         ms.Position = 0;
         using Image<Rgba32> img2 = PngDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, ms);
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
index d57b597b06..32ade4a1e9 100644
--- a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
@@ -24,7 +24,7 @@ public class QoiEncoderTests
     public static void Encode<TPixel>(TestImageProvider<TPixel> provider, QoiChannels channels, QoiColorSpace colorSpace)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        using Image<TPixel> image = provider.GetImage(new MagickReferenceDecoder());
+        using Image<TPixel> image = provider.GetImage(new MagickReferenceDecoder(QoiFormat.Instance));
         using MemoryStream stream = new();
         QoiEncoder encoder = new()
         {
@@ -34,7 +34,7 @@ public static void Encode<TPixel>(TestImageProvider<TPixel> provider, QoiChannel
         image.Save(stream, encoder);
         stream.Position = 0;
 
-        using Image<TPixel> encodedImage = (Image<TPixel>)Image.Load(stream);
+        using Image<TPixel> encodedImage = Image.Load<TPixel>(stream);
         QoiMetadata qoiMetadata = encodedImage.Metadata.GetQoiMetadata();
 
         ImageComparer.Exact.CompareImages(image, encodedImage);
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
index 709a3207aa..615e0fc921 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
@@ -15,17 +15,17 @@ public class TgaEncoderTests
     public static readonly TheoryData<TgaBitsPerPixel> BitsPerPixel =
         new()
         {
-            TgaBitsPerPixel.Pixel24,
-            TgaBitsPerPixel.Pixel32
+            TgaBitsPerPixel.Bit24,
+            TgaBitsPerPixel.Bit32
         };
 
     public static readonly TheoryData<string, TgaBitsPerPixel> TgaBitsPerPixelFiles =
         new()
         {
-            { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 },
-            { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 },
-            { Bit24BottomLeft, TgaBitsPerPixel.Pixel24 },
-            { Bit32BottomLeft, TgaBitsPerPixel.Pixel32 },
+            { Gray8BitBottomLeft, TgaBitsPerPixel.Bit8 },
+            { Bit16BottomLeft, TgaBitsPerPixel.Bit16 },
+            { Bit24BottomLeft, TgaBitsPerPixel.Bit24 },
+            { Bit32BottomLeft, TgaBitsPerPixel.Bit32 },
         };
 
     [Theory]
@@ -73,46 +73,46 @@ public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, Tg
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit8_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+    public void TgaEncoder_Bit8_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit8)
 
         // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit16_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+    public void TgaEncoder_Bit16_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit16)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit24_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+    public void TgaEncoder_Bit24_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit24)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit32_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+    public void TgaEncoder_Bit32_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit32)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit8_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+    public void TgaEncoder_Bit8_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit8)
 
         // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit16_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+    public void TgaEncoder_Bit16_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit16)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit24_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+    public void TgaEncoder_Bit24_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit24)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
 
     [Theory]
     [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
-    public void TgaEncoder_Bit32_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+    public void TgaEncoder_Bit32_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit32)
         where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
 
     [Theory]
@@ -121,16 +121,14 @@ public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets<TPixel>(TestImageProvide
         where TPixel : unmanaged, IPixel<TPixel>
     {
         // The test image has alternating black and white pixels, which should make using always RLE data inefficient.
-        using (Image<TPixel> image = provider.GetImage())
-        {
-            var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
-            using (var memStream = new MemoryStream())
-            {
-                image.Save(memStream, options);
-                byte[] imageBytes = memStream.ToArray();
-                Assert.Equal(expectedBytes, imageBytes.Length);
-            }
-        }
+        using Image<TPixel> image = provider.GetImage();
+        TgaEncoder options = new() { BitsPerPixel = TgaBitsPerPixel.Bit24, Compression = TgaCompression.RunLength };
+
+        using MemoryStream memStream = new();
+        image.Save(memStream, options);
+        byte[] imageBytes = memStream.ToArray();
+
+        Assert.Equal(expectedBytes, imageBytes.Length);
     }
 
     // Run length encoded pixels should not exceed row boundaries.
@@ -152,8 +150,8 @@ public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries()
     }
 
     [Theory]
-    [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)]
-    [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)]
+    [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Bit32)]
+    [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Bit24)]
     public void TgaEncoder_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel)
         where TPixel : unmanaged, IPixel<TPixel>
     {
@@ -175,6 +173,7 @@ private static void TestTgaEncoderCore<TPixel>(
 
             using (var memStream = new MemoryStream())
             {
+                image.DebugSave(provider, encoder);
                 image.Save(memStream, encoder);
                 memStream.Position = 0;
                 using (var encodedImage = (Image<TPixel>)Image.Load(memStream))
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
index 2f96b6d435..efbdb8eebb 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
@@ -35,10 +35,10 @@ public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatExcepti
     }
 
     [Theory]
-    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 }, 250, 195, TgaBitsPerPixel.Pixel32)]
-    [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Pixel8)]
-    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Pixel16)]
-    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)]
+    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 }, 250, 195, TgaBitsPerPixel.Bit32)]
+    [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Bit8)]
+    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Bit16)]
+    [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Bit24)]
     public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel)
     {
         using MemoryStream stream = new(data);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
index 4acdf3e509..b574c7e55c 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
 
 public abstract class TiffDecoderBaseTester
 {
-    protected static MagickReferenceDecoder ReferenceDecoder => new();
+    protected static MagickReferenceDecoder ReferenceDecoder => new(TiffFormat.Instance);
 
     protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
         where TPixel : unmanaged, IPixel<TPixel>
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
index 2a822e7054..1bf9f5a400 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
 [Trait("Format", "Tiff")]
 public abstract class TiffEncoderBaseTester
 {
-    protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
+    protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(TiffFormat.Instance);
 
     protected static void TestStripLength<TPixel>(
         TestImageProvider<TPixel> provider,
@@ -30,7 +30,7 @@ protected static void TestStripLength<TPixel>(
         using Image<TPixel> input = provider.GetImage();
         using var memStream = new MemoryStream();
         TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
-        TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
+        TiffCompression inputCompression = inputMeta.Compression;
 
         // act
         input.Save(memStream, tiffEncoder);
@@ -48,7 +48,6 @@ protected static void TestStripLength<TPixel>(
         Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value;
         Assert.NotNull(stripByteCounts);
         Assert.True(stripByteCounts.Length > 1);
-        Assert.NotNull(outputMeta.BitsPerPixel);
 
         foreach (Number sz in stripByteCounts)
         {
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
index 8724147301..282966ea85 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
@@ -15,7 +15,7 @@ public class TiffEncoderHeaderTests
     public void WriteHeader_WritesValidHeader()
     {
         using MemoryStream stream = new();
-        TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
+        TiffEncoderCore encoder = new(Encoder, Configuration.Default);
 
         using (TiffStreamWriter writer = new(stream))
         {
@@ -29,7 +29,7 @@ public void WriteHeader_WritesValidHeader()
     public void WriteHeader_ReturnsFirstIfdMarker()
     {
         using MemoryStream stream = new();
-        TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
+        TiffEncoderCore encoder = new(Encoder, Configuration.Default);
 
         using TiffStreamWriter writer = new(stream);
         long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index 1fafb4cd04..1972101164 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -31,7 +31,7 @@ public class TiffEncoderTests : TiffEncoderBaseTester
     public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel)
     {
         // arrange
-        TiffEncoder tiffEncoder = new() { PhotometricInterpretation = photometricInterpretation };
+        TiffEncoder tiffEncoder = new() { BitsPerPixel = expectedBitsPerPixel, PhotometricInterpretation = photometricInterpretation };
         using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16
             ? new Image<L16>(10, 10)
             : new Image<Rgb24>(10, 10);
@@ -57,8 +57,7 @@ public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInt
     public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel)
     {
         // arrange
-        TiffEncoder tiffEncoder = new()
-        { BitsPerPixel = bitsPerPixel };
+        TiffEncoder tiffEncoder = new() { BitsPerPixel = bitsPerPixel };
         using Image input = new Image<Rgb24>(10, 10);
         using MemoryStream memStream = new();
 
@@ -156,7 +155,11 @@ public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(
     {
         // arrange
         TiffEncoder tiffEncoder = new()
-        { PhotometricInterpretation = photometricInterpretation, Compression = compression };
+        {
+            BitsPerPixel = expectedBitsPerPixel,
+            PhotometricInterpretation = photometricInterpretation,
+            Compression = compression
+        };
         using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16
             ? new Image<L16>(10, 10)
             : new Image<Rgb24>(10, 10);
@@ -199,25 +202,6 @@ public void TiffEncoder_PreservesBitsPerPixel<TPixel>(TestImageProvider<TPixel>
         Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
     }
 
-    [Fact]
-    public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8()
-    {
-        // arrange
-        TiffEncoder tiffEncoder = new();
-        using Image input = new Image<L8>(10, 10);
-        using MemoryStream memStream = new();
-        const TiffBitsPerPixel expectedBitsPerPixel = TiffBitsPerPixel.Bit8;
-
-        // act
-        input.Save(memStream, tiffEncoder);
-
-        // assert
-        memStream.Position = 0;
-        using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
-        TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata();
-        Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
-    }
-
     [Theory]
     [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)]
     [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)]
@@ -241,11 +225,11 @@ public void TiffEncoder_PreservesCompression<TPixel>(TestImageProvider<TPixel> p
     }
 
     [Theory]
-    [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, null)]
+    [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffPredictor.None)]
     [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
-    [WithFile(RgbDeflate, PixelTypes.Rgba32, null)]
+    [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffPredictor.None)]
     [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
-    public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> provider, TiffPredictor? expectedPredictor)
+    public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> provider, TiffPredictor expectedPredictor)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         // arrange
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
index 0dda304b64..657ab25546 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
 [ValidateDisposedMemoryAllocations]
 public class WebpDecoderTests
 {
-    private static MagickReferenceDecoder ReferenceDecoder => new();
+    private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.WebP;
 
     private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter);
 
@@ -30,8 +30,8 @@ public class WebpDecoderTests
 
     [Theory]
     [InlineData(Lossless.GreenTransform1, 1000, 307, 32)]
-    [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)]
-    [InlineData(Lossless.NoTransform2, 128, 128, 32)]
+    [InlineData(Lossless.BikeThreeTransforms, 250, 195, 24)]
+    [InlineData(Lossless.NoTransform2, 128, 128, 24)]
     [InlineData(Lossy.Alpha1, 1000, 307, 32)]
     [InlineData(Lossy.Alpha2, 1000, 307, 32)]
     [InlineData(Lossy.BikeWithExif, 250, 195, 24)]
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
index d1d83ffb9a..10492af8a7 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
@@ -2,6 +2,7 @@
 // Licensed under the Six Labors Split License.
 
 using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Formats.Webp;
@@ -94,16 +95,16 @@ public void Encode_AnimatedFormatTransform_FromGif<TPixel>(TestImageProvider<TPi
 
             Assert.Equal(gifF.FrameDelay, (int)(webpF.FrameDelay / 10));
 
-            switch (gifF.DisposalMethod)
+            switch (gifF.DisposalMode)
             {
-                case GifDisposalMethod.RestoreToBackground:
-                    Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMethod);
                     break;
-                case GifDisposalMethod.RestoreToPrevious:
-                case GifDisposalMethod.Unspecified:
-                case GifDisposalMethod.NotDispose:
+                case FrameDisposalMode.RestoreToPrevious:
+                case FrameDisposalMode.Unspecified:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMethod);
                     break;
             }
         }
@@ -143,35 +144,36 @@ public void Encode_AnimatedFormatTransform_FromPng<TPixel>(TestImageProvider<TPi
 
             Assert.Equal((uint)(pngF.FrameDelay.ToDouble() * 1000), webpF.FrameDelay);
 
-            switch (pngF.BlendMethod)
+            switch (pngF.BlendMode)
             {
-                case PngBlendMethod.Source:
-                    Assert.Equal(WebpBlendMethod.Source, webpF.BlendMethod);
+                case FrameBlendMode.Source:
+                    Assert.Equal(FrameBlendMode.Source, webpF.BlendMethod);
                     break;
-                case PngBlendMethod.Over:
+                case FrameBlendMode.Over:
                 default:
-                    Assert.Equal(WebpBlendMethod.Over, webpF.BlendMethod);
+                    Assert.Equal(FrameBlendMode.Over, webpF.BlendMethod);
                     break;
             }
 
-            switch (pngF.DisposalMethod)
+            switch (pngF.DisposalMode)
             {
-                case PngDisposalMethod.RestoreToBackground:
-                    Assert.Equal(WebpDisposalMethod.RestoreToBackground, webpF.DisposalMethod);
+                case FrameDisposalMode.RestoreToBackground:
+                    Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMethod);
                     break;
-                case PngDisposalMethod.DoNotDispose:
+                case FrameDisposalMode.DoNotDispose:
                 default:
-                    Assert.Equal(WebpDisposalMethod.DoNotDispose, webpF.DisposalMethod);
+                    Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMethod);
                     break;
             }
         }
     }
 
     [Theory]
-    [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // If its not a webp input image, it should default to lossy.
+    [WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // Input is lossy jpeg.
+    [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] // Input is lossless png.
     [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)]
     [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32, WebpFileFormatType.Lossy)]
-    public void Encode_PreserveRatio<TPixel>(TestImageProvider<TPixel> provider, WebpFileFormatType expectedFormat)
+    public void Encode_PreserveEncodingType<TPixel>(TestImageProvider<TPixel> provider, WebpFileFormatType expectedFormat)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         WebpEncoder options = new();
@@ -219,7 +221,7 @@ public void Encode_Lossless_WithDifferentQuality_Works<TPixel>(TestImageProvider
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossless", "_q", quality);
+        string testOutputDetails = $"lossless_q{quality}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
     }
 
@@ -249,7 +251,7 @@ public void Encode_Lossless_WithDifferentMethodAndQuality_Works<TPixel>(TestImag
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossless", "_m", method, "_q", quality);
+        string testOutputDetails = $"lossless_m{method}_q{quality}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
     }
 
@@ -289,7 +291,7 @@ public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality);
+        string testOutputDetails = $"nearlossless_q{nearLosslessQuality}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality));
     }
 
@@ -313,7 +315,7 @@ public void Encode_Lossless_WithPreserveTransparentColor_Works<TPixel>(TestImage
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossless", "_m", method);
+        string testOutputDetails = $"lossless_m{method}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
     }
 
@@ -343,7 +345,7 @@ public void Encode_Lossy_WithDifferentQuality_Works<TPixel>(TestImageProvider<TP
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossy", "_q", quality);
+        string testOutputDetails = $"lossy_q{quality}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality));
     }
 
@@ -363,7 +365,7 @@ public void Encode_Lossy_WithDifferentFilterStrength_Works<TPixel>(TestImageProv
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossy", "_f", filterStrength);
+        string testOutputDetails = $"lossy_f{filterStrength}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75));
     }
 
@@ -383,7 +385,7 @@ public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works<TPixel>(
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossy", "_sns", snsStrength);
+        string testOutputDetails = $"lossy_sns{snsStrength}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75));
     }
 
@@ -413,7 +415,7 @@ public void Encode_Lossy_WithDifferentMethodsAndQuality_Works<TPixel>(TestImageP
         };
 
         using Image<TPixel> image = provider.GetImage();
-        string testOutputDetails = string.Concat("lossy", "_m", method, "_q", quality);
+        string testOutputDetails = $"lossy_m{method}_q{quality}";
         image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality));
     }
 
@@ -439,7 +441,7 @@ public void Encode_Lossy_WithAlpha_Works<TPixel>(TestImageProvider<TPixel> provi
             "with_alpha",
             encoder,
             ImageComparer.Tolerant(0.04f),
-            referenceDecoder: new MagickReferenceDecoder());
+            referenceDecoder: MagickReferenceDecoder.WebP);
 
         int encodedBytes = File.ReadAllBytes(encodedFile).Length;
         Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}");
@@ -467,7 +469,7 @@ public void Encode_Lossy_WithAlphaUsingCompression_Works<TPixel>(TestImageProvid
             "with_alpha_compressed",
             encoder,
             ImageComparer.Tolerant(0.04f),
-            referenceDecoder: new MagickReferenceDecoder());
+            referenceDecoder: MagickReferenceDecoder.WebP);
 
         int encodedBytes = File.ReadAllBytes(encodedFile).Length;
         Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}");
diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
index 3ae6601b18..f50bc89335 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
 [Trait("Format", "Webp")]
 public class YuvConversionTests
 {
-    private static MagickReferenceDecoder ReferenceDecoder => new();
+    private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.WebP;
 
     private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Webp.Lossy.NoFilter06);
 
diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
index a42dcc4812..f70623f515 100644
--- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
+++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -312,10 +313,10 @@ private static void CompareGifMetadata(ImageFrame a, ImageFrame b)
             GifFrameMetadata aData = a.Metadata.GetGifMetadata();
             GifFrameMetadata bData = b.Metadata.GetGifMetadata();
 
-            Assert.Equal(aData.DisposalMethod, bData.DisposalMethod);
+            Assert.Equal(aData.DisposalMode, bData.DisposalMode);
             Assert.Equal(aData.FrameDelay, bData.FrameDelay);
 
-            if (aData.ColorTableMode == GifColorTableMode.Local && bData.ColorTableMode == GifColorTableMode.Local)
+            if (aData.ColorTableMode == FrameColorTableMode.Local && bData.ColorTableMode == FrameColorTableMode.Local)
             {
                 Assert.Equal(aData.LocalColorTable.Value.Length, bData.LocalColorTable.Value.Length);
             }
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
index 996310d8c3..51e2a01a28 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
@@ -4,6 +4,7 @@
 using Moq;
 
 using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.IO;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
@@ -57,7 +58,7 @@ protected ImageLoadTestBase()
             // TODO: Remove all this mocking. It's too complicated and we can now use fakes.
             this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1);
             this.localStreamReturnImageAgnostic = new Image<Bgra4444>(1, 1);
-            this.LocalImageInfo = new(new PixelTypeInfo(8), new(1, 1), new ImageMetadata());
+            this.LocalImageInfo = new(new(1, 1), new ImageMetadata() { DecodedImageFormat = PngFormat.Instance });
 
             this.localImageFormatMock = new Mock<IImageFormat>();
 
diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs
index 576d14396e..322b0af196 100644
--- a/tests/ImageSharp.Tests/ImageInfoTests.cs
+++ b/tests/ImageSharp.Tests/ImageInfoTests.cs
@@ -1,8 +1,8 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Metadata;
-using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Tests;
 
@@ -15,12 +15,14 @@ public void ImageInfoInitializesCorrectly()
         const int height = 60;
         Size size = new(width, height);
         Rectangle rectangle = new(0, 0, width, height);
-        PixelTypeInfo pixelType = new(8);
-        ImageMetadata meta = new();
 
-        ImageInfo info = new(pixelType, size, meta);
+        // Initialize the metadata to match standard decoding behavior.
+        ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance };
+        meta.GetPngMetadata();
 
-        Assert.Equal(pixelType, info.PixelType);
+        ImageInfo info = new(size, meta);
+
+        Assert.NotEqual(default, info.PixelType);
         Assert.Equal(width, info.Width);
         Assert.Equal(height, info.Height);
         Assert.Equal(size, info.Size);
@@ -35,13 +37,16 @@ public void ImageInfoInitializesCorrectlyWithFrameMetadata()
         const int height = 60;
         Size size = new(width, height);
         Rectangle rectangle = new(0, 0, width, height);
-        PixelTypeInfo pixelType = new(8);
-        ImageMetadata meta = new();
-        IReadOnlyList<ImageFrameMetadata> frameMetadata = new List<ImageFrameMetadata>() { new() };
 
-        ImageInfo info = new(pixelType, size, meta, frameMetadata);
+        // Initialize the metadata to match standard decoding behavior.
+        ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance };
+        meta.GetPngMetadata();
+
+        IReadOnlyList<ImageFrameMetadata> frameMetadata = [new()];
+
+        ImageInfo info = new(size, meta, frameMetadata);
 
-        Assert.Equal(pixelType, info.PixelType);
+        Assert.NotEqual(default, info.PixelType);
         Assert.Equal(width, info.Width);
         Assert.Equal(height, info.Height);
         Assert.Equal(size, info.Size);
diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
index bcc9675404..cdd6f0cc4f 100644
--- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Formats.Gif;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@@ -21,20 +22,20 @@ public void ConstructorImageFrameMetadata()
     {
         const int frameDelay = 42;
         const int colorTableLength = 128;
-        const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground;
+        const FrameDisposalMode disposalMethod = FrameDisposalMode.RestoreToBackground;
 
         ImageFrameMetadata metaData = new();
         GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata();
         gifFrameMetadata.FrameDelay = frameDelay;
         gifFrameMetadata.LocalColorTable = Enumerable.Repeat(Color.HotPink, colorTableLength).ToArray();
-        gifFrameMetadata.DisposalMethod = disposalMethod;
+        gifFrameMetadata.DisposalMode = disposalMethod;
 
         ImageFrameMetadata clone = new(metaData);
         GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata();
 
         Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay);
         Assert.Equal(colorTableLength, cloneGifFrameMetadata.LocalColorTable.Value.Length);
-        Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod);
+        Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMode);
     }
 
     [Fact]
diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs
index 330b701474..ae02c3d57b 100644
--- a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs
@@ -16,9 +16,9 @@ public class ImageMetadataTests
     [Fact]
     public void ConstructorImageMetadata()
     {
-        var metaData = new ImageMetadata();
+        ImageMetadata metaData = new();
 
-        var exifProfile = new ExifProfile();
+        ExifProfile exifProfile = new();
 
         metaData.ExifProfile = exifProfile;
         metaData.HorizontalResolution = 4;
@@ -34,7 +34,7 @@ public void ConstructorImageMetadata()
     [Fact]
     public void CloneIsDeep()
     {
-        var metaData = new ImageMetadata
+        ImageMetadata metaData = new()
         {
             ExifProfile = new ExifProfile(),
             HorizontalResolution = 4,
@@ -53,7 +53,7 @@ public void CloneIsDeep()
     [Fact]
     public void HorizontalResolution()
     {
-        var metaData = new ImageMetadata();
+        ImageMetadata metaData = new();
         Assert.Equal(96, metaData.HorizontalResolution);
 
         metaData.HorizontalResolution = 0;
@@ -69,7 +69,7 @@ public void HorizontalResolution()
     [Fact]
     public void VerticalResolution()
     {
-        var metaData = new ImageMetadata();
+        ImageMetadata metaData = new();
         Assert.Equal(96, metaData.VerticalResolution);
 
         metaData.VerticalResolution = 0;
@@ -85,20 +85,19 @@ public void VerticalResolution()
     [Fact]
     public void SyncProfiles()
     {
-        var exifProfile = new ExifProfile();
+        ExifProfile exifProfile = new();
         exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
         exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
 
-        using (var image = new Image<Rgba32>(1, 1))
-        {
-            image.Metadata.ExifProfile = exifProfile;
-            image.Metadata.HorizontalResolution = 400;
-            image.Metadata.VerticalResolution = 500;
+        using Image<Rgba32> image = new(1, 1);
+        image.Metadata.ExifProfile = exifProfile;
+        image.Metadata.HorizontalResolution = 400;
+        image.Metadata.VerticalResolution = 500;
 
-            image.Metadata.SyncProfiles();
+        using MemoryStream memoryStream = new();
+        image.SaveAsBmp(memoryStream);
 
-            Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
-            Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
-        }
+        Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
+        Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
     }
 }
diff --git a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs
index 2ddf1accb6..7f0a4217c1 100644
--- a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs
@@ -161,7 +161,7 @@ public void L16_PixelInformation()
         PixelTypeInfo info = L16.GetPixelTypeInfo();
         Assert.Equal(Unsafe.SizeOf<L16>() * 8, info.BitsPerPixel);
         Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation);
-        Assert.Equal(PixelColorType.Grayscale, info.ColorType);
+        Assert.Equal(PixelColorType.Luminance, info.ColorType);
 
         PixelComponentInfo componentInfo = info.ComponentInfo.Value;
         Assert.Equal(1, componentInfo.ComponentCount);
diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs
index 40c746cf21..1ca865ef41 100644
--- a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs
@@ -245,7 +245,7 @@ public void L8_PixelInformation()
             PixelTypeInfo info = L8.GetPixelTypeInfo();
             Assert.Equal(Unsafe.SizeOf<L8>() * 8, info.BitsPerPixel);
             Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation);
-            Assert.Equal(PixelColorType.Grayscale, info.ColorType);
+            Assert.Equal(PixelColorType.Luminance, info.ColorType);
 
             PixelComponentInfo componentInfo = info.ComponentInfo.Value;
             Assert.Equal(1, componentInfo.ComponentCount);
diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs
index a18b31f6ba..f6cbfc4426 100644
--- a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs
@@ -248,7 +248,7 @@ public void La16_PixelInformation()
             PixelTypeInfo info = La16.GetPixelTypeInfo();
             Assert.Equal(Unsafe.SizeOf<La16>() * 8, info.BitsPerPixel);
             Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation);
-            Assert.Equal(PixelColorType.Grayscale | PixelColorType.Alpha, info.ColorType);
+            Assert.Equal(PixelColorType.Luminance | PixelColorType.Alpha, info.ColorType);
 
             PixelComponentInfo componentInfo = info.ComponentInfo.Value;
             Assert.Equal(2, componentInfo.ComponentCount);
diff --git a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs
index 3c702419d3..fd5556d3bc 100644
--- a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs
@@ -167,7 +167,7 @@ public void La32_PixelInformation()
         PixelTypeInfo info = La32.GetPixelTypeInfo();
         Assert.Equal(Unsafe.SizeOf<La32>() * 8, info.BitsPerPixel);
         Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation);
-        Assert.Equal(PixelColorType.Grayscale | PixelColorType.Alpha, info.ColorType);
+        Assert.Equal(PixelColorType.Luminance | PixelColorType.Alpha, info.ColorType);
 
         PixelComponentInfo componentInfo = info.ComponentInfo.Value;
         Assert.Equal(2, componentInfo.ComponentCount);
diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs
new file mode 100644
index 0000000000..05fd25cca9
--- /dev/null
+++ b/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs
@@ -0,0 +1,333 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests.PixelFormats;
+
+public class PixelColorTypeTests
+{
+    [Fact]
+    public void PixelColorType_RedFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Red;
+        Assert.True(colorType.HasFlag(PixelColorType.Red));
+    }
+
+    [Fact]
+    public void PixelColorType_GreenFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Green;
+        Assert.True(colorType.HasFlag(PixelColorType.Green));
+    }
+
+    [Fact]
+    public void PixelColorType_BlueFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Blue;
+        Assert.True(colorType.HasFlag(PixelColorType.Blue));
+    }
+
+    [Fact]
+    public void PixelColorType_AlphaFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Alpha;
+        Assert.True(colorType.HasFlag(PixelColorType.Alpha));
+    }
+
+    [Fact]
+    public void PixelColorType_Exponent_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Exponent;
+        Assert.True(colorType.HasFlag(PixelColorType.Exponent));
+    }
+
+    [Fact]
+    public void PixelColorType_LuminanceFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Luminance;
+        Assert.True(colorType.HasFlag(PixelColorType.Luminance));
+    }
+
+    [Fact]
+    public void PixelColorType_Binary_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Binary;
+        Assert.True(colorType.HasFlag(PixelColorType.Binary));
+    }
+
+    [Fact]
+    public void PixelColorType_Indexed_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Indexed;
+        Assert.True(colorType.HasFlag(PixelColorType.Indexed));
+    }
+
+    [Fact]
+    public void PixelColorType_RGBFlags_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.RGB;
+        Assert.True(colorType.HasFlag(PixelColorType.Red));
+        Assert.True(colorType.HasFlag(PixelColorType.Green));
+        Assert.True(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.BGR));
+    }
+
+    [Fact]
+    public void PixelColorType_BGRFlags_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.BGR;
+        Assert.True(colorType.HasFlag(PixelColorType.Blue));
+        Assert.True(colorType.HasFlag(PixelColorType.Green));
+        Assert.True(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.RGB));
+    }
+
+    [Fact]
+    public void PixelColorType_ChrominanceBlueFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.ChrominanceBlue;
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+    }
+
+    [Fact]
+    public void PixelColorType_ChrominanceRedFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.ChrominanceRed;
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed));
+    }
+
+    [Fact]
+    public void PixelColorType_YCbCrFlags_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.YCbCr;
+        Assert.True(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed));
+    }
+
+    [Fact]
+    public void PixelColorType_CyanFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Cyan;
+        Assert.True(colorType.HasFlag(PixelColorType.Cyan));
+    }
+
+    [Fact]
+    public void PixelColorType_MagentaFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Magenta;
+        Assert.True(colorType.HasFlag(PixelColorType.Magenta));
+    }
+
+    [Fact]
+    public void PixelColorType_YellowFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Yellow;
+        Assert.True(colorType.HasFlag(PixelColorType.Yellow));
+    }
+
+    [Fact]
+    public void PixelColorType_KeyFlag_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Key;
+        Assert.True(colorType.HasFlag(PixelColorType.Key));
+    }
+
+    [Fact]
+    public void PixelColorType_CMYKFlags_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.CMYK;
+        Assert.True(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.True(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.True(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.True(colorType.HasFlag(PixelColorType.Key));
+    }
+
+    [Fact]
+    public void PixelColorType_YCCKFlags_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.YCCK;
+        Assert.True(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.True(colorType.HasFlag(PixelColorType.Key));
+    }
+
+    [Fact]
+    public void PixelColorType_Other_ShouldBeSet()
+    {
+        const PixelColorType colorType = PixelColorType.Other;
+        Assert.True(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_None_ShouldBeZero()
+    {
+        const PixelColorType colorType = PixelColorType.None;
+        Assert.Equal(0, (int)colorType);
+    }
+
+    [Fact]
+    public void PixelColorType_RGB_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.RGB;
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_BGR_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.BGR;
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_YCbCr_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.YCbCr;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_CMYK_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.CMYK;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_YCCK_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.YCCK;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_Binary_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.Binary;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.RGB));
+        Assert.False(colorType.HasFlag(PixelColorType.BGR));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.YCbCr));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.CMYK));
+        Assert.False(colorType.HasFlag(PixelColorType.YCCK));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_Indexed_ShouldNotContainOtherFlags()
+    {
+        const PixelColorType colorType = PixelColorType.Indexed;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.RGB));
+        Assert.False(colorType.HasFlag(PixelColorType.BGR));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.YCbCr));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.CMYK));
+        Assert.False(colorType.HasFlag(PixelColorType.YCCK));
+        Assert.False(colorType.HasFlag(PixelColorType.Other));
+    }
+
+    [Fact]
+    public void PixelColorType_Other_ShouldNotContainPreviousFlags()
+    {
+        const PixelColorType colorType = PixelColorType.Other;
+        Assert.False(colorType.HasFlag(PixelColorType.Red));
+        Assert.False(colorType.HasFlag(PixelColorType.Green));
+        Assert.False(colorType.HasFlag(PixelColorType.Blue));
+        Assert.False(colorType.HasFlag(PixelColorType.Alpha));
+        Assert.False(colorType.HasFlag(PixelColorType.Exponent));
+        Assert.False(colorType.HasFlag(PixelColorType.Luminance));
+        Assert.False(colorType.HasFlag(PixelColorType.Binary));
+        Assert.False(colorType.HasFlag(PixelColorType.Indexed));
+        Assert.False(colorType.HasFlag(PixelColorType.RGB));
+        Assert.False(colorType.HasFlag(PixelColorType.BGR));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue));
+        Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed));
+        Assert.False(colorType.HasFlag(PixelColorType.YCbCr));
+        Assert.False(colorType.HasFlag(PixelColorType.Cyan));
+        Assert.False(colorType.HasFlag(PixelColorType.Magenta));
+        Assert.False(colorType.HasFlag(PixelColorType.Yellow));
+        Assert.False(colorType.HasFlag(PixelColorType.Key));
+        Assert.False(colorType.HasFlag(PixelColorType.CMYK));
+        Assert.False(colorType.HasFlag(PixelColorType.YCCK));
+    }
+}
diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs
index 10211f386c..d30ce7846d 100644
--- a/tests/ImageSharp.Tests/TestFormat.cs
+++ b/tests/ImageSharp.Tests/TestFormat.cs
@@ -7,6 +7,7 @@
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Tests.TestUtilities;
+using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
 
 namespace SixLabors.ImageSharp.Tests;
 
@@ -89,7 +90,7 @@ public Image<TPixel> Sample<TPixel>()
         {
             if (!this.sampleImages.ContainsKey(typeof(TPixel)))
             {
-                this.sampleImages.Add(typeof(TPixel), new Image<TPixel>(1, 1));
+                this.sampleImages.Add(typeof(TPixel), ReferenceCodecUtilities.EnsureDecodedMetadata(new Image<TPixel>(1, 1), this));
             }
 
             return (Image<TPixel>)this.sampleImages[typeof(TPixel)];
@@ -202,11 +203,12 @@ public class TestDecoder : SpecializedImageDecoder<TestDecoderOptions>
 
         protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
         {
-            Image<TestPixelForAgnosticDecode> image =
-                this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
-            ImageFrameCollection<TestPixelForAgnosticDecode> m = image.Frames;
-
-            return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
+            using Image<TestPixelForAgnosticDecode> image = this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
+            ImageMetadata metadata = image.Metadata;
+            return new(image.Size, metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)))
+            {
+                PixelType = metadata.GetDecodedPixelTypeInfo()
+            };
         }
 
         protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
index 80b5536ebd..f96dc19ee0 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
@@ -5,6 +5,11 @@
 using ImageMagick;
 using ImageMagick.Formats;
 using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Webp;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
@@ -13,19 +18,34 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
 
 public class MagickReferenceDecoder : ImageDecoder
 {
+    private readonly IImageFormat imageFormat;
     private readonly bool validate;
 
-    public MagickReferenceDecoder()
-        : this(true)
+    public MagickReferenceDecoder(IImageFormat imageFormat)
+        : this(imageFormat, true)
     {
     }
 
-    public MagickReferenceDecoder(bool validate) => this.validate = validate;
+    public MagickReferenceDecoder(IImageFormat imageFormat, bool validate)
+    {
+        this.imageFormat = imageFormat;
+        this.validate = validate;
+    }
+
+    public static MagickReferenceDecoder Png { get; } = new MagickReferenceDecoder(PngFormat.Instance);
+
+    public static MagickReferenceDecoder Bmp { get; } = new MagickReferenceDecoder(BmpFormat.Instance);
+
+    public static MagickReferenceDecoder Jpeg { get; } = new MagickReferenceDecoder(JpegFormat.Instance);
 
-    public static MagickReferenceDecoder Instance { get; } = new();
+    public static MagickReferenceDecoder Tiff { get; } = new MagickReferenceDecoder(TiffFormat.Instance);
+
+    public static MagickReferenceDecoder WebP { get; } = new MagickReferenceDecoder(WebpFormat.Instance);
 
     protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
     {
+        ImageMetadata metadata = new();
+
         Configuration configuration = options.Configuration;
         BmpReadDefines bmpReadDefines = new()
         {
@@ -44,7 +64,7 @@ protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream s
         settings.SetDefines(pngReadDefines);
 
         using MagickImageCollection magickImageCollection = new(stream, settings);
-        List<ImageFrame<TPixel>> framesList = new();
+        List<ImageFrame<TPixel>> framesList = [];
         foreach (IMagickImage<ushort> magicFrame in magickImageCollection)
         {
             ImageFrame<TPixel> frame = new(configuration, magicFrame.Width, magicFrame.Height);
@@ -61,6 +81,11 @@ protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream s
             }
             else if (magicFrame.Depth is 16 or 14)
             {
+                if (this.imageFormat is PngFormat png)
+                {
+                    metadata.GetPngMetadata().BitDepth = PngBitDepth.Bit16;
+                }
+
                 ushort[] data = pixels.ToShortArray(PixelMapping.RGBA);
                 Span<byte> bytes = MemoryMarshal.Cast<ushort, byte>(data.AsSpan());
                 FromRgba64Bytes(configuration, bytes, framePixels);
@@ -71,7 +96,7 @@ protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream s
             }
         }
 
-        return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
+        return ReferenceCodecUtilities.EnsureDecodedMetadata<TPixel>(new(configuration, metadata, framesList), this.imageFormat);
     }
 
     protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
@@ -80,7 +105,11 @@ protected override Image Decode(DecoderOptions options, Stream stream, Cancellat
     protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
     {
         using Image<Rgba32> image = this.Decode<Rgba32>(options, stream, cancellationToken);
-        return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
+        ImageMetadata metadata = image.Metadata;
+        return new(image.Size, metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)))
+        {
+            PixelType = metadata.GetDecodedPixelTypeInfo()
+        };
     }
 
     private static void FromRgba32Bytes<TPixel>(Configuration configuration, Span<byte> rgbaBytes, IMemoryGroup<TPixel> destinationGroup)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs
new file mode 100644
index 0000000000..e48116fed3
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Pbm;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Webp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
+
+internal static class ReferenceCodecUtilities
+{
+    /// <summary>
+    /// Ensures that the metadata is properly initialized for reference and test encoders which cannot initialize
+    /// metadata in the same manner as our built in decoders.
+    /// </summary>
+    /// <typeparam name="TPixel">The type of pixel format.</typeparam>
+    /// <param name="image">The decoded image.</param>
+    /// <param name="format">The image format</param>
+    /// <exception cref="NotSupportedException">The format is unknown.</exception>
+    public static Image<TPixel> EnsureDecodedMetadata<TPixel>(Image<TPixel> image, IImageFormat format)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        if (image.Metadata.DecodedImageFormat is null)
+        {
+            image.Metadata.DecodedImageFormat = format;
+        }
+
+        foreach (ImageFrame frame in image.Frames)
+        {
+            frame.Metadata.DecodedImageFormat = format;
+        }
+
+        switch (format)
+        {
+            case BmpFormat:
+                image.Metadata.GetBmpMetadata();
+                break;
+            case GifFormat:
+                image.Metadata.GetGifMetadata();
+                foreach (ImageFrame frame in image.Frames)
+                {
+                    frame.Metadata.GetGifMetadata();
+                }
+
+                break;
+            case JpegFormat:
+                image.Metadata.GetJpegMetadata();
+                break;
+            case PbmFormat:
+                image.Metadata.GetPbmMetadata();
+                break;
+            case PngFormat:
+                image.Metadata.GetPngMetadata();
+                foreach (ImageFrame frame in image.Frames)
+                {
+                    frame.Metadata.GetPngMetadata();
+                }
+
+                break;
+            case QoiFormat:
+                image.Metadata.GetQoiMetadata();
+                break;
+            case TgaFormat:
+                image.Metadata.GetTgaMetadata();
+                break;
+            case TiffFormat:
+                image.Metadata.GetTiffMetadata();
+                foreach (ImageFrame frame in image.Frames)
+                {
+                    frame.Metadata.GetTiffMetadata();
+                }
+
+                break;
+            case WebpFormat:
+                image.Metadata.GetWebpMetadata();
+                foreach (ImageFrame frame in image.Frames)
+                {
+                    frame.Metadata.GetWebpMetadata();
+                }
+
+                break;
+        }
+
+        return image;
+    }
+}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
index a3408bedb4..14a655823d 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
@@ -1,23 +1,35 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+#pragma warning disable CA1416 // Validate platform compatibility
 using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Bmp;
+using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
 using SDBitmap = System.Drawing.Bitmap;
-using SDImage = System.Drawing.Image;
 
 namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
 
 public class SystemDrawingReferenceDecoder : ImageDecoder
 {
-    public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
+    private readonly IImageFormat imageFormat;
+
+    public SystemDrawingReferenceDecoder(IImageFormat imageFormat)
+        => this.imageFormat = imageFormat;
+
+    public static SystemDrawingReferenceDecoder Png { get; } = new SystemDrawingReferenceDecoder(PngFormat.Instance);
+
+    public static SystemDrawingReferenceDecoder Bmp { get; } = new SystemDrawingReferenceDecoder(BmpFormat.Instance);
 
     protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
     {
-        using SDBitmap sourceBitmap = new(stream);
-        PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));
-        return new ImageInfo(pixelType, new(sourceBitmap.Width, sourceBitmap.Height), new ImageMetadata());
+        using Image<Rgba32> image = this.Decode<Rgba32>(options, stream, cancellationToken);
+        ImageMetadata metadata = image.Metadata;
+        return new(image.Size, metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)))
+        {
+            PixelType = metadata.GetDecodedPixelTypeInfo()
+        };
     }
 
     protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
@@ -42,7 +54,9 @@ protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream s
             g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height);
         }
 
-        return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap<TPixel>(convertedBitmap);
+        return ReferenceCodecUtilities.EnsureDecodedMetadata(
+            SystemDrawingBridge.From32bppArgbSystemDrawingBitmap<TPixel>(convertedBitmap),
+            this.imageFormat);
     }
 
     protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs
index d8dda2eea8..0789ab2634 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+#pragma warning disable CA1416 // Validate platform compatibility
 using System.Drawing.Imaging;
 using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.PixelFormats;
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
index 9508de2469..e4bee955b9 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@@ -71,13 +71,13 @@ private static Configuration CreateDefaultConfiguration()
         // Magick codecs should work on all platforms
         cfg.ConfigureCodecs(
             PngFormat.Instance,
-            MagickReferenceDecoder.Instance,
+            MagickReferenceDecoder.Png,
             pngEncoder,
             new PngImageFormatDetector());
 
         cfg.ConfigureCodecs(
             BmpFormat.Instance,
-            IsWindows ? SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance,
+            IsWindows ? SystemDrawingReferenceDecoder.Bmp : MagickReferenceDecoder.Bmp,
             bmpEncoder,
             new BmpImageFormatDetector());
 
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
index 5da12f2643..05abedbd8e 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
@@ -697,23 +697,6 @@ internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity
         return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf<TPixel>());
     }
 
-    internal static Image<Rgba32> ToGrayscaleImage(this Buffer2D<float> buffer, float scale)
-    {
-        Image<Rgba32> image = new(buffer.Width, buffer.Height);
-
-        Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> pixelMem));
-        Span<Rgba32> pixels = pixelMem.Span;
-        Span<float> bufferSpan = buffer.DangerousGetSingleSpan();
-
-        for (int i = 0; i < bufferSpan.Length; i++)
-        {
-            float value = bufferSpan[i] * scale;
-            pixels[i] = Rgba32.FromVector4(new Vector4(value, value, value, 1f));
-        }
-
-        return image;
-    }
-
     private class MakeOpaqueProcessor : IImageProcessor
     {
         public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
index 81ea77b6b6..b818e80b05 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
@@ -7,14 +7,13 @@
 using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
 using Xunit.Abstractions;
 
-// ReSharper disable InconsistentNaming
 namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests;
 
 public class MagickReferenceCodecTests
 {
     public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output;
 
-    private ITestOutputHelper Output { get; }
+    public ITestOutputHelper Output { get; }
 
     public const PixelTypes PixelTypesToTest32 = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24;
 
@@ -32,8 +31,8 @@ public void MagickDecode_8BitDepthImage_IsEquivalentTo_SystemDrawingResult<TPixe
     {
         string path = TestFile.GetInputFileFullPath(testImage);
 
-        var magickDecoder = new MagickReferenceDecoder();
-        var sdDecoder = new SystemDrawingReferenceDecoder();
+        MagickReferenceDecoder magickDecoder = MagickReferenceDecoder.Png;
+        SystemDrawingReferenceDecoder sdDecoder = SystemDrawingReferenceDecoder.Png;
 
         ImageComparer comparer = ImageComparer.Exact;
 
@@ -64,11 +63,11 @@ public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawi
     {
         string path = TestFile.GetInputFileFullPath(testImage);
 
-        var magickDecoder = new MagickReferenceDecoder();
-        var sdDecoder = new SystemDrawingReferenceDecoder();
+        MagickReferenceDecoder magickDecoder = MagickReferenceDecoder.Png;
+        SystemDrawingReferenceDecoder sdDecoder = SystemDrawingReferenceDecoder.Png;
 
-        // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space)
-        var comparer = ImageComparer.TolerantPercentage(1, 1020);
+        // 1020 == 4 * 255 (Equivalent to Manhattan distance of 1+1+1+1=4 in Rgba32 space)
+        ImageComparer comparer = ImageComparer.TolerantPercentage(1, 1020);
         using FileStream mStream = File.OpenRead(path);
         using FileStream sdStream = File.OpenRead(path);
         using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream);
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs
index 3a3fcefdbe..6e1eba28e8 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs
@@ -47,7 +47,7 @@ public ReferenceDecoderBenchmarks(ITestOutputHelper output)
     public void BenchmarkMagickPngDecoder<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png");
+        this.BenchmarkDecoderImpl(PngBenchmarkFiles, MagickReferenceDecoder.Png, "Magick Decode Png");
     }
 
     [Theory(Skip = SkipBenchmarks)]
@@ -55,7 +55,7 @@ public void BenchmarkMagickPngDecoder<TPixel>(TestImageProvider<TPixel> provider
     public void BenchmarkSystemDrawingPngDecoder<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png");
+        this.BenchmarkDecoderImpl(PngBenchmarkFiles, SystemDrawingReferenceDecoder.Png, "System.Drawing Decode Png");
     }
 
     [Theory(Skip = SkipBenchmarks)]
@@ -63,7 +63,7 @@ public void BenchmarkSystemDrawingPngDecoder<TPixel>(TestImageProvider<TPixel> p
     public void BenchmarkMagickBmpDecoder<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp");
+        this.BenchmarkDecoderImpl(BmpBenchmarkFiles, MagickReferenceDecoder.Bmp, "Magick Decode Bmp");
     }
 
     [Theory(Skip = SkipBenchmarks)]
@@ -71,7 +71,7 @@ public void BenchmarkMagickBmpDecoder<TPixel>(TestImageProvider<TPixel> provider
     public void BenchmarkSystemDrawingBmpDecoder<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp");
+        this.BenchmarkDecoderImpl(BmpBenchmarkFiles, SystemDrawingReferenceDecoder.Bmp, "System.Drawing Decode Bmp");
     }
 
     private void BenchmarkDecoderImpl(IEnumerable<string> testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount)
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs
index a89feb3c35..4608583791 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs
@@ -93,7 +93,7 @@ public void OpenWithReferenceDecoder<TPixel>(TestImageProvider<TPixel> dummyProv
     {
         string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash);
         using FileStream stream = File.OpenRead(path);
-        using Image<TPixel> image = SystemDrawingReferenceDecoder.Instance.Decode<TPixel>(DecoderOptions.Default, stream);
+        using Image<TPixel> image = SystemDrawingReferenceDecoder.Png.Decode<TPixel>(DecoderOptions.Default, stream);
         image.DebugSave(dummyProvider);
     }
 
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
index 2f5291131c..f33811206a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
@@ -3,9 +3,11 @@
 
 using System.Collections.Concurrent;
 using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Png;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.Metadata;
 using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
 using Xunit.Abstractions;
 
 // ReSharper disable InconsistentNaming
@@ -25,7 +27,7 @@ public class TestImageProviderTests
         TestImageProvider<HalfVector4>.File(TestImages.Bmp.F)
     };
 
-    public static string[] AllBmpFiles = { TestImages.Bmp.F, TestImages.Bmp.Bit8 };
+    public static string[] AllBmpFiles = [TestImages.Bmp.F, TestImages.Bmp.Bit8];
 
     public TestImageProviderTests(ITestOutputHelper output) => this.Output = output;
 
@@ -80,9 +82,9 @@ public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache<TPixel>(
         TestDecoder.DoTestThreadSafe(
             () =>
                 {
-                    string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache);
+                    const string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache);
 
-                    var decoder = new TestDecoder();
+                    TestDecoder decoder = new();
                     decoder.InitCaller(testName);
 
                     provider.GetImage(decoder);
@@ -335,7 +337,7 @@ private static void EnsureCustomConfigurationIsApplied<TPixel>(TestImageProvider
     {
         using (provider.GetImage())
         {
-            var customConfiguration = Configuration.CreateDefaultInstance();
+            Configuration customConfiguration = Configuration.CreateDefaultInstance();
             provider.Configuration = customConfiguration;
 
             using Image<TPixel> image2 = provider.GetImage();
@@ -365,13 +367,17 @@ public static void DoTestThreadSafe(Action action)
         protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
         {
             using Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
-            return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
+            ImageMetadata metadata = image.Metadata;
+            return new(image.Size, metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)))
+            {
+                PixelType = metadata.GetDecodedPixelTypeInfo()
+            };
         }
 
         protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
         {
             InvocationCounts[this.callerName]++;
-            return new Image<TPixel>(42, 42);
+            return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image<TPixel>(42, 42), PngFormat.Instance);
         }
 
         protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
@@ -408,13 +414,17 @@ public static void DoTestThreadSafe(Action action)
         protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
         {
             using Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
-            return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
+            ImageMetadata metadata = image.Metadata;
+            return new(image.Size, metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)))
+            {
+                PixelType = metadata.GetDecodedPixelTypeInfo()
+            };
         }
 
         protected override Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
         {
             InvocationCounts[this.callerName]++;
-            return new Image<TPixel>(42, 42);
+            return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image<TPixel>(42, 42), PngFormat.Instance);
         }
 
         protected override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
