diff --git a/src/libraries/Common/tests/System/Drawing/Helpers.cs b/src/libraries/Common/tests/System/Drawing/Helpers.cs
index bf154135a8c0cb..2e0652ccce1897 100644
--- a/src/libraries/Common/tests/System/Drawing/Helpers.cs
+++ b/src/libraries/Common/tests/System/Drawing/Helpers.cs
@@ -14,6 +14,7 @@ public static class Helpers
{
public const string IsDrawingSupported = nameof(Helpers) + "." + nameof(GetIsDrawingSupported);
public const string IsWindowsOrAtLeastLibgdiplus6 = nameof(Helpers) + "." + nameof(GetIsWindowsOrAtLeastLibgdiplus6);
+ public const string IsCachedBitmapSupported = nameof(Helpers) + "." + nameof(GetIsCachedBitmapSupported);
public const string RecentGdiplusIsAvailable = nameof(Helpers) + "." + nameof(GetRecentGdiPlusIsAvailable);
public const string RecentGdiplusIsAvailable2 = nameof(Helpers) + "." + nameof(GetRecentGdiPlusIsAvailable2);
public const string GdiPlusIsAvailableNotRedhat73 = nameof(Helpers) + "." + nameof(GetGdiPlusIsAvailableNotRedhat73);
@@ -23,7 +24,10 @@ public static class Helpers
public static bool GetIsDrawingSupported() => PlatformDetection.IsDrawingSupported;
- public static bool GetIsWindowsOrAtLeastLibgdiplus6()
+ public static bool GetIsCachedBitmapSupported() => GetIsWindowsOrAtLeastLibgdiplus(6, 1);
+ public static bool GetIsWindowsOrAtLeastLibgdiplus6() => GetIsWindowsOrAtLeastLibgdiplus(6, 0);
+
+ public static bool GetIsWindowsOrAtLeastLibgdiplus(int major, int minor)
{
if (!PlatformDetection.IsDrawingSupported)
{
@@ -50,7 +54,7 @@ public static bool GetIsWindowsOrAtLeastLibgdiplus6()
return false;
}
- return installedVersion.Major >= 6;
+ return installedVersion.Major > major || installedVersion.Major == major && installedVersion.Minor >= minor;
}
public static bool IsNotUnix => PlatformDetection.IsWindows;
diff --git a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs
index 9f54ee11672f92..f732546d6153e6 100644
--- a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs
+++ b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs
@@ -408,6 +408,7 @@ public void DrawBezier(System.Drawing.Pen pen, System.Drawing.PointF pt1, System
public void DrawBezier(System.Drawing.Pen pen, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { }
public void DrawBeziers(System.Drawing.Pen pen, System.Drawing.PointF[] points) { }
public void DrawBeziers(System.Drawing.Pen pen, System.Drawing.Point[] points) { }
+ public void DrawCachedBitmap(System.Drawing.Imaging.CachedBitmap cachedBitmap, int x, int y) { }
public void DrawClosedCurve(System.Drawing.Pen pen, System.Drawing.PointF[] points) { }
public void DrawClosedCurve(System.Drawing.Pen pen, System.Drawing.PointF[] points, float tension, System.Drawing.Drawing2D.FillMode fillmode) { }
public void DrawClosedCurve(System.Drawing.Pen pen, System.Drawing.Point[] points) { }
@@ -1752,6 +1753,11 @@ public BitmapData() { }
public int Stride { get { throw null; } set { } }
public int Width { get { throw null; } set { } }
}
+ public sealed class CachedBitmap : System.IDisposable
+ {
+ public CachedBitmap(System.Drawing.Bitmap bitmap, System.Drawing.Graphics graphics) { throw null; }
+ public void Dispose() { }
+ }
public enum ColorAdjustType
{
Default = 0,
diff --git a/src/libraries/System.Drawing.Common/src/Resources/Strings.resx b/src/libraries/System.Drawing.Common/src/Resources/Strings.resx
index f5ba77ee8ed377..5b3e673d95d68f 100644
--- a/src/libraries/System.Drawing.Common/src/Resources/Strings.resx
+++ b/src/libraries/System.Drawing.Common/src/Resources/Strings.resx
@@ -461,4 +461,8 @@
System.Drawing.Common is not supported on this platform.
-
\ No newline at end of file
+
+ CachedBitmap is not supported on the installed version of libgdiplus. It is supported from version 6.1 onwards.
+
+
+
diff --git a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj
index 0a20f12215117d..bbd1b238afa910 100644
--- a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj
+++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj
@@ -27,6 +27,7 @@
+
@@ -186,6 +187,7 @@
+
@@ -303,6 +305,7 @@
+
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs
index 06b37317862db3..1a994b5df544f7 100644
--- a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs
@@ -19,6 +19,8 @@ internal unsafe partial class Gdip
internal const string LibraryName = "libgdiplus";
public static IntPtr Display = IntPtr.Zero;
+ public static GetLibgdiplusVersion? GetLibgdiplusVersion;
+
// Indicates whether X11 is available. It's available on Linux but not on recent macOS versions
// When set to false, where Carbon Drawing is used instead.
// macOS users can force X11 by setting the SYSTEM_DRAWING_COMMON_FORCE_X11 flag.
@@ -43,10 +45,18 @@ internal static IntPtr LoadNativeLibrary()
// the name suffixed with ".0".
if (!NativeLibrary.TryLoad("libgdiplus.so", assembly, default, out lib))
{
- NativeLibrary.TryLoad("libgdiplus.so.0", assembly, default, out lib);
+ NativeLibrary.TryLoad("libgdiplus.so.0", assembly, default, out lib);
}
}
+ // The GetLibgdiplusVersion function is relatively new. It is needed to check for CachedBitmap support.
+ // Instead of blindly P/Invoking into this function, we should safely search for the export.
+ // If it's not present, then we will know that CachedBitmap is not supported anyway.
+ if (lib != IntPtr.Zero && NativeLibrary.TryGetExport(lib, "GetLibgdiplusVersion", out IntPtr func))
+ {
+ GetLibgdiplusVersion = Marshal.GetDelegateForFunctionPointer(func);
+ }
+
// This function may return a null handle. If it does, individual functions loaded from it will throw a DllNotFoundException,
// but not until an attempt is made to actually use the function (rather than load it). This matches how PInvokes behave.
return lib;
@@ -418,4 +428,7 @@ internal static extern int GdipGetPostScriptGraphicsContext(
internal unsafe delegate int StreamPutBytesDelegate(byte* buf, int bufsz);
internal delegate void StreamCloseDelegate();
internal delegate long StreamSizeDelegate();
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)]
+ internal unsafe delegate string GetLibgdiplusVersion();
}
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.cs
index f35039fb59561a..b1b52c8174aa29 100644
--- a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.cs
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.cs
@@ -1353,6 +1353,15 @@ internal static partial class Gdip
[DllImport(LibraryName, ExactSpelling = true)]
internal static extern int GdipGetEncoderParameterList(HandleRef image, ref Guid encoder, int size, IntPtr buffer);
+
+ [DllImport(LibraryName, ExactSpelling = true)]
+ internal static extern int GdipCreateCachedBitmap(HandleRef bitmap, HandleRef graphics, out IntPtr cachedBitmap);
+
+ [DllImport(LibraryName, ExactSpelling = true)]
+ internal static extern int GdipDeleteCachedBitmap(HandleRef cachedBitmap);
+
+ [DllImport(LibraryName, ExactSpelling = true)]
+ internal static extern int GdipDrawCachedBitmap(HandleRef graphics, HandleRef cachedBitmap, int x, int y);
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs
index 29b6b24749241d..466a97b1b37bb8 100644
--- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs
@@ -2091,6 +2091,26 @@ public void DrawImage(
CheckErrorStatus(status);
}
+ ///
+ /// Draws the image stored in the a object.
+ ///
+ /// The that contains the image to be drawn.
+ /// The x-coordinate of the upper-left corner of the drawn image.
+ /// The y-coordinate of the upper-left corner of the drawn image.
+ /// is .
+ public void DrawCachedBitmap(CachedBitmap cachedBitmap, int x, int y)
+ {
+ if (cachedBitmap is null)
+ throw new ArgumentNullException(nameof(cachedBitmap));
+
+ int status = Gdip.GdipDrawCachedBitmap(
+ new HandleRef(this, NativeGraphics),
+ new HandleRef(cachedBitmap, cachedBitmap.nativeCachedBitmap),
+ x, y);
+
+ CheckErrorStatus(status);
+ }
+
///
/// Draws a line connecting the two specified points.
///
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Unix.cs
new file mode 100644
index 00000000000000..09e9cc98984bf1
--- /dev/null
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Unix.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Gdip = System.Drawing.SafeNativeMethods.Gdip;
+
+namespace System.Drawing.Imaging
+{
+ public sealed partial class CachedBitmap
+ {
+ internal static bool IsCachedBitmapSupported()
+ {
+ // CachedBitmap is only supported on libgdiplus 6.1 and above.
+ // The function to check for the version is only present on libgdiplus 6.0 and above.
+ if (Gdip.GetLibgdiplusVersion is null)
+ return false;
+
+ var version = new Version(Gdip.GetLibgdiplusVersion());
+ return version.Major > 6 || version.Major == 6 && version.Minor >= 1;
+ }
+ }
+}
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Windows.cs
new file mode 100644
index 00000000000000..e3650c568b7090
--- /dev/null
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.Windows.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Drawing.Imaging
+{
+ public sealed partial class CachedBitmap
+ {
+ internal static bool IsCachedBitmapSupported() => true;
+ }
+}
diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.cs
new file mode 100644
index 00000000000000..a407292b373f3b
--- /dev/null
+++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/CachedBitmap.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+using Gdip = System.Drawing.SafeNativeMethods.Gdip;
+
+namespace System.Drawing.Imaging
+{
+ ///
+ /// Stores a in a format that is optimized for display on a particular device.
+ ///
+ public sealed partial class CachedBitmap : IDisposable
+ {
+ internal static readonly bool IsSupported = IsCachedBitmapSupported();
+ internal IntPtr nativeCachedBitmap;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The bitmap to take the pixel data from.
+ /// A object, representing the display device to optimize the bitmap for.
+ ///
+ /// is .
+ /// - or -
+ /// is
+ ///
+ ///
+ /// The installed version of libgdiplus is lower than 6.1. This does not apply on Windows.
+ ///
+ public CachedBitmap(Bitmap bitmap, Graphics graphics)
+ {
+ if (bitmap is null)
+ throw new ArgumentNullException(nameof(bitmap));
+
+ if (graphics is null)
+ throw new ArgumentNullException(nameof(graphics));
+
+ if (!IsSupported)
+ throw new PlatformNotSupportedException(SR.CachedBitmapNotSupported);
+
+ int status = Gdip.GdipCreateCachedBitmap(new HandleRef(bitmap, bitmap.nativeImage),
+ new HandleRef(graphics, graphics.NativeGraphics),
+ out nativeCachedBitmap);
+
+ Gdip.CheckStatus(status);
+ }
+
+ ///
+ /// Releases all resources used by this .
+ ///
+ public void Dispose()
+ {
+ if (nativeCachedBitmap != IntPtr.Zero)
+ {
+ int status = Gdip.GdipDeleteCachedBitmap(new HandleRef(this, nativeCachedBitmap));
+ nativeCachedBitmap = IntPtr.Zero;
+ Gdip.CheckStatus(status);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Drawing.Common/tests/Imaging/CachedBitmapTests.cs b/src/libraries/System.Drawing.Common/tests/Imaging/CachedBitmapTests.cs
new file mode 100644
index 00000000000000..6556be0b62f087
--- /dev/null
+++ b/src/libraries/System.Drawing.Common/tests/Imaging/CachedBitmapTests.cs
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Security.Permissions;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xunit;
+
+namespace System.Drawing.Imaging.Tests
+{
+ public class CachedBitmapTests
+ {
+ [ConditionalFact(Helpers.IsCachedBitmapSupported)]
+ public void Ctor_Throws_ArgumentNullException()
+ {
+ using var bitmap = new Bitmap(10, 10);
+ using var graphics = Graphics.FromImage(bitmap);
+
+ Assert.Throws(() => new CachedBitmap(bitmap, null));
+ Assert.Throws(() => new CachedBitmap(null, graphics));
+ }
+
+ [ConditionalFact(Helpers.IsCachedBitmapSupported)]
+ public void Disposed_CachedBitmap_Throws_ArgumentException()
+ {
+ using var bitmap = new Bitmap(10, 10);
+ using var graphics = Graphics.FromImage(bitmap);
+ using var cached = new CachedBitmap(bitmap, graphics);
+
+ cached.Dispose();
+
+ Assert.Throws(() => graphics.DrawCachedBitmap(cached, 0, 0));
+ }
+
+ [ConditionalFact(Helpers.IsCachedBitmapSupported)]
+ public void DrawCachedBitmap_Throws_ArgumentNullException()
+ {
+ using var bitmap = new Bitmap(10, 10);
+ using var graphics = Graphics.FromImage(bitmap);
+ Assert.Throws(() => graphics.DrawCachedBitmap(null, 0, 0));
+ }
+
+ static string[] bitmaps = new string[]
+ {
+ "81674-2bpp.png",
+ "64x64_one_entry_8bit.ico",
+ "16x16_one_entry_4bit.ico",
+ "16x16_nonindexed_24bit.png"
+ };
+
+ public class CachedBitmapOffsetTestData : IEnumerable