XGraph v1.1.1.0 : Real time data plotting.

Post your working scripts, libraries and tools for AHK v1.1 and older
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: XGraph v1.1.1.0 : Real time data plotting.

16 Oct 2018, 23:34

I tried to port the Wu Line to ahk but couldn't get it to work properly. It draws the original unantialiased line ok, and then draws some extra antialiasing pixels in the right places where you would expect them, but they are the wrong colour, and in many places no antialiasing pixels at all. The function uses bitshifting and bitwise-XOR which I'm not sure are implemented the same in ahk as C++, maybe that's got something to do with it. Anyway I'll post my attempt here, maybe someone else can look at it and have a go. Comments are added.

Code: Select all

DrawWuLine(pDC, X0, Y0, X1, Y1, BaseColor, NumLevels:=256, IntensityBits:=8){


	/*
	https://www.codeproject.com/Articles/13360/Antialiasing-Wu-Algorithm
	
	Arguments:
		+  pDC is where line is drawn.  Pass hSourceDC from XGraph.  
		+  (X0,Y0) is start point of line. 
		+  (X1, Y1) is end point of line.
		+  BaseColor is intensity of line. Pass hex 0xBBGGRR in ahk.
		+  NumLevels is number of gray scale levels. Pass 256.
		+  IntensityBits denotes bits used to represent color component. Pass 8.
	*/
	
	/*
	Use DrawWuLine() instead of LineTo() in XGraph_Plot()
	X0 and Y0 can be the current position:
	
	VarSetCapacity(CurrPos, 8, 0)
	DllCall( "GetCurrentPositionEx" , "Ptr" , hSourceDC , "Ptr" , &CurrPos )
	CurrPosX := NumGet(CurrPos,0,"Int")
	CurrPosY := NumGet(CurrPos,4,"Int")
	DrawWuLine(hSourceDC, CurrPosX, CurrPosY, SetVal, MY2, 0x808080)
	
	Then move the current position to the newly plotted position for the next iteration
	DllCall( "MoveToEx" 
	, "Ptr", hSourceDC
	, "Int", SetVal
	, "Int", MY2
	, "Ptr", 0 )
	
	To save CPU you can do just a single final BitBlt() to the hTargetDC after you've drawn all your Wu lines.
	*/

	
	/*
	unsigned short IntensityShift, ErrorAdj, ErrorAcc;
	unsigned short ErrorAccTemp, Weighting, WeightingComplementMask;
	short DeltaX, DeltaY, Temp, XDir
	*/

	VarSetCapacity(IntensityShift,2,0)
	VarSetCapacity(ErrorAdj,2,0)
	VarSetCapacity(ErrorAcc,2,0)
	VarSetCapacity(ErrorAccTemp,2,0)
	VarSetCapacity(Weighting,2,0)
	VarSetCapacity(WeightingComplementMask,2,0)
	VarSetCapacity(DeltaX,2,0)
	VarSetCapacity(DeltaY,2,0)
	VarSetCapacity(Temp,2,0)
	VarSetCapacity(XDir,2,0)

	;Make sure the line runs top to bottom
	
	if (Y0 > Y1){
		Temp := Y0 ,  Y0 := Y1 ,  Y1 := Temp
		Temp := X0 ,  X0 := X1 ,  X1 := Temp
	}


	/* 
	Draw the initial pixel, which is always exactly intersected by
	the line and so needs no weighting 
	*/

	DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor)

	DeltaX := X1 - X0
	if (DeltaX >= 0)
		XDir := 1
	else 
	{
		XDir := -1
		DeltaX := -DeltaX  ;make DeltaX positive 
	}


	/* 
	Special-case horizontal, vertical, and diagonal lines, which
	require no weighting because they go right through the center of
	every pixel 
	*/
	DeltaY := Y1 - Y0
	if (DeltaY == 0){
	  
		;Horizontal line
		loop
		{
			If (DeltaX = 0)
				break
			Else
			{
				X0 += XDir
				DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor)
				DeltaX--					
			}
		}
		return
		
	}


	if (DeltaX == 0) {
		;Vertical line   
		loop
		{
			Y0++
			DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor)
			DeltaY--
			If (DeltaY = 0)
				break
		}
		return
	}



	if (DeltaX == DeltaY) {
		;Diagonal line 
		loop
		{
			X0 += XDir
			Y0++
			DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor)
			DeltaY--
			If (DeltaY = 0)
				break
		}
		return
 
	}

	;Line is not horizontal, diagonal, or vertical
	ErrorAcc := 0  ;initialize the line error accumulator to 0
	
	;# of bits by which to shift ErrorAcc to get intensity level
	IntensityShift := 16 - IntensityBits
		
	/* 
	Mask used to flip all bits in an intensity weighting, producing the
	result (1 - intensity weighting) 
	*/
	WeightingComplementMask := NumLevels - 1
	
	;Is this an X-major or Y-major line? 
	if (DeltaY > DeltaX) {
	  
		/* 
		Y-major line; calculate 16-bit fixed-point fractional part of a
		pixel that X advances each time Y advances 1 pixel, truncating the
		result so that we won't overrun the endpoint along the X axis 
		*/  
		ErrorAdj := (DeltaX << 16) / DeltaY
	 

		;Draw all pixels other than the first and last 			
		loop
		{
			DeltaY--
			If (DeltaY){
			
				ErrorAccTemp := ErrorAcc   ;remember current accumulated error
				ErrorAcc += ErrorAdj      ;calculate error for next pixel 
				 
				if (ErrorAcc <= ErrorAccTemp) 
					X0 += XDir  ;The error accumulator turned over, so advance the X coord
						 
				Y0++ ;Y-major, so always advance Y
				 
				/* 
				The IntensityBits most significant bits of ErrorAcc give us the
				intensity weighting for this pixel, and the complement of the
				weighting for the paired pixel 
				*/				
				Weighting := ErrorAcc >> IntensityShift
				
				
				;--- In ahk, problem seems to be occuring here? ----
				
				;Original is just these two lines
				;DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor+Weighting)
				;DllCall("SetPixel", "Ptr", pDC, "Int", X0+XDir, "Int", Y0, "UInt", BaseColor+(Weighting ^ WeightingComplementMask))
				
				;But Weighting seems to be an int between 0 and 255, therefore convert Basecolor (hex) to RGB ints before adding it?		
				Red1 := (BaseColor & 0xFF) + (Weighting)  ;or BaseColor * (Weighting/255)?
				Green1 := (BaseColor >> 8 & 0xFF) + (Weighting)
				Blue1 := (BaseColor >> 16 & 0xFF) + (Weighting)					
				NewColor1 := format("0x{1:02x}{2:02x}{3:02x}", Red1, Green1, Blue1)				
				DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", NewColor1)

				Red2 := (BaseColor & 0xFF) + (Weighting^WeightingComplementMask) ; ^ = bitwise-XOR
				Green2 := (BaseColor >> 8 & 0xFF) + (Weighting^WeightingComplementMask)
				Blue2 := (BaseColor >> 16 & 0xFF) + (Weighting^WeightingComplementMask)
				NewColor2 := format("0x{1:02x}{2:02x}{3:02x}", Red2, Green2, Blue2)
				DllCall("SetPixel", "Ptr", pDC, "Int", X0+XDir, "Int", Y0, "UInt",NewColor2)
				
				;MsgBox % "Debug Y-major`nWeighting: " . Weighting . "`nNewColor1: " . NewColor1 . "`nNewColor2: " . NewColor2
				
				;-----------------------------------------------------
				
			}
			Else
				break
		
	
		}
		
	
		/* 
		Draw the final pixel, which is always exactly intersected by the line
		and so needs no weighting 
		*/
		DllCall("SetPixel", "Ptr", pDC, "Int", X1, "Int", Y1, "UInt", BaseColor)
		return
	}
	
	/* 
	It's an X-major line; calculate 16-bit fixed-point fractional part of a
	pixel that Y advances each time X advances 1 pixel, truncating the
	result to avoid overrunning the endpoint along the X axis 
	*/
	ErrorAdj := (DeltaY << 16) / DeltaX
	
	;Draw all pixels other than the first and last
	loop
	{
		DeltaX--
		If (DeltaX){
		
			ErrorAccTemp := ErrorAcc   ;remember current accumulated error
			ErrorAcc += ErrorAdj      ;calculate error for next pixel
			if (ErrorAcc <= ErrorAccTemp){ 
				;The error accumulator turned over, so advance the Y coord
				Y0++
			}	  
			X0 += XDir  ;X-major, so always advance X
			
			/* 
			The IntensityBits most significant bits of ErrorAcc give us the
			intensity weighting for this pixel, and the complement of the
			weighting for the paired pixel
			*/
			
			Weighting := ErrorAcc >> IntensityShift
			
			
			;--- In ahk, problem seems to be occuring here? ----
				
			;Original is just these two lines
			;DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", BaseColor + Weighting)
			;DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0+1, "UInt", BaseColor+(Weighting ^ WeightingComplementMask))
			
			;But Weighting seems to be an int between 0 and 255, therefore convert Basecolor (hex) to RGB ints before adding it?		
			Red1 := (BaseColor & 0xFF) + (Weighting) ;or BaseColor * (Weighting/255)?
			Green1 := (BaseColor >> 8 & 0xFF) + (Weighting)
			Blue1 := (BaseColor >> 16 & 0xFF) + (Weighting)					
			NewColor1 := format("0x{1:02x}{2:02x}{3:02x}", Red1, Green1, Blue1)
			DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0, "UInt", NewColor1)
			 
			Red2 := (BaseColor & 0xFF) + (Weighting^WeightingComplementMask) ^ = bitwise-XOR
			Green2 := (BaseColor >> 8 & 0xFF) + (Weighting^WeightingComplementMask)
			Blue2 := (BaseColor >> 16 & 0xFF) + (Weighting^WeightingComplementMask)
			NewColor2 := format("0x{1:02x}{2:02x}{3:02x}", Red2, Green2, Blue2)
			DllCall("SetPixel", "Ptr", pDC, "Int", X0, "Int", Y0+1, "UInt", NewColor2)
			
			;MsgBox % "Debug X-major`nWeighting: " . Weighting . "`nNewColor1: " . NewColor1 . "`nNewColor2: " . NewColor2
				
			;-----------------------------------------------------
			
		}
		Else
			break
	

	}

	/* 
	Draw the final pixel, which is always exactly intersected by the line
	and so needs no weighting 
	*/
	DllCall("SetPixel", "Ptr", pDC, "Int", X1, "Int", Y1, "UInt", BaseColor)
	

}

In the meantime I managed to get a satisfactory result by applying the A_ScreenDPI/96 scaling at an earlier point in my draw function instead of to the final 96dpi plotted points. This way the line is based on the highest resolution source data rather than being simply "upscaled" from the 96dpi final plotted points, which was causing all the really nasty aliasing. Also as mentioned increasing the pen size to 2 pixels, and reducing the contrast between the plotted line and the background colour helps a lot too.
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: XGraph v1.1.1.0 : Real time data plotting.

05 Jan 2019, 14:10

There is a memory leak in XGraph of about ~700kB on my system (Windows 7 x64) which can be fixed by

Code: Select all

DllCall( "DeleteObject", "Ptr", hTargetBM  )
I just call this once when the user closes the window containing the XGraph
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: XGraph v1.1.1.0 : Real time data plotting.

06 Jan 2019, 06:15

It might be better to just use one of the alternative graph libraries on this forum then.
Recommends AHK Studio
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: XGraph v1.1.1.0 : Real time data plotting.

06 Jan 2019, 07:13

pneumatic wrote: There is a memory leak in XGraph of about ~700kB on my system (Windows 7 x64) which can be fixed by
..
I just call this once when the user closes the window containing the XGraph
Why not call XGraph_Detach() in GuiClose routine?
My Scripts and Functions: V1  V2
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: XGraph v1.1.1.0 : Real time data plotting.

06 Jan 2019, 07:22

SKAN wrote:
06 Jan 2019, 07:13
Why not call XGraph_Detach() in GuiClose routine?
Because it doesn't delete the hTargetBM!

Also just wanted to say a huge thanks for XGraph which makes my app a lot better :+1:
nnnik wrote:
06 Jan 2019, 06:15
It might be better to just use one of the alternative graph libraries on this forum then.
I didn't know there were any others! Well, it's too late now, I've already written everything in XGraph :lol:
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: XGraph v1.1.1.0 : Real time data plotting.

06 Jan 2019, 07:31

pneumatic wrote: Because it doesn't delete the hTargetBM!
Thanks for the bug report. I will investigate and fix this.
My Scripts and Functions: V1  V2
pneumatic
Posts: 338
Joined: 05 Dec 2016, 01:51

Re: XGraph v1.1.1.0 : Real time data plotting.

21 Nov 2019, 07:07

I notice XGraph uses DllCall("RtlMoveMemory")

According to WinAPI here, it resides in ntdll.dll, which is not part of ahk's automatically loaded .dlls (User32.dll, Kernel32.dll, ComCtl32.dll, and Gdi32.dll).

According to ahk doc, the only time we can omit DllFile name, is if the function is inside one of those automatically loaded dll's.

Therefore I am curious whether for safety we should be calling it by DllCall("ntdll\RtlMoveMemory")

It seems to work fine without it (64-bit Unicode) but I wonder whether it may fail on certain systems.
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: XGraph v1.1.1.0 : Real time data plotting.

21 Nov 2019, 08:09

pneumatic wrote:
21 Nov 2019, 07:07
Therefore I am curious whether for safety we should be calling it by DllCall("ntdll\RtlMoveMemory")
Should be DllCall("Kernel32\RtlMoveMemory"
My Scripts and Functions: V1  V2
User avatar
SirSocks
Posts: 360
Joined: 26 Oct 2018, 08:14

Re: XGraph v1.1.1.0 : Real time data plotting.

21 Jan 2021, 22:29

🤯 Wow, this is amazing! Thank you very much for sharing it. It looks very useful!

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: just me and 129 guests