Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

analytic graphing facility


  • Please log in to reply
37 replies to this topic
shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
A fun proof of concept, which enables graphing/drawing in AHk Gui windows.

The really cool part is that all functions are implemented entirely within AHk -- no external calls, whatsoever.

SelectCanvas* p_title = name of AHk GUI window
* p_origin?x, p_origin?y = origin of associated coordinate system (cs), or specify "native" for p_origin?x to use display coordinate systemCanvas_SetPixel* p_x, p_y = coordinates
* p_size = size of pixel
* p_color = 0xRRGGBB or color name
* p_refresh = forced refreshCanvas_DrawLine* p_x1, p_y1, p_x2, p_y2
* p_w = width of line
* p_colorCanvas_DrawCircle* p_x, p_y = center
* p_r = radius
* p_t = thickness of outline
* p_color
* p_smooth = true/false[/list]
Enjoy!

demonstration:
Gui, Show, x100 y50 w281 h281, Analytic Graphing Facility

SelectCanvas( "Analytic Graphing Facility", 140, 140, ?, ? )
	Canvas_DrawCircle( 0, 0, 40, 1, "blue" )
	Canvas_DrawCircle( 0, 0, 60, 2, "navy", false )
	Canvas_DrawCircle( 0, 0, 80, 3, "red" )
	Canvas_DrawCircle( 0, 0, 100, 4, "maroon", false )
	
	Canvas_DrawCircle( -78, 78, 40, 2, "teal" )
	Canvas_DrawCircle( 78, 78, 40, 2, "teal", false )
	Canvas_DrawCircle( 78, -78, 40, 2, "teal" )
	Canvas_DrawCircle( -78, -78, 40, 2, "teal", false )
	
	Canvas_DrawLine( -140, 140, -80, 80, 2, "white" )
	Canvas_DrawLine( -140, -140, -80, -80, 2, "white" )
	Canvas_DrawLine( 140, 140, 80, 80, 2, "white" )
	Canvas_DrawLine( 140, -140, 80, -80, 2, "white" )

	Canvas_DrawLine( -29, -1, -21, -1, 2, "green" )
		Canvas_DrawLine( -25, 10, -35, -10, 2, "green" )
		Canvas_DrawLine( -25, 10, -15, -10, 2, "green" )
	
	Canvas_DrawLine( -10, 0, 10, 0, 2, "green" )
		Canvas_DrawLine( -10, 10, -10, -10, 2, "green" )
		Canvas_DrawLine( 10, 10, 10, -10, 2, "green" )
	
	Canvas_DrawLine( 15, 10, 15, -10, 2, "green" )
		Canvas_DrawLine( 15, -3, 24, 6, 2, "green" )
		Canvas_DrawLine( 15, -1, 28, -10, 2, "green" )
return

functions:
SelectCanvas( p_title, p_origin?x, p_origin?y, byref r_origin?x, byref r_origin?y )
{
	static	hw_canvas, origin?x, origin?y
	
	if ( p_title )
	{
		Process, Exist
		WinGet, hw_canvas, ID, %p_title% ahk_class AutoHotkeyGUI ahk_pid %ErrorLevel%

		origin?x := p_origin?x
		origin?y := p_origin?y
	}
	else
	{
		r_origin?x := origin?x
		r_origin?y := origin?y
	}

	return, hw_canvas
}

Canvas_SetPixel( p_x, p_y, p_size, p_color, p_refresh=true )
{
	static	total
	
	total++
	
	hw_canvas := SelectCanvas( false, ?, ?, origin?x, origin?y )

	if ( origin?x != "native" )
	{
		p_x += origin?x
		p_y := origin?y-p_y
	}

	Gui, Add, Progress, % "x" ( p_x-1 ) " y" ( p_y-1 ) " w" ( p_size+2 ) " h" ( p_size+2 ) " background" p_color
	
	; WS_EX_STATICEDGE	
	Control, ExStyle, -0x20000, msctls_progress32%total%, ahk_id %hw_canvas% 
	
	if ( p_refresh )
		WinSet, Redraw,, ahk_id %hw_canvas%
}

Canvas_DrawLine( p_x1, p_y1, p_x2, p_y2, p_w, p_color )
{
	x := p_x1-1
	if ( p_x1 > p_x2 )
	{
		x := p_x2-1
		p_x2 := p_x1
		p_x1 := x+1
		
		temp := p_y2
		p_y2 := p_y1
		p_y1 := temp
	}
	
	if ( p_x1 = p_x2 )
	{
		loop, % ( p_y1-p_y2+1 )
			Canvas_SetPixel( p_x1, p_y1-A_Index+1, p_w, p_color, false )
	}
	else
	{
		m := ( p_y2-p_y1 )/( p_x2-p_x1 )
		
		loop, % ( p_x2-p_x1+1 )
		{
			x++
			Canvas_SetPixel( x, m*( x-p_x1 )+p_y1, p_w, p_color, false )
		}
	}
	
	WinSet, Redraw,, % "ahk_id " SelectCanvas( "", ?, ?, ?, ? )
}

