How to draw multicolored text using GDIp

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

How to draw multicolored text using GDIp

Post by c7aesa7r » 02 Aug 2021, 18:26

I'm trying to learn how to draw multi colored text in a image using GDIp.

I already did till the point to draw each word with a different color, now I'm trying to find a way to calculate and drawn the text in the correct position according to the hFormat choosen:
image.png
(8.74 KiB) Downloaded 67 times
While searching i have found GdipMeasureString:

Code: Select all

MeasureString: 5.195312|51.718750|389.609375|96.562500|28|2
Txt: This is a Multi Colored Text
I wonder if theres any other way to calculate it.


Code in progress, I'm still trying to figure out how to calculate the correct position of the text:

Code: Select all

VarSetCapacity(SI, 24, 0)
Numput(1, SI, 0, "Int")
If !DllCall("Gdiplus.dll\GdiplusStartup", "PtrP", GDIPToken, "Ptr", &SI, "Ptr", 0)

File = %A_ScriptDir%\pbitmap.png
DllCall("gdiplus.dll\GdipCreateBitmapFromFile", "WStr", File, "PtrP",pBitmap)

Gui, Font, s25, Tahoma
Gui, Add, Text, hwndTEXT_HWND hidden,

OBJ := {hWnd: TEXT_HWND
, Txt:        "This is a Multi Colored Text"
, TxtPos:     "Center"}

DrawMultiColorTxt(OBJ, pBitmap)

DllCall( "gdiplus\GdipCreateHBITMAPFromBitmap", "PTR",pBitmap, "PTR*",hBitmap, "UInt",0 )
Gui, Add, Picture, x0 y0, % "HBITMAP:" . hBitmap
Gui, Show, w600 h200, DrawTxt
return



DrawMultiColorTxt(OBJ, PBITMAP) {
   
   Txt            := OBJ.Txt
   TxtPos         := OBJ.TxtPos
   HWND           := OBJ.hWnd
   
   ; Create the FONT from the given HWND.
   HFONT := DllCall("User32.dll\SendMessage", "Ptr", HWND, "UInt", 0x31, "Ptr",  0, "Ptr", 0, "Ptr")
   DC := DllCall("User32.dll\GetDC", "Ptr", HWND, "Ptr")
   DllCall("Gdi32.dll\SelectObject", "Ptr", DC, "Ptr", HFONT)
   DllCall("Gdiplus.dll\GdipCreateFontFromDC", "Ptr", DC, "PtrP", PFONT)
   DllCall("User32.dll\ReleaseDC", "Ptr", HWND, "Ptr", DC)
   DllCall("Gdiplus.dll\GdipDeleteFont", "Ptr", HFONT)

   ; Get the pointer to the bitmap graphics.
   DllCall("Gdiplus.dll\GdipGetImageGraphicsContext", "Ptr", PBITMAP, "PtrP", PGRAPHICS)

   ; Set render quality to system default
   DllCall("Gdiplus.dll\GdipSetTextRenderingHint", "Ptr", PGRAPHICS, "Int", 0)


   ; Create a StringFormat object
   static HFORMAT_L, HFORMAT_R, HFORMAT_C

   if (HFORMAT_L = "") {
      ;FileAppend, HFORMAT! `n,*
      DllCall("Gdiplus.dll\GdipStringFormatGetGenericTypographic", "PtrP", HFORMAT_L)
      DllCall("Gdiplus.dll\GdipSetStringFormatAlign", "Ptr", HFORMAT_L, "Int",  0x00)
      DllCall("Gdiplus.dll\GdipSetStringFormatLineAlign", "Ptr", HFORMAT_L, "Int", 1)
   }

   if (HFORMAT_R = "") {
      DllCall("Gdiplus.dll\GdipStringFormatGetGenericTypographic", "PtrP", HFORMAT_R)
      DllCall("Gdiplus.dll\GdipSetStringFormatAlign", "Ptr", HFORMAT_R, "Int",  0x02)
      DllCall("Gdiplus.dll\GdipSetStringFormatLineAlign", "Ptr", HFORMAT_R, "Int", 1)
   }

   if (HFORMAT_C = "") {
      DllCall("Gdiplus.dll\GdipStringFormatGetGenericTypographic", "PtrP", HFORMAT_C)
      ; Horizontal alignment.
      DllCall("Gdiplus.dll\GdipSetStringFormatAlign", "Ptr", HFORMAT_C, "Int",  0x01)
      ; Vertical alignment.
      DllCall("Gdiplus.dll\GdipSetStringFormatLineAlign", "Ptr", HFORMAT_C, "Int", 1)
   }

   if (TxtPos = "Left")
      hFORMAT := HFORMAT_L
   else if (TxtPos = "Right") 
      hFORMAT := HFORMAT_R       
   else ; Center
      hFORMAT := HFORMAT_C



   ; Create a rect.
   DllCall("gdiplus\GdipGetImageDimension", Ptr, pBitmap, "float*", Width, "float*", Height)
   VarSetCapacity(RECT, 16, 0)
   NumPut(Width, RECT,  8, "Float")
   NumPut(Height, RECT, 12, "Float")
   Array := StrSplit(Txt, " ")


   for each, Txt in Array {

      ; No need for a separate function
      VarSetCapacity(RC, 16)
      DllCall("gdiplus\GdipMeasureString", UPtr, pGraphics, "WStr", Txt, "int", -1, Ptr, PFONT, Ptr, &Rec, Ptr, hFORMAT, Ptr, &RC, "uint*", 0, "uint*", 0)
      CurrentTxtW := NumGet(RC, 8, "float")
      ;------------------------------------


      VarSetCapacity(RECT, 16, 0)

      NumPut(X, RECT,  0, "Float")
      NumPut(0, RECT,  4, "Float")
      NumPut(CurrentTxtW, RECT,  8, "Float")
      NumPut(Height, RECT, 12, "Float")

      WordSpacing := 5

      X += CurrentTxtW + WordSpacing


      ; Generate a random color.
      Random, vRand, 0, 0xFFFFFF
      TxtColor := 0xFF000000 | vRand
      
      ; Fill the text color
      DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", TxtColor, "PtrP", PBRUSH)
      
      ; Draw the text.
      DllCall("Gdiplus.dll\GdipDrawString", "Ptr", PGRAPHICS, "WStr", Txt, "Int",   -1, "Ptr", PFONT, "Ptr", &RECT, "Ptr", hFORMAT, "Ptr", PBRUSH)
      
      DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)

   }

   ;DllCall("Gdiplus.dll\GdipDeleteStringFormat", "Ptr", HFORMAT)
   DllCall("Gdiplus.dll\GdipDeleteGraphics", "Ptr", PGRAPHICS)      

}



:arrow: Edit
I think i did it, the spacing is not 100% but looks good:
Spoiler
Still working in a better way to calculate the spacing, it may not look good on different size/length.
The code is usable, and i wonder if it could be done in any other way.

Code: Select all

VarSetCapacity(SI, 24, 0)
Numput(1, SI, 0, "Int")
If !DllCall("Gdiplus.dll\GdiplusStartup", "PtrP", GDIPToken, "Ptr", &SI, "Ptr", 0)

File = %A_ScriptDir%\pbitmap.png
DllCall("gdiplus.dll\GdipCreateBitmapFromFile", "WStr", File, "PtrP",pBitmap)

Gui, Font, s23, Tahoma
Gui, Add, Text, hwndTEXT_HWND hidden,

OBJ := {hWnd: TEXT_HWND
, Txt:        "This is a Multi Colored Text"
, TxtPos:     "Left"}

DrawMultiColorTxt(OBJ, pBitmap)

DllCall( "gdiplus\GdipCreateHBITMAPFromBitmap", "PTR",pBitmap, "PTR*",hBitmap, "UInt",0 )
Gui, Add, Picture, x0 y0, % "HBITMAP:" . hBitmap


Gui, Add, Text, xm100 y+10 , How it should be spaced:

; ===============================================================

DllCall("gdiplus.dll\GdipCreateBitmapFromFile", "WStr", File, "PtrP",pBitmap)
DrawMultiColorTxt(OBJ, PBITMAP, 1)
DllCall( "gdiplus\GdipCreateHBITMAPFromBitmap", "PTR",pBitmap, "PTR*",hBitmap, "UInt",0 )

Gui, Add, Picture, x0 y+10, % "HBITMAP:" . hBitmap

Gui, Show, w600 h460, DrawTxt
return



DrawMultiColorTxt(OBJ, PBITMAP, SingleColor:=0) {
   
   Txt            := OBJ.Txt
   TxtPos         := OBJ.TxtPos
   HWND           := OBJ.hWnd
   
   ; Create the FONT from the given HWND.
   HFONT := DllCall("User32.dll\SendMessage", "Ptr", HWND, "UInt", 0x31, "Ptr",  0, "Ptr", 0, "Ptr")
   DC := DllCall("User32.dll\GetDC", "Ptr", HWND, "Ptr")
   DllCall("Gdi32.dll\SelectObject", "Ptr", DC, "Ptr", HFONT)
   DllCall("Gdiplus.dll\GdipCreateFontFromDC", "Ptr", DC, "PtrP", PFONT)
   DllCall("User32.dll\ReleaseDC", "Ptr", HWND, "Ptr", DC)
   DllCall("Gdiplus.dll\GdipDeleteFont", "Ptr", HFONT)

   ; Get the pointer to the bitmap graphics.
   DllCall("Gdiplus.dll\GdipGetImageGraphicsContext", "Ptr", PBITMAP, "PtrP", PGRAPHICS)

   ; Set render quality to system default
   DllCall("Gdiplus.dll\GdipSetTextRenderingHint", "Ptr", PGRAPHICS, "Int", 0)



   ; Create a StringFormat object
   if (TxtPos = "Left")
      N := 0x00
   else if (TxtPos = "Right") 
      N := 0x02
   else ; Center
      N := 0x01

   DllCall("Gdiplus.dll\GdipStringFormatGetGenericTypographic", "PtrP", hFORMAT)
   ; Horizontal alignment.
   DllCall("Gdiplus.dll\GdipSetStringFormatAlign", "Ptr", hFORMAT, "Int",  N)
   ; Vertical alignment.
   DllCall("Gdiplus.dll\GdipSetStringFormatLineAlign", "Ptr", hFORMAT, "Int", 1)



   ; Create a rect.
   DllCall("gdiplus\GdipGetImageDimension", Ptr, pBitmap, "float*", Width, "float*", Height)
   VarSetCapacity(RECT, 16, 0)
   NumPut(Width, RECT,  8, "Float")
   NumPut(Height, RECT, 12, "Float")
   Array := StrSplit(Txt, " ")
   


   ; ===========================================
   Lines := 0
   Chars := 0
   VarSetCapacity(RC, 16)
   DllCall("gdiplus\GdipMeasureString", UPtr, pGraphics, "WStr", Txt, "int", -1, Ptr, PFONT, Ptr, &Rect, Ptr, hFORMAT, Ptr, &RC, "uint*", Chars, "uint*", Lines)

   X2 := NumGet(RC, 0, "float")
   Y2 := NumGet(RC, 4, "float")
   Width2 := NumGet(RC, 8, "float")
   Height2 := NumGet(RC, 12, "float")

   FileAppend, X: %X2%`n,*
   FileAppend, Y: %Y2%`n,*
   FileAppend, W: %Width2%`n,*
   FileAppend, H: %Height2%`n,*
   FileAppend, Chars: %Chars%`n,*
   
   WordSpacing := Width2 / Chars
   FileAppend, WordSpacing: %WordSpacing%`n`n,*

   X := X2
   ; ======================================


   if (SingleColor) {

      ; Generate a random color.
      Random, vRand, 0, 0xFFFFFF
      TxtColor := 0xFF000000 | vRand
      
      ; Fill the text color
      DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", TxtColor, "PtrP", PBRUSH)
      
      ; Draw the text.
      DllCall("Gdiplus.dll\GdipDrawString", "Ptr", PGRAPHICS, "WStr", Txt, "Int",   -1, "Ptr", PFONT, "Ptr", &RECT, "Ptr", hFORMAT, "Ptr", PBRUSH)
      
      DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)
   }
   else {

      for each, Txt in Array {

         VarSetCapacity(RC, 16)
         DllCall("gdiplus\GdipMeasureString", UPtr, pGraphics, "WStr", Txt, "int", -1, Ptr, PFONT, Ptr, &Rec, Ptr, hFORMAT, Ptr, &RC, "uint*", 0, "uint*", 0)
         CurrentTxtW := NumGet(RC, 8, "float")

         ;------------------------------------


         VarSetCapacity(RECT, 16, 0)

         NumPut(X, RECT,  0, "Float")
         NumPut(0, RECT,  4, "Float")
         NumPut(CurrentTxtW, RECT,  8, "Float")
         NumPut(Height, RECT, 12, "Float")

         X += CurrentTxtW + WordSpacing


         ; Generate a random color.
         Random, vRand, 0, 0xFFFFFF
         TxtColor := 0xFF000000 | vRand
         
         ; Fill the text color
         DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", TxtColor, "PtrP", PBRUSH)
         
         ; Draw the text.
         DllCall("Gdiplus.dll\GdipDrawString", "Ptr", PGRAPHICS, "WStr", Txt, "Int",   -1, "Ptr", PFONT, "Ptr", &RECT, "Ptr", hFORMAT, "Ptr", PBRUSH)
         
         DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)

      }

   }
   ;DllCall("Gdiplus.dll\GdipDeleteStringFormat", "Ptr", HFORMAT)
   DllCall("Gdiplus.dll\GdipDeleteGraphics", "Ptr", PGRAPHICS)      

}
Spoiler
User avatar
Hellbent
Posts: 2102
Joined: 23 Sep 2017, 13:34

Re: How to draw multicolored text using GDIp

Post by Hellbent » 03 Aug 2021, 01:43

A simple method. Has worked well for me in the past.
Not for every use case.

Code: Select all

;***************************************************************************************************
#Include <My Altered Gdip Lib>   ;Replace with your path to the Gdip.ahk lib
;***************************************************************************************************
#SingleInstance force
GDIP_StartUp()

global Gui1 := New PopUpWindow( { WindowName: "1" , WindowOptions: " -DPIScale +AlwaysOnTop " , WindowSmoothing: 2 , X: 50 , Y: 100 , W: A_ScreenWidth - 100 , H: 150 } )
Gui1.ShowWindow( "Demo" )

fs := 6
Loop, 30	{
	AddText( fs++ )
	Sleep, 200
}

Loop, 30	
	AddText( Random( 10 , 30 ) )

Return
GuiContextMenu:
*ESC::ExitApp

Numpad3::
    PopUpWindow.Helper()
    Return

AddText( fs := 22 ){
	static Colors := StrSplit( "0xFFff0000,0xFF00ff00,0xFF0000ff,0xFFffff00,0xFF00ffff,0xFFff00ff" , "," , ",") 
	, TestStrings := StrSplit( "I'm trying to learn how to draw multi colored text in a image using GDIp. `n I already did till the point to draw each word with a different color, `n now I'm trying to find a way to calculate and drawn the text in the correct" , " " , " ")
	FontObj := { Font: "Segoe Ui" , FontSize: fs , FontOptions: "Bold Center vCenter" }
	
	Gui1.PaintBackground( "0xFF000000" )
	;**************************************************************************************************************************************************************************
	;**************************************************************************************************************************************************************************
	;**************************************************************************************************************************************************************************
	;Adding the text
	x := 10 , y := 10
	Loop, % TestStrings.Length()	{
		SSize := _GetTextSize( FontObj , TestStrings[ A_Index ] )
		if( TestStrings[ A_Index ] = "`n" ){
			x := 10 , y += SSize.4
			continue
		}
		Brush := Gdip_BrushCreateSolid( Colors[ Random( 1 , Colors.Length() ) ] ) , Gdip_TextToGraphics( Gui1.G , TestStrings[ A_Index ] , " s" FontObj.Fontsize " c" Brush " " FontObj.FontOptions " x" x " y" y , FontObj.Font , SSize.3 , SSize.4  ) , Gdip_DeleteBrush( Brush )
		x += SSize.3
	}
	;**************************************************************************************************************************************************************************
	;**************************************************************************************************************************************************************************
	;**************************************************************************************************************************************************************************
	Gui1.UpdateWindow()
}

_GetTextSize( Fobj , text ){
	local pBitmap, G, temparr
	pBitmap := Gdip_CreateBitmap( 10 , 10 ) , G := Gdip_GraphicsFromImage( pBitmap ), Gdip_SetSmoothingMode( G , 2 )
	temparr := StrSplit( Gdip_TextToGraphics( G , Text, " s" Fobj.Fontsize " " Fobj.FontOptions " x" 0 " y" 0 , Fobj.Font , 10000, 10000  ),"|","|"  ) 
	Gdip_DeleteGraphics( G ) , Gdip_DisposeImage( pBitmap )
	return temparr
}

Random( Min , Max ){
	Random, out, min, max
	return out
}

;************************************************************************************************************************************************************************************************
;************************************************************************************************************************************************************************************************
;************************************************************************************************************************************************************************************************
;************************************************************************************************************************************************************************************************
;************************************************************************************************************************************************************************************************
class PopUpWindow	{
	;Class By: Hellbent
	;Apr 2021
	static Index := 0 , Windows := [] , Handles := [] , HelpHandles := [] , HelperEditHwnd
	__New( obj := "" ){
		This._SetDefaults()
		if( isObject( obj ) )
			This.SetWindowProperties( obj )
		This._SetupWindowGraphics()
	}
	_SetDefaults(){
		PopUpWindow.Index++
		This.WindowName := "HBLayeredWindow" PopUpWindow.Index
		This.WindowSmoothing := 2
		This.WindowOptions := " -DPIScale +AlwaysOnTop "
		This.X := 10
		This.Y := 10
		This.W := 10
		This.H := 10
	}
	PaintBackground( color := "0xFF000000" ){
		Brush := Gdip_BrushCreateSolid( color ) 
		Gdip_FillRectangle( This.G , Brush , -1 , -1 , This.W + 2 , This.H + 2 ) 
		Gdip_DeleteBrush( Brush )
	}
	_SetupWindowGraphics(){
		This.Hwnd := This._CreateGUI()
		This.hbm := CreateDIBSection( This.W , This.H )
		This.hdc := CreateCompatibleDC()
		This.obm := SelectObject( This.hdc , This.hbm )
		This.G := Gdip_GraphicsFromHDC( This.hdc )
		Gdip_SetSmoothingMode( This.G , This.WindowSmoothing )
		PopUpWindow.Handles[ This.Hwnd ] := PopUpWindow.Index
		PopUpWindow.Windows[ PopUpWindow.Index ] := This
	}
	SetWindowProperties( obj ){
		local k , v 
		for k , v in obj
			if( k != "hwnd" )
				This[k] := v
	}
	ShowWindow( Title := "" ){
		Gui , % This.WindowName ":Show", % "x" This.X " y" This.Y " w" This.W " h" This.H " NA", % Title
	}
	HideWindow(){
		Gui , % This.WindowName ":Hide",
	}
	UpdateWindow(){
		UpdateLayeredWindow( This.hwnd , This.hdc , This.X , This.Y , This.W , This.H )
	}
	ClearWindow(){
		Gdip_GraphicsClear( This.G )
	}
	DrawBitmap( pBitmap , obj , dispose := 1 ){
		Gdip_DrawImage( This.G , pBitmap , obj.X , obj.Y , obj.W , obj.H )
		if( dispose )
			Gdip_DisposeImage( pBitmap )
	}
	DeleteWindow(){
		Gui, % This.WindowName ":Destroy"
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
		Gdip_DeleteGraphics( This.G )
		hwnd := This.Hwnd
		for k, v in PopUpWindow.Windows[ Hwnd ]
			This[k] := ""
		PopUpWindow.Windows[ Hwnd ] := ""
	}
	_CreateGUI(){
		local hwnd
		Gui , % This.WindowName ":New" , % " +E0x80000 hwndhwnd -Caption  " This.WindowOptions
		return hwnd
	}
	Helper(){
		local List := ["New Window","SetWindowProperties","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow"]
		local hwnd, bd
		
		
		Gui, HBLWHelperGui:New, +AlwaysOnTop 
		Gui, HBLWHelperGui:Color, 62666a, 24282c
		Gui, HBLWHelperGui:Font, cWhite s10 , Segoe UI
		Gui, HBLWHelperGui:Margin, 5 , 5
		
		Gui, HBLWHelperGui:Add, Edit, xm ym w200 r1 Center hwndHwnd, Gui1
		PopUpWindow.HelperEditHwnd := Hwnd
		Gui, HBLWHelperGui:Margin, 5 , 1
		Loop, % List.Length()	{
		
			Gui, HBLWHelperGui:Add, Button, xm wp h23 -Theme hwndhwnd, % List[ A_Index ]
			PopUpWindow.HelpHandles[hwnd] := List[ A_Index ]
			bd := PopUpWindow._ClipIt.Bind( PopUpWindow )
			GuiControl , HBLWHelperGui: +G , % Hwnd , % bd
		}
		
		Gui, HBLWHelperGui:Show, 
		
	}
	_ClipIt(){
		local List := ["New Window","SetWindowProperties","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow"]
		local Output , FQ := 400
		GuiControlGet, Output , HBLWHelperGui: , % PopUpWindow.HelperEditHwnd
		Switch A_GuiControl
		{
			case List[1]:
				Clipboard := Output " := New PopUpWindow( { WindowName: ""1"" , WindowOptions: "" -DPIScale +AlwaysOnTop "" , WindowSmoothing: 2 , X: ""Center"" , Y: ""Center"" , W: 100 , H: 100 } )"
				loop 2
					SoundBeep, FQ
				return
			case List[2]:
				Clipboard := Output ".SetWindowProperties( { X: """" , Y: """" , W: """" , H: """" } )"
				loop 2
					SoundBeep, FQ
				return
			case List[3]:
				Clipboard := Output ".ShowWindow( MyWindowTitle := """" )"
				loop 2
					SoundBeep, FQ
				return
			case List[4]:
				Clipboard := Output ".HideWindow()"
				loop 2
					SoundBeep, FQ
				return
			case List[5]:
				Clipboard := Output ".UpdateWindow()"
				loop 2
					SoundBeep, FQ
				return
			case List[6]:
				Clipboard := Output ".ClearWindow()"
				loop 2
					SoundBeep, FQ
				return
			case List[7]:
				Clipboard := Output ".DrawBitmap( pBitmap := """" , { X: """" , Y: """" , W: """" , H: """" } , dispose := 1 )"
				loop 2
					SoundBeep, FQ
				return
			case List[8]:
				Clipboard := Output ".PaintBackground( color := ""0xFF000000"" )"
				loop 2
					SoundBeep, FQ
				return
			case List[9]:
				Clipboard := Output ".DeleteWindow()"
				loop 2
					SoundBeep, FQ
				return
			Default:
				ToolTip, Looks like a new case needs to be added
				return
			
		}
	}
}

Temp (1).gif
(436.72 KiB) Downloaded 42 times
Post Reply

Return to “Ask for Help (v1)”