From ee0781684e08a8cec438bc460afae9445cceefa6 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 12 Feb 2024 20:10:40 -0800 Subject: [PATCH] Add support for converting Gdi+ bitmap to Direct2D bitmap Add basic wraps to load a Gdi+ bitmap and a conversion method to RenderTarget to create a Direct2D bitmap. --- src/samples/DirectDraw/ImagingDemo/Program.cs | 9 +- src/thirtytwo/GlobalSuppressions.cs | 1 + src/thirtytwo/NativeMethods.txt | 34 ++++- .../Win32/Graphics/Direct2D/RenderTarget.cs | 76 ++++++++++ .../Direct2D/RenderTargetExtensions.cs | 19 --- .../Win32/Graphics/GdiPlus/Bitmap.cs | 61 ++++++++ src/thirtytwo/Win32/Graphics/GdiPlus/Image.cs | 78 ++++++++++ .../Win32/Graphics/GdiPlus/PixelFormat.cs | 136 ++++++++++++++++++ .../Win32/Graphics/Imaging/Bitmap.cs | 17 +++ 9 files changed, 406 insertions(+), 25 deletions(-) create mode 100644 src/thirtytwo/Win32/Graphics/GdiPlus/Bitmap.cs create mode 100644 src/thirtytwo/Win32/Graphics/GdiPlus/Image.cs create mode 100644 src/thirtytwo/Win32/Graphics/GdiPlus/PixelFormat.cs create mode 100644 src/thirtytwo/Win32/Graphics/Imaging/Bitmap.cs diff --git a/src/samples/DirectDraw/ImagingDemo/Program.cs b/src/samples/DirectDraw/ImagingDemo/Program.cs index 1012f19..ddd397f 100644 --- a/src/samples/DirectDraw/ImagingDemo/Program.cs +++ b/src/samples/DirectDraw/ImagingDemo/Program.cs @@ -7,6 +7,7 @@ using Windows.Win32.Graphics.Direct2D; using Windows.Win32.Graphics.Imaging; using Bitmap = Windows.Win32.Graphics.Direct2D.Bitmap; +using GdiPlusBitmap = Windows.Win32.Graphics.GdiPlus.Bitmap; namespace ImagingDemo; @@ -19,6 +20,7 @@ private unsafe class ImagingDemo : MainWindow { private FormatConverter? _converter; private Bitmap? _bitmap; + private GdiPlusBitmap? _gdiPlusBitmap; public ImagingDemo() : base( @@ -29,6 +31,7 @@ public ImagingDemo() : base( } [MemberNotNull(nameof(_converter))] + [MemberNotNull(nameof(_gdiPlusBitmap))] private void CreateBitmapFromFile(string fileName) { _converter?.Dispose(); @@ -37,16 +40,20 @@ private void CreateBitmapFromFile(string fileName) using BitmapDecoder decoder = new(fileName); using BitmapFrameDecode frame = decoder.GetFrame(0); _converter = new(frame); + + _gdiPlusBitmap = new(fileName); } protected override void RenderTargetCreated() { - if (_converter is null) + if (_converter is null || _gdiPlusBitmap is null) { CreateBitmapFromFile("Blue Marble 2012 Original.jpg"); } _bitmap?.Dispose(); + + //_bitmap = RenderTarget.CreateBitmapFromGdiPlusBitmap(_gdiPlusBitmap); _bitmap = RenderTarget.CreateBitmapFromWicBitmap(_converter); base.RenderTargetCreated(); } diff --git a/src/thirtytwo/GlobalSuppressions.cs b/src/thirtytwo/GlobalSuppressions.cs index 3ebcd87..08bff05 100644 --- a/src/thirtytwo/GlobalSuppressions.cs +++ b/src/thirtytwo/GlobalSuppressions.cs @@ -12,5 +12,6 @@ [assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.MessageBoxStyles")] [assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.WindowStyles")] [assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.VirtualKey")] +[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.Win32.Graphics.GdiPlus.PixelFormat")] [assembly: SuppressMessage("Usage", "CA2231:Overload operator equals on overriding value type Equals", Justification = "CsWin32", Scope = "type", Target = "~T:Windows.Win32.Foundation.PCWSTR")] [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Thread Local", Scope = "member", Target = "~F:Windows.WindowClass.t_initializeProcedure")] \ No newline at end of file diff --git a/src/thirtytwo/NativeMethods.txt b/src/thirtytwo/NativeMethods.txt index 3482d86..ee1ef57 100644 --- a/src/thirtytwo/NativeMethods.txt +++ b/src/thirtytwo/NativeMethods.txt @@ -16,6 +16,7 @@ CLEARTYPE_NATURAL_QUALITY ClientToScreen CLIPBOARD_FORMAT CloseClipboard +CLSID_WICImagingFactory2 CoCreateInstance CoGetClassObject CombineRgn @@ -93,15 +94,36 @@ FONT_WEIGHT FormatMessage FrameRect FVE_E_LOCKED_VOLUME +GdipBitmapLockBits +GdipBitmapUnlockBits +GdipCreateBitmapFromDirectDrawSurface +GdipCreateBitmapFromFile +GdipCreateBitmapFromGdiDib +GdipCreateBitmapFromGraphics +GdipCreateBitmapFromHBITMAP +GdipCreateBitmapFromHICON +GdipCreateBitmapFromResource +GdipCreateBitmapFromScan0 +GdipCreateBitmapFromStream GdipCreateFromHDC +GdipCreateHBITMAPFromBitmap +GdipCreateHICONFromBitmap GdipCreatePen1 GdipCreateSolidFill GdipDeleteBrush GdipDeleteGraphics GdipDeletePen +GdipDisposeImage +GdipDrawImageRect +GdipDrawImageRectRect GdipDrawLines GdipDrawLinesI GdipFillEllipse +GdipGetImageBounds +GdipGetImageFlags +GdipGetImageGraphicsContext +GdipGetImagePixelFormat +GdipGetImageRawFormat GdiplusShutdown GdiplusStartup GdipSetSmoothingMode @@ -168,6 +190,7 @@ GlobalFree GlobalLock GlobalSize GlobalUnlock +GUID_WICPixelFormat32bppPBGRA HFONT HKEY_* HRESULT @@ -194,6 +217,8 @@ IFileDialogCustomize IFileDialogEvents IFileOpenDialog IGlobalInterfaceTable +ImageFlags +ImageLockMode IMarshal IModalWindow InitCommonControlsEx @@ -223,6 +248,7 @@ IsWindowEnabled IsWindowVisible IUIAutomationElement IUnknown +IWICImagingFactory2 KEY_INFORMATION_CLASS KEY_NAME_INFORMATION KF_* @@ -262,6 +288,7 @@ OLEIVERB_* OpenClipboard PARAMDATA PeekMessage +PixelFormat* PlaySound POINTS PolyBezier @@ -281,8 +308,8 @@ RegQueryInfoKey RegQueryValueEx ReleaseCapture ReleaseDC -RestoreDC RemoveClipboardFormatListener +RestoreDC ROLE_SYSTEM_* RoundRect S_FALSE @@ -362,7 +389,4 @@ VIRTUAL_KEY WIN32_ERROR WINDOWPOS WM_* -XFORMCOORDS -IWICImagingFactory2 -CLSID_WICImagingFactory2 -GUID_WICPixelFormat32bppPBGRA \ No newline at end of file +XFORMCOORDS \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs index e23c15f..59cce7b 100644 --- a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs +++ b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs @@ -1,7 +1,11 @@ // Copyright (c) Jeremy W. Kuhne. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Drawing; using Windows.Support; +using Windows.Win32.Graphics.Direct2D.Common; +using Windows.Win32.Graphics.Dxgi.Common; +using Windows.Win32.Graphics.Imaging; namespace Windows.Win32.Graphics.Direct2D; @@ -13,5 +17,77 @@ public RenderTarget(ID2D1RenderTarget* renderTarget) : base((ID2D1Resource*)rend { } + /// + public Bitmap CreateBitmapFromWicBitmap( + TBitmapSource wicBitmap) + where TBitmapSource : IPointer + { + ID2D1Bitmap* d2dBitmap; + Pointer->CreateBitmapFromWicBitmap( + wicBitmap.Pointer, + bitmapProperties: (D2D1_BITMAP_PROPERTIES*)null, + &d2dBitmap).ThrowOnFailure(); + + Bitmap bitmap = new(d2dBitmap); + GC.KeepAlive(this); + GC.KeepAlive(wicBitmap); + return bitmap; + } + + public Bitmap CreateBitmapFromGdiPlusBitmap(GdiPlus.Bitmap bitmap) + { + GdiPlus.PixelFormat pixelFormat = bitmap.PixelFormat; + RectangleF bounds = bitmap.Bounds; + + const int BytesPerPixel = 4; + + // We could let GDI+ do the buffer allocation, but for illustrative purposes I've done it here. + // Note that GDI+ always copies the data, even if it internally is in the desired format. + using BufferScope buffer = new((int)bounds.Width * (int)bounds.Height * BytesPerPixel); + + fixed (byte* b = buffer) + { + GdiPlus.BitmapData bitmapData = new() + { + Width = (uint)bounds.Width, + Height = (uint)bounds.Height, + Stride = (int)bounds.Width * BytesPerPixel, + PixelFormat = (int)GdiPlus.PixelFormat.Format32bppArgb, + Scan0 = b + }; + + bitmap.LockBits( + new((int)bounds.X, (int)bounds.Y, (int)bounds.Width, (int)bounds.Height), + GdiPlus.ImageLockMode.ImageLockModeUserInputBuf | GdiPlus.ImageLockMode.ImageLockModeRead, + GdiPlus.PixelFormat.Format32bppArgb, + ref bitmapData); + + D2D1_BITMAP_PROPERTIES bitmapProperties = new() + { + pixelFormat = new D2D1_PIXEL_FORMAT + { + format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, + alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_IGNORE + }, + dpiX = 96, + dpiY = 96 + }; + + ID2D1Bitmap* newBitmap; + HRESULT result = Pointer->CreateBitmap( + new((uint)bounds.Width, (uint)bounds.Height), + b, + (uint)bitmapData.Stride, + &bitmapProperties, + &newBitmap); + + bitmap.UnlockBits(ref bitmapData); + result.ThrowOnFailure(); + + GC.KeepAlive(this); + return new Bitmap(newBitmap); + } + } + public static implicit operator ID2D1RenderTarget*(RenderTarget renderTarget) => renderTarget.Pointer; } \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs index efc2aef..417313e 100644 --- a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs +++ b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs @@ -117,25 +117,6 @@ public static void DrawTextLayout( GC.KeepAlive(defaultFillBrush); } - /// - public static Bitmap CreateBitmapFromWicBitmap( - this TRenderTarget target, - TBitmapSource wicBitmap) - where TRenderTarget : IPointer - where TBitmapSource : IPointer - { - ID2D1Bitmap* d2dBitmap; - target.Pointer->CreateBitmapFromWicBitmap( - wicBitmap.Pointer, - bitmapProperties: (D2D1_BITMAP_PROPERTIES*)null, - &d2dBitmap).ThrowOnFailure(); - - Bitmap bitmap = new(d2dBitmap); - GC.KeepAlive(target); - GC.KeepAlive(wicBitmap); - return bitmap; - } - public static void DrawBitmap( this TRenderTarget target, TBitmap bitmap, diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Bitmap.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Bitmap.cs new file mode 100644 index 0000000..9a34e57 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Bitmap.cs @@ -0,0 +1,61 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Drawing; +using System.Runtime.CompilerServices; +using Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class Bitmap : Image, IPointer +{ + public unsafe new GpBitmap* Pointer => (GpBitmap*)base.Pointer; + + public Bitmap(GpBitmap* bitmap) : base((GpImage*)bitmap) { } + public Bitmap(string filename) : this(Create(filename)) { } + + private static GpBitmap* Create(string filename) + { + ArgumentNullException.ThrowIfNull(filename); + GdiPlus.Init(); + + fixed (char* fn = filename) + { + GpBitmap* bitmap; + Interop.GdipCreateBitmapFromFile(fn, &bitmap).ThrowIfFailed(); + return bitmap; + } + } + + /// + /// Locks a rectangular portion of this bitmap and provides a temporary buffer that you can use to read or write + /// pixel data in a specified format. Any pixel data that you write to the buffer is copied to the + /// object when you call . + /// + /// + /// + /// + /// + /// + /// + public void LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format, ref BitmapData data) + { + // LockBits always creates a temporary copy of the data. + Interop.GdipBitmapLockBits( + Pointer, + (Rect*)&rect, + (uint)flags, + (int)format, + (BitmapData*)Unsafe.AsPointer(ref data)).ThrowIfFailed(); + + GC.KeepAlive(this); + } + + public void UnlockBits(ref BitmapData data) + { + Interop.GdipBitmapUnlockBits(Pointer, (BitmapData*)Unsafe.AsPointer(ref data)).ThrowIfFailed(); + GC.KeepAlive(this); + } + + public static implicit operator GpBitmap*(Bitmap bitmap) => bitmap.Pointer; +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Image.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Image.cs new file mode 100644 index 0000000..86e6372 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Image.cs @@ -0,0 +1,78 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Drawing; +using Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class Image : DisposableBase.Finalizable, IPointer +{ + private GpImage* _pointer; + + public GpImage* Pointer => _pointer; + + public Image(GpImage* pointer) => _pointer = pointer; + + protected override void Dispose(bool disposing) + { + Status status = Interop.GdipDisposeImage(_pointer); + if (disposing) + { + status.ThrowIfFailed(); + } + + _pointer = null; + } + + public PixelFormat PixelFormat + { + get + { + PixelFormat format; + Interop.GdipGetImagePixelFormat(Pointer, (int*)&format).ThrowIfFailed(); + GC.KeepAlive(this); + return format; + } + } + + public Guid RawFormat + { + get + { + Guid format; + Interop.GdipGetImageRawFormat(Pointer, &format).ThrowIfFailed(); + GC.KeepAlive(this); + return format; + } + } + + public ImageFlags Flags + { + get + { + ImageFlags flags; + Interop.GdipGetImageFlags(Pointer, (uint*)&flags).ThrowIfFailed(); + GC.KeepAlive(this); + return flags; + } + } + + /// + /// The bounds of the image in pixels. + /// + public RectangleF Bounds + { + get + { + RectangleF bounds; + Unit unit; + Interop.GdipGetImageBounds(Pointer, (RectF*)&bounds, &unit).ThrowIfFailed(); + + // GdipGetImageBounds is hardcoded to return Unit.Pixel + Debug.Assert(unit == Unit.UnitPixel); + GC.KeepAlive(this); + return bounds; + } + } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/PixelFormat.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/PixelFormat.cs new file mode 100644 index 0000000..162f0a2 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/PixelFormat.cs @@ -0,0 +1,136 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Copied from Windows Forms, original license: + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.Graphics.GdiPlus; + +/// +/// Specifies the format of the color data for each pixel in the image. +/// +public enum PixelFormat +{ + /// + /// Specifies that pixel data contains color indexed values which means they are an index to colors in the + /// system color table, as opposed to individual color values. + /// + Indexed = (int)Interop.PixelFormatIndexed, + + /// + /// Specifies that pixel data contains GDI colors. + /// + Gdi = (int)Interop.PixelFormatGDI, + + /// + /// Specifies that pixel data contains alpha values that are not pre-multiplied. + /// + Alpha = (int)Interop.PixelFormatAlpha, + + /// + /// Specifies that pixel format contains pre-multiplied alpha values. + /// + PAlpha = (int)Interop.PixelFormatPAlpha, + + /// + /// Specifies that pixel format contains extended color values of 16 bits per channel. + /// + Extended = (int)Interop.PixelFormatExtended, + + Canonical = (int)Interop.PixelFormatCanonical, + + /// + /// Specifies that pixel format is undefined. + /// + Undefined = (int)Interop.PixelFormatUndefined, + + /// + /// Specifies that pixel format doesn't matter. + /// + DontCare = (int)Interop.PixelFormatDontCare, + + /// + /// Specifies that pixel format is 1 bit per pixel indexed color. The color table therefore has two colors in it. + /// + Format1bppIndexed = 1 | (1 << 8) | Indexed | Gdi, + + /// + /// Specifies that pixel format is 4 bits per pixel indexed color. The color table therefore has 16 colors in it. + /// + Format4bppIndexed = 2 | (4 << 8) | Indexed | Gdi, + + /// + /// Specifies that pixel format is 8 bits per pixel indexed color. The color table therefore has 256 colors in it. + /// + Format8bppIndexed = 3 | (8 << 8) | Indexed | Gdi, + + /// + /// Specifies that pixel format is 16 bits per pixel. The color information specifies 65536 shades of gray. + /// + Format16bppGrayScale = 4 | (16 << 8) | Extended, + + /// + /// Specifies that pixel format is 16 bits per pixel. The color information specifies 32768 shades of color of + /// which 5 bits are red, 5 bits are green and 5 bits are blue. + /// + Format16bppRgb555 = 5 | (16 << 8) | Gdi, + + Format16bppRgb565 = 6 | (16 << 8) | Gdi, + + /// + /// Specifies that pixel format is 16 bits per pixel. The color information specifies 32768 shades of color of + /// which 5 bits are red, 5 bits are green, 5 bits are blue and 1 bit is alpha. + /// + Format16bppArgb1555 = 7 | (16 << 8) | Alpha | Gdi, + + /// + /// Specifies that pixel format is 24 bits per pixel. The color information specifies 16777216 shades of color + /// of which 8 bits are red, 8 bits are green and 8 bits are blue. + /// + Format24bppRgb = 8 | (24 << 8) | Gdi, + + /// + /// Specifies that pixel format is 24 bits per pixel. The color information specifies 16777216 shades of color + /// of which 8 bits are red, 8 bits are green and 8 bits are blue. + /// + Format32bppRgb = 9 | (32 << 8) | Gdi, + + /// + /// Specifies that pixel format is 32 bits per pixel. The color information specifies 16777216 shades of color + /// of which 8 bits are red, 8 bits are green and 8 bits are blue. The 8 additional bits are alpha bits. + /// + Format32bppArgb = 10 | (32 << 8) | Alpha | Gdi | Canonical, + + /// + /// Specifies that pixel format is 32 bits per pixel. The color information specifies 16777216 shades of color + /// of which 8 bits are red, 8 bits are green and 8 bits are blue. The 8 additional bits are pre-multiplied alpha bits. + /// + Format32bppPArgb = 11 | (32 << 8) | Alpha | PAlpha | Gdi, + + /// + /// Specifies that pixel format is 48 bits per pixel. The color information specifies 16777216 shades of color + /// of which 8 bits are red, 8 bits are green and 8 bits are blue. The 8 additional bits are alpha bits. + /// + Format48bppRgb = 12 | (48 << 8) | Extended, + + /// + /// Specifies pixel format is 64 bits per pixel. The color information specifies 16777216 shades of color of + /// which 16 bits are red, 16 bits are green and 16 bits are blue. The 16 additional bits are alpha bits. + /// + Format64bppArgb = 13 | (64 << 8) | Alpha | Canonical | Extended, + + /// + /// Specifies that pixel format is 64 bits per pixel. The color information specifies 16777216 shades of color + /// of which 16 bits are red, 16 bits are green and 16 bits are blue. The 16 additional bits are pre-multiplied + /// alpha bits. + /// + Format64bppPArgb = 14 | (64 << 8) | Alpha | PAlpha | Extended, + + /// + /// Specifies that pixel format is 64 bits per pixel. The color information specifies 16777216 shades of color + /// of which 16 bits are red, 16 bits are green and 16 bits are blue. The 16 additional bits are alpha bits. + /// + Max = 15, +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/Imaging/Bitmap.cs b/src/thirtytwo/Win32/Graphics/Imaging/Bitmap.cs new file mode 100644 index 0000000..a03e778 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/Imaging/Bitmap.cs @@ -0,0 +1,17 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Windows.Support; + +namespace Windows.Win32.Graphics.Imaging; + +public unsafe class Bitmap : BitmapSource, IPointer +{ + public unsafe new IWICBitmap* Pointer => (IWICBitmap*)base.Pointer; + + public Bitmap(IWICBitmap* bitmap) : base((IWICBitmapSource*)bitmap) + { + } + + public static implicit operator IWICBitmap*(Bitmap bitmap) => bitmap.Pointer; +} \ No newline at end of file