GDI+ Text Border / Bubble

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

GDI+ Text Border / Bubble

Post by Hellbent » 19 Jan 2023, 01:11

iseahound wrote:
09 Feb 2022, 22:23
@Hellbent LiquidatePixels is the best name ever. :)

If you're looking for speed you'll need to use a C function compiled to machine code. GetPixel is notoriously slow.
@iseahound I ended up modifying the LipuidatePixels function ( viewtopic.php?f=76&t=100097&p=444673&hilit=LiquidatePixels#p444619 )
so that I can draw nice / multiple borders around text but as you can imagine, it's not the fastest. Would what you were talking about require external assets? If not, how would one go about doing it?

Here is what my script does, I'd like to speed this up 5-10x if possible, or have any function that can do this fast.

.
20230119023423.png
20230119023423.png (503.54 KiB) Viewed 1542 times
.


Example and BubbleText class

Code: Select all

;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;<<<<<<<<<<<<<<<<<<---------------------------     gdip.ahk
;~ #Include <PopUpWindow_V2> ; At the bottom of the script 
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()

Gui1 := New PopUpWindow( { AutoShow: 1 , X: "Center" , Y: wy := 220 , W: 800 , H: 600 , Options: " -DPIScale +AlwaysOnTop " } )
Gui1.PaintBackground( { Color: "0xff22262a" , X: 2 , Y: 2 , W: Gui1.W - 4 , H: Gui1.H - 4 , Round: 10 } , AutoUpdate := 0 )  
y := 0
x := 10
MyText := "Blah Blah Blah Blah Blah Blah`nBlah Blah Blah Blah Blah Blah`nBlah Bob  Blah Blah Blah Blah`nBlah Blah Blah Blah Blah Blah`nBlah Blah Blah Blah Blah Blah"

MyString := New BubbleText( { Text: "Funky / Colorful Text" , FontColor: "0xFFFF0000" , FontSize: 66 , FontType: "Comic Sans MS" } ,  [ "0xFF00FFFF" , "0x66FF6600" , "0x66005555" , "0x66008888" , "0x66000000" ] , [ 24 , 20 , 16 , 8 , 4 ] )
Gui1.DrawBitmap( MyString.Bitmap , { X: 10 , Y: y , W: MyString.Width , H: MyString.Height } , dispose := 1 , AutoUpdate := 0 )
y += MyString.Height

MyString := New BubbleText( { Text: "##~AutoHotkey~##" , FontColor: "0xFFffffff" , FontSize: 66 , FontType: "Segoe UI" } ,  [ "0xFF00FFFF" , "0x66002222" , "0x66005555" , "0x66008888" , "0x6600aaaa" ] , [ 24 , 20 , 16 , 8 , 4 ] )
Gui1.DrawBitmap( MyString.Bitmap , { X: 10 , Y: y  , W: MyString.Width , H: (  MyString.Height ) } , dispose := 1 , AutoUpdate := 0 )
y += MyString.Height

MyString := New BubbleText( { Text: "AHK" , FontColor: "0xFF000001" , BackgroundColor: "0xFFFFFFFF" , FontSize: 52 , FontType: "ink free" } ,  [ "0xFF00FF00" , "0x66002222" , "0x66ffff00" , "0x66000000" , "0x6600ff00" ] , [ 24 , 20 , 16 , 8 , 4 ] )
Gui1.DrawBitmap( MyString.Bitmap , { X: 10 , Y: y  , W: MyString.Width , H: (  MyString.Height ) } , dispose := 1 , AutoUpdate := 0 )
y += MyString.Height

MyString := New BubbleText( { Text: myText , FontColor: "0xFFffffff" , FontSize: 22 , FontType: "Arial" , FontOptions: " vCenter" } ,  [ "0xFFff00FF" , "0x66002222"  ] , [  4 , 2 ] )
Gui1.DrawBitmap( MyString.Bitmap , { X: 10 , Y: y  , W: MyString.Width , H: (  MyString.Height ) } , dispose := 1 , AutoUpdate := 0 )
x += MyString.Width

MyString := New BubbleText( { Text: myText , FontColor: "0xFF22262a" , BackgroundColor: "0xFFFFFFFF" , FontSize: 26 , FontType: "Impact" , FontOptions: " vCenter" } ,  [ "0xFFFFFFFF" , "0x660000FF" , "0xFF000000" , "0x6600aa88" ] , [  18 , 16 , 8 , 4 ] )
Gui1.DrawBitmap( MyString.Bitmap , { X: x , Y: y - 40 , W: MyString.Width , H: (  MyString.Height ) } , dispose := 1 , AutoUpdate := 0 )

Gui1.UpdateWindow()

return
GuiClose:
GuiContextMenu:
*ESC::ExitApp


;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;Color Text class
;Written By: Hellbent
;Date: Jan 18th , 2023
class BubbleText	{
	__New( obj := "" , Colors := "0xFF000000" , Sizes := 2 ){
		This._SetDefaults()
		This._UpdateDefaults( obj , Colors , Sizes )
		This._CreateBitmaps()
		This._LiquidatePixels()
		return This.Output
	}
	
	_SetDefaults(){
		This.Text := "Hello World"
		This.FontType := "Comic Sans MS"
		This.FontSize := 16
		This.FontColor := "0xFFFFFFFF"
		This.FontOptions := " Center vCenter "
		This.BackgroundColor := ""
		This.StringHeight := ""
		This.StringWidth := ""
		This.TextBitmap := ""
		This.Bitmaps := []
		This.Colors := []
		This.SizeInterval := 1
		This.Smoothing := 4
		This.RedRange := 128
		This.GreenRange := 128
		This.BlueRange := 128
	}
	
	_UpdateDefaults( obj := "" , Colors := "0xFF000000" , Sizes := "4" ){
		for k , v in obj	
			This[ k ] := obj[ k ]
		if( IsObject( Colors ) ){
			for k , v in Colors
				This.Colors[ A_Index ] := Colors[ A_Index ]
		}else if( colors != "" ){
			This.Colors[ 1 ] := colors
		}
		if( IsObject( Sizes ) ){
			for k , v in Sizes
				This.Sizes[ A_Index ] := Sizes[ A_Index ]
		}else if( Sizes != "" ){
			This.Sizes[ 1 ] := Sizes
		}
		if( ( dif := This.Sizes.Length() - This.Colors.Length() ) < 0 ){
			index := This.Sizes.Length()
			loop, % dif	{
				This.Sizes[ index + A_Index ] := This.Sizes[ index + A_Index - 1 ] - This.SizeInterval
			}
		}else if( ( dif := This.Colors.Length() - This.Sizes.Length() ) < 0 ){
			index := This.Colors.Length()
			loop, % dif	{
				This.Colors[ index + A_Index ] := This.Colors[ index + A_Index - 1 ] 
			}
		}
	}
	
	_CreateBitmaps(){
		This.StringWidth := This._GetTextSize( 1 , This.Text ) 
		This.StringHeight := This._GetTextSize( 2 , This.Text ) 
		This.TextBitmap := Gdip_CreateBitmap( This.StringWidth , This.StringHeight )
		This.TextBitmapGraphics := Gdip_GraphicsFromImage( This.TextBitmap )
		Gdip_SetSmoothingMode( This.TextBitmapGraphics , This.Smoothing )
		if( This.BackgroundColor != ""){
			Brush := Gdip_BrushCreateSolid( This.BackgroundColor )
			Gdip_FillRectangle( This.TextBitmapGraphics , Brush, -10 , -10 , This.StringWidth + 20 , This.StringHeight + 20 )
			Gdip_DeleteBrush( Brush )
		}
		Brush := Gdip_BrushCreateSolid( This.FontColor )
		Gdip_TextToGraphics( This.TextBitmapGraphics , This.Text , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , This.StringWidth, This.StringHeight  )
		Gdip_DeleteBrush( Brush )
		This.MaxSize := 0
		Loop, % This.Sizes.Length()	{
			if( This.Sizes[ A_Index ] > This.MaxSize ){
				This.MaxSize := This.Sizes[ A_Index ]
			}
		}
		Loop, % This.Colors.Length()	{
			This.Bitmaps[ A_Index ] := {}
			This.Bitmaps[ A_Index ].Bitmap := Gdip_CreateBitmap( This.StringWidth + 2 * This.MaxSize + 4 , This.StringHeight + 2 * This.MaxSize + 4 )
			This.Bitmaps[ A_Index ].Graphics := Gdip_GraphicsFromImage( This.Bitmaps[ A_Index ].Bitmap )
			Gdip_SetSmoothingMode( This.Bitmaps[ A_Index ].Graphics , This.Smoothing )
		}
		This.Output := {}
		This.Output.Width := This.StringWidth + 2 * This.MaxSize + 4
		This.Output.Height := This.StringHeight + 2 * This.MaxSize + 4
		This.Output.Bitmap := Gdip_CreateBitmap( This.Output.Width , This.Output.Height )
		This.Output.Graphics := Gdip_GraphicsFromImage( This.Output.Bitmap )
	}
	
	_GetTextSize( outputType := 0 , String := "" ){ ; 0 = all , 1 = width , 2 = height 
		local pBitmap, G, Brush, temparr 
		pBitmap := Gdip_CreateBitmap( 10,10), G := Gdip_GraphicsFromImage( pBitmap ), Gdip_SetSmoothingMode( G , 2 )
		Brush := Gdip_BrushCreateSolid( "0xFF000000")
		temparr := StrSplit( Gdip_TextToGraphics( G , ( String != "" ) ? ( String ) : ( "T" ) , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , 10000, 10000  ),"|","|"  )
		Gdip_DeleteBrush( Brush ), Gdip_DeleteGraphics( G ), Gdip_DisposeImage( pBitmap )
		if( outputType = 0 )
			return temparr
		else if( outputType = 1 )
			return temparr[ 3 ]
		else if( outputType = 2 )
			return temparr[ 4 ]
	}
	
	_GetGray( OUTPUTCOLOR ){ ;From HB color picker v2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , r ,  OUTPUTCOLOR , 2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , g ,  OUTPUTCOLOR , 2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , b ,  OUTPUTCOLOR , 2
		r := "0x" r , g := "0x" g , b := "0x" b
		REDSLIDERVALUE:=r+0,GreenSLIDERVALUE:=g+0,BlueSLIDERVALUE:=b+0
		GreyScaleSLIDERVALUE:=Round((REDSLIDERVALUE+GreenSLIDERVALUE+BlueSLIDERVALUE)/3)
		return GreyScaleSLIDERVALUE
	}
	
	_LiquidatePixels(){
		local sRed , sGreen , sBlue , sAlpha , x , y , brushes := []
		This._hex2rgb( This.FontColor , sRed , sGreen , sBlue , sAlpha )
		Loop, % This.Colors.Length()	{
			
			Brushes[ A_Index ] := Gdip_BrushCreateSolid( This.Colors[ A_Index ] )
		}
		y := 0
		Loop, % This.StringHeight 	{
			x := 0
			Loop, % This.StringWidth	{
				SetFormat, IntegerFast, hex
				col := Gdip_GetPixel( This.TextBitmap , x , y )
				SetFormat, IntegerFast, dec
				This._hex2rgb( col , cRed , cGreen , cBlue , sAlpha )
				if( sAlpha = "" || !sAlpha ){
					x++
					continue
				}
				if( col = This.FontColor || ( cRed >= sRed - This.RedRange && cRed <= sRed + This.RedRange  && cGreen >= sGreen - This.GreenRange && cGreen <= sGreen + This.GreenRange && cBlue >= sBlue - This.BlueRange && cBlue <= sBlue + This.BlueRange ) ){ 
					Loop, % This.Bitmaps.Length()	{
						Gdip_FillEllipse( This.Bitmaps[ A_Index ].Graphics , Brushes[ A_Index ] , x + This.MaxSize + 2 - This.Sizes[ A_Index ] , y + This.MaxSize + 2 - This.Sizes[ A_Index ] , 2 * This.Sizes[ A_Index ] , 2 * This.Sizes[ A_Index ] )
					}
				}
				x++
			}
			y++
		}
		Loop, % This.Bitmaps.Length()	{
			Gdip_DeleteGraphics( This.Bitmaps[ A_Index ].Graphics )
			Gdip_DrawImage( This.Output.Graphics , This.Bitmaps[ A_Index ].Bitmap , 0 , 0 , This.Output.Width , This.Output.Height )
		}
		if( This.BackgroundColor = "" )
			Gdip_DrawImage( This.Output.Graphics , This.TextBitmap , This.MaxSize + 2 , This.MaxSize + 2 , This.StringWidth , This.StringHeight )
		else{
			Gdip_DeleteGraphics( This.TextBitmapGraphics )
			Gdip_DisposeImage( This.TextBitmap )
			This.TextBitmap := Gdip_CreateBitmap( This.StringWidth , This.StringHeight )
			This.TextBitmapGraphics := Gdip_GraphicsFromImage( This.TextBitmap )
			Gdip_SetSmoothingMode( This.TextBitmapGraphics , This.Smoothing )
			Brush := Gdip_BrushCreateSolid( This.FontColor )
			Gdip_TextToGraphics( This.TextBitmapGraphics , This.Text , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , This.StringWidth, This.StringHeight  )
			Gdip_DeleteBrush( Brush )
			Gdip_DrawImage( This.Output.Graphics , This.TextBitmap , This.MaxSize + 2 , This.MaxSize + 2 , This.StringWidth , This.StringHeight )
		}
		Gdip_DeleteGraphics( This.Output.Graphics )
		Loop, % This.Colors.Length()	{
			Gdip_DeleteBrush( Brushes[ A_Index ] )
		}
		Gdip_DeleteGraphics( This.TextBitmapGraphics )
	}
	
	_hex2rgb( CR , ByRef R , ByRef G , ByRef B , ByRef A := "NA" ){ ;https://www.autohotkey.com/boards/viewtopic.php?t=3925&p=312862
		H := InStr(CR, "0x") ? CR : ( InStr(CR, "#" ) ? "0x" SubStr( CR , 2 ) : "0x" CR )
		R := (H & 0xFF0000) >> 16 , G := (H & 0xFF00) >> 8 , B := (H & 0xFF) , ( A != "NA" ) ? ( A := (H & 0xFF000000) >> 24 ) 
	}
}

;Layered Window Class
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
class PopUpWindow	{
;PopUpWindow v2.2
;Date Written: Oct 28th, 2021
;Last Edit: Feb 7th, 2022 :Changed the trigger method.
;Written By: Hellbent aka CivReborn
;SpcThanks: teadrinker , malcev 
	static Index := 0 , Windows := [] , Handles := [] , EditHwnd , HelperHwnd
	__New( obj := "" ){
		This._SetDefaults()
		This.UpdateSettings( obj )
		This._CreateWindow()
		This._CreateWindowGraphics()
		if( This.AutoShow )
			This.ShowWindow( This.Title )
	}
	_SetDefaults(){
		This.X := 10
		This.Y := 10
		This.W := 10
		This.H := 10
		This.Smoothing := 2
		This.Options := " -DPIScale +AlwaysOnTop "
		This.AutoShow := 0
		This.GdipStartUp := 0
		This.Title := ""
		
		This.Controls := []
		This.Handles := []
		This.Index := 0 
	}
	AddTrigger( obj ){
		local k , v , cc , bd
		
		This.Controls[ ++This.Index ] := { 	X:		10
										,	Y:		10
										,	W:		10
										,	H:		10	}
		for k, v in obj
			This.Controls[ This.Index ][ k ] := obj[ k ] 
		cc := This.Controls[ This.Index ]
		Gui, % This.Hwnd ":Add", Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd"
		This.Handles[ hwnd ] := This.Index
		This.Controls[ This.Index ].Hwnd := hwnd
		
		if( IsObject( cc.Label ) ){
			bd := cc.Label
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}else{
			bd := This._TriggerCall.Bind( This )
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}
		return hwnd
		
	}
	_TriggerCall(){
		MouseGetPos,,,, ctrl, 2
		Try
			;~ SetTimer, % This.Controls[ This.Handles[ ctrl ] ].Label, -0
			gosub, % This.Controls[ This.Handles[ ctrl ] ].Label
		
				
	}
	DrawTriggers( color := "0xFFFF0000" , AutoUpdate := 0 ){
		local brush , cc 
		Brush := Gdip_BrushCreateSolid( color ) 
		Gdip_SetSmoothingMode( This.G , 3 )
		loop, % This.Controls.Length()	{
			cc := This.Controls[ A_Index ]
			Gdip_FillRectangle( This.G , Brush , cc.x , cc.y , cc.w , cc.h )
		
		}
		Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( This.G , This.Smoothing )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	UpdateSettings( obj := "" , UpdateGraphics := 0 ){
		local k , v
		if( IsObject( obj ) )
			for k, v in obj
				This[ k ] := obj[ k ]
		( This.X = "Center" ) ? ( This.X := ( A_ScreenWidth - This.W ) / 2 ) 	
		( This.Y = "Center" ) ? ( This.Y := ( A_ScreenHeight - This.H ) / 2 ) 	
		if( UpdateGraphics ){
			This._DestroyWindowsGraphics()
			This._CreateWindowGraphics()
		}
	}
	_CreateWindow(){
		local hwnd
		Gui , New, % " +LastFound +E0x80000 hwndhwnd -Caption  " This.Options
		PopUpWindow.Index++
		This.Index := PopUpWindow.Index
		PopUpWindow.Windows[ PopUpWindow.Index ] := This
		This.Hwnd := hwnd
		PopUpWindow.Handles[ hwnd ] := PopUpWindow.Index
		if( This.GdipStartUp && !PopUpWindow.pToken )
			PopUpWindow.pToken := GDIP_STARTUP()
	}
	_DestroyWindowsGraphics(){
		Gdip_DeleteGraphics( This.G )
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
	}
	_CreateWindowGraphics(){
		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.Smoothing )
	}
	ShowWindow( Title := "" ){
		Gui , % This.Hwnd ":Show", % "x" This.X " y" This.Y " w" This.W " h" This.H " NA", % Title
	}
	HideWindow(){
		Gui , % This.Hwnd ":Hide",
	}
	UpdateWindow( alpha := 255 ){
		UpdateLayeredWindow( This.hwnd , This.hdc , This.X , This.Y , This.W , This.H , alpha )
	}
	ClearWindow( AutoUpdate := 0 ){
		Gdip_GraphicsClear( This.G )
		if( Autoupdate )
			This.UpdateWindow()
	}
	DrawBitmap( pBitmap , obj , dispose := 1 , AutoUpdate := 0 ){
		Gdip_DrawImage( This.G , pBitmap , obj.X , obj.Y , obj.W , obj.H )
		if( dispose )
			Gdip_DisposeImage( pBitmap )
		if( Autoupdate )
			This.UpdateWindow()
	}
	PaintBackground( color := "0xFF000000" , AutoUpdate := 0 ){
		if( isObject( color ) ){
			Brush := Gdip_BrushCreateSolid( ( color.HasKey( "Color" ) ) ? ( color.Color ) : ( "0xFF000000" ) ) 
			if( color.Haskey( "Round" ) )
				Gdip_FillRoundedRectangle( This.G , Brush , color.X , color.Y , color.W , color.H , color.Round )
			else
				Gdip_FillRectangle( This.G , Brush , color.X , color.Y , color.W , color.H ) 
		}else{
			Brush := Gdip_BrushCreateSolid( color ) 
			Gdip_FillRectangle( This.G , Brush , -1 , -1 , This.W + 2 , This.H + 2 ) 
		}
		Gdip_DeleteBrush( Brush )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DeleteWindow( GDIPShutdown := 0 ){
		Gui, % This.Hwnd ":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 ] := ""
		if( GDIPShutdown ){
			Gdip_Shutdown( PopUpWindow.pToken )
			PopUpWindow.pToken := ""
		}
	}
	_OnClose( wParam ){
		if( wParam = 0xF060 ){	;SC_CLOSE ;[ clicking on the gui close button ]
			Try{
				Gui, % PopUpWindow.HelperHwnd ":Destroy"
				SoundBeep, 555
			}
		}
	}
	CreateCachedBitmap( pBitmap , Dispose := 0 ){
		local pCachedBitmap
		if( This.CachedBitmap )
			This.DisposeCachedbitmap()
		DllCall( "gdiplus\GdipCreateCachedBitmap" , "Ptr" , pBitmap , "Ptr" , this.G , "PtrP" , pCachedBitmap )
		This.CachedBitmap := pCachedBitmap
		if( Dispose )
			Gdip_DisposeImage( pBitmap )
	}
	DrawCachedBitmap( AutoUpdate := 0 ){
		DllCall( "gdiplus\GdipDrawCachedBitmap" , "Ptr" , this.G , "Ptr" , This.CachedBitmap , "Int" , 0 , "Int" , 0 )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DisposeCachedbitmap(){
		DllCall( "gdiplus\GdipDeleteCachedBitmap" , "Ptr" , This.CachedBitmap )
	}
	Helper(){
		local hwnd , MethodList := ["__New","UpdateSettings","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow" , "AddTrigger" , "DrawTriggers", "CreateCachedBitmap" , "DrawCachedBitmap" , "DisposeCachedbitmap" ]
		Gui, New, +AlwaysOnTop +ToolWindow +HwndHwnd
		PopUpWindow.HelperHwnd := hwnd
		Gui, Add, Edit, xm ym w250 r1 Center hwndhwnd, Gui1
		PopUpWindow.EditHwnd := hwnd
		loop, % MethodList.Length()	
			Gui, Add, Button, xm y+1 w250 r1 gPopUpWindow._HelperClip, % MethodList[ A_Index ]
		Gui, Show,,
		OnMessage( 0x112 , This._OnClose.Bind( hwnd ) )
	}
	_HelperClip(){
		local ClipList 
		
		GuiControlGet, out, % PopUpWindow.HelperHwnd ":", % PopUpWindow.EditHwnd	
		
		ClipList := 		{ 	__New: 					" := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 0 , W: A_ScreenWidth , H: A_ScreenHeight , Options: "" -DPIScale +AlwaysOnTop "" } )"
							,	UpdateSettings:			".UpdateSettings( { X: """" , Y: """" , W: """" , H: """" } , UpdateGraphics := 0 )"
							,	ShowWindow:				".ShowWindow( Title := """" )"
							,	HideWindow:				".HideWindow()"
							,	UpdateWindow:			".UpdateWindow()"
							,	ClearWindow:			".ClearWindow( AutoUpdate := 0 )"
							,	DrawBitmap:				".DrawBitmap( pBitmap := """" , { X: 0 , Y: 0 , W: " Out ".W , H: " Out ".H } , dispose := 1 , AutoUpdate := 0 )"
							,	PaintBackground:		".PaintBackground( color := ""0xFF000000"" , AutoUpdate := 0 )  "  ";{ Color: ""0xFF000000"" , X: 2 , Y: 2 , W: " Out ".W - 4 , H: " Out ".H - 4 , Round: 10 }"
							,	DeleteWindow:			".DeleteWindow( GDIPShutdown := 0 )"
							,	AddTrigger:				".AddTrigger( { X: """" , Y: """" , W: """" , H: """" , Value: """" , Label: """" } )"	
							,	DrawTriggers:			".DrawTriggers( color := ""0xFFFF0000"" , AutoUpdate := 0 )"	
							,	CreateCachedBitmap:		".CreateCachedBitmap( pBitmap , Dispose := 0 )"	
							,	DrawCachedBitmap: 		".DrawCachedBitmap( AutoUpdate := 0 )"	
							,	DisposeCachedbitmap:	".DisposeCachedbitmap()"	}
							
		clipboard := Out ClipList[ A_GuiControl ]
		
	}
}


While I'm at it, @teadrinker and @robodesign.

Thanks for reading.


***Edit***
Added a check for an alpha value.

Code: Select all

This._hex2rgb( col , cRed , cGreen , cBlue , sAlpha )
if( sAlpha = "" || !sAlpha ){
	x++
	continue
}

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 19 Jan 2023, 05:04

If more information helps, I'm going to be using this with a little screen clipping tool that I am working on.
Since my last post I hacked the bubble text function into the clipper and although it is a bit slow, I don't think that it is too bad.

In the demo I have it run the bubble function when I press the arrow button. The text I use is quite large and the number of border layers is much higher than I think I will need. A little speed boost should be good.
SC1.gif
SC1.gif (518.81 KiB) Viewed 1526 times

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: GDI+ Text Border / Bubble

Post by jNizM » 19 Jan 2023, 05:29

Here is what my script does, I'd like to speed this up 5-10x if possible, or have any function that can do this fast.
Not 5-10x and not sure if this is still relevant in the current version of AutoHotkey, but check this out.
-> https://www.autohotkey.com/board/topic/90266-funktionen-loadlibrary-freelibrary-schnellere-dllcalls/ (but is from the german subforum)
-> viewtopic.php?p=25966#p25966 (should work with latest v1)
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 19 Jan 2023, 05:50

jNizM wrote:
19 Jan 2023, 05:29
Here is what my script does, I'd like to speed this up 5-10x if possible, or have any function that can do this fast.
Not 5-10x and not sure if this is still relevant in the current version of AutoHotkey, but check this out.
-> https://www.autohotkey.com/board/topic/90266-funktionen-loadlibrary-freelibrary-schnellere-dllcalls/ (but is from the german subforum)
-> viewtopic.php?p=25966#p25966 (should work with latest v1)
Thanks for the reply.
I'm not sure what I'm looking at there, it looks a lot like this function though.

Code: Select all

Gdip_Startup()
{
	Ptr := A_PtrSize ? "UPtr" : "UInt"
	
	if !DllCall("GetModuleHandle", "str", "gdiplus", Ptr)
		DllCall("LoadLibrary", "str", "gdiplus")
	VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
	DllCall("gdiplus\GdiplusStartup", A_PtrSize ? "UPtr*" : "uint*", pToken, Ptr, &si, Ptr, 0)
	return pToken
}
Any more info you can share?

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: GDI+ Text Border / Bubble

Post by jNizM » 19 Jan 2023, 06:10

It is a bit different than just use GdiplusStartup.
Take a look at this: viewtopic.php?p=48392#p48392
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: GDI+ Text Border / Bubble

Post by robodesign » 19 Jan 2023, 06:16

@Hellbent . In Quick Picto Viewer I do something different.

I draw the texts using GDI, not GDI+. It is much faster. The borders can be drawn with GDI also. To solve the tranparency issue, I use a function in C++.

Please check Gdi_DrawTextInBox() from quick-picto-viewer.ahk and Gdi_DrawTextHelper() and Gdi_DrawTextOutline() from Lib\gdi.ahk .

These files are at https://github.com/marius-sucan/Quick-Picto-Viewer .

If you have further questions , please let me know .

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 20 Jan 2023, 23:03

@robodesign Can you please point to where I can find

Code: Select all

#Include Lib\Gdi.ahk 

Also if possible, can you please provide a very simple example I can work from
Thank you.

jNizM wrote:
19 Jan 2023, 05:29
Here is what my script does, I'd like to speed this up 5-10x if possible, or have any function that can do this fast.
Not 5-10x and not sure if this is still relevant in the current version of AutoHotkey, but check this out.
-> https://www.autohotkey.com/board/topic/90266-funktionen-loadlibrary-freelibrary-schnellere-dllcalls/ (but is from the german subforum)
-> viewtopic.php?p=25966#p25966 (should work with latest v1)
Thank you for that, I was able to to cut the time by a good 20-30% with that. I was also able to do some other optimizations to my function such as removing the search / replace method it had before, no real point if I just want this for text.

I think that the results I have now are suitable for my needs but if anyone knows how to get the speed and efficiency up to the point of reasonable live updating text previews I am all ears.

.
My current results.
Screen clipper text.gif
Screen clipper text.gif (896.75 KiB) Viewed 1393 times
.

Example Code:

Code: Select all

GDIP_StartUp()
global gdiplus := LoadLibrary("gdiplus")

MyString := New BubbleText( { Text: "Sample Text" , FontColor: "0xFFFFFFFF" , FontSize: 36 , FontType: "Arial" , FullWrap: 1 } , [ "0xFF000000"  ] , [ 4 ] )

msgbox, % MyString.Bitmap  ;pBitmap
		."`n" MyString.Width
		."`n" MyString.Height
		
		
;####################################################################################################################################################################################
;####################################################################################################################################################################################
LoadLibrary(filename){ ;https://www.autohotkey.com/boards/viewtopic.php?p=48392#p48392
    static ref := {}
    if (!(ptr := p := DllCall("LoadLibrary", "str", filename, "ptr")))
        return 0
    ref[ptr,"count"] := (ref[ptr]) ? ref[ptr,"count"]+1 : 1
    p += NumGet(p+0, 0x3c, "int")+24
    o := {_ptr:ptr, __delete:func("FreeLibrary"), _ref:ref[ptr]}
    if (NumGet(p+0, (A_PtrSize=4) ? 92 : 108, "uint")<1 || (ts := NumGet(p+0, (A_PtrSize=4) ? 96 : 112, "uint")+ptr)=ptr || (te := NumGet(p+0, (A_PtrSize=4) ? 100 : 116, "uint")+ts)=ts)
        return o
    n := ptr+NumGet(ts+0, 32, "uint")
    loop % NumGet(ts+0, 24, "uint"){
        if (p := NumGet(n+0, (A_Index-1)*4, "uint")){
            o[f := StrGet(ptr+p, "cp0")] := DllCall("GetProcAddress", "ptr", ptr, "astr", f, "ptr")
            if (Substr(f, 0)==((A_IsUnicode) ? "W" : "A"))
                o[Substr(f, 1, -1)] := o[f]
        }
    }
    return o
}

FreeLibrary(lib){
    if (lib._ref.count>=1)
        lib._ref.count -= 1
    if (lib._ref.count<1)
        DllCall("FreeLibrary", "ptr", lib._ptr)
}
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;Color Text class
;Written By: Hellbent
;Date: Jan 18th , 2023
;Last Edit: Jan 20th , 2023
;Change: Can now set to use a pie shape to do a drop shadow. [ Key: FullWrap | Values: 0/1 ]
;Thanks To: jNizM (  https://www.autohotkey.com/boards/viewtopic.php?f=76&t=112861#p502745 )
class BubbleText	{
	__New( obj := "" , Colors := "0xFF000000" , Sizes := 2 ){
		This._SetDefaults()
		This._UpdateDefaults( obj , Colors , Sizes )
		This._CreateBitmaps()
		This._LiquidatePixels()
		return This.Output
	}
	
	_SetDefaults(){
		This.Text := "Hello World"
		This.FontType := "Comic Sans MS"
		This.FontSize := 16
		This.FontColor := "0xFFFFFFFF"
		This.FontOptions := " Center vCenter "
		This.BackgroundColor := ""
		This.StringHeight := ""
		This.StringWidth := ""
		This.TextBitmap := ""
		This.Bitmaps := []
		This.Colors := []
		This.SizeInterval := 1
		This.Smoothing := 4
		This.RedRange := 128
		This.GreenRange := 128
		This.BlueRange := 128
		This.FullWrap := 1
	}
	
	_UpdateDefaults( obj := "" , Colors := "0xFF000000" , Sizes := 2 ){ 
		for k , v in obj	
			This[ k ] := obj[ k ]
		if( IsObject( Colors ) ){
			for k , v in Colors
				This.Colors[ A_Index ] := Colors[ A_Index ]
		}else if( colors != "" ){
			This.Colors[ 1 ] := colors
		}
		if( IsObject( Sizes ) ){
			for k , v in Sizes
				This.Sizes[ A_Index ] := Sizes[ A_Index ]
		}else if( Sizes != "" ){
			This.Sizes[ 1 ] := Sizes
		}
		if( ( dif := This.Sizes.Length() - This.Colors.Length() ) < 0 ){
			index := This.Sizes.Length()
			loop, % dif	{
				This.Sizes[ index + A_Index ] := This.Sizes[ index + A_Index - 1 ] - This.SizeInterval
			}
		}else if( ( dif := This.Colors.Length() - This.Sizes.Length() ) < 0 ){
			index := This.Colors.Length()
			loop, % dif	{
				This.Colors[ index + A_Index ] := This.Colors[ index + A_Index - 1 ] 
			}
		}
	}
	
	_CreateBitmaps(){
		This.StringWidth := This._GetTextSize( 1 , This.Text ) 
		This.StringHeight := This._GetTextSize( 2 , This.Text ) / 1.05
		This.TextBitmap := Gdip_CreateBitmap( This.StringWidth , This.StringHeight )
		This.TextBitmapGraphics := Gdip_GraphicsFromImage( This.TextBitmap )
		Gdip_SetSmoothingMode( This.TextBitmapGraphics , This.Smoothing )
		if( This.BackgroundColor != ""){
			Brush := Gdip_BrushCreateSolid( This.BackgroundColor )
			Gdip_FillRectangle( This.TextBitmapGraphics , Brush, -10 , -10 , This.StringWidth + 20 , This.StringHeight + 20 )
			Gdip_DeleteBrush( Brush )
		}
		Brush := Gdip_BrushCreateSolid( This.FontColor )
		Gdip_TextToGraphics( This.TextBitmapGraphics , This.Text , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , This.StringWidth, This.StringHeight  )
		Gdip_DeleteBrush( Brush )
		This.MaxSize := 0
		Loop, % This.Sizes.Length()	{
			if( This.Sizes[ A_Index ] > This.MaxSize ){
				This.MaxSize := This.Sizes[ A_Index ]
			}
		}
		Loop, % This.Colors.Length()	{
			This.Bitmaps[ A_Index ] := {}
			This.Bitmaps[ A_Index ].Bitmap := Gdip_CreateBitmap( This.StringWidth + 2 * This.MaxSize + 4 , This.StringHeight + 2 * This.MaxSize + 4 )
			This.Bitmaps[ A_Index ].Graphics := Gdip_GraphicsFromImage( This.Bitmaps[ A_Index ].Bitmap )
			Gdip_SetSmoothingMode( This.Bitmaps[ A_Index ].Graphics , This.Smoothing )
		}
		This.Output := {}
		This.Output.Width := This.StringWidth + 2 * This.MaxSize + 4
		This.Output.Height := This.StringHeight + 2 * This.MaxSize + 4
		This.Output.Bitmap := Gdip_CreateBitmap( This.Output.Width , This.Output.Height )
		This.Output.Graphics := Gdip_GraphicsFromImage( This.Output.Bitmap )
	}
	
	_GetTextSize( outputType := 0 , String := "" ){ ; 0 = all , 1 = width , 2 = height 
		local pBitmap, G, Brush, temparr 
		pBitmap := Gdip_CreateBitmap( 10,10), G := Gdip_GraphicsFromImage( pBitmap ), Gdip_SetSmoothingMode( G , 2 )
		Brush := Gdip_BrushCreateSolid( "0xFF000000")
		temparr := StrSplit( Gdip_TextToGraphics( G , ( String != "" ) ? ( String ) : ( "T" ) , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , 10000, 10000  ),"|","|"  )
		Gdip_DeleteBrush( Brush ), Gdip_DeleteGraphics( G ), Gdip_DisposeImage( pBitmap )
		if( outputType = 0 )
			return temparr
		else if( outputType = 1 )
			return temparr[ 3 ]
		else if( outputType = 2 )
			return temparr[ 4 ]
	}
	
	_GetGray( OUTPUTCOLOR ){ ;From HB color picker v2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , r ,  OUTPUTCOLOR , 2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , g ,  OUTPUTCOLOR , 2
		StringTrimLeft,OUTPUTCOLOR,OUTPUTCOLOR,2
		StringLeft , b ,  OUTPUTCOLOR , 2
		r := "0x" r , g := "0x" g , b := "0x" b
		REDSLIDERVALUE:=r+0,GreenSLIDERVALUE:=g+0,BlueSLIDERVALUE:=b+0
		GreyScaleSLIDERVALUE:=Round((REDSLIDERVALUE+GreenSLIDERVALUE+BlueSLIDERVALUE)/3)
		return GreyScaleSLIDERVALUE
	}
	_LiquidatePixels(){
		local x , y , brushes := [] , Ptr := A_PtrSize ? "UPtr" : "UInt" 
		Loop, % This.Colors.Length()	
			Brushes[ A_Index ] := Gdip_BrushCreateSolid( This.Colors[ A_Index ] )
		y := 0
		Loop, % This.StringHeight 	{
			x := 0
			Loop, % This.StringWidth	{
				SetFormat, IntegerFast, hex
				DllCall( gdiplus.GdipBitmapGetPixel , Ptr , This.TextBitmap, "int", x, "int", y, "uint*", col )
				if( col != 0x0 ){
					Loop, % This.Bitmaps.Length()	{
						if( This.FullWrap ){
							DllCall( gdiplus.GdipFillEllipse , Ptr, This.Bitmaps[ A_Index ].Graphics, Ptr, Brushes[ A_Index ] , "float", x + This.MaxSize + 2 - This.Sizes[ A_Index ] , "float", y + This.MaxSize + 2 - This.Sizes[ A_Index ] , "float", 2 * This.Sizes[ A_Index ] , "float", 2 * This.Sizes[ A_Index ] )
						}else{
							DllCall( gdiplus.GdipFillPie , Ptr, This.Bitmaps[ A_Index ].Graphics , Ptr, Brushes[ A_Index ] , "float", x + This.MaxSize + 2 - This.Sizes[ A_Index ] , "float", y + This.MaxSize + 2 - This.Sizes[ A_Index ] , "float", 2 * This.Sizes[ A_Index ] , "float", 2 * This.Sizes[ A_Index ] , "float", 0 , "float", 90)
						}
					}
				}
				SetFormat, IntegerFast, dec
				x++
			}
			y++
		}
		Loop, % This.Bitmaps.Length()	{
			Gdip_DeleteGraphics( This.Bitmaps[ A_Index ].Graphics )
			Gdip_DrawImage( This.Output.Graphics , This.Bitmaps[ A_Index ].Bitmap , 0 , 0 , This.Output.Width , This.Output.Height )
		}
		if( This.BackgroundColor = "" )
			Gdip_DrawImage( This.Output.Graphics , This.TextBitmap , This.MaxSize + 2 , This.MaxSize + 2 , This.StringWidth , This.StringHeight )
		else{
			Gdip_DeleteGraphics( This.TextBitmapGraphics )
			Gdip_DisposeImage( This.TextBitmap )
			This.TextBitmap := Gdip_CreateBitmap( This.StringWidth , This.StringHeight )
			This.TextBitmapGraphics := Gdip_GraphicsFromImage( This.TextBitmap )
			Gdip_SetSmoothingMode( This.TextBitmapGraphics , This.Smoothing )
			Brush := Gdip_BrushCreateSolid( This.FontColor )
			Gdip_TextToGraphics( This.TextBitmapGraphics , This.Text , " s" This.Fontsize " c" Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , This.StringWidth, This.StringHeight  )
			Gdip_DeleteBrush( Brush )
			Gdip_DrawImage( This.Output.Graphics , This.TextBitmap , This.MaxSize + 2 , This.MaxSize + 2 , This.StringWidth , This.StringHeight )
		}
		Gdip_DeleteGraphics( This.Output.Graphics )
		Loop, % This.Colors.Length()	{
			Gdip_DeleteBrush( Brushes[ A_Index ] )
		}
		Gdip_DeleteGraphics( This.TextBitmapGraphics )
	}
	_hex2rgb( CR , ByRef R , ByRef G , ByRef B , ByRef A := "NA" ){ ;https://www.autohotkey.com/boards/viewtopic.php?t=3925&p=312862
		H := InStr(CR, "0x") ? CR : ( InStr(CR, "#" ) ? "0x" SubStr( CR , 2 ) : "0x" CR )
		R := (H & 0xFF0000) >> 16 , G := (H & 0xFF00) >> 8 , B := (H & 0xFF) , ( A != "NA" ) ? ( A := (H & 0xFF000000) >> 24 ) 
	}
}

Thanks again.

robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: GDI+ Text Border / Bubble

Post by robodesign » 21 Jan 2023, 06:32

I think you did not read my message entirely.

I said... the files , the project is on Github. I gave you the link. Lib folder is there, and Gdi.ahk.

As a code example, I extract this from Gdi_DrawTextInBox() found in Quick-Picto-Viewer.ahk:

Code: Select all

; Create font
    FontName := "Arial", fontSize := 15
    hFont := Gdi_CreateFontByName(FontName, FontSize)
    offsetX := offsetY := 1
    w := h:= 900
    theString := "Hello World"
    txtColor := "ff0055"
    bgrColor := "002255"

; draw text in DIB and the convert DIB to pBitmap [GDI+]
; this draw a text
 
       hbm := Gdi_CreateDIBSection(w, h)
       hDC := Gdi_CreateCompatibleDC()
       obm := Gdi_SelectObject(hDc, hbm)
       Gdi_SetMapMode(hDC, 1)
       Gdi_DrawTextHelper(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr(txtColor), "0x" rgb2bgr(bgrColor))
       pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
       Gdi_SelectObject(hDC, obm)
       Gdi_DeleteObject(hbm)
       Gdi_DeleteDC(hdc)

 ; draw text in DIB and the convert DIB to pBitmap [GDI+]
 ; this draws a text outline/border
 
       BorderSize := 9
       hbm := Gdi_CreateDIBSection(w, h)
       hDC := Gdi_CreateCompatibleDC()
       obm := Gdi_SelectObject(hDC, hbm)
       Gdi_SetMapMode(hDC, 1)
       Gdi_DrawTextOutline(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr(txtColor), BorderSize)
       pBitmap2 := trGdip_CreateBitmapFromHBITMAP(hbm)
       Gdi_SelectObject(hDC, obm)
       Gdi_DeleteObject(hbm)
       Gdi_DeleteDC(hDC)

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 21 Jan 2023, 09:59

@robodesign sorry about that. I have only been on github a few times and don't know how it works. I only saw one thing with .ahk in it and assumed that GDI must be somewhere else ( I have now found the GDI lib ).


Thanks. I'll give your code a look through and get back to you if I have any more questions.

robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: GDI+ Text Border / Bubble

Post by robodesign » 21 Jan 2023, 10:30

Hellbent wrote:
21 Jan 2023, 09:59
@robodesign sorry about that. I have only been on github a few times and don't know how it works. I only saw one thing with .ahk in it and assumed that GDI must be somewhere else ( I have now found the GDI lib ).


Thanks. I'll give your code a look through and get back to you if I have any more questions.
No worries . I hope you will find it useful. Please , feel free to ask me questions.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

iseahound
Posts: 1445
Joined: 13 Aug 2016, 21:04
Contact:

Re: GDI+ Text Border / Bubble

Post by iseahound » 21 Jan 2023, 12:53

I'm not entirely sure what you are doing in your code (since there aren't any comments!) but I surmise that you are drawing an ellipse at every point where the color matches the font color. Then I assume you layer over the actual string of text again.

If you want to optimize this routine you'd have to do so algorithmically - in the sense that you need a better way of doing this action. You'll probably want to use an approximation or something that can make your code an order of magnitude faster. For example you can add a check to see if the pixel has pixels of the same colors around it - if so, skip it.

Also, the variable names are a little confusing, I'm not sure what cGreen stands for, channel green? and is sGreen source Green? In any case, col is never short for color, but rather column.

Great examples and work! The demos are really impressive.

iseahound
Posts: 1445
Joined: 13 Aug 2016, 21:04
Contact:

Re: GDI+ Text Border / Bubble

Post by iseahound » 21 Jan 2023, 13:02

So as a short summary on how to optimize:

1. Look for algorithmic changes. (Such as: how many pixels of the same color should be nearby for the action to be discarded)
2. Maybe look into the PathRegion or Clipping Region functions. You can "add" ellipses using paths, and perform one fill operation at the end. (Keep in mind your current approach doesn't use antialiasing (?) )
3. You may want to avoid GetPixel by "owning" your bitmap. Use GlobalAlloc or VarSetCapacity to allocate memory. Then use CreateBitmapFromScan0 to allocate a pBitmap on top of it. This gives you the ability to use NumGet or a C machine code function.

If you are not sure how to do step 3, you can look here:
https://github.com/iseahound/ImagePut/blob/be443ff9d2c2dfae410d55bb07e8747a9d4f888e/ImagePut%20(for%20v1).ahk#L1920
or call ImagePutBuffer(pBitmap) by including the ImagePut library. If you scroll a little more, I've included some simple machine code functions that you can use as examples.

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 26 Jan 2023, 22:15

@robodesign
Thank for the sample code, I played around with it a bit and it is super fast. It does however seem to have some quality issues.

I'm going to play around with it more in the future to see what I can do it.

Test Code

Code: Select all

#Include <GDI Lib>
;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;<<<<<<<<<<<<<<<<<<---------------------------     gdip.ahk
;~ #Include <PopUpWindow_V2> ; At the bottom of the script 
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()

; Create font
    FontName := "Impact", fontSize := 115
    hFont := Gdi_CreateFontByName(FontName, FontSize)
    offsetX := offsetY := 100
    w := 1200 , h:= 800
    theString := "##~AutoHotkey~##"
    txtColor := "ff0055"
    ;~ bgrColor := "002255"
    bgrColor := "003399"

; draw text in DIB and the convert DIB to pBitmap [GDI+]
; this draw a text
 
       hbm := Gdi_CreateDIBSection(w, h)
       hDC := Gdi_CreateCompatibleDC()
       obm := Gdi_SelectObject(hDc, hbm)
       Gdi_SetMapMode(hDC, 1)
       Gdi_DrawTextHelper(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr(txtColor), "0x" rgb2bgr(bgrColor))
       pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
       Gdi_SelectObject(hDC, obm)
       Gdi_DeleteObject(hbm)
       Gdi_DeleteDC(hdc)

 ; draw text in DIB and the convert DIB to pBitmap [GDI+]
 ; this draws a text outline/border
 
       BorderSize := 9
       hbm := Gdi_CreateDIBSection(w, h)
       hDC := Gdi_CreateCompatibleDC()
       obm := Gdi_SelectObject(hDC, hbm)
       Gdi_SetMapMode(hDC, 1)
       Gdi_DrawTextOutline(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr(txtColor), BorderSize)
       Gdi_DrawTextOutline(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr("ffff00"), 3)
       Gdi_DrawTextOutline(hDC, hFont, theString, offsetX, offsetY, "0x" rgb2bgr("ff0000"), 1)
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY += 110 , "0x" rgb2bgr("FFFFFF"), 3)
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY += 110 , "0x" rgb2bgr("FF0000"), 12)
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY += 110 , "0x" rgb2bgr("ffff00"), 3)
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY += 110 , "0x" rgb2bgr("00FFFF"), 9 )
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY  , "0x" rgb2bgr("009999"), 6 )
       Gdi_DrawTextOutline(hDC, hFont, theString, 130 , offsetY  , "0x" rgb2bgr("004444"), 3 )
	   
       pBitmap2 := trGdip_CreateBitmapFromHBITMAP(hbm)
       Gdi_SelectObject(hDC, obm)
       Gdi_DeleteObject(hbm)
       Gdi_DeleteDC(hDC)