Canvas_DrawCircle( p_x, p_y, p_r, p_t, p_color, p_smooth=true )
{
	delta := p_t*!p_smooth+p_smooth

	r2 := p_r**2

	x = 0
	y = %p_r%
	loop, % Ceil( p_r/1.4142/delta )
	{
		if ( y-Sqrt( r2-x**2 ) > 0.5 )
			y -= delta
	
		Canvas_SetPixel( p_x+x, p_y+y, p_t, p_color, false )
			Canvas_SetPixel( p_x+y, p_y+x, p_t, p_color, false )
		Canvas_SetPixel( p_x-x, p_y+y, p_t, p_color, false )
			Canvas_SetPixel( p_x-y, p_y+x, p_t, p_color, false )
		Canvas_SetPixel( p_x-x, p_y-y, p_t, p_color, false )
			Canvas_SetPixel( p_x-y, p_y-x, p_t, p_color, false )
		Canvas_SetPixel( p_x+x, p_y-y, p_t, p_color, false )
			Canvas_SetPixel( p_x+y, p_y-x, p_t, p_color, false )
		
		x += delta
	}
	
	WinSet, Redraw,, % "ahk_id " SelectCanvas( "", ?, ?, ?, ? )
}


Thalon
  • Members
  • 641 posts
  • Last active: Jan 02 2017 12:17 PM
  • Joined: 12 Jul 2005
I love it :!:

I will have a look into this great example, because (I think) it figures the solution for one problem I had in a script and which was never solved in a fine way 8)

Thank you shimanov!

Thalon

Greg
  • Members
  • 245 posts
  • Last active: Jun 02 2006 05:39 PM
  • Joined: 22 Dec 2005
There are gaps of color within the cirlcles while they are being drawn. But once they finish drawing the gaps are filled in. Is that perhaps because of the speed of the drawing? Or my computer's refresh rate?

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
This is a funny proof of concept...
For complex drawings, you may hit the limit of 5,000-11,000 controls (depending on control type) described in the documentation.
I wondered why you took the Progress control, as I would have favored a simplier, perhaps less memory hungry Text (Static) control, but it seems that you can't change its background color, at least in AHK.
Anyway, that's impressive.
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
This is really cool! If only it could be an order of magnitude faster..., but for static graphs it works as it is. Shimanov rocks!

polyethene
  • Members
  • 5519 posts
  • Last active: May 17 2015 06:39 AM
  • Joined: 26 Oct 2012
A great innovation with the current resources. I highly commend your work shimanov :)

autohotkey.com/net Site Manager

 

Contact me by email (polyethene at autohotkey.net) or message tidbit


shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
I would like to thank everyone for their responses.

Remember, this is a proof of concept; not a prototype, not even a Google beta, and certainly not a release.

It simply demonstrates a novel mechanism for graphing/drawing using AHk exclusive resources.

To Greg:
The visual discrepancy is due to removing the WS_EX_STATICEDGE style after the control is created, and sometimes, after the canvas is refreshed. The alternative is to remove that style during creation, but it was not possible to do so during testing on my system.
To PhiLho:

There are limitations inherent in the mechanisms and algorithms used, but memory is not one of them (at least not generally). Any issue with creation of a large number of windows (i.e., controls) is significantly dependant on OS limits imposed on the number of USER handles and related:

There is a theoretical limit of 65,536 user handles per session. However, the maximum number of user handles that can be opened per session is usually lower, since it is affected by available memory. There is also a default per-process limit of 10,000 user handles. To change this limit, set the following registry value:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\Windows\USERProcessHandleQuota

Windows XP: This value can be set to a number between 256 and 65,536.
Windows 2000: This value can be set to a number between 256 and 16,384.


and AHk (?):

Each GUI window may have up to 11,000 controls. However, use caution when creating more than 5000 controls because system instability may occur for certain control types.

To Laszlo:

You can remove significant overhead by unrolling the function calls (e.g., Canvas_SetPixel), at the expense of code size.

The fastest method, other than the DirectX API, would be to modify the DC's bitmap directly.

SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005

The really cool part is that all functions are implemented entirely within AHk -- no external calls, whatsoever


Highly impressive :)

I'm not able to guess your next post!!!
I will eagerly await it!

:)
kWo4Lk1.png

BoBo
  • Guests
  • Last active:
  • Joined: --
Based on that info above ...

There is a theoretical limit of 65,536 user handles per session. However, the maximum number of user handles that can be opened per session is usually lower, since it is affected by available memory. There is also a default per-process limit of 10,000 user handles. To change this limit, set the following registry value:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\Windows\USERProcessHandleQuota

