diff --git a/src/System.Drawing.Common/src/System/Drawing/Image.cs b/src/System.Drawing.Common/src/System/Drawing/Image.cs
index 33bd7b69e88..f2940bc4f51 100644
--- a/src/System.Drawing.Common/src/System/Drawing/Image.cs
+++ b/src/System.Drawing.Common/src/System/Drawing/Image.cs
@@ -8,9 +8,6 @@
using System.Runtime.Serialization;
using Windows.Win32.System.Com;
-#if DEBUG
-#endif
-
namespace System.Drawing;
///
@@ -259,18 +256,29 @@ public void Save(string filename, ImageFormat format)
{
ArgumentNullException.ThrowIfNull(format);
- ImageCodecInfo codec = format.FindEncoder() ?? ImageFormat.Png.FindEncoder()!;
+ Guid encoder = format.Encoder;
+ if (encoder == Guid.Empty)
+ {
+ encoder = ImageCodecInfo.GetEncoderClsid(PInvokeCore.ImageFormatPNG);
+ }
- Save(filename, codec, null);
+ Save(filename, encoder, null);
}
///
/// Saves this to the specified file in the specified format and with the specified encoder parameters.
///
public void Save(string filename, ImageCodecInfo encoder, Imaging.EncoderParameters? encoderParams)
+ => Save(filename, encoder.Clsid, encoderParams);
+
+ private void Save(string filename, Guid encoder, Imaging.EncoderParameters? encoderParams)
{
ArgumentNullException.ThrowIfNull(filename);
- ArgumentNullException.ThrowIfNull(encoder);
+ if (encoder == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(encoder));
+ }
+
ThrowIfDirectoryDoesntExist(filename);
GdiPlus.EncoderParameters* nativeParameters = null;
@@ -283,22 +291,17 @@ public void Save(string filename, ImageCodecInfo encoder, Imaging.EncoderParamet
try
{
- Guid guid = encoder.Clsid;
- bool saved = false;
-
- if (_animatedGifRawData is not null && RawFormat.FindEncoder() is { } rawEncoder && rawEncoder.Clsid == guid)
+ if (_animatedGifRawData is not null && RawFormat.Encoder == encoder)
{
+ // Special case for animated gifs. We don't have an encoder for them, so we just write the raw data.
using var fs = File.OpenWrite(filename);
fs.Write(_animatedGifRawData, 0, _animatedGifRawData.Length);
- saved = true;
+ return;
}
- if (!saved)
+ fixed (char* fn = filename)
{
- fixed (char* fn = filename)
- {
- PInvoke.GdipSaveImageToFile(_nativeImage, fn, &guid, nativeParameters).ThrowIfFailed();
- }
+ PInvoke.GdipSaveImageToFile(_nativeImage, fn, &encoder, nativeParameters).ThrowIfFailed();
}
}
finally
@@ -315,15 +318,18 @@ public void Save(string filename, ImageCodecInfo encoder, Imaging.EncoderParamet
private void Save(MemoryStream stream)
{
- // Jpeg loses data, so we don't want to use it to serialize...
- ImageFormat dest = RawFormat;
- if (dest.Guid == ImageFormat.Jpeg.Guid)
- dest = ImageFormat.Png;
+ Guid format = RawFormat.Guid;
+ Guid encoder = ImageCodecInfo.GetEncoderClsid(format);
- // If we don't find an Encoder (for things like Icon), we just switch back to PNG...
- ImageCodecInfo codec = dest.FindEncoder() ?? ImageFormat.Png.FindEncoder()!;
+ // Jpeg loses data, so we don't want to use it to serialize. We'll use PNG instead.
+ // If we don't find an Encoder (for things like Icon), we just switch back to PNG.
+ if (format == PInvokeCore.ImageFormatJPEG || encoder == Guid.Empty)
+ {
+ format = PInvokeCore.ImageFormatPNG;
+ encoder = ImageCodecInfo.GetEncoderClsid(format);
+ }
- Save(stream, codec);
+ Save(this, stream, encoder, format, null);
}
///
@@ -332,23 +338,18 @@ private void Save(MemoryStream stream)
public void Save(Stream stream, ImageFormat format)
{
ArgumentNullException.ThrowIfNull(format);
-
- ImageCodecInfo codec = format.FindEncoder()!;
- Save(stream, codec);
+ Save(this, stream, format.Encoder, format.Guid, null);
}
- internal void Save(Stream stream, ImageCodecInfo encoder) =>
- Save(stream, encoder, encoderParameters: null);
-
- internal void Save(Stream stream, ImageCodecInfo encoder, GdiPlus.EncoderParameters* encoderParameters) =>
- Save(this, stream, encoder.Clsid, encoder.FormatID, encoderParameters);
-
internal static void Save(IImage image, Stream stream, Guid encoder, Guid format, GdiPlus.EncoderParameters* encoderParameters)
{
ArgumentNullException.ThrowIfNull(stream);
- ArgumentNullException.ThrowIfNull(encoder);
+ if (encoder == Guid.Empty)
+ {
+ throw new ArgumentNullException(nameof(encoder));
+ }
- if (format == ImageFormat.Gif.Guid && image.Data is { } rawData && rawData.Length > 0)
+ if (format == PInvokeCore.ImageFormatGIF && image.Data is { } rawData && rawData.Length > 0)
{
stream.Write(rawData);
return;
@@ -376,7 +377,7 @@ public void Save(Stream stream, ImageCodecInfo encoder, Imaging.EncoderParameter
try
{
- Save(stream, encoder, nativeParameters);
+ Save(this, stream, encoder.Clsid, encoder.FormatID, nativeParameters);
}
finally
{
diff --git a/src/System.Drawing.Common/src/System/Drawing/ImageConverter.cs b/src/System.Drawing.Common/src/System/Drawing/ImageConverter.cs
index eb56d7129d8..64aab9f103b 100644
--- a/src/System.Drawing.Common/src/System/Drawing/ImageConverter.cs
+++ b/src/System.Drawing.Common/src/System/Drawing/ImageConverter.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
-using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
@@ -40,7 +39,7 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(
}
}
- public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
+ public unsafe override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string))
{
@@ -63,17 +62,18 @@ public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? c
{
using MemoryStream ms = new();
- ImageFormat dest = image.RawFormat;
- // Jpeg loses data, so we don't want to use it to serialize.
- if (dest == ImageFormat.Jpeg)
+ Guid format = image.RawFormat.Guid;
+ Guid encoder = ImageCodecInfo.GetEncoderClsid(format);
+
+ // Jpeg loses data, so we don't want to use it to serialize. We'll use PNG instead.
+ // If we don't find an Encoder (for things like Icon), we just switch back to PNG.
+ if (format == PInvokeCore.ImageFormatJPEG || encoder == Guid.Empty)
{
- dest = ImageFormat.Png;
+ format = PInvokeCore.ImageFormatPNG;
+ encoder = ImageCodecInfo.GetEncoderClsid(format);
}
- // If we don't find an Encoder (for things like Icon), we
- // just switch back to PNG.
- ImageCodecInfo codec = FindEncoder(dest) ?? FindEncoder(ImageFormat.Png)!;
- image.Save(ms, codec);
+ Image.Save(image, ms, encoder, format, null);
return ms.ToArray();
}
}
@@ -81,19 +81,6 @@ public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? c
throw GetConvertFromException(value);
}
- // Find any random encoder which supports this format.
- private static ImageCodecInfo? FindEncoder(ImageFormat imageformat)
- {
- ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
- foreach (ImageCodecInfo codec in codecs)
- {
- if (codec.FormatID.Equals(imageformat.Guid))
- return codec;
- }
-
- return null;
- }
-
[RequiresUnreferencedCode("The Type of value cannot be statically discovered. The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")]
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object? value, Attribute[]? attributes)
{
diff --git a/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageCodecInfo.cs b/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageCodecInfo.cs
index cc10b23bace..be452b5e659 100644
--- a/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageCodecInfo.cs
+++ b/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageCodecInfo.cs
@@ -36,6 +36,58 @@ internal ImageCodecInfo()
// Encoder/Decoder selection APIs
+ // Getting the entire array of ImageCodecInfo objects is expensive and not necessary when all we need are the
+ // encoder CLSIDs. There are only 5 encoders: PNG, JPEG, GIF, BMP, and TIFF. We'll just build the small cache
+ // here to avoid all of the overhead. (We could probably just hard-code the values, but on the very small chance
+ // that the list of encoders changes, we'll keep it dynamic.)
+ private static (Guid Format, Guid Encoder)[]? s_encoders;
+
+ ///
+ /// Get the encoder guid for the given image format guid.
+ ///
+ internal static Guid GetEncoderClsid(Guid format)
+ {
+ foreach ((Guid Format, Guid Encoder) in Encoders)
+ {
+ if (Format == format)
+ {
+ return Encoder;
+ }
+ }
+
+ return Guid.Empty;
+ }
+
+ private static (Guid Format, Guid Encoder)[] Encoders
+ {
+ get
+ {
+ if (s_encoders is null)
+ {
+ uint numEncoders;
+ uint size;
+
+ PInvoke.GdipGetImageEncodersSize(&numEncoders, &size).ThrowIfFailed();
+
+ using BufferScope buffer = new((int)size);
+
+ fixed (byte* b = buffer)
+ {
+ PInvoke.GdipGetImageEncoders(numEncoders, size, (GdiPlus.ImageCodecInfo*)b).ThrowIfFailed();
+ ReadOnlySpan codecInfo = new((GdiPlus.ImageCodecInfo*)b, (int)numEncoders);
+ s_encoders = new (Guid Format, Guid Encoder)[codecInfo.Length];
+
+ for (int i = 0; i < codecInfo.Length; i++)
+ {
+ s_encoders[i] = (codecInfo[i].FormatID, codecInfo[i].Clsid);
+ }
+ }
+ }
+
+ return s_encoders;
+ }
+ }
+
public static ImageCodecInfo[] GetImageDecoders()
{
ImageCodecInfo[] imageCodecs;
diff --git a/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs b/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs
index 84ae8d747b3..4adb3015b6d 100644
--- a/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs
+++ b/src/System.Drawing.Common/src/System/Drawing/Imaging/ImageFormat.cs
@@ -14,18 +14,18 @@ public sealed class ImageFormat
{
// Format IDs
// private static ImageFormat undefined = new ImageFormat(new Guid("{b96b3ca9-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_memoryBMP = new(new Guid("{b96b3caa-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_bmp = new(new Guid("{b96b3cab-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_emf = new(new Guid("{b96b3cac-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_wmf = new(new Guid("{b96b3cad-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_jpeg = new(new Guid("{b96b3cae-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_png = new(new Guid("{b96b3caf-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_gif = new(new Guid("{b96b3cb0-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_tiff = new(new Guid("{b96b3cb1-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_exif = new(new Guid("{b96b3cb2-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_icon = new(new Guid("{b96b3cb5-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_heif = new(new Guid("{b96b3cb6-0728-11d3-9d7b-0000f81ef32e}"));
- private static readonly ImageFormat s_webp = new(new Guid("{b96b3cb7-0728-11d3-9d7b-0000f81ef32e}"));
+ private static readonly ImageFormat s_memoryBMP = new(PInvokeCore.ImageFormatMemoryBMP);
+ private static readonly ImageFormat s_bmp = new(PInvokeCore.ImageFormatBMP);
+ private static readonly ImageFormat s_emf = new(PInvokeCore.ImageFormatEMF);
+ private static readonly ImageFormat s_wmf = new(PInvokeCore.ImageFormatWMF);
+ private static readonly ImageFormat s_jpeg = new(PInvokeCore.ImageFormatJPEG);
+ private static readonly ImageFormat s_png = new(PInvokeCore.ImageFormatPNG);
+ private static readonly ImageFormat s_gif = new(PInvokeCore.ImageFormatGIF);
+ private static readonly ImageFormat s_tiff = new(PInvokeCore.ImageFormatTIFF);
+ private static readonly ImageFormat s_exif = new(PInvokeCore.ImageFormatEXIF);
+ private static readonly ImageFormat s_icon = new(PInvokeCore.ImageFormatIcon);
+ private static readonly ImageFormat s_heif = new(PInvokeCore.ImageFormatHEIF);
+ private static readonly ImageFormat s_webp = new(PInvokeCore.ImageFormatWEBP);
private readonly Guid _guid;
@@ -93,16 +93,16 @@ public sealed class ImageFormat
/// Specifies the High Efficiency Image Format (HEIF).
///
///
- /// This format is supported since Windows 10 1809.
+ /// This format is supported since Windows 10 1809.
///
[SupportedOSPlatform("windows10.0.17763.0")]
public static ImageFormat Heif => s_heif;
///
- /// Specifies the WebP image format.
+ /// Specifies the WebP image format.
///
///
- /// This format is supported since Windows 10 1809.
+ /// This format is supported since Windows 10 1809.
///
[SupportedOSPlatform("windows10.0.17763.0")]
public static ImageFormat Webp => s_webp;
@@ -111,52 +111,35 @@ public sealed class ImageFormat
/// Returns a value indicating whether the specified object is an equivalent to this
/// .
///
- public override bool Equals([NotNullWhen(true)] object? o)
- {
- ImageFormat? format = o as ImageFormat;
- if (format is null)
- return false;
- return _guid == format._guid;
- }
+ public override bool Equals([NotNullWhen(true)] object? o) => o is ImageFormat format && _guid == format._guid;
///
/// Returns a hash code.
///
- public override int GetHashCode()
- {
- return _guid.GetHashCode();
- }
+ public override int GetHashCode() => _guid.GetHashCode();
- // Find any random encoder which supports this format
- internal ImageCodecInfo? FindEncoder()
- {
- ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
- foreach (ImageCodecInfo codec in codecs)
- {
- if (codec.FormatID.Equals(_guid))
- return codec;
- }
-
- return null;
- }
+ ///
+ /// The encoder that supports this format, if any.
+ ///
+ internal Guid Encoder => ImageCodecInfo.GetEncoderClsid(_guid);
///
/// Converts this to a human-readable string.
///
public override string ToString()
{
- if (Guid == s_memoryBMP.Guid) return "MemoryBMP";
- if (Guid == s_bmp.Guid) return "Bmp";
- if (Guid == s_emf.Guid) return "Emf";
- if (Guid == s_wmf.Guid) return "Wmf";
- if (Guid == s_gif.Guid) return "Gif";
- if (Guid == s_jpeg.Guid) return "Jpeg";
- if (Guid == s_png.Guid) return "Png";
- if (Guid == s_tiff.Guid) return "Tiff";
- if (Guid == s_exif.Guid) return "Exif";
- if (Guid == s_icon.Guid) return "Icon";
- if (Guid == s_heif.Guid) return "Heif";
- if (Guid == s_webp.Guid) return "Webp";
+ if (Guid == PInvokeCore.ImageFormatMemoryBMP) return "MemoryBMP";
+ if (Guid == PInvokeCore.ImageFormatBMP) return "Bmp";
+ if (Guid == PInvokeCore.ImageFormatEMF) return "Emf";
+ if (Guid == PInvokeCore.ImageFormatWMF) return "Wmf";
+ if (Guid == PInvokeCore.ImageFormatGIF) return "Gif";
+ if (Guid == PInvokeCore.ImageFormatJPEG) return "Jpeg";
+ if (Guid == PInvokeCore.ImageFormatPNG) return "Png";
+ if (Guid == PInvokeCore.ImageFormatTIFF) return "Tiff";
+ if (Guid == PInvokeCore.ImageFormatEXIF) return "Exif";
+ if (Guid == PInvokeCore.ImageFormatIcon) return "Icon";
+ if (Guid == PInvokeCore.ImageFormatHEIF) return "Heif";
+ if (Guid == PInvokeCore.ImageFormatWEBP) return "Webp";
return $"[ImageFormat: {_guid}]";
}
}
diff --git a/src/System.Private.Windows.Core/src/NativeMethods.txt b/src/System.Private.Windows.Core/src/NativeMethods.txt
index 7cc54421654..650dce25845 100644
--- a/src/System.Private.Windows.Core/src/NativeMethods.txt
+++ b/src/System.Private.Windows.Core/src/NativeMethods.txt
@@ -106,6 +106,7 @@ HWND_*
IDI_*
IEnumUnknown
IGlobalInterfaceTable
+ImageFormat*
INPLACE_E_NOTOOLSPACE
IntersectClipRect
IStream