Direct2D Include [Windows]

Share your advanced PureBasic knowledge/code with the community.
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Direct2D Include [Windows]

Post by Justin »

I wanted to try Direct2D GPU accelerated graphics so i converted some includes, i could not find a typelib but i found an .idl from here:
https://github.com/wine-mirror/wine/tree/master/include

Then compiled to .tlb using MIDL compiler and converted to pb using my converter. It needed some minor manual adjustements but now it`s ok.

The files are at github:
https://github.com/omegakode/PBDirect2D

Simply run DemoApp.pb wich is a conversion of the ms sample:
https://learn.microsoft.com/en-us/windo ... quickstart
Nothing fancy, just draws a grid and two rectangles, but the include is enough to start playing with D2D.

I leave the DemoApp.pb here just for the forum search, but the includes are at github.
Points are passed by value as quads using D2D1_Point2F(x.f, y.f) helper and returned by reference.

Code: Select all

IncludeFile "PBDirect2D.pb"

;https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart
;https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-and-directwrite
;https://learn.microsoft.com/en-us/windows/win32/wic/-wic-bitmapsources-howto-drawusingd2d
;https://github.com/microsoft/Windows-universal-samples/tree/main/Samples/D2DSvgImage
;https://learn.microsoft.com/en-us/windows/win32/direct2d/effects-overview

EnableExplicit

;- Enum MENU_ID
Enumeration
	#MENU_ID_CHANGE_SVG_COLOR
EndEnumeration

;- _APP
Structure _APP
	win.i
	menu.i
	d2dFactory.ID2D1Factory
	renderTarget.ID2D1HwndRenderTarget
	brush1.ID2D1SolidColorBrush
	brush2.ID2D1SolidColorBrush
	strokeStyle.ID2D1StrokeStyle
	dWriteFactory.IDWriteFactory
	txtFormat.IDWriteTextFormat
	imgFactory.IWICImagingFactory
	d2dBmp.ID2D1Bitmap
	svgDoc.ID2D1SvgDocument
	scaleEffect.ID2D1Effect
	grayScaleEffect.ID2D1Effect

	oldProc.i
EndStructure
Global._APP app

Procedure.f minF(a.f, b.f)
	If a < b
		ProcedureReturn a
		
	Else
		ProcedureReturn b
	EndIf 
EndProcedure

Procedure imgFit(srcWidth.f, srcHeight.f, maxWidth.f, maxHeight.f, *sz.D2D1_SIZE_F)
	Protected.f ratio
	
	If *sz = 0 : ProcedureReturn : EndIf
	
	ratio = minF(maxWidth / srcWidth, maxHeight / srcHeight)
	*sz\width = srcWidth * ratio
	*sz\height = srcHeight * ratio
EndProcedure

Procedure imgCenter(srcWidth.f, srcHeight.f, maxWidth.f, maxHeight.f, *pt.D2D1_POINT_2F)
	If *pt = 0 : ProcedureReturn : EndIf
	
	*pt\x = (maxWidth - srcWidth) / 2
	*pt\y = (maxHeight - srcHeight) / 2
EndProcedure

Procedure app_discardDeviceResources()	
	If app\renderTarget
		app\renderTarget\Release() : app\renderTarget = 0
	EndIf
	
	If app\brush1
		app\brush1\Release() : app\brush1 = 0
	EndIf
	
	If app\brush2
		app\brush2\Release() : app\brush2 = 0
	EndIf
	
	If app\d2dBmp
		app\d2dBmp\Release() : app\d2dBmp = 0
	EndIf
	
	If app\svgDoc
		app\svgDoc\Release() : app\svgDoc = 0
	EndIf 
	
	If app\grayScaleEffect
		app\grayScaleEffect\Release() : app\grayScaleEffect = 0
	EndIf 
	
	If app\scaleEffect
		app\scaleEffect\Release() : app\scaleEffect = 0
	EndIf 
EndProcedure

Procedure app_release()
	If app\d2dFactory
		app\d2dFactory\Release() : app\d2dFactory = 0
	EndIf
	
	If app\strokeStyle
		app\strokeStyle\Release() : app\strokeStyle = 0
	EndIf
	
	If app\dWriteFactory
		app\dWriteFactory\Release() : app\dWriteFactory = 0
	EndIf
	
	If app\txtFormat
		app\txtFormat\Release() : app\txtFormat = 0
	EndIf 
	
	If app\imgFactory
		app\imgFactory\Release() : app\imgFactory = 0
	EndIf 
	
	app_discardDeviceResources()
EndProcedure	

Procedure app_createDeviceIndependentResources()
	Protected.D2D1_FACTORY_OPTIONS fOpts
	Protected.l hr
	Protected.D2D1_STROKE_STYLE_PROPERTIES props
	
	fOpts\DebugLevel = #D2D1_DEBUG_LEVEL_NONE
	hr = D2D1CreateFactory_(#D2D1_FACTORY_TYPE_SINGLE_THREADED, ?IID_ID2D1Factory, @fOpts, @app\d2dFactory)
	
	If hr = #S_OK
		;Stroke style
		props\startCap = #D2D1_CAP_STYLE_FLAT
		props\endCap = #D2D1_CAP_STYLE_FLAT
		props\dashCap = #D2D1_CAP_STYLE_FLAT
		props\lineJoin = #D2D1_LINE_JOIN_MITER
		props\miterLimit = 1.0
		props\dashStyle = #D2D1_DASH_STYLE_SOLID
		props\dashOffset = 0.0
		hr = app\d2dFactory\CreateStrokeStyle(@props, 0, 0, @app\strokeStyle)
	EndIf
	
	hr = DWriteCreateFactory_(#DWRITE_FACTORY_TYPE_SHARED, ?IID_IDWriteFactory, @app\dWriteFactory)
	If hr = #S_OK
		;Text format
		hr = app\dWriteFactory\CreateTextFormat("Segoe UI Emoji", #Null, #DWRITE_FONT_WEIGHT_REGULAR, 
			#DWRITE_FONT_STYLE_NORMAL, #DWRITE_FONT_STRETCH_NORMAL, 18.0,
			"en-us", @app\txtFormat)
		
		If hr = #S_OK
			app\txtFormat\SetTextAlignment(#DWRITE_TEXT_ALIGNMENT_CENTER)
			app\txtFormat\SetParagraphAlignment(#DWRITE_PARAGRAPH_ALIGNMENT_CENTER)
		EndIf 
	EndIf
		
	hr = CoCreateInstance_(?CLSID_WICImagingFactory, #Null, #CLSCTX_INPROC_SERVER, ?IID_IWICImagingFactory, @app\imgFactory)
	
	ProcedureReturn hr
EndProcedure

#D2D1_SCALE_PROP_SCALE = 0
Procedure app_createDeviceResources()
	Protected.RECT rc
	Protected.l hr
	Protected.i hwnd
	Protected.D2D1_SIZE_U sz
	Protected.D2D1_RENDER_TARGET_PROPERTIES props
	Protected.D2D1_HWND_RENDER_TARGET_PROPERTIES  hwndProps
	Protected.D2D1_COLOR_F color
	Protected.D2D1_BRUSH_PROPERTIES bProp
	Protected.IWICFormatConverter wicBmp
	Protected.IWICBitmapDecoder imgDecoder
	Protected.IWICBitmapFrameDecode pFrame
	Protected.ID2D1DeviceContext5 dc5
	Protected.IStream svgStream
	Protected.ID2D1Image img
	Protected.ID2D1DeviceContext dc

	hr = #S_OK
	
	If app\renderTarget = 0
		hwnd = WindowID(app\win)
		GetClientRect_(hwnd, @rc)
		sz\width = rc\right - rc\left
		sz\height = rc\bottom - rc\top
		
		;Create a Direct2D render target.
		props\type = #D2D1_RENDER_TARGET_TYPE_DEFAULT
		props\pixelFormat\format = #DXGI_FORMAT_UNKNOWN
		props\pixelFormat\alphaMode = #D2D1_ALPHA_MODE_UNKNOWN
		props\dpiX = 0.0
		props\dpiY = 0.0
		props\usage = #D2D1_RENDER_TARGET_USAGE_NONE
		props\minLevel = #D2D1_FEATURE_LEVEL_DEFAULT
		
		hwndProps\hwnd = hwnd
		hwndProps\pixelSize\height = sz\height
		hwndProps\pixelSize\width = sz\width
		hwndProps\presentOptions = #D2D1_PRESENT_OPTIONS_NONE
		
		hr = app\d2dFactory\CreateHwndRenderTarget(@props, @hwndProps, @app\renderTarget)
		If hr = #S_OK		
			;Brushes
			bProp\opacity = 1.0
			
			color\r = 0 : color\g = 162/255 : color\b = 232/255 : color\a = 1.0
			app\renderTarget\CreateSolidColorBrush(@color, @bProp, @app\brush1)
			
			color\r = 0.0 : color\g = 1.0 : color\b = 0.0 : color\a = 1.0
			app\renderTarget\CreateSolidColorBrush(@color, @bProp, @app\brush2)
			
			;Bitmap
			If app\imgFactory
				If app\imgFactory\CreateDecoderFromFilename("assets\pic1.jpg", #Null, #GENERIC_READ, #WICDecodeMetadataCacheOnDemand, @imgDecoder) = #S_OK
					If imgDecoder\GetFrame(0, @pFrame) = #S_OK
						If app\imgFactory\CreateFormatConverter(@wicBmp) = #S_OK
							If wicBmp\Initialize(pFrame, ?GUID_WICPixelFormat32bppPBGRA, #WICBitmapDitherTypeNone, #Null, 0.0, #WICBitmapPaletteTypeCustom) = #S_OK
								app\renderTarget\CreateBitmapFromWicBitmap(wicBmp, #Null, @app\d2dBmp)
								
								;Effects
								If app\renderTarget\QueryInterface(?IID_ID2D1DeviceContext, @dc) = #S_OK
									dc\CreateEffect(?CLSID_D2D1Scale, @app\scaleEffect)
									dc\CreateEffect(?CLSID_D2D1Grayscale, @app\grayScaleEffect)
									If app\grayScaleEffect = 0
										Debug "Grayscale not supported"
									EndIf 									
								EndIf
							EndIf
							wicBmp\Release()
						EndIf
						pFrame\Release()
					EndIf 
					imgDecoder\Release()
				EndIf	
			EndIf
			
			;Svg
			If app\renderTarget\QueryInterface(?IID_ID2D1DeviceContext5, @dc5) = #S_OK
				If SHCreateStreamOnFile_("assets\drawing.svg", #STGM_READ, @svgStream) = #S_OK
					dc5\CreateSvgDocument(svgStream, D2D1_SizeF(100, 100), @app\svgDoc)
					svgStream\Release()
				EndIf 
				dc5\Release()
				
			Else 
				Debug "SVG Not supported"
			EndIf 
		EndIf 
	EndIf
	
	ProcedureReturn hr
EndProcedure

Procedure app_onSize(width.w, height.w)
	Protected.D2D1_SIZE_U sz
		
	If app\renderTarget
		sz\width = width
		sz\height = height
		
		app\renderTarget\Resize(@sz)
	EndIf
EndProcedure

Procedure app_onRender()
	Protected.l hr, x, y, width, height, txtOpts
	Protected.D2D1_MATRIX_3X2_F im, tm1, tm2, tm3
	Protected.D2D1_COLOR_F color
	Protected.q tag1, tag2
	Protected.D2D1_SIZE_F rectSize, rtSize, bmpSize, scaledBmpSize
	Protected.D2D1_RECT_F rcF2
	Protected.D2D1_POINT_2F bmpPos, pt
	Protected.s txt
	Protected.ID2D1DeviceContext5 dc5
	Protected.ID2D1DeviceContext dc
	Protected.ID2D1Image img
	Protected.ID2D1Bitmap scaledBmp
	Protected.ID2D1Effect effect
	
	hr = #S_OK
	
	hr = app_createDeviceResources()
	If hr = #S_OK	
		rectSize\width = 200.0
		rectSize\height = 200.0
				
		app\renderTarget\BeginDraw()
		D2D1_Matrix3x2F_Identity(@im)
		D2D1_Matrix3x2F_Identity(@tm1)
		D2D1_Matrix3x2F_Identity(@tm2)
		D2D1_Matrix3x2F_Identity(@tm3)

		color\r = 1.0 : color\g = 1.0 : color\b = 1.0 : color\a = 1.0
		app\renderTarget\Clear(@color)
		app\renderTarget\GetSize(@rtSize)
	
		;Grid
		width = rtSize\width
		height = rtSize\height

		For x = 0 To width Step 10
			app\renderTarget\DrawLine(D2D1_Point2F(x, 0.0), D2D1_Point2F(x, rtSize\height), app\brush1, 0.5, app\strokeStyle)
		Next 

		For y = 0 To height Step 10
			app\renderTarget\DrawLine(D2D1_Point2F(0.0, y), D2D1_Point2F(rtSize\width, y), app\brush1, 0.5, app\strokeStyle) 
		Next 
    
		;Rectangles
		rcF2\left = (rtSize\width / 2) - rectSize\width / 2
		rcF2\top = (rtSize\height / 2) - rectSize\width / 2
		rcF2\right = (rtSize\width / 2) + rectSize\width / 2
		rcF2\bottom = (rtSize\height / 2) + rectSize\width / 2
	
		;Image
		If app\d2dBmp And app\renderTarget\QueryInterface(?IID_ID2D1DeviceContext, @dc) = #S_OK
			app\d2dBmp\GetSize(@bmpSize)
			
			;Scale to rectangle keeping ratio
			pt\x = (rectSize\width  / bmpSize\width) * DesktopResolutionX()
			pt\y = (rectSize\height / bmpSize\width) * DesktopResolutionX()

			app\scaleEffect\SetInput(0, app\d2dBmp, 0)
			app\scaleEffect\SetValue(#D2D1_SCALE_PROP_SCALE, #D2D1_PROPERTY_TYPE_VECTOR2, @pt, SizeOf(D2D1_POINT_2F))

			;Apply gray scale
			If app\grayScaleEffect
				app\scaleEffect\GetOutput(@img)
				app\grayScaleEffect\SetInput(0, img, 0)
				img\Release()
				effect = app\grayScaleEffect

			Else
				effect = app\scaleEffect
			EndIf

			;Get scaled size and center in rectangle
			scaledBmpSize\width = (bmpSize\width * pt\x) / DesktopResolutionX()
			scaledBmpSize\height = (bmpSize\height * pt\y) / DesktopResolutionY()
			imgCenter(scaledBmpSize\width, scaledBmpSize\height, rectSize\width, rectSize\height, @bmpPos)
			
			pt\x = rcF2\left + bmpPos\x
			pt\y = rcF2\top + bmpPos\y
			effect\GetOutput(@img)
			If img
				dc\DrawImage(img, @pt, 0, #D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, #D2D1_COMPOSITE_MODE_SOURCE_OVER)
				img\Release()
			EndIf
			
			dc\Release()
		EndIf
		
		;SVG
		If app\svgDoc And app\renderTarget\QueryInterface(?IID_ID2D1DeviceContext5, @dc5) = #S_OK
			;DrawSvgDocument() always draws at 0,0 scale and translate
			D2D1_Matrix3x2F_Scale(@tm2, 1.5, 1.5, 0, 0)
			D2D1_Matrix3x2F_Translation(@tm1, 0, rcF2\top)
			D2D1_Matrix3x2F_Multiply(@tm2, @tm1, @tm3)
			
			dc5\SetTransform(@tm3)
			dc5\DrawSvgDocument(app\svgDoc)
			dc5\SetTransform(@im)
			dc5\Release()
		EndIf 

		;Text
		txt = "Hello World! " + #CRLF$ + "😀 😬 😁 😂 😃 😄 😅 😆" 
		If OSVersion() >= #PB_OS_Windows_8_1
			txtOpts = #D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT
			
		Else ;Color emoji not supported
			txtOpts = #D2D1_DRAW_TEXT_OPTIONS_NONE
		EndIf 
		dc\DrawText(txt, Len(txt), app\txtFormat, @rcF2, app\brush2, txtOpts, #DWRITE_MEASURING_MODE_NATURAL)
	
		;Border
		dc\DrawRectangle(@rcF2, app\brush2, 2.0, app\strokeStyle)
		hr = dc\EndDraw(@tag1, @tag2)
	EndIf
	
	If (hr = #D2DERR_RECREATE_TARGET)
		hr = #S_OK
		app_discardDeviceResources()
	EndIf 
	
	ProcedureReturn hr
EndProcedure

Procedure win_proc(hwnd.i, msg.l, wparam.i, lparam.i)	
	Select msg
		Case #WM_SIZE
			app_onSize(PeekW(@lparam), PeekW(@lparam + SizeOf(Word)))
			
		Case #WM_DISPLAYCHANGE
			InvalidateRect_(hwnd, #Null, #False)
 			
		Case #WM_ERASEBKGND
			Protected.RECT rc
			
			GetClientRect_(hwnd, @rc)
			app_onSize(rc\right, rc\bottom)
			app_onRender()
			ProcedureReturn 1
			
		Case #WM_PAINT
			app_onRender()
			ValidateRect_(hwnd, #Null)
			ProcedureReturn 0
	EndSelect
	
	ProcedureReturn CallWindowProc_(app\oldProc, hwnd, msg, wparam, lparam)
EndProcedure

Procedure changeSVGColor()
	Protected.ID2D1SvgElement head
	Protected.ID2D1SvgPaint paint
	Protected.D2D1_COLOR_F c

	If app\svgDoc = 0 : ProcedureReturn : EndIf
	
	If app\svgDoc\FindElementById("head", @head) = #S_OK
		If head\GetAttributeValue("fill", ?IID_ID2D1SvgPaint, @paint) = #S_OK
			If paint\GetPaintType() = #D2D1_SVG_PAINT_TYPE_COLOR
				c\r = Random(255) / 255 : c\g = Random(255) / 255 : c\b = Random(255) / 255 : c\a = 1.0
				paint\SetColor(@c)
				InvalidateRect_(WindowID(app\win), 0, #True)
			EndIf
			paint\Release()
		EndIf
		head\Release()
	EndIf
EndProcedure

Procedure main()
	Protected.l ev
	
	If app_createDeviceIndependentResources() <> #S_OK
		MessageRequester("Error" , "Failed to initialize Direct2D")
		End
	EndIf
		
	app\win = OpenWindow(#PB_Any, 10, 10, 600, 400, "Direct2D", #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget | 
		#PB_Window_SizeGadget)
	app\oldProc = SetWindowLongPtr_(WindowID(app\win), #GWLP_WNDPROC, @win_proc())
	
	app\menu = CreateMenu(#PB_Any, WindowID(app\win))
	MenuTitle("Test")
	MenuItem(#MENU_ID_CHANGE_SVG_COLOR, "Change SVG Color")
	BindMenuEvent(app\menu, #MENU_ID_CHANGE_SVG_COLOR, @changeSVGColor())

	Repeat
		ev = WaitWindowEvent()
		
	Until ev = #PB_Event_CloseWindow
	
	app_release()
EndProcedure

main()
Last edited by Justin on Thu Sep 05, 2024 7:58 pm, edited 3 times in total.
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Added DirectWrite support and example of drawing text and emojis.

Image
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Added Windows Imaging Component support and example of drawing a jpg.

Image
dige
Addict
Addict
Posts: 1405
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Direct2D Include [Windows]

Post by dige »

Hi Justim, pls help me, that I can understand it with my little grey cell, what are the advantages over using Canvas?
"Daddy, I'll run faster, then it is not so far..."
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Hi dige,

The main difference is that D2D is always hardware accelerated using the graphics card so it's much faster, and has more features.
The canvas uses GDI plus wich is mostly not hardware accelerated.
It's explained here:
https://learn.microsoft.com/en-us/windo ... 2d-and-gdi

At this point it's difficult to use if you are not familiar with COM, maybe i'll try to do a windows D2DCanvas gadget.
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Updated to Direct2D 1.1
User avatar
Caronte3D
Addict
Addict
Posts: 1361
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Direct2D Include [Windows]

Post by Caronte3D »

Justin wrote: Sun Sep 01, 2024 6:51 pm ...maybe i'll try to do a windows D2DCanvas gadget.
It sounds like a fantastic aportation :wink:
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Updated to Direct2D 1.3
Added example to render a SVG file, shown in the smiley picture below.

Caronte, before doing the gadget i want to test animations i want them smooth as in a browser.

Image
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4953
Joined: Sun Apr 12, 2009 6:27 am

Re: Direct2D Include [Windows]

Post by RASHAD »

Hi
My 2 cents
1- The project is x64 only
2- The *.lib are COFF lib not static that means it must be available with the compiled exe

But it will be good addition to PB
Thanks for the good work and sharing it
Egypt my love
User avatar
Caronte3D
Addict
Addict
Posts: 1361
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Direct2D Include [Windows]

Post by Caronte3D »

Justin wrote: Tue Sep 03, 2024 12:01 am Caronte, before doing the gadget i want to test animations i want them smooth as in a browser.
Epic! 8)
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Hi RASHAD,

All the code is X86 / X64 compatible.

Only the .libs i created with polib are the X64 version, but it's only a dll definition file! It's only used at compile time, you don't need to ship anything with the executables they are standalone.

I'll update it later with the X86 lib.
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Updated the lib defs to X86, tested on Windows 7 32 bit.
Added Effects support.
Example of how to apply a size scale and color grayscale effects together to the cat image.

Image
User avatar
jacdelad
Addict
Addict
Posts: 2009
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Direct2D Include [Windows]

Post by jacdelad »

Oh man I can't wait to be able to code again. I want to try this!
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
User avatar
Otrebor
Enthusiast
Enthusiast
Posts: 210
Joined: Mon Mar 17, 2014 1:42 pm
Location: São Paulo, Brasil
Contact:

Re: Direct2D Include [Windows]

Post by Otrebor »

Thank you for X86 update!
Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: Direct2D Include [Windows]

Post by Justin »

Hi thanks for the feedback,

Added all the SVG interfaces, example of how to change the SVG fill color dynamically in Menu -> Test -> Change SVG Color
Post Reply