Skip to content

Commit

Permalink
Add support for converting Gdi+ bitmap to Direct2D bitmap
Browse files Browse the repository at this point in the history
Add basic wraps to load a Gdi+ bitmap and a conversion method to RenderTarget to create a Direct2D bitmap.
  • Loading branch information
JeremyKuhne committed Feb 13, 2024
1 parent 80335ab commit ee07816
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 25 deletions.
9 changes: 8 additions & 1 deletion src/samples/DirectDraw/ImagingDemo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -19,6 +20,7 @@ private unsafe class ImagingDemo : MainWindow
{
private FormatConverter? _converter;
private Bitmap? _bitmap;
private GdiPlusBitmap? _gdiPlusBitmap;


public ImagingDemo() : base(
Expand All @@ -29,6 +31,7 @@ public ImagingDemo() : base(
}

[MemberNotNull(nameof(_converter))]
[MemberNotNull(nameof(_gdiPlusBitmap))]
private void CreateBitmapFromFile(string fileName)
{
_converter?.Dispose();
Expand All @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions src/thirtytwo/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
34 changes: 29 additions & 5 deletions src/thirtytwo/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ CLEARTYPE_NATURAL_QUALITY
ClientToScreen
CLIPBOARD_FORMAT
CloseClipboard
CLSID_WICImagingFactory2
CoCreateInstance
CoGetClassObject
CombineRgn
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -168,6 +190,7 @@ GlobalFree
GlobalLock
GlobalSize
GlobalUnlock
GUID_WICPixelFormat32bppPBGRA
HFONT
HKEY_*
HRESULT
Expand All @@ -194,6 +217,8 @@ IFileDialogCustomize
IFileDialogEvents
IFileOpenDialog
IGlobalInterfaceTable
ImageFlags
ImageLockMode
IMarshal
IModalWindow
InitCommonControlsEx
Expand Down Expand Up @@ -223,6 +248,7 @@ IsWindowEnabled
IsWindowVisible
IUIAutomationElement
IUnknown
IWICImagingFactory2
KEY_INFORMATION_CLASS
KEY_NAME_INFORMATION
KF_*
Expand Down Expand Up @@ -262,6 +288,7 @@ OLEIVERB_*
OpenClipboard
PARAMDATA
PeekMessage
PixelFormat*
PlaySound
POINTS
PolyBezier
Expand All @@ -281,8 +308,8 @@ RegQueryInfoKey
RegQueryValueEx
ReleaseCapture
ReleaseDC
RestoreDC
RemoveClipboardFormatListener
RestoreDC
ROLE_SYSTEM_*
RoundRect
S_FALSE
Expand Down Expand Up @@ -362,7 +389,4 @@ VIRTUAL_KEY
WIN32_ERROR
WINDOWPOS
WM_*
XFORMCOORDS
IWICImagingFactory2
CLSID_WICImagingFactory2
GUID_WICPixelFormat32bppPBGRA
XFORMCOORDS
76 changes: 76 additions & 0 deletions src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs
Original file line number Diff line number Diff line change
@@ -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;

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

/// <inheritdoc cref="ID2D1RenderTarget.CreateBitmapFromWicBitmap(IWICBitmapSource*, D2D1_BITMAP_PROPERTIES*, ID2D1Bitmap**)"/>
public Bitmap CreateBitmapFromWicBitmap<TBitmapSource>(
TBitmapSource wicBitmap)
where TBitmapSource : IPointer<IWICBitmapSource>
{
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<byte> 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;
}
19 changes: 0 additions & 19 deletions src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,6 @@ public static void DrawTextLayout<TTarget, TLayout, TBrush>(
GC.KeepAlive(defaultFillBrush);
}

/// <inheritdoc cref="ID2D1RenderTarget.CreateBitmapFromWicBitmap(IWICBitmapSource*, D2D1_BITMAP_PROPERTIES*, ID2D1Bitmap**)"/>
public static Bitmap CreateBitmapFromWicBitmap<TRenderTarget, TBitmapSource>(
this TRenderTarget target,
TBitmapSource wicBitmap)
where TRenderTarget : IPointer<ID2D1RenderTarget>
where TBitmapSource : IPointer<IWICBitmapSource>
{
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<TRenderTarget, TBitmap>(
this TRenderTarget target,
TBitmap bitmap,
Expand Down
61 changes: 61 additions & 0 deletions src/thirtytwo/Win32/Graphics/GdiPlus/Bitmap.cs
Original file line number Diff line number Diff line change
@@ -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<GpBitmap>
{
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;
}
}

/// <summary>
/// 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
/// <see cref="Bitmap"/> object when you call <see cref="UnlockBits(ref BitmapData)"/>.
/// </summary>
/// <remarks>
/// <para>
/// <see href="https://learn.microsoft.com/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits">
/// </see>
/// </para>
/// </remarks>
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;
}
Loading

0 comments on commit ee07816

Please sign in to comment.