Gui1 := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 0 , W: w , H: h , Options: " -DPIScale " } )
Gui1.DrawBitmap( pBitmap2 , { X: 0 , Y: 0 , W: Gui1.W , H: Gui1.H } , dispose := 1 , AutoUpdate := 1 )



return
GuiClose:
GuiContextMenu:
*ESC::ExitApp

RALT::PopUpWindow.Helper()

trGdip_CreateBitmapFromHBITMAP(hBitmap, hPalette:=0) {
    r := Gdip_CreateBitmapFromHBITMAP(hBitmap, hPalette)
    If (gdipLastError=1 && A_LastError=8)
       gdipLastError := 3

    ;~ If (gdipLastError && r)
       ;~ addJournalEntry(A_ThisFunc "() has created possibly a faulty object: " Gdip_ErrorHandler(gdipLastError, 0))

    If r
       createdGDIobjsArray["x" r] := [r, "bmp", 1, A_ThisFunc]
    ;~ Else
       ;~ addJournalEntry(A_ThisFunc "() failed, hBitmap = " hBitmap ": " Gdip_ErrorHandler(gdipLastError, 0))

    Return r
}


;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
class PopUpWindow	{
;PopUpWindow v2.2
;Date Written: Oct 28th, 2021
;Last Edit: Feb 7th, 2022 :Changed the trigger method.
;Written By: Hellbent aka CivReborn
;SpcThanks: teadrinker , malcev 
	static Index := 0 , Windows := [] , Handles := [] , EditHwnd , HelperHwnd
	__New( obj := "" ){
		This._SetDefaults()
		This.UpdateSettings( obj )
		This._CreateWindow()
		This._CreateWindowGraphics()
		if( This.AutoShow )
			This.ShowWindow( This.Title )
	}
	_SetDefaults(){
		This.X := 10
		This.Y := 10
		This.W := 10
		This.H := 10
		This.Smoothing := 2
		This.Options := " -DPIScale +AlwaysOnTop "
		This.AutoShow := 0
		This.GdipStartUp := 0
		This.Title := ""
		
		This.Controls := []
		This.Handles := []
		This.Index := 0 
	}
	AddTrigger( obj ){
		local k , v , cc , bd
		
		This.Controls[ ++This.Index ] := { 	X:		10
										,	Y:		10
										,	W:		10
										,	H:		10	}
		for k, v in obj
			This.Controls[ This.Index ][ k ] := obj[ k ] 
		cc := This.Controls[ This.Index ]
		Gui, % This.Hwnd ":Add", Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd"
		This.Handles[ hwnd ] := This.Index
		This.Controls[ This.Index ].Hwnd := hwnd
		
		if( IsObject( cc.Label ) ){
			bd := cc.Label
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}else{
			bd := This._TriggerCall.Bind( This )
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}
		return hwnd
		
	}
	_TriggerCall(){
		MouseGetPos,,,, ctrl, 2
		Try
			;~ SetTimer, % This.Controls[ This.Handles[ ctrl ] ].Label, -0
			gosub, % This.Controls[ This.Handles[ ctrl ] ].Label
		
				
	}
	DrawTriggers( color := "0xFFFF0000" , AutoUpdate := 0 ){
		local brush , cc 
		Brush := Gdip_BrushCreateSolid( color ) 
		Gdip_SetSmoothingMode( This.G , 3 )
		loop, % This.Controls.Length()	{
			cc := This.Controls[ A_Index ]
			Gdip_FillRectangle( This.G , Brush , cc.x , cc.y , cc.w , cc.h )
		
		}
		Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( This.G , This.Smoothing )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	UpdateSettings( obj := "" , UpdateGraphics := 0 ){
		local k , v
		if( IsObject( obj ) )
			for k, v in obj
				This[ k ] := obj[ k ]
		( This.X = "Center" ) ? ( This.X := ( A_ScreenWidth - This.W ) / 2 ) 	
		( This.Y = "Center" ) ? ( This.Y := ( A_ScreenHeight - This.H ) / 2 ) 	
		if( UpdateGraphics ){
			This._DestroyWindowsGraphics()
			This._CreateWindowGraphics()
		}
	}
	_CreateWindow(){
		local hwnd
		Gui , New, % " +LastFound +E0x80000 hwndhwnd -Caption  " This.Options
		PopUpWindow.Index++
		This.Index := PopUpWindow.Index
		PopUpWindow.Windows[ PopUpWindow.Index ] := This
		This.Hwnd := hwnd
		PopUpWindow.Handles[ hwnd ] := PopUpWindow.Index
		if( This.GdipStartUp && !PopUpWindow.pToken )
			PopUpWindow.pToken := GDIP_STARTUP()
	}
	_DestroyWindowsGraphics(){
		Gdip_DeleteGraphics( This.G )
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
	}
	_CreateWindowGraphics(){
		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.Smoothing )
	}
	ShowWindow( Title := "" ){
		Gui , % This.Hwnd ":Show", % "x" This.X " y" This.Y " w" This.W " h" This.H " NA", % Title
	}
	HideWindow(){
		Gui , % This.Hwnd ":Hide",
	}
	UpdateWindow( alpha := 255 ){
		UpdateLayeredWindow( This.hwnd , This.hdc , This.X , This.Y , This.W , This.H , alpha )
	}
	ClearWindow( AutoUpdate := 0 ){
		Gdip_GraphicsClear( This.G )
		if( Autoupdate )
			This.UpdateWindow()
	}
	DrawBitmap( pBitmap , obj , dispose := 1 , AutoUpdate := 0 ){
		Gdip_DrawImage( This.G , pBitmap , obj.X , obj.Y , obj.W , obj.H )
		if( dispose )
			Gdip_DisposeImage( pBitmap )
		if( Autoupdate )
			This.UpdateWindow()
	}
	PaintBackground( color := "0xFF000000" , AutoUpdate := 0 ){
		if( isObject( color ) ){
			Brush := Gdip_BrushCreateSolid( ( color.HasKey( "Color" ) ) ? ( color.Color ) : ( "0xFF000000" ) ) 
			if( color.Haskey( "Round" ) )
				Gdip_FillRoundedRectangle( This.G , Brush , color.X , color.Y , color.W , color.H , color.Round )
			else
				Gdip_FillRectangle( This.G , Brush , color.X , color.Y , color.W , color.H ) 
		}else{
			Brush := Gdip_BrushCreateSolid( color ) 
			Gdip_FillRectangle( This.G , Brush , -1 , -1 , This.W + 2 , This.H + 2 ) 
		}
		Gdip_DeleteBrush( Brush )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DeleteWindow( GDIPShutdown := 0 ){
		Gui, % This.Hwnd ":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 ] := ""
		if( GDIPShutdown ){
			Gdip_Shutdown( PopUpWindow.pToken )
			PopUpWindow.pToken := ""
		}
	}
	_OnClose( wParam ){
		if( wParam = 0xF060 ){	;SC_CLOSE ;[ clicking on the gui close button ]
			Try{
				Gui, % PopUpWindow.HelperHwnd ":Destroy"
				SoundBeep, 555
			}
		}
	}
	CreateCachedBitmap( pBitmap , Dispose := 0 ){
		local pCachedBitmap
		if( This.CachedBitmap )
			This.DisposeCachedbitmap()
		DllCall( "gdiplus\GdipCreateCachedBitmap" , "Ptr" , pBitmap , "Ptr" , this.G , "PtrP" , pCachedBitmap )
		This.CachedBitmap := pCachedBitmap
		if( Dispose )
			Gdip_DisposeImage( pBitmap )
	}
	DrawCachedBitmap( AutoUpdate := 0 ){
		DllCall( "gdiplus\GdipDrawCachedBitmap" , "Ptr" , this.G , "Ptr" , This.CachedBitmap , "Int" , 0 , "Int" , 0 )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DisposeCachedbitmap(){
		DllCall( "gdiplus\GdipDeleteCachedBitmap" , "Ptr" , This.CachedBitmap )
	}
	Helper(){
		local hwnd , MethodList := ["__New","UpdateSettings","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow" , "AddTrigger" , "DrawTriggers", "CreateCachedBitmap" , "DrawCachedBitmap" , "DisposeCachedbitmap" ]
		Gui, New, +AlwaysOnTop +ToolWindow +HwndHwnd
		PopUpWindow.HelperHwnd := hwnd
		Gui, Add, Edit, xm ym w250 r1 Center hwndhwnd, Gui1
		PopUpWindow.EditHwnd := hwnd
		loop, % MethodList.Length()	
			Gui, Add, Button, xm y+1 w250 r1 gPopUpWindow._HelperClip, % MethodList[ A_Index ]
		Gui, Show,,
		OnMessage( 0x112 , This._OnClose.Bind( hwnd ) )
	}
	_HelperClip(){
		local ClipList 
		
		GuiControlGet, out, % PopUpWindow.HelperHwnd ":", % PopUpWindow.EditHwnd	
		
		ClipList := 		{ 	__New: 					" := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 0 , W: A_ScreenWidth , H: A_ScreenHeight , Options: "" -DPIScale +AlwaysOnTop "" } )"
							,	UpdateSettings:			".UpdateSettings( { X: """" , Y: """" , W: """" , H: """" } , UpdateGraphics := 0 )"
							,	ShowWindow:				".ShowWindow( Title := """" )"
							,	HideWindow:				".HideWindow()"
							,	UpdateWindow:			".UpdateWindow()"
							,	ClearWindow:			".ClearWindow( AutoUpdate := 0 )"
							,	DrawBitmap:				".DrawBitmap( pBitmap := """" , { X: 0 , Y: 0 , W: " Out ".W , H: " Out ".H } , dispose := 1 , AutoUpdate := 0 )"
							,	PaintBackground:		".PaintBackground( color := ""0xFF000000"" , AutoUpdate := 0 )  "  ";{ Color: ""0xFF000000"" , X: 2 , Y: 2 , W: " Out ".W - 4 , H: " Out ".H - 4 , Round: 10 }"
							,	DeleteWindow:			".DeleteWindow( GDIPShutdown := 0 )"
							,	AddTrigger:				".AddTrigger( { X: """" , Y: """" , W: """" , H: """" , Value: """" , Label: """" } )"	
							,	DrawTriggers:			".DrawTriggers( color := ""0xFFFF0000"" , AutoUpdate := 0 )"	
							,	CreateCachedBitmap:		".CreateCachedBitmap( pBitmap , Dispose := 0 )"	
							,	DrawCachedBitmap: 		".DrawCachedBitmap( AutoUpdate := 0 )"	
							,	DisposeCachedbitmap:	".DisposeCachedbitmap()"	}
							
		clipboard := Out ClipList[ A_GuiControl ]
		
	}
}



Test Results
20230126045112.png
20230126045112.png (73.33 KiB) Viewed 1228 times

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 26 Jan 2023, 22:47

iseahound wrote:
21 Jan 2023, 13:02
So as a short summary on how to optimize:

1. Look for algorithmic changes. (Such as: how many pixels of the same color should be nearby for the action to be discarded)
2. Maybe look into the PathRegion or Clipping Region functions. You can "add" ellipses using paths, and perform one fill operation at the end. (Keep in mind your current approach doesn't use antialiasing (?) )
3. You may want to avoid GetPixel by "owning" your bitmap. Use GlobalAlloc or VarSetCapacity to allocate memory. Then use CreateBitmapFromScan0 to allocate a pBitmap on top of it. This gives you the ability to use NumGet or a C machine code function.

If you are not sure how to do step 3, you can look here:
https://github.com/iseahound/ImagePut/blob/be443ff9d2c2dfae410d55bb07e8747a9d4f888e/ImagePut%20(for%20v1).ahk#L1920
or call ImagePutBuffer(pBitmap) by including the ImagePut library. If you scroll a little more, I've included some simple machine code functions that you can use as examples.
Thank you for your insights. I played around with a number of algorithms and now have a number of ideas on how to not just speed things up but to also manipulate things in different ways.
(Keep in mind your current approach doesn't use antialiasing (?) )
Not really. It has it but because the same spots get layered many times much of the antialiasing is lost.
The only thing I am doing to keep some antialiasing is by using very low level alpha values for some of the colors on the edge.
It doesn't give full control but it does clean up the edges a bit.

Out of curiosity, what is "Scan0"?

At any rate, I now have what I currently need and a few ideas of where to start from when I come back to this subject, so thank you and to everyone else that helped.

This is where my code currently sits a few examples.

20230126033847.png
20230126033847.png (667.63 KiB) Viewed 1221 times

Code: Select all

;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;<<<<<<<<<<<<<<<<<<---------------------------     gdip.ahk
;~ #Include <PopUpWindow_V2> ; At the bottom of the script 
;~ #Include <HB Vectors v2>  ; At the bottom of the script
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()
global gdiplus := LoadLibrary("gdiplus")

Start := A_TickCount
global Gui1 := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 130 , W: A_ScreenWidth , H: A_ScreenHeight , Options: " -DPIScale +AlwaysOnTop +OwnDialogs" } )

;Create the points map for some text
MyText := new BubbleText( { Text: "##~AutoHotkey~##" , FontSize: 66 , FontOptions: "  Center vCenter " , FontType: "Segoe UI" , FontColor: "0xFF22262a" } )

;extra params ( all that is normally required is a set of color / radius pairs. [ see ColorsArray and SizesArray ] )
UseGradient := 1
GradientColor := "0x33ffffff" 
LinearGradientMode := 2
SwapColors := 0
UseStartText := 1

AngleLineProjection := 1
AngleLineX := -10
AngleLineY := -10
AngleLineThickness := 2
AngleLineColor := "0x62000000"
AngleSize := 3

;Render the points map. ( get a bitmap of the rendered text ) 
MyTextRenders := []
MyTextRenders.Push( MyText.RenderString( ColorsArray  := [ "0xFFEC999F" , "0xFF2C1020" , "0xFF4E1C4F" , "0xFF722A80" , "0xFF8E2B64" , "0xFF922D4B" , "0xFFA8334B" , "0xFFB93D4D" , "0xFFE16267" , "0xFFE16269" , "0xFFF6777C" , "0xFFEC999F" ] , SizesArray := [ 43 , 40 , 34 , 30 , 26 , 20 , 15 , 10 , 7 , 4 , 2 ] , UseGradient , GradientColor , LinearGradientMode , SwapColors , UseStartText ) ) ;, AngleLineProjection , AngleLineX , AngleLineY , AngleLineThickness , AngleLineColor , AngleSize ) )
MyTextRenders.Push( MyText.RenderString(  "0xFFFFFFFF" , , UseGradient , GradientColor , LinearGradientMode , SwapColors , UseStartText , AngleLineProjection , AngleLineX , AngleLineY , AngleLineThickness , AngleLineColor , AngleSize ) )
MyTextRenders.Push( MyText.RenderString( ColorsArray   := [ "0xFF8FCAE6" , "0xFF229EBD" , "0xFF023048" , "0xFFFEB704" , "0xFFFEB706" ] , SizesArray := [ 18 , 14 , 10 , 5 , 2 ] , UseGradient , GradientColor , LinearGradientMode , SwapColors , UseStartText ) )
MyTextRenders.Push( MyText.RenderString( [ "0x2200FFFF" , "0x66002222" , "0x66005555" , "0x66008888" , "0x6600aaaa" ] , [ 24 , 20 , 16 , 8 , 4 ] , UseGradient , GradientColor , LinearGradientMode , SwapColors , UseStartText ) )

AngleLineX := +5
AngleLineY := +5
MyTextRenders.Push( MyText.RenderString( "0xFF88aa00"  ,  , UseGradient , GradientColor , LinearGradientMode , SwapColors , UseStartText , AngleLineProjection , AngleLineX , AngleLineY , AngleLineThickness , AngleLineColor , AngleSize ) )

;Draw the rendered text bitmaps.
;There is one bitmap in the class that would need to be deleted for full clean up. ( This.SetupBitmaps[ 3 ].Bitmap )
;That is on top of the bitmap being drawn here in this next step.
Loop, % MyTextRenders.Length()	{
	if( A_Index = 1 )
		Gui1.DrawBitmap( MyTextRenders[ A_Index ].pBitmap , { X: x := 0 , Y: y := 0 , W: MyTextRenders[ A_Index ].W , H: MyTextRenders[ A_Index ].H } , dispose := 0 , AutoUpdate := 0 )
	else if( A_Index = 5 )
		Gui1.DrawBitmap( MyTextRenders[ A_Index ].pBitmap , { X: x += MyTextRenders[ 1 ].W , Y: y := 0 , W: MyTextRenders[ A_Index ].W , H: MyTextRenders[ A_Index ].H } , dispose := 0 , AutoUpdate := 0 )
	else	
		Gui1.DrawBitmap( MyTextRenders[ A_Index ].pBitmap , { X: x , Y: y += MyTextRenders[ A_Index - 1 ].H * .9 , W: MyTextRenders[ A_Index ].W , H: MyTextRenders[ A_Index ].H } , dispose := 0 , AutoUpdate := 0 )
}


;Show the updated graphics.
Gui1.UpdateWindow()

;~ ToolTip, % ( A_TickCount - Start ) / 1000
;~ sleep, 1000
;~ ToolTip, 

return

;~ GuiClose:
GuiContextMenu:
*ESC::ExitApp

;************
;BubbleText Class
;**************************************************************************************************************************************************************************
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;**************************************************************************************************************************************************************************
class BubbleText	{
	__New( obj := "" ){
		
		This._SetDefaults()
		This._UpdateDefaults( obj )
		This._GetTextSizeBitmap()
		This._GetAbsoluteTextSizeBitmap()
		This._MapVectors()
		
	}
	
	_SetDefaults(){
		
		This.Ptr := A_PtrSize ? "UPtr" : "UInt"
		
		This.Text := "AutoHotkey"
		This.FontType := "Comic Sans MS"
		This.FontSize := 36
		This.FontOptions := " Center vCenter Bold "
		This.FontColor := "0xFF000000"
		
		This.SetupBitmaps := []
		This.GetTextSizeArray := []
		
		This.W := 10
		This.H := 10
		This.Left := 0
		This.Right := 0
		This.Top := 0
		This.Bottom := 0
		This.Margin := 1
		
		This.Points := []
		This.BorderVectors := []
		
		This.ColorsArray := []
		This.SizesArray := []
		This.MaxSize := 0
		
		This.Layers := []
		
	}
	
	_UpdateDefaults( obj := "" ){
		local k , v 
		for k , v in obj 
			This[ k ] := v
	}
	
	_GetTextSizeBitmap(){ 
		
		This.SetupBitmaps[ 1 ] 				:= { Bitmap: "" , Graphics: "" , Smoothing: 2 , Brush: "" }
		This.SetupBitmaps[ 1 ].Bitmap 		:= Gdip_CreateBitmap( This.W , This.H ) 
		This.SetupBitmaps[ 1 ].Graphics 	:= Gdip_GraphicsFromImage( This.SetupBitmaps[ 1 ].Bitmap ) 
		This.SetupBitmaps[ 1 ].Brush 		:= Gdip_BrushCreateSolid( This.FontColor )
		
		Gdip_SetSmoothingMode( This.SetupBitmaps[ 1 ].Graphics , This.SetupBitmaps[ 1 ].Smoothing )
		This.GetTextSizeArray := StrSplit( Gdip_TextToGraphics( This.SetupBitmaps[ 1 ].Graphics , This.Text , " s" This.Fontsize " c" This.SetupBitmaps[ 1 ].Brush  " " This.FontOptions " x" 0 " y" 0 , This.FontType , 10000, 10000  ),"|","|"  )
		
		This.W := This.GetTextSizeArray[ 3 ]
		This.H := This.GetTextSizeArray[ 4 ]
		
		
		This.SetupBitmaps[ 2 ] 				:= { Bitmap: "" , Graphics: "" , Smoothing: 2 , Brush: "" }
		This.SetupBitmaps[ 2 ].Bitmap 		:= Gdip_CreateBitmap( This.W , This.H ) 
		This.SetupBitmaps[ 2 ].Graphics 	:= Gdip_GraphicsFromImage( This.SetupBitmaps[ 2 ].Bitmap ) 
		This.SetupBitmaps[ 2 ].Brush 		:= Gdip_BrushCreateSolid( This.FontColor )
		
		Gdip_SetSmoothingMode( This.SetupBitmaps[ 2 ].Graphics , This.SetupBitmaps[ 2 ].Smoothing )
		Gdip_TextToGraphics( This.SetupBitmaps[ 2 ].Graphics , This.Text , " s" This.Fontsize " c" This.SetupBitmaps[ 2 ].Brush " " This.FontOptions " x" 0 " y" 0 , This.FontType , This.W , This.H  )
	
	}
	_GetAbsoluteTextSizeBitmap(){
		
		local color , x , y 
		
		;Get Top
		;-----------------------------
		y := 1
		Loop, % This.H	{
			x := 1
			Loop, % This.W	{
				DllCall( gdiplus.GdipBitmapGetPixel , This.Ptr , This.SetupBitmaps[ 2 ].Bitmap , "int", x, "int", y, "uint*", color )
				if( color ){
					This.Top := y - This.Margin
					break 2
				}
				x++
			}
			y++
		}
		
		;Get Bottom
		;-----------------------------
		y := Floor( This.H )
		Loop, % This.H	{
			x := 1
			Loop, % This.W	{
				DllCall( gdiplus.GdipBitmapGetPixel , This.Ptr , This.SetupBitmaps[ 2 ].Bitmap , "int", x, "int", y, "uint*", color )
				if( color ){
					This.Bottom := y + This.Margin
					break 2
				}
				x++
			}
			y--
		}
		
		;Get Left
		;-----------------------------
		x := 1
		Loop, % This.W	{
			y := 1
			Loop, % This.H	{
				DllCall( gdiplus.GdipBitmapGetPixel , This.Ptr , This.SetupBitmaps[ 2 ].Bitmap , "int", x, "int", y, "uint*", color )
				if( color ){
					This.Left := x - This.Margin - 2
					break 2
				}
				y++
			}
			x++
		}
		
		;Get Right
		;-----------------------------
		x := Floor( This.W )
		Loop, % This.W	{
			y := 1
			Loop, % This.H	{
				DllCall( gdiplus.GdipBitmapGetPixel , This.Ptr , This.SetupBitmaps[ 2 ].Bitmap , "int", x, "int", y, "uint*", color )
				if( color ){
					This.Right := x + This.Margin
					break 2
				}
				y++
			}
			x--
		}
		
		This.SetupBitmaps[ 3 ] 				:= { Bitmap: "" , Graphics: "" , Smoothing: 2 , Brush: "" }
		This.SetupBitmaps[ 3 ].Bitmap 		:= Gdip_CreateBitmap( This.Right - This.Left , This.Bottom - This.Top ) 
		This.SetupBitmaps[ 3 ].Graphics 	:= Gdip_GraphicsFromImage( This.SetupBitmaps[ 3 ].Bitmap ) 
		This.SetupBitmaps[ 3 ].Brush 		:= Gdip_BrushCreateSolid( This.FontColor )
		
		Gdip_SetSmoothingMode( This.SetupBitmaps[ 3 ].Graphics , This.SetupBitmaps[ 3 ].Smoothing )
		Gdip_TextToGraphics( This.SetupBitmaps[ 3 ].Graphics , This.Text , " s" This.Fontsize " c" This.SetupBitmaps[ 3 ].Brush " " This.FontOptions " x" -This.Left " y" -This.Top , This.FontType , This.W , This.H  )
		This.W := This.Right - This.Left
		This.H := This.Bottom - This.Top
		
	}
	
	_MapVectors(){
		
		local x , y 
		
		y := 1
		
		Loop, % This.H	{
			
			This.Points[ Index := A_Index ] := []
			x := 1
			
			Loop, % This.W	{
				
				DllCall( gdiplus.GdipBitmapGetPixel , This.Ptr , This.SetupBitmaps[ 3 ].Bitmap , "int", x, "int", y, "uint*", color )
				( color ) ? ( This.Points[ Index ].Push( 1 ) ) : ( This.Points[ Index ].Push( 0 ) )
				x++
			}
			y++
		}
		;**********************************************************
		;**********************************************************
		y := 1
		
		Loop, % This.H	{
			
			Points[ Index := A_Index ] := []
			x := 1
			
			Loop, % This.W  {
				
				if( 		This.Points[ Index , A_Index ] 
					&& (	This.Points[ Index - 1 , A_Index ] = 0 
					|| 		This.Points[ Index + 1 , A_Index ] = 0 
					|| 		This.Points[ Index , A_Index - 1 ] = 0 
					|| 		This.Points[ Index , A_Index + 1 ] = 0 ) 
					&& 		x <= This.Right 
					&& 		y <= This.Bottom - 1 ){
					
					This.BorderVectors.Push( New Vector( x , y ) )
	
				}
				x++
			}
			y++
		}
		
		Loop, 2	{
			
			Gdip_DisposeImage( This.SetupBitmaps[ A_Index ].Bitmap ) 
			Gdip_DeleteGraphics( This.SetupBitmaps[ A_Index ].Graphics )
			Gdip_DeleteBrush( This.SetupBitmaps[ A_Index ].Brush )
			
		}
		
	}
	
	RenderString( ColorsArray := "" , SizesArray := "" , UseGradient := 0 , GradientColor := "0xFF000000" , LinearGradientMode := 1 , SwapColors := 0 , UseStartText := 1 , AngleLineProjection := 0 , AngleLineX := 10 , AngleLineY := 40 , AngleLineThickness := 6 , AngleLineColor := "0xFF000000" , AngleSize := 3 ){
		
		local removeAmount

		This.ColorsArray := []
		This.SizesArray := []
		
		if( ColorsArray != "" && !IsObject( ColorsArray ) ){
			
			This.ColorsArray[ 1 ] := ColorsArray
			
		}else if( IsObject( ColorsArray ) ){
			
			loop, % ColorsArray.Length()	{
				
				This.ColorsArray[ A_Index ] := ColorsArray[ A_Index ]
				
			}
			
		}else{
			
			This.ColorsArray[ 1 ] := "0xFF000000"
			
		}
		
		if( SizesArray != "" && !IsObject( SizesArray ) ){
			
			This.SizesArray[ 1 ] := SizesArray
			
		}else if( IsObject( SizesArray ) ){
			
			loop, % SizesArray.Length()	{
				
				This.SizesArray[ A_Index ] := SizesArray[ A_Index ]
				
			}
			
		}else{
			
			This.SizesArray[ 1 ] := 6
		}
		
		if( This.SizesArray.Length() != This.ColorsArray.Length() ){
			
			if( This.SizesArray.Length() < This.ColorsArray.Length() ){
				
				Loop, % This.ColorsArray.Length() - This.SizesArray.Length() {
					This.ColorsArray.Pop()
					
				}
				
			}else{
				
				Loop, % This.SizesArray.Length() - This.ColorsArray.Length()	{
					This.SizesArray.Pop()
				
				}
				
			}
		}
		
		loop, % This.SizesArray.Length()	{
			
			This.Layers[ A_Index ] 				:= { Bitmap: "" , Graphics: "" , Smoothing: 2 , Brush: "" }
			This.Layers[ A_Index ].Bitmap 		:= Gdip_CreateBitmap( This.W + ( This.SizesArray[ 1 ] * 2 ) , This.H + ( This.SizesArray[ 1 ] * 2 ) ) 
			This.Layers[ A_Index ].Graphics 	:= Gdip_GraphicsFromImage( This.Layers[ A_Index ].Bitmap ) 
			
			if( UseGradient ){
				
				This.Layers[ A_Index ].Brush 	:= Gdip_CreateLineBrushFromRect( 0 , 0 , This.W + ( This.SizesArray[ 1 ] * 2 ) , This.H + ( This.SizesArray[ 1 ] * 2 ) , ( !SwapColors ) ? ( This.ColorsArray[ A_Index ] ) : ( GradientColor ) , ( SwapColors ) ? ( This.ColorsArray[ A_Index ] ) : ( GradientColor ) , LinearGradientMode , 1 )
			
			}else{
				
				This.Layers[ A_Index ].Brush 	:= Gdip_BrushCreateSolid( This.ColorsArray[ A_Index ] )
				
			}
			Gdip_SetSmoothingMode( This.Layers[ A_Index ].Graphics , This.Layers[ A_Index ].Smoothing )
		}
		
		if( AngleLineProjection ){
			
			Pen := Gdip_CreatePen( AngleLineColor , AngleLineThickness )
			
			if( UseStartText ){
				
				Gdip_DrawImage( This.Layers[ 1 ].Graphics , This.SetupBitmaps[ 3 ].Bitmap , This.SizesArray[ 1 ] , This.SizesArray[ 1 ] , This.W , This.H )
			
			}
			Loop, % This.BorderVectors.Length()	{
				
				Gdip_DrawLine( This.Layers[ 1 ].Graphics , Pen , This.BorderVectors[ A_Index ].X + This.SizesArray[ 1 ] , This.BorderVectors[ A_Index ].Y + This.SizesArray[ 1 ] , This.BorderVectors[ A_Index ].X + AngleLineX + This.SizesArray[ 1 ] , This.BorderVectors[ A_Index ].Y + AngleLineY + This.SizesArray[ 1 ] )
					
			}
			
			Gdip_DeletePen( Pen )
			
			Loop, % This.BorderVectors.Length()	{
				
				DllCall( gdiplus.GdipFillEllipse , This.Ptr , This.Layers[ 1 ].Graphics , This.Ptr , This.Layers[ 1 ].Brush , "float", This.BorderVectors[ A_Index ].X + AngleLineX + This.SizesArray[ 1 ] , "float", This.BorderVectors[ A_Index ].Y + AngleLineY + This.SizesArray[ 1 ] , "float", AngleSize , "float", AngleSize )
					
			}
			
			
		}else{
			
			Loop, % This.BorderVectors.Length()	{
				Index := A_Index
			
				loop, % This.SizesArray.Length()	{
					
					DllCall( gdiplus.GdipFillEllipse , This.Ptr , This.Layers[ A_Index ].Graphics , This.Ptr , This.Layers[ A_Index ].Brush , "float", This.BorderVectors[ Index ].X + ( This.SizesArray[ 1 ] - This.SizesArray[ A_Index ] ) , "float", This.BorderVectors[ Index ].Y + ( This.SizesArray[ 1 ] - This.SizesArray[ A_Index ] ) , "float", This.SizesArray[ A_Index ] * 2 , "float", This.SizesArray[ A_Index ] * 2 )
							
				}
			}
		}
		
		
		Output					:= { pBitmap: "" , Graphics: "" , Smoothing: 2 , W: This.W + ( This.SizesArray[ 1 ] * 2 ) , H: This.H + ( This.SizesArray[ 1 ] * 2 ) }
		Output.pBitmap			:= Gdip_CreateBitmap( Output.W , Output.H ) 
		Output.Graphics			:= Gdip_GraphicsFromImage( Output.pBitmap ) 
		Gdip_SetSmoothingMode( Output.Graphics , Output.Smoothing )
		
		Loop, % This.SizesArray.Length()	{
			
			Gdip_DrawImage( Output.Graphics , This.Layers[ A_Index ].Bitmap , 0 , 0 , Output.W , Output.H )
			Gdip_DisposeImage( This.Layers[ A_Index ].Bitmap ) 
			Gdip_DeleteGraphics( This.Layers[ A_Index ].Graphics )
			Gdip_DeleteBrush( This.Layers[ A_Index ].Brush )
			
		}
		
		if( UseStartText && !AngleLineProjection ){
			
			Gdip_DrawImage( Output.Graphics , This.SetupBitmaps[ 3 ].Bitmap , This.SizesArray[ 1 ] , This.SizesArray[ 1 ] , This.W , This.H )
		}
		
		Gdip_DeleteGraphics( Output.Graphics )
		return output
		
	}
	
}


LoadLibrary(filename){ ;https://www.autohotkey.com/boards/viewtopic.php?p=48392#p48392
    static ref := {}
    if (!(ptr := p := DllCall("LoadLibrary", "str", filename, "ptr")))
        return 0
    ref[ptr,"count"] := (ref[ptr]) ? ref[ptr,"count"]+1 : 1
    p += NumGet(p+0, 0x3c, "int")+24
    o := {_ptr:ptr, __delete:func("FreeLibrary"), _ref:ref[ptr]}
    if (NumGet(p+0, (A_PtrSize=4) ? 92 : 108, "uint")<1 || (ts := NumGet(p+0, (A_PtrSize=4) ? 96 : 112, "uint")+ptr)=ptr || (te := NumGet(p+0, (A_PtrSize=4) ? 100 : 116, "uint")+ts)=ts)
        return o
    n := ptr+NumGet(ts+0, 32, "uint")
    loop % NumGet(ts+0, 24, "uint"){
        if (p := NumGet(n+0, (A_Index-1)*4, "uint")){
            o[f := StrGet(ptr+p, "cp0")] := DllCall("GetProcAddress", "ptr", ptr, "astr", f, "ptr")
            if (Substr(f, 0)==((A_IsUnicode) ? "W" : "A"))
                o[Substr(f, 1, -1)] := o[f]
        }
    }
    return o
}

FreeLibrary(lib){
    if (lib._ref.count>=1)
        lib._ref.count -= 1
    if (lib._ref.count<1)
        DllCall("FreeLibrary", "ptr", lib._ptr)
}
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
class PopUpWindow	{
;PopUpWindow v2.2
;Date Written: Oct 28th, 2021
;Last Edit: Feb 7th, 2022 :Changed the trigger method.
;Written By: Hellbent aka CivReborn
;SpcThanks: teadrinker , malcev 
	static Index := 0 , Windows := [] , Handles := [] , EditHwnd , HelperHwnd
	__New( obj := "" ){
		This._SetDefaults()
		This.UpdateSettings( obj )
		This._CreateWindow()
		This._CreateWindowGraphics()
		if( This.AutoShow )
			This.ShowWindow( This.Title )
	}
	_SetDefaults(){
		This.X := 10
		This.Y := 10
		This.W := 10
		This.H := 10
		This.Smoothing := 2
		This.Options := " -DPIScale +AlwaysOnTop "
		This.AutoShow := 0
		This.GdipStartUp := 0
		This.Title := ""
		
		This.Controls := []
		This.Handles := []
		This.Index := 0 
	}
	AddTrigger( obj ){
		local k , v , cc , bd
		
		This.Controls[ ++This.Index ] := { 	X:		10
										,	Y:		10
										,	W:		10
										,	H:		10	}
		for k, v in obj
			This.Controls[ This.Index ][ k ] := obj[ k ] 
		cc := This.Controls[ This.Index ]
		Gui, % This.Hwnd ":Add", Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd"
		This.Handles[ hwnd ] := This.Index
		This.Controls[ This.Index ].Hwnd := hwnd
		
		if( IsObject( cc.Label ) ){
			bd := cc.Label
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}else{
			bd := This._TriggerCall.Bind( This )
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}
		return hwnd
		
	}
	_TriggerCall(){
		MouseGetPos,,,, ctrl, 2
		Try
			;~ SetTimer, % This.Controls[ This.Handles[ ctrl ] ].Label, -0
			gosub, % This.Controls[ This.Handles[ ctrl ] ].Label
		
				
	}
	DrawTriggers( color := "0xFFFF0000" , AutoUpdate := 0 ){
		local brush , cc 
		Brush := Gdip_BrushCreateSolid( color ) 
		Gdip_SetSmoothingMode( This.G , 3 )
		loop, % This.Controls.Length()	{
			cc := This.Controls[ A_Index ]
			Gdip_FillRectangle( This.G , Brush , cc.x , cc.y , cc.w , cc.h )
		
		}
		Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( This.G , This.Smoothing )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	UpdateSettings( obj := "" , UpdateGraphics := 0 ){
		local k , v
		if( IsObject( obj ) )
			for k, v in obj
				This[ k ] := obj[ k ]
		( This.X = "Center" ) ? ( This.X := ( A_ScreenWidth - This.W ) / 2 ) 	
		( This.Y = "Center" ) ? ( This.Y := ( A_ScreenHeight - This.H ) / 2 ) 	
		if( UpdateGraphics ){
			This._DestroyWindowsGraphics()
			This._CreateWindowGraphics()
		}
	}
	_CreateWindow(){
		local hwnd
		Gui , New, % " +LastFound +E0x80000 hwndhwnd -Caption  " This.Options
		PopUpWindow.Index++
		This.Index := PopUpWindow.Index
		PopUpWindow.Windows[ PopUpWindow.Index ] := This
		This.Hwnd := hwnd
		PopUpWindow.Handles[ hwnd ] := PopUpWindow.Index
		if( This.GdipStartUp && !PopUpWindow.pToken )
			PopUpWindow.pToken := GDIP_STARTUP()
	}
	_DestroyWindowsGraphics(){
		Gdip_DeleteGraphics( This.G )
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
	}
	_CreateWindowGraphics(){
		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.Smoothing )
	}
	ShowWindow( Title := "" ){
		Gui , % This.Hwnd ":Show", % "x" This.X " y" This.Y " w" This.W " h" This.H " NA", % Title
	}
	HideWindow(){
		Gui , % This.Hwnd ":Hide",
	}
	UpdateWindow( alpha := 255 ){
		UpdateLayeredWindow( This.hwnd , This.hdc , This.X , This.Y , This.W , This.H , alpha )
	}
	ClearWindow( AutoUpdate := 0 ){
		Gdip_GraphicsClear( This.G )
		if( Autoupdate )
			This.UpdateWindow()
	}
	DrawBitmap( pBitmap , obj , dispose := 1 , AutoUpdate := 0 ){
		Gdip_DrawImage( This.G , pBitmap , obj.X , obj.Y , obj.W , obj.H )
		if( dispose )
			Gdip_DisposeImage( pBitmap )
		if( Autoupdate )
			This.UpdateWindow()
	}
	PaintBackground( color := "0xFF000000" , AutoUpdate := 0 ){
		if( isObject( color ) ){
			Brush := Gdip_BrushCreateSolid( ( color.HasKey( "Color" ) ) ? ( color.Color ) : ( "0xFF000000" ) ) 
			if( color.Haskey( "Round" ) )
				Gdip_FillRoundedRectangle( This.G , Brush , color.X , color.Y , color.W , color.H , color.Round )
			else
				Gdip_FillRectangle( This.G , Brush , color.X , color.Y , color.W , color.H ) 
		}else{
			Brush := Gdip_BrushCreateSolid( color ) 
			Gdip_FillRectangle( This.G , Brush , -1 , -1 , This.W + 2 , This.H + 2 ) 
		}
		Gdip_DeleteBrush( Brush )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DeleteWindow( GDIPShutdown := 0 ){
		Gui, % This.Hwnd ":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 ] := ""
		if( GDIPShutdown ){
			Gdip_Shutdown( PopUpWindow.pToken )
			PopUpWindow.pToken := ""
		}
	}
	_OnClose( wParam ){
		if( wParam = 0xF060 ){	;SC_CLOSE ;[ clicking on the gui close button ]
			Try{
				Gui, % PopUpWindow.HelperHwnd ":Destroy"
				SoundBeep, 555
			}
		}
	}
	CreateCachedBitmap( pBitmap , Dispose := 0 ){
		local pCachedBitmap
		if( This.CachedBitmap )
			This.DisposeCachedbitmap()
		DllCall( "gdiplus\GdipCreateCachedBitmap" , "Ptr" , pBitmap , "Ptr" , this.G , "PtrP" , pCachedBitmap )
		This.CachedBitmap := pCachedBitmap
		if( Dispose )
			Gdip_DisposeImage( pBitmap )
	}
	DrawCachedBitmap( AutoUpdate := 0 ){
		DllCall( "gdiplus\GdipDrawCachedBitmap" , "Ptr" , this.G , "Ptr" , This.CachedBitmap , "Int" , 0 , "Int" , 0 )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DisposeCachedbitmap(){
		DllCall( "gdiplus\GdipDeleteCachedBitmap" , "Ptr" , This.CachedBitmap )
	}
	Helper(){
		local hwnd , MethodList := ["__New","UpdateSettings","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow" , "AddTrigger" , "DrawTriggers", "CreateCachedBitmap" , "DrawCachedBitmap" , "DisposeCachedbitmap" ]
		Gui, New, +AlwaysOnTop +ToolWindow +HwndHwnd
		PopUpWindow.HelperHwnd := hwnd
		Gui, Add, Edit, xm ym w250 r1 Center hwndhwnd, Gui1
		PopUpWindow.EditHwnd := hwnd
		loop, % MethodList.Length()	
			Gui, Add, Button, xm y+1 w250 r1 gPopUpWindow._HelperClip, % MethodList[ A_Index ]
		Gui, Show,,
		OnMessage( 0x112 , This._OnClose.Bind( hwnd ) )
	}
	_HelperClip(){
		local ClipList 
		
		GuiControlGet, out, % PopUpWindow.HelperHwnd ":", % PopUpWindow.EditHwnd	
		
		ClipList := 		{ 	__New: 					" := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 0 , W: A_ScreenWidth , H: A_ScreenHeight , Options: "" -DPIScale +AlwaysOnTop "" } )"
							,	UpdateSettings:			".UpdateSettings( { X: """" , Y: """" , W: """" , H: """" } , UpdateGraphics := 0 )"
							,	ShowWindow:				".ShowWindow( Title := """" )"
							,	HideWindow:				".HideWindow()"
							,	UpdateWindow:			".UpdateWindow()"
							,	ClearWindow:			".ClearWindow( AutoUpdate := 0 )"
							,	DrawBitmap:				".DrawBitmap( pBitmap := """" , { X: 0 , Y: 0 , W: " Out ".W , H: " Out ".H } , dispose := 1 , AutoUpdate := 0 )"
							,	PaintBackground:		".PaintBackground( color := ""0xFF000000"" , AutoUpdate := 0 )  "  ";{ Color: ""0xFF000000"" , X: 2 , Y: 2 , W: " Out ".W - 4 , H: " Out ".H - 4 , Round: 10 }"
							,	DeleteWindow:			".DeleteWindow( GDIPShutdown := 0 )"
							,	AddTrigger:				".AddTrigger( { X: """" , Y: """" , W: """" , H: """" , Value: """" , Label: """" } )"	
							,	DrawTriggers:			".DrawTriggers( color := ""0xFFFF0000"" , AutoUpdate := 0 )"	
							,	CreateCachedBitmap:		".CreateCachedBitmap( pBitmap , Dispose := 0 )"	
							,	DrawCachedBitmap: 		".DrawCachedBitmap( AutoUpdate := 0 )"	
							,	DisposeCachedbitmap:	".DisposeCachedbitmap()"	}
							
		clipboard := Out ClipList[ A_GuiControl ]
		
	}
}
;************
;Vector Class
;**************************************************************************************************************************************************************************
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;**************************************************************************************************************************************************************************
Class Vector	{
	;Written By: HB
	;Date: Sept 23rd, 2022
	;Last Edit: Sept 24th, 2022
	;Purpose: Vector math class 
	;Credit: Rohwedder 
	;Resources: 
		;Line intercept concepts and code: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=37175
		;Create an Arrow: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=92039&p=479129#p478944
		;Getting an angle: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=108760&p=483661#p483678
		;Setting an Angle: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=108760&p=483786#p483811
		;
		
	static RadToDeg := 45 / ATan( 1 ) 
		, DegToRad := ATan( 1 ) / 45 
		
	__New( x := 0 , y := 0 , rotate := 0 ){ 
		if( IsObject( x ) ){
			if( rotate = 3 ){
				This.X := x.X * -1
				,This.Y := x.Y * -1
			}else if( rotate = 2 ){
				This.X := x.Y 
				,This.Y := x.X * -1
			}else if( rotate = 1 ){
				This.X := x.Y * -1
				,This.Y := x.X 
			}else{
				This.X := x.X
				,This.Y := x.Y
			}
		}else{
			if( rotate = 3 ){
				This.X := X * -1
				,This.Y := Y * -1
			}else if( rotate = 2 ){
				This.X := Y 
				,This.Y := X * -1
			}else if( rotate = 1 ){
				This.X := Y * -1
				,This.Y := X 
			}else{
				This.X := X
				,This.Y := Y
			}
		}
	}
	Add( x , y := "" ){
		if( IsObject( x ) ){
			This.X += x.X
			,This.Y += x.Y
		}else if( y = "" ){
			This.X += x 
			,This.Y += x
		}else{
			This.X += x 
			,This.Y += y 
		}
	}
	Sub( x , y := "" ){
		if( IsObject( x ) ){
			This.X -= x.X
			,This.Y -= x.Y
		}else if( y = "" ){
			This.X -= X
			,This.Y -= X
		}else{
			This.X -= X
			,This.Y -= Y
		}
	}
	Div( x , y := "" ){
		if( IsObject( x ) ){
			This.X /= x.X
			,This.Y /= x.Y
		}else if( x && y = "" ){
			This.X /= x 
			,This.Y /= x 
		}else{
			This.X /= X
			,This.Y /= Y
		}
	}
	Mult( x , y := "" ){
		if( IsObject( x ) ){
			This.X *= x.X
			,This.Y *= x.Y
		}else if( x && y = "" ){
			This.X *= x 
			,This.Y *= x 
		}else{
			This.X *= X
			,This.Y *= Y
		}
	}
	Dist( x , y := "" ){
		if( IsObject( x ) )
			return Sqrt( ( ( This.X - x.X ) **2 ) + ( ( This.Y - x.Y ) **2 ) )
		else 
			return Sqrt( ( ( This.X - X ) **2 ) + ( ( This.Y - Y ) **2 ) )
	}
	GetMag(){
		return Sqrt( This.X * This.X + This.Y * This.Y )
	}
	SetMag( magnitude ){
		local m := This.GetMag()
		This.X := This.X * magnitude / m
		,This.Y := This.Y * magnitude / m
	}
	MagSq(){
		return This.GetMag()**2
	}	
	Dot( x , y := "" ){
		if( IsObject( x ) )
			return ( This.X * x.X ) + ( This.Y * x.Y )
		else
			return ( This.X * X ) + ( This.Y * Y )
	}
	Cross( x , y := "" ){
		if( IsObject( x ) )
			return This.X * x.Y - This.Y * x.X
		else
			return This.X * Y - This.Y * X
		
	}
	Norm(){
		local m := This.GetMag()
		This.X /= m
		This.Y /= m
	}
	GetAngle(){ 
		local angle 
		( (  angle := Vector.RadToDeg * DllCall( "msvcrt\atan2" , "Double" , This.Y , "Double" , This.X , "CDECL Double" ) ) < 0 ) ? ( angle += 360 )
		return angle
	}
	SetAngle( newAngle := 0 , NewVector := 0 ){
		local Angle := This.GetAngle()
		, ChangeAngle := newAngle - Angle 
		, Co := Cos( Vector.DegToRad * ChangeAngle )
		, Si := Sin( Vector.DegToRad * ChangeAngle )
		, X := This.X 
		, Y := This.Y
		, X2 := X * Co - Y * Si 
		, Y2 := X * Si + Y * Co 
		
		if( !NewVector )
			This.X := X2 , This.Y := Y2
		else 
			return New Vector( X2 , Y2 )
	}
	RotateAngle( rotationAmount := 90 , NewVector := 0 ){
		local Co := Cos( Vector.DegToRad * rotationAmount )
		, Si := Sin( Vector.DegToRad * rotationAmount )
		, X := This.X 
		, Y := This.Y
		, X2 := X * Co - Y * Si 
		, Y2 := X * Si + Y * Co 
		
		if( !NewVector )
			This.X := X2 , This.Y := Y2
		else 
			return New Vector( X2 , Y2 )
	}
	;********************************************
	;class methods
	TestLineInterceptPoint( interceptPoint , Line1 , Line2 ){ ; Line = { Start: { X: , Y: } , End: { X: , Y: } } , interceptPoint = { X: , Y: }
		local
		for k , v in [ "X" , "Y" ]	
			M%v%_Min := min( Line1.Start[ v ] , Line1.End[ v ] )
			,M%v%_Max := max( Line1.Start[ v ] , Line1.End[ v ] )
			,L%v%_Min := min( Line2.Start[ v ] , Line2.End[ v ] )
			,L%v%_Max := max( Line2.Start[ v ] , Line2.End[ v ] )
		if( !( interceptPoint.X < Mx_Min || interceptPoint.X > Mx_Max || interceptPoint.X < Lx_Min || interceptPoint.X > Lx_Max ) && !( interceptPoint.Y < My_Min || interceptPoint.Y > My_Max || interceptPoint.Y < Ly_Min || interceptPoint.Y > Ly_Max ) )
			return 1
		return 0
	}
	GetLineInterceptPoint( Line1 , Line2 ){ ; Line = { Start: { X: , Y: } , End: { X: , Y: } }
		local A1 := Line1.End.Y - Line1.Start.Y
		,B1 := Line1.Start.X - Line1.End.X
		,C1 := A1 * Line1.Start.X + B1 * Line1.Start.Y
		,A2 := Line2.End.Y - Line2.Start.Y
		,B2 := Line2.Start.X - Line2.End.X
		,C2 := A2 * Line2.Start.X + B2 * Line2.Start.Y
		,Denominator := A1 * B2 - A2 * B1 
		return New Vector( { X: ( ( B2 * C1 - B1 * C2 ) / Denominator )  , Y: ( ( A1 * C2 - A2 * C1 ) / Denominator ) } )
	}
	;********************************************
}
;**************************************************************************************************************************************************************************
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;**************************************************************************************************************************************************************************

iseahound
Posts: 1445
Joined: 13 Aug 2016, 21:04
Contact:

Re: GDI+ Text Border / Bubble

Post by iseahound » 18 Feb 2023, 02:10

Scan0 is the pointer to the first scanline of an image. It is not always the pointer to the start of the buffer. (See: bottom up bitmaps)
image.png
image.png (27.29 KiB) Viewed 1090 times
Scan0 would be the where it says top-left pixel. It is this way because some mathematicians thought that the unit circle was ideal, i.e. quadrant I of the Cartesian plane. Notice how the y-axis goes upwards? Completely different to what a programmer expects.
image.png
image.png (23.95 KiB) Viewed 1090 times

User avatar
Hellbent
Posts: 2109
Joined: 23 Sep 2017, 13:34

Re: GDI+ Text Border / Bubble

Post by Hellbent » 18 Feb 2023, 04:32

@iseahound Thanks for the explanation.

Post Reply

Return to “Ask for Help (v1)”