Skip to content

Commit ee07816

Browse files
committed
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.
1 parent 80335ab commit ee07816

File tree

9 files changed

+406
-25
lines changed

9 files changed

+406
-25
lines changed

src/samples/DirectDraw/ImagingDemo/Program.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Windows.Win32.Graphics.Direct2D;
88
using Windows.Win32.Graphics.Imaging;
99
using Bitmap = Windows.Win32.Graphics.Direct2D.Bitmap;
10+
using GdiPlusBitmap = Windows.Win32.Graphics.GdiPlus.Bitmap;
1011

1112
namespace ImagingDemo;
1213

@@ -19,6 +20,7 @@ private unsafe class ImagingDemo : MainWindow
1920
{
2021
private FormatConverter? _converter;
2122
private Bitmap? _bitmap;
23+
private GdiPlusBitmap? _gdiPlusBitmap;
2224

2325

2426
public ImagingDemo() : base(
@@ -29,6 +31,7 @@ public ImagingDemo() : base(
2931
}
3032

3133
[MemberNotNull(nameof(_converter))]
34+
[MemberNotNull(nameof(_gdiPlusBitmap))]
3235
private void CreateBitmapFromFile(string fileName)
3336
{
3437
_converter?.Dispose();
@@ -37,16 +40,20 @@ private void CreateBitmapFromFile(string fileName)
3740
using BitmapDecoder decoder = new(fileName);
3841
using BitmapFrameDecode frame = decoder.GetFrame(0);
3942
_converter = new(frame);
43+
44+
_gdiPlusBitmap = new(fileName);
4045
}
4146

4247
protected override void RenderTargetCreated()
4348
{
44-
if (_converter is null)
49+
if (_converter is null || _gdiPlusBitmap is null)
4550
{
4651
CreateBitmapFromFile("Blue Marble 2012 Original.jpg");
4752
}
4853

4954
_bitmap?.Dispose();
55+
56+
//_bitmap = RenderTarget.CreateBitmapFromGdiPlusBitmap(_gdiPlusBitmap);
5057
_bitmap = RenderTarget.CreateBitmapFromWicBitmap(_converter);
5158
base.RenderTargetCreated();
5259
}

src/thirtytwo/GlobalSuppressions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.MessageBoxStyles")]
1313
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.WindowStyles")]
1414
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.VirtualKey")]
15+
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Matches Windows", Scope = "type", Target = "~T:Windows.Win32.Graphics.GdiPlus.PixelFormat")]
1516
[assembly: SuppressMessage("Usage", "CA2231:Overload operator equals on overriding value type Equals", Justification = "CsWin32", Scope = "type", Target = "~T:Windows.Win32.Foundation.PCWSTR")]
1617
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Thread Local", Scope = "member", Target = "~F:Windows.WindowClass.t_initializeProcedure")]

src/thirtytwo/NativeMethods.txt

+29-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ CLEARTYPE_NATURAL_QUALITY
1616
ClientToScreen
1717
CLIPBOARD_FORMAT
1818
CloseClipboard
19+
CLSID_WICImagingFactory2
1920
CoCreateInstance
2021
CoGetClassObject
2122
CombineRgn
@@ -93,15 +94,36 @@ FONT_WEIGHT
9394
FormatMessage
9495
FrameRect
9596
FVE_E_LOCKED_VOLUME
97+
GdipBitmapLockBits
98+
GdipBitmapUnlockBits
99+
GdipCreateBitmapFromDirectDrawSurface
100+
GdipCreateBitmapFromFile
101+
GdipCreateBitmapFromGdiDib
102+
GdipCreateBitmapFromGraphics
103+
GdipCreateBitmapFromHBITMAP
104+
GdipCreateBitmapFromHICON
105+
GdipCreateBitmapFromResource
106+
GdipCreateBitmapFromScan0
107+
GdipCreateBitmapFromStream
96108
GdipCreateFromHDC
109+
GdipCreateHBITMAPFromBitmap
110+
GdipCreateHICONFromBitmap
97111
GdipCreatePen1
98112
GdipCreateSolidFill
99113
GdipDeleteBrush
100114
GdipDeleteGraphics
101115
GdipDeletePen
116+
GdipDisposeImage
117+
GdipDrawImageRect
118+
GdipDrawImageRectRect
102119
GdipDrawLines
103120
GdipDrawLinesI
104121
GdipFillEllipse
122+
GdipGetImageBounds
123+
GdipGetImageFlags
124+
GdipGetImageGraphicsContext
125+
GdipGetImagePixelFormat
126+
GdipGetImageRawFormat
105127
GdiplusShutdown
106128
GdiplusStartup
107129
GdipSetSmoothingMode
@@ -168,6 +190,7 @@ GlobalFree
168190
GlobalLock
169191
GlobalSize
170192
GlobalUnlock
193+
GUID_WICPixelFormat32bppPBGRA
171194
HFONT
172195
HKEY_*
173196
HRESULT
@@ -194,6 +217,8 @@ IFileDialogCustomize
194217
IFileDialogEvents
195218
IFileOpenDialog
196219
IGlobalInterfaceTable
220+
ImageFlags
221+
ImageLockMode
197222
IMarshal
198223
IModalWindow
199224
InitCommonControlsEx
@@ -223,6 +248,7 @@ IsWindowEnabled
223248
IsWindowVisible
224249
IUIAutomationElement
225250
IUnknown
251+
IWICImagingFactory2
226252
KEY_INFORMATION_CLASS
227253
KEY_NAME_INFORMATION
228254
KF_*
@@ -262,6 +288,7 @@ OLEIVERB_*
262288
OpenClipboard
263289
PARAMDATA
264290
PeekMessage
291+
PixelFormat*
265292
PlaySound
266293
POINTS
267294
PolyBezier
@@ -281,8 +308,8 @@ RegQueryInfoKey
281308
RegQueryValueEx
282309
ReleaseCapture
283310
ReleaseDC
284-
RestoreDC
285311
RemoveClipboardFormatListener
312+
RestoreDC
286313
ROLE_SYSTEM_*
287314
RoundRect
288315
S_FALSE
@@ -362,7 +389,4 @@ VIRTUAL_KEY
362389
WIN32_ERROR
363390
WINDOWPOS
364391
WM_*
365-
XFORMCOORDS
366-
IWICImagingFactory2
367-
CLSID_WICImagingFactory2
368-
GUID_WICPixelFormat32bppPBGRA
392+
XFORMCOORDS

src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs

+76
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Drawing;
45
using Windows.Support;
6+
using Windows.Win32.Graphics.Direct2D.Common;
7+
using Windows.Win32.Graphics.Dxgi.Common;
8+
using Windows.Win32.Graphics.Imaging;
59

610
namespace Windows.Win32.Graphics.Direct2D;
711

@@ -13,5 +17,77 @@ public RenderTarget(ID2D1RenderTarget* renderTarget) : base((ID2D1Resource*)rend
1317
{
1418
}
1519

20+
/// <inheritdoc cref="ID2D1RenderTarget.CreateBitmapFromWicBitmap(IWICBitmapSource*, D2D1_BITMAP_PROPERTIES*, ID2D1Bitmap**)"/>
21+
public Bitmap CreateBitmapFromWicBitmap<TBitmapSource>(
22+
TBitmapSource wicBitmap)
23+
where TBitmapSource : IPointer<IWICBitmapSource>
24+
{
25+
ID2D1Bitmap* d2dBitmap;
26+
Pointer->CreateBitmapFromWicBitmap(
27+
wicBitmap.Pointer,
28+
bitmapProperties: (D2D1_BITMAP_PROPERTIES*)null,
29+
&d2dBitmap).ThrowOnFailure();
30+
31+
Bitmap bitmap = new(d2dBitmap);
32+
GC.KeepAlive(this);
33+
GC.KeepAlive(wicBitmap);
34+
return bitmap;
35+
}
36+
37+
public Bitmap CreateBitmapFromGdiPlusBitmap(GdiPlus.Bitmap bitmap)
38+
{
39+
GdiPlus.PixelFormat pixelFormat = bitmap.PixelFormat;
40+
RectangleF bounds = bitmap.Bounds;
41+
42+
const int BytesPerPixel = 4;
43+
44+
// We could let GDI+ do the buffer allocation, but for illustrative purposes I've done it here.
45+
// Note that GDI+ always copies the data, even if it internally is in the desired format.
46+
using BufferScope<byte> buffer = new((int)bounds.Width * (int)bounds.Height * BytesPerPixel);
47+
48+
fixed (byte* b = buffer)
49+
{
50+
GdiPlus.BitmapData bitmapData = new()
51+
{
52+
Width = (uint)bounds.Width,
53+
Height = (uint)bounds.Height,
54+
Stride = (int)bounds.Width * BytesPerPixel,
55+
PixelFormat = (int)GdiPlus.PixelFormat.Format32bppArgb,
56+
Scan0 = b
57+
};
58+
59+
bitmap.LockBits(
60+
new((int)bounds.X, (int)bounds.Y, (int)bounds.Width, (int)bounds.Height),
61+
GdiPlus.ImageLockMode.ImageLockModeUserInputBuf | GdiPlus.ImageLockMode.ImageLockModeRead,
62+
GdiPlus.PixelFormat.Format32bppArgb,
63+
ref bitmapData);
64+
65+
D2D1_BITMAP_PROPERTIES bitmapProperties = new()
66+
{
67+
pixelFormat = new D2D1_PIXEL_FORMAT
68+
{
69+
format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
70+
alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_IGNORE
71+
},
72+
dpiX = 96,
73+
dpiY = 96
74+
};
75+
76+
ID2D1Bitmap* newBitmap;
77+
HRESULT result = Pointer->CreateBitmap(
78+
new((uint)bounds.Width, (uint)bounds.Height),
79+
b,
80+
(uint)bitmapData.Stride,
81+
&bitmapProperties,
82+
&newBitmap);
83+
84+
bitmap.UnlockBits(ref bitmapData);
85+
result.ThrowOnFailure();
86+
87+
GC.KeepAlive(this);
88+
return new Bitmap(newBitmap);
89+
}
90+
}
91+
1692
public static implicit operator ID2D1RenderTarget*(RenderTarget renderTarget) => renderTarget.Pointer;
1793
}

src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs

-19
Original file line numberDiff line numberDiff line change
@@ -117,25 +117,6 @@ public static void DrawTextLayout<TTarget, TLayout, TBrush>(
117117
GC.KeepAlive(defaultFillBrush);
118118
}
119119

120-
/// <inheritdoc cref="ID2D1RenderTarget.CreateBitmapFromWicBitmap(IWICBitmapSource*, D2D1_BITMAP_PROPERTIES*, ID2D1Bitmap**)"/>
121-
public static Bitmap CreateBitmapFromWicBitmap<TRenderTarget, TBitmapSource>(
122-
this TRenderTarget target,
123-
TBitmapSource wicBitmap)
124-
where TRenderTarget : IPointer<ID2D1RenderTarget>
125-
where TBitmapSource : IPointer<IWICBitmapSource>
126-
{
127-
ID2D1Bitmap* d2dBitmap;
128-
target.Pointer->CreateBitmapFromWicBitmap(
129-
wicBitmap.Pointer,
130-
bitmapProperties: (D2D1_BITMAP_PROPERTIES*)null,
131-
&d2dBitmap).ThrowOnFailure();
132-
133-
Bitmap bitmap = new(d2dBitmap);
134-
GC.KeepAlive(target);
135-
GC.KeepAlive(wicBitmap);
136-
return bitmap;
137-
}
138-
139120
public static void DrawBitmap<TRenderTarget, TBitmap>(
140121
this TRenderTarget target,
141122
TBitmap bitmap,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Drawing;
5+
using System.Runtime.CompilerServices;
6+
using Windows.Support;
7+
8+
namespace Windows.Win32.Graphics.GdiPlus;
9+
10+
public unsafe class Bitmap : Image, IPointer<GpBitmap>
11+
{
12+
public unsafe new GpBitmap* Pointer => (GpBitmap*)base.Pointer;
13+
14+
public Bitmap(GpBitmap* bitmap) : base((GpImage*)bitmap) { }
15+
public Bitmap(string filename) : this(Create(filename)) { }
16+
17+
private static GpBitmap* Create(string filename)
18+
{
19+
ArgumentNullException.ThrowIfNull(filename);
20+
GdiPlus.Init();
21+
22+
fixed (char* fn = filename)
23+
{
24+
GpBitmap* bitmap;
25+
Interop.GdipCreateBitmapFromFile(fn, &bitmap).ThrowIfFailed();
26+
return bitmap;
27+
}
28+
}
29+
30+
/// <summary>
31+
/// Locks a rectangular portion of this bitmap and provides a temporary buffer that you can use to read or write
32+
/// pixel data in a specified format. Any pixel data that you write to the buffer is copied to the
33+
/// <see cref="Bitmap"/> object when you call <see cref="UnlockBits(ref BitmapData)"/>.
34+
/// </summary>
35+
/// <remarks>
36+
/// <para>
37+
/// <see href="https://learn.microsoft.com/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits">
38+
/// </see>
39+
/// </para>
40+
/// </remarks>
41+
public void LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format, ref BitmapData data)
42+
{
43+
// LockBits always creates a temporary copy of the data.
44+
Interop.GdipBitmapLockBits(
45+
Pointer,
46+
(Rect*)&rect,
47+
(uint)flags,
48+
(int)format,
49+
(BitmapData*)Unsafe.AsPointer(ref data)).ThrowIfFailed();
50+
51+
GC.KeepAlive(this);
52+
}
53+
54+
public void UnlockBits(ref BitmapData data)
55+
{
56+
Interop.GdipBitmapUnlockBits(Pointer, (BitmapData*)Unsafe.AsPointer(ref data)).ThrowIfFailed();
57+
GC.KeepAlive(this);
58+
}
59+
60+
public static implicit operator GpBitmap*(Bitmap bitmap) => bitmap.Pointer;
61+
}

0 commit comments

Comments
 (0)