Windows XP: This value can be set to a number between 256 and 65,536.
Windows 2000: This value can be set to a number between 256 and 16,384.

I've no idea if this is of help, but I found it interesting ... [here]

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

I've no idea if this is of help, but I found it interesting ... [here]


It is interesting, but outdated. The information revealed in the thread is now documented.

BoBo
  • Guests
  • Last active:
  • Joined: --
Sorry to step into the scene with something which might be off topic: I'm running a 24/7 frontend/GUI process/application. For unknown reason (meanwhile I've "implemented" a memory defrag utility) that process/app dies from time to time based on a "out of memory" incident. Currently I've not identifed a strict pattern what's indicates the issue.
Now I think it could be that "default per-process limit of 10,000 user handles"-kinda thing ??? Eh, ... :?:

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

process/app dies from time to time based on a "out of memory" incident


I would suggest some detective work to verify your observation and assertion.

Process Explorer: to monitor memory and handle allocation per process
- check the "Performance page" in the "Properties window"

Window Objects (and related handle information)

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Back to the drawing... There is a minor bug in the circle drawing function: the (0,0) point is always used as center. The correct one:
Canvas_DrawCircle( p_x, p_y, p_r, p_t, p_color )
{
   Loop 360
   {
      theta := A_Index*( 3.1415/180 )
      x := p_x + p_r*cos( theta )     ; <--
      y := p_y + p_r*sin( theta )     ; <--
      Canvas_SetPixel( x, y, p_t, p_color, false )
   }
   WinSet Redraw,, % "ahk_id " SelectCanvas( "", ?, ?, ?, ? )
}
It uses 360 points to draw, which is too many for small circles and too few for large ones. (It is perfectly OK for a proof of concept script.) Below is another function, which puts circle points at most 1 pixel apart. It does not use the slow trigonometric functions inside the loop, only two multiplications and 4 additions per points, so it should be faster. But it is not, showing that the running time is dominated by setting of the pixels, not by the numerics.
SetFormat Float, 0.15   ; high precision for iterative algorithms
;...
Canvas_DrawCircle0( x0, y0, r, s, color )
{
   If (r < 0.5)               ; small circle = 4 pixels
      t := 0.7854             ; the loop below will not execute
   Else
      t := asin(0.5/r)        ; angle, corresponding a 1 pixel step

   dx := x0 + r*cos(3*t)      ; four equi-angle starting points
   dy := y0 + r*sin(3*t)
   cx := x0 + r*cos(t)
   cy := y0 + 0.5
   bx := cx
   by := y0 - 0.5
   ax := dx
   ay := 2*y0 - dy
   Canvas_SetPixel( ax, ay, s, color, false )
   Canvas_SetPixel( bx, by, s, color, false )
   Canvas_SetPixel( cx, cy, s, color, false )
   Canvas_SetPixel( dx, dy, s, color, false )

   q := (dy-ay) / (cy-by)     ; chord increase factor

   Loop % round(0.78539816339744830962/t)-1 ; 2pi/8t
   {                          ; 4 unrolled iterations to avoid copy or indexing
      ax := bx + q*(dx-cx)
      ay := by + q*(dy-cy)
      Canvas_SetPixel( ax, ay, s, color, false )
      bx := cx + q*(ax-dx)
      by := cy + q*(ay-dy)
      Canvas_SetPixel( bx, by, s, color, false )
      cx := dx + q*(bx-ax)
      cy := dy + q*(by-ay)
      Canvas_SetPixel( cx, cy, s, color, false )
      dx := ax + q*(cx-bx)
      dy := ay + q*(cy-by)
      Canvas_SetPixel( dx, dy, s, color, false )
   }
   WinSet Redraw,,% "ahk_id " SelectCanvas( "", ?, ?, ?, ? )
}
This algorithm is close to the fastest (you could do it with just a few additions and comparisons, but conditionals could be slow, if they flush the processor pipeline). The main advantage of this algorithm is that it can be used to draw arcs of general second order curves with properly setting the initial values.

In case of a full circle, you only need the arc in the first octant. It can be reflected over some simple lines to give the other seven segments, using only a couple of additions each, so the point computation can be sped up almost eight fold. It still will not be noticeable, because of the very slow pixel setting procedure.

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

There is a minor bug in the circle drawing function


Nice catch. I though there was something missing.

I have updated the code to correct this oversight.

Canvas_DrawCircle0


Interesting approach to drawing curves. It is reminiscent of Archimedes's method for calculating Pi.

However, this function seems to draw off-center ellipses.

In any case, this could be the beginning of an AHk graphics library.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I forgot to copy over the line
SetFormat Float, 0.15
As any iterative algorithm, this one, too, is very sensitive to rounding errors. The standard AHK precision is not enough.