Jump to content

Scrolling Single Line Graph UDF


Bilgus
 Share

Recommended Posts

Waveform.thumb.png.b1d0a6168268c476c4eb32fc98c2cf7c.png

This is an update or derivative work of Beege 's Scrolling Line Graph UDF

https://www.autoitscript.com/forum/topic/109599-scrolling-line-graph-udf

 

I noticed a few issues for my use case with the UDF one being that adding a sample required updating the waveform

High CPU usage went hand in hand with that requirement

Another issue was just how long updating took to complete

I've hopefully rectified that with this version

 

There are a few changes (only 1 line per graph for instance)

  • The addition of a function AddSample (uses graphics paths to speed up drawing samples on update)
  • Gridlines are only generated once
  • A sample finished line can be added
  • UpdateGraph allows you to compress the discarded portion of the graph (it looks kinda cool but uses more CPU)
  • Lower Cpu usage
  • Uses real Control Ids - it is a label control underneath so you get click events and can display text when control is disabled

Example (Waveform.au3)

Spoiler

waveform.au3 

#include <GUIConstantsEx.au3> ; $GUI_DISABLE
#include "SSLG.au3"
#include <Array.au3>
;Just some timing variables
Global $iAvg = 0, $iAvg_Add = 0
Global $iCt = 0, $iCt_Add = 0
Global $iMax = 0, $iMin = 60000

;Setup
Global Const $Increments = 100
Global $UpdateIntervalMs = 100
Global $iSleep = 25
Global $bGenSamples = True

Opt('GuiOnEventMode', 1)
$GUI = GUICreate("GDI+ Scrolling Single Line Graph", 800, 500, -1, -1, BitOR($WS_SIZEBOX, $WS_SYSMENU, $WS_MINIMIZEBOX))
OnAutoItExitRegister(_Exit)
GUISetOnEvent(-3, _Exit)
GUISetState()

;_SSLG_CreateGraph($iLeft, $iTop, $iWidth, $iHeight, $iY_Min, $iY_Max, $iIncrements, $iBackGround = Default)
Global $Graph1 = _SSLG_CreateGraph(10, 10, -20, 300, 0, 100, $Increments)
GUICtrlSetData(-1, "Click me to enable" & @CRLF & "Resize the window to your liking" & @CRLF & "Click Again for a randomized experience")
GUICtrlSetOnEvent(-1, graph_)
GUICtrlSetState(-1, BitOR(GUICtrlGetState(-1), $GUI_DISABLE))
_SSLG_ClearGraph($Graph1) ;Create already cleared the graph

;Only needed for the example ==================================================
Global $Slider1 = GUICtrlCreateSlider(35, 350, 800 - 45, 40)
GUICtrlSetLimit(-1, 100, 1)
GUICtrlSetData(-1, $iSleep)
GUICtrlSetOnEvent(-1, slider_)
GUICtrlSetTip(-1, $iSleep)
UpdateSpeed($iSleep)

Global $Checkbox1 = GUICtrlCreateCheckbox("", 10, 350, 25, 25)
GUICtrlSetState(-1, $GUI_CHECKED)
GUICtrlSetOnEvent(-1, checkbox_)
;==============================================================================


GUIRegisterMsg($WM_EXITSIZEMOVE, WM_EXITSIZEMOVE)

;Wait for first activation
While BitAND(GUICtrlGetState($Graph1), $GUI_DISABLE) = $GUI_DISABLE
    Sleep(1000)
WEnd

AdlibRegister(Update, $UpdateIntervalMs)

Local $i = 0, $t
Local $y
While 1
    $t = TimerInit()

    If $bGenSamples Then
        ;_SSLG_AddSample($Graph1, $i)

        $y = Int(Sin($i / 30) * 40) + 45
        _SSLG_AddSample($Graph1, $y)

        _SSLG_AddSample($Graph1, Random($y / 2, $y * 2))

        For $j = 0 To Random(1, 10, 1)
            _SSLG_AddSample($Graph1, Random(0, 100))
        Next
    Else
        For $j = 0 To 5
            _SSLG_AddSample($Graph1)
        Next
    EndIf

    $iAvg_Add += TimerDiff($t)
    $iCt_Add += 1

    Sleep($iSleep)

    $i += Random(1, 15, 1)
    If $i > 179 Then $i = 0
WEnd


Func Update()
    Local $iErr, $iSamples
    Local $bCompress, $bFill

    Local $t = TimerInit()
    Static $i = 0
    $i = $i + 1

    If $i > 0 Then
        $bCompress = True
        $bFill = False
    Else
        $bCompress = False
        $bFill = True
    EndIf

    ;_SSLG_UpdateGraph($idSSLG, $bCompress = False, $bFill = False)
    If _SSLG_UpdateGraph($Graph1, $bCompress, $bFill) Then
        $iSamples = @extended
        Local $iT = TimerDiff($t)
        If $iT > $iMax Then $iMax = $iT
        If $iT < $iMin Then $iMin = $iT
        $iAvg += $iT
        $iCt += 1
        If $iCt >= 1000 Then Exit
    EndIf
    If $i > 100 Then $i = -100
EndFunc   ;==>Update

Func UpdateSpeed($iSleepMs)
    ;Try to keep update interval in sync with sample generation
    $UpdateIntervalMs = $iSleepMs * 10
    AdlibRegister(Update, $UpdateIntervalMs)
EndFunc   ;==>UpdateSpeed

;Callbacks ====================================================================
Func WM_EXITSIZEMOVE($hWnd, $Msg, $wParam, $lParam)
    _SSLG_SetResizeGraph($Graph1)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_EXITSIZEMOVE

Func slider_()
    $iSleep = GUICtrlRead($Slider1)
    GUICtrlSetTip($Slider1, $iSleep)
    UpdateSpeed($iSleep)
EndFunc   ;==>slider_

Func checkbox_()
    $bGenSamples = (GUICtrlRead($Checkbox1) = $GUI_CHECKED)
EndFunc   ;==>checkbox_

Func graph_()
    ConsoleWrite("*Click*" & @CRLF)
    Local $iState = GUICtrlGetState($Graph1)
    If Not BitAND($iState, $GUI_DISABLE) Then
        Sleep(1000)
        GUICtrlSetData($Graph1, "Click me to enable")
        GUICtrlSetState($Graph1, BitOR($iState, $GUI_DISABLE))
        Return
    Else
        Local $iY_Min = Random(0, 1, 1)
        Local $iY_Max = ($iY_Min - 1) * -100
        $iY_Min *= 100
        _SSLG_SetYRange($Graph1, $iY_Min, $iY_Max)
        GUICtrlSetData($Graph1, "")
    EndIf
    Local $iGrid = Random(-5, 50, 1)
    If $iGrid < 0 Then $iGrid = 0
    GUICtrlSetState($Graph1, BitXOR($iState, $GUI_DISABLE))

    ;_SSLG_SetSetSampleIndicator($idSSLG, $iLineColor = False, $iPenSize = Default)
    If Random(0, 1, 1) = 0 Then
        _SSLG_SetSetSampleIndicator($Graph1, 0xffFF0000, 2)
    Else
        _SSLG_SetSetSampleIndicator($Graph1, False)
    EndIf

    ;_SSLG_SetGrid($idSSLG, $iX_c, $iY_c, $iGridColor = Default, $iPenSize = Default)
    _SSLG_SetGrid($Graph1, $iGrid, $iGrid / 2)

    Local $iColor = Random(0x0, 0xFF, 1) + Random(0x0, 0xFF, 1) * 256 + Random(0x0, 0xFF, 1) * 65536

    ;_SSLG_SetLine($idSSLG, $iLineColor, $iPenSize = Default, $iFillColor = Default)
    _SSLG_SetLine($Graph1, 0xFF00FF00, Random(1, 5, 1), $iColor)


    GUICtrlSetBkColor($Graph1, $iColor)
EndFunc   ;==>graph_

Func _Exit()
    AdlibUnRegister(Update)
    Local $sRes = StringFormat("Update: %dx Avg %0.2fMs\r\nMin %0.2fMs, Max %0.2fMs\r\n\r\nAdd: %dx Avg %0.2fMs\r\n", $iCt, ($iAvg / $iCt), $iMin, $iMax, $iCt_Add, ($iAvg_Add / $iCt_Add))
    ConsoleWrite($sRes)
    MsgBox(0, @ScriptName, $sRes)
    OnAutoItExitUnRegister(_Exit)
    Exit
EndFunc   ;==>_Exit

 

 

Example 2 (peak.au3)

Spoiler
#include <GuiConstants.au3>
#include "SSLG.au3"

Global $oAudioMeterInformation = _AudioVolObject()
If Not IsObj($oAudioMeterInformation) Then Exit -1 ; Will happen on non-supported systems

Opt('GuiOnEventMode', 1)
Global $GUI = GUICreate("GDI+ Scrolling Single Line Graph", 800, 500, -1, -1, BitOR($WS_SIZEBOX, $WS_SYSMENU, $WS_MINIMIZEBOX))
OnAutoItExitRegister(_Exit)
GUISetOnEvent(-3, _Exit)

;_SSLG_CreateGraph($iLeft, $iTop, $iWidth, $iHeight, $iY_Min, $iY_Max, $iIncrements, $iBackGround = Default)
Global $Graph1 = _SSLG_CreateGraph(10, 10, -20, 300, 0, 100, 600)
GUICtrlSetOnEvent(-1, graph_)

;_SSLG_SetGrid($idSSLG, $iX_c, $iY_c, $iGridColor = Default, $iPenSize = Default)
_SSLG_SetGrid($Graph1, 100, 10)
_SSLG_ClearGraph($Graph1) ;Create already cleared the graph but we want a grid

GUISetState()

AdlibRegister(_SamplePeak, 25)
AdlibRegister(_UpdateGraph, 300)
GUIRegisterMsg($WM_EXITSIZEMOVE, WM_EXITSIZEMOVE)

While 1
    Sleep(1000)
WEnd

Func _SamplePeak()
    Local Const $S_OK = 0
    Local $iPeak
    If $oAudioMeterInformation.GetPeakValue($iPeak) = $S_OK Then
        _SSLG_AddSample($Graph1, 100 * $iPeak)
    EndIf
EndFunc   ;==>_SamplePeak

Func _UpdateGraph()
    ;_SSLG_UpdateGraph($idSSLG, $bCompress = False, $bFill = False)
    _SSLG_UpdateGraph($Graph1, True, False)
EndFunc   ;==>_UpdateGraph

Func WM_EXITSIZEMOVE($hWnd, $Msg, $wParam, $lParam)
    _SSLG_SetResizeGraph($Graph1)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_EXITSIZEMOVE

Func graph_()
    ConsoleWrite("*Click*" & @CRLF)
EndFunc   ;==>graph_

Func _Exit()
    AdlibUnRegister(_SamplePeak)
    AdlibUnRegister(_UpdateGraph)
    OnAutoItExitUnRegister(_Exit)
    Exit
EndFunc   ;==>_Exit

;-------------------------------------------------------------------------------------------------------------------------------------
; https://www.autoitscript.com/forum/topic/142523-master-loudnessvolume-via-peak-meter-windows-7-64-bit/?tab=comments#comment-1003940
;-------------------------------------------------------------------------------------------------------------------------------------
Func _AudioVolObject()
    Local Const $sCLSID_MMDeviceEnumerator = "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
    Local Const $sIID_IMMDeviceEnumerator = "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
    Local Const $tagIMMDeviceEnumerator = "EnumAudioEndpoints hresult(dword;dword;ptr*);" & _
            "GetDefaultAudioEndpoint hresult(dword;dword;ptr*);" & _
            "GetDevice hresult(wstr;ptr*);" & _
            "RegisterEndpointNotificationCallback hresult(ptr);" & _
            "UnregisterEndpointNotificationCallback hresult(ptr);"
    ; Sequences of code below are taken from the source of plugin written for AutoIt for setting master volume on Vista and above systems.
    ; Code was written by wraithdu in C++.
    ; MMDeviceEnumerator
    Local $oMMDeviceEnumerator = ObjCreateInterface($sCLSID_MMDeviceEnumerator, $sIID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator)
    If @error Then Return SetError(1, 0, 0)
    Local Const $eRender = 0
    Local Const $eConsole = 0
    ; DefaultAudioEndpoint
    Local $pDefaultDevice
    $oMMDeviceEnumerator.GetDefaultAudioEndpoint($eRender, $eConsole, $pDefaultDevice)
    If Not $pDefaultDevice Then Return SetError(2, 0, 0)
    ; Turn that pointer into object
    Local $oIMMDefaultDevice = ObjCreateInterface($pDefaultDevice, "{D666063F-1587-4E43-81F1-B948E807363F}", _
            "Activate hresult(clsid;dword;variant*;ptr*);")
    Local Const $CLSCTX_INPROC_SERVER = 0x1
    ; AudioMeterInformation
    Local $pAudioMeterInformation
    $oIMMDefaultDevice.Activate("{C02216F6-8C67-4B5B-9D00-D008E73E0064}", $CLSCTX_INPROC_SERVER, 0, $pAudioMeterInformation)
    If Not $pAudioMeterInformation Then Return SetError(3, 0, 0)
    Return ObjCreateInterface($pAudioMeterInformation, "{C02216F6-8C67-4B5B-9D00-D008E73E0064}", "GetPeakValue hresult(float*);")
EndFunc   ;==>_AudioVolObject
;-------------------------------------------------------------------------------------------------------------------------------------
; https://www.autoitscript.com/forum/topic/142523-master-loudnessvolume-via-peak-meter-windows-7-64-bit/?tab=comments#comment-1003940
;-------------------------------------------------------------------------------------------------------------------------------------

 

UDF

Spoiler

SSLG.au3

;
#Region Header
; #INDEX# =======================================================================================================================
; Title ............: SSLG
; AutoIt Version ...: 3.3.14.5
; Language .........: English
; Description ......: Functions to assist in creating and updating Scrolling Single Line Graphs
; Original Author(s): Beege ;http://www.autoitscript.com/forum/index.php?showtopic=109599
; Author(s) ........: Bilgus
; ===============================================================================================================================

; #CURRENT# =====================================================================================================================
;_SSLG_CreateGraph
;_SSLG_AddSample
;_SSLG_UpdateGraph
;_SSLG_SetSetSampleIndicator
;_SSLG_SetGrid
;_SSLG_SetLine
;_SSLG_SetSmoothingMode
;_SSLG_ClearGraph
;_SSLG_SetYRange
;_SSLG_SetBackGroundColor
; ===============================================================================================================================

#include-once
#include <GDIPlus.au3>
#include <WindowsConstants.au3>

_GDIPlus_Startup()
OnAutoItExitRegister(__SSLG_Exit)
#EndRegion Header

#Region Global Variables and Constants
Global Const $gSSLG_DICT = 1
Global Enum $gSSLG_idCtrl, $gSSLG_aRect, $gSSLG_hCtrl, $gSSLG_hPen, $gSSLG_hPenSamp, $gSSLG_hPenGrid, $gSSLG_hBrushLine, $gSSLG_hBrushBkgnd, _
        $gSSLG_hDCBuf, $gSSLG_hHBitmapGDI_, $gSSLG_hBitmap_1, $gSSLG_hGrBuf_1, $gSSLG_hBitmap_0, $gSSLG_hGrBuf_0, $gSSLG_hPathGridH, _
        $gSSLG_iY_Min, $gSSLG_iY_Max, $gSSLG_iY_Range, $gSSLG_iY_Last, $gSSLG_iX_Inc, $gSSLG_hPathLine, $gSSLG_hPathGridV, $gSSLG_hMatrixHT, _
        $gSSLG_bNeedResize, $gSSLG_iIncrements, $gSSLG_iIncrementSize, $gSSLG_iBackColor, $gSSLG_iSamples, $gSSLG_iSamplesTotal, $MAX

Global $g_SSLG[1][$MAX]

$g_SSLG[0][0] = 0
$g_SSLG[0][$gSSLG_DICT] = ObjCreate("Scripting.Dictionary")

#cs
    $g_SSLG[0][0]          = List Count
    [0][1]                  = Dictionary object (associative array)
    [0][2-27]               = Nothing

    [$i][$gSSLG_idCtrl]         = ControlID
    [$i][$gSSLG_aRect]          = Control position array [x, y, w, h]
    [$i][$gSSLG_hCtrl]          = Control Handle
    [$i][$gSSLG_hPen]           = Line Pen Handle
    [$i][$gSSLG_hPenSamp]       = Sample Finished Pen Handle
    [$i][$gSSLG_hPenGrid]       = Gridline Pen Handle
    [$i][$gSSLG_hBrushLine]     = Brush to fill under graph line
    [$i][$gSSLG_hBrushBkgnd]    = Brush for background ([$gSSLG_iBackColor])

    [$i][$gSSLG_hDCBuf]         = Device context Handle for buffer
    [$i][$gSSLG_hHBitmapGDI_]   = Standard GDI bitmap (selected into hDCBuf)
    [$i][$gSSLG_hBitmap_1]      = Buffer 1 Bitmap Object Handle
    [$i][$gSSLG_hGrBuf_1]       = Buffer 1 Graphic Object Handle
    [$i][$gSSLG_hBitmap_0]      = Buffer 0 Bitmap Object Handle
    [$i][$gSSLG_hGrBuf_0]       = Buffer 0 Graphic Object Handle

    [$i][$gSSLG_iY_Min]         = Y Min
    [$i][$gSSLG_iY_Max]         = Y Max
    [$i][$gSSLG_iY_Range]       = Y Range
    [$i][$gSSLG_iY_Last]        = Last Y Coordinate

    [$i][$gSSLG_iX_Inc]         = Vertical Gridline every #iX_Inc samples
    [$i][$gSSLG_hPathLine]      = Graphics Path Object Handle
    [$i][$gSSLG_hPathGridV]     = Graphics Path Object Handle Vertical Grid Lines
    [$i][$gSSLG_hPathGridH]     = Graphics Path Object Handle Horizontal Grid Lines
    [$i][$gSSLG_hMatrixHT]      = Graphics Path Horizontal Translation Matrice

    [$i][$gSSLG_bNeedResize]    = Resize graphics objects
    [$i][$gSSLG_iIncrements]    = Step Count
    [$i][$gSSLG_iIncrementSize] = Step Size
    [$i][$gSSLG_iBackColor]     = Background Color value
    [$i][$gSSLG_iSamples]       = Count of samples waiting to be drawn
    [$i][$gSSLG_iSamplesTotal]  = Count of total samples since ClearGraph()

#ce
#EndRegion Global Variables and Constants

#Region Public Functions
; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_CreateGraph
; Description ...: Creates a Scrolling Line Graph
; Syntax.........: _SSLG_CreateGraph($iLeft, $iTop, $iW, $iH, $iY_Min, $iY_Max, $iIncrements, $iBackGround = Default)

; Parameters ....:
;   $iLeft      - Left side of the graph
;   $iTop       - Top side of the graph
;   $iW             - Width of the graph
;   $iH             - Height of the graph
;   $iY_Min         - Minimum Y Value
;   $iY_Max         - Maximum Y Value
;   $iIncrements    - How many parts the Graph is divided up into.
;   $iBackGround    - BackGround Color. Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB). Default = Black

; Return values .: Success  - ControlId
; Author ........: Beege, Bilgus
; Remarks .......: $iIncrements will be rounded if $iW is not evenly divisible
; ===============================================================================================================================
Func _SSLG_CreateGraph($iLeft, $iTop, $iW, $iH, $iY_Min, $iY_Max, $iIncrements, $iBackGround = Default)
    If $iBackGround = Default Then $iBackGround = 0xFF000000

    ReDim $g_SSLG[UBound($g_SSLG) + 1][UBound($g_SSLG, 2)]
    $g_SSLG[0][0] += 1
    Local $oDict = $g_SSLG[0][$gSSLG_DICT]
    Local $iIdx = $g_SSLG[0][0]
    Local $aResult, $aPos

    Local $iCtrlID = GUICtrlCreateLabel("", $iLeft, $iTop, $iW, $iH, $WS_CLIPSIBLINGS)
    Local $hCtrl = GUICtrlGetHandle($iCtrlID)

    If $iW < 0 Or $iH < 0 Then ;Allow -W/H
        $aResult = DllCall("user32.dll", "hwnd", "GetParent", "hwnd", $hCtrl)
        If Not @error Then
            $aPos = WinGetClientSize($aResult[0])
            If Not @error Then
                If $iW < 0 Then $iW = $aPos[0] + $iW
                If $iH < 0 Then $iH = $aPos[1] + $iH
                GUICtrlSetPos($iCtrlID, $iLeft, $iTop, $iW, $iH)
            EndIf
        EndIf
    EndIf

    $g_SSLG[$iIdx][$gSSLG_idCtrl] = $iCtrlID
    $g_SSLG[$iIdx][$gSSLG_hCtrl] = $hCtrl

    Local $hDC = _WinAPI_GetDC($hCtrl)
    $g_SSLG[$iIdx][$gSSLG_hDCBuf] = _WinAPI_CreateCompatibleDC($hDC)
    _WinAPI_ReleaseDC($hCtrl, $hDC)

    ;Store references
    $oDict.Add(Hex($iCtrlID), $iIdx)
    $oDict.Add(Hex($hCtrl), $iIdx)

    $g_SSLG[$iIdx][$gSSLG_iIncrements] = $iIncrements

    $g_SSLG[$iIdx][$gSSLG_hPathLine] = _GDIPlus_PathCreate() ;Create new path object
    $g_SSLG[$iIdx][$gSSLG_hPathGridV] = _GDIPlus_PathCreate() ;Create new path object
    $g_SSLG[$iIdx][$gSSLG_hPathGridH] = _GDIPlus_PathCreate() ;Create new path object
    $g_SSLG[$iIdx][$gSSLG_hMatrixHT] = _GDIPlus_MatrixCreate() ;Horizontal translation matrix

    ;Initially these are disabled; use _SetGrid & _SetSampleIndicator
    $g_SSLG[$iIdx][$gSSLG_hPenSamp] = False
    $g_SSLG[$iIdx][$gSSLG_hPenGrid] = False

    $g_SSLG[$iIdx][$gSSLG_iBackColor] = __SSLG_RGB_ARGB($iBackGround)
    $g_SSLG[$iIdx][$gSSLG_hBrushBkgnd] = _GDIPlus_BrushCreateSolid($g_SSLG[$iIdx][$gSSLG_iBackColor])
    GUICtrlSetBkColor(-1, BitAND(BitNOT(0xFF000000), $iBackGround))
    GUICtrlSetColor(-1, 0xFFFFFF)

    _SSLG_SetLine($iCtrlID, 0xFF00FF00, 2)

    $g_SSLG[$iIdx][$gSSLG_iY_Last] = False
    $g_SSLG[$iIdx][$gSSLG_iX_Inc] = 0

    $g_SSLG[$iIdx][$gSSLG_iSamples] = 0
    $g_SSLG[$iIdx][$gSSLG_iSamplesTotal] = 0

    _SSLG_SetYRange($iCtrlID, $iY_Min, $iY_Max)

    _SSLG_SetSmoothingMode($iCtrlID)

    $g_SSLG[$iIdx][$gSSLG_bNeedResize] = True
    _SSLG_ClearGraph($iCtrlID)

    Return $iCtrlID

EndFunc   ;==>_SSLG_CreateGraph

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_AddSample
; Description ...: Adds a new value to Line Graph
; Syntax.........: _SSLG_AddSample($iIdx, $iValue)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;                  $iValue - Value to add to graph Default advances graph without a data point displayed
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
;                               - 3 Too many samples ~truncated
;                               - 4 Busy in another SSLG function
; Author ........: Bilgus
; Remarks .......: Adding too many samples between calls to UpdateGraph will cause samples to be lost
; ===============================================================================================================================

Func _SSLG_AddSample($idSSLG, $iValue = Default)
    Local $iIdx = __SSLG_LookupHandle($idSSLG, True)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    Local $hPath = $g_SSLG[$iIdx][$gSSLG_hPathLine]
    Local $hPath_Grid = $g_SSLG[$iIdx][$gSSLG_hPathGridV]
    Local $aRect = $g_SSLG[$iIdx][$gSSLG_aRect]
    Local $iH = $aRect[3]
    
    Local $iY = $g_SSLG[$iIdx][$gSSLG_iY_Last]
    Local $iY_Last = $g_SSLG[$iIdx][$gSSLG_iY_Last]
    Local $iY_Min = $g_SSLG[$iIdx][$gSSLG_iY_Min]
    Local $iY_Max = $g_SSLG[$iIdx][$gSSLG_iY_Max]

    If $g_SSLG[$iIdx][$gSSLG_iY_Last] = Default And $iValue <> Default Then 
        Return SetError(4, @extended, 0)
    Else
        $g_SSLG[$iIdx][$gSSLG_iY_Last] = Default ;Used as Mutex
    EndIf

    If $g_SSLG[$iIdx][$gSSLG_iSamples] <= 0 Then ; First sample, reset paths
        _GDIPlus_PathReset($hPath)
        _GDIPlus_PathReset($g_SSLG[$iIdx][$gSSLG_hPathGridV])
    EndIf

    If $iValue <> Default Then
        If $iY_Min < $iY_Max Then
            $iY = $g_SSLG[$iIdx][$gSSLG_iY_Max] - $iValue ;Need to flip direction of Y
        Else
            $iY = $iValue
        EndIf
        If Not $iY_Last Then $iY_Last = $iY
        _GDIPlus_PathAddLine($hPath, $g_SSLG[$iIdx][$gSSLG_iSamples] - 1, $iY_Last, $g_SSLG[$iIdx][$gSSLG_iSamples], $iY)
        ;ConsoleWrite(StringFormat("AddLine (%d, %d) (%d, %d) \r\n", $g_SSLG[$iIdx][$gSSLG_iSamples] - 1, $iY_Last, $g_SSLG[$iIdx][$gSSLG_iSamples], $iY ))
    EndIf

    If $g_SSLG[$iIdx][$gSSLG_hPenGrid] And Mod($g_SSLG[$iIdx][$gSSLG_iSamplesTotal], $g_SSLG[$iIdx][$gSSLG_iX_Inc]) = 0 Then
        ;Make a new vertical grid line
        _GDIPlus_PathStartFigure($hPath_Grid)
        _GDIPlus_PathAddLine($hPath_Grid, $g_SSLG[$iIdx][$gSSLG_iSamples], $iY_Min, $g_SSLG[$iIdx][$gSSLG_iSamples], $iY_Max)
        _GDIPlus_PathCloseFigure($hPath_Grid)
    EndIf

    $g_SSLG[$iIdx][$gSSLG_iSamples] += 1
    $g_SSLG[$iIdx][$gSSLG_iSamplesTotal] += 1 ;Used to track gridline increment
    $g_SSLG[$iIdx][$gSSLG_iY_Last] = $iY ;Release Mutex

    If $g_SSLG[$iIdx][$gSSLG_iSamples] > $g_SSLG[$iIdx][$gSSLG_iIncrements] Then Return SetError(3, @extended, 0)

    Return 1

EndFunc   ;==>_SSLG_AddSample

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_UpdateGraph
; Description ...: Updates Line Graph with new values
; Syntax.........: _SSLG_UpdateGraph($iIdx, $iValue)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;                  $bCompress - Compress end of graph rather than discard (slower)
;                  $bFill - Fill under the graph line with hBrushLine
;                  $bForce Update even though control is disabled
; Return values .: Success  - 1
;   @Extended   - Number of samples displayed
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
;                               - 2 No Samples
;                               - 3 Samples overflow graph
;                               - 4 Busy in another SSLG function
; Author ........: Bilgus
; Remarks .......: Flip Y_Min and Y_Max in the call to CreateGraph to change fill area from under to above
;                  $bCompress = True uses more CPU and increases memory churn due to GDI+ temp objects
; ===============================================================================================================================
Func _SSLG_UpdateGraph($idSSLG, $bCompress = False, $bFill = False, $bForce = False)
    Local $iIdx = __SSLG_LookupHandle($idSSLG, $bForce)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)
    Local $iErr = 0, $iY_Last

    If $g_SSLG[$iIdx][$gSSLG_bNeedResize] Then _SSLG_ClearGraph($idSSLG)

    Local $iSamples = $g_SSLG[$iIdx][$gSSLG_iSamples]

    If $iSamples < 1 Then
        Return SetError(2, @extended, 0)
    ElseIf $iSamples > $g_SSLG[$iIdx][$gSSLG_iIncrements] Then
        ;ConsoleWrite("!Full!" & @CRLF)
        $iSamples = $g_SSLG[$iIdx][$gSSLG_iIncrements]
        $iErr = 3
    EndIf

    If $g_SSLG[$iIdx][$gSSLG_iY_Last] = Default Then
        SetError(4, @extended, 0) ; Currently in the AddSample or GraphClear Function
    Else
        $iY_Last = $g_SSLG[$iIdx][$gSSLG_iY_Last]
        $g_SSLG[$iIdx][$gSSLG_iY_Last] = Default ;Used as Mutex
    EndIf

    $g_SSLG[$iIdx][$gSSLG_iSamples] = 0

    Local $hPath = $g_SSLG[$iIdx][$gSSLG_hPathLine]

    Local $hMatrixHT = $g_SSLG[$iIdx][$gSSLG_hMatrixHT]

    Local $aRect = $g_SSLG[$iIdx][$gSSLG_aRect]
    Local $iW = $aRect[2]
    Local $iH = $aRect[3]

    Local $iIncrement = $g_SSLG[$iIdx][$gSSLG_iIncrementSize]
    Local $iInc_Total = $iIncrement * $iSamples
    Local $iShiftDist = $iW - $iInc_Total
    ;ConsoleWrite(StringFormat("!Display (%d, %d; %d;) \r\n", $iSamples, $iInc_Total, $iShiftDist))

    Local $iHistX = $iInc_Total
    Local $iHistW = $iShiftDist
    If $bCompress Then
        $iHistX = 0
        $iHistW = $iW
    EndIf

    Local $aGraphic[2], $aBmp[2]
    Static Local $iLastBuf = 0
    ;Swap back buffer each call
    Local $iBuf0 = $iLastBuf, $iBuf1 = (1 - $iLastBuf) ;Toggles 0,1
    $aGraphic[$iBuf0] = $g_SSLG[$iIdx][$gSSLG_hGrBuf_0]
    $aGraphic[$iBuf1] = $g_SSLG[$iIdx][$gSSLG_hGrBuf_1]
    $aBmp[$iBuf0] = $g_SSLG[$iIdx][$gSSLG_hBitmap_0]
    $aBmp[$iBuf1] = $g_SSLG[$iIdx][$gSSLG_hBitmap_1]
    $iLastBuf = $iBuf1

    ;Shift/Copy/Compress the last data to the current bitmap
    _GDIPlus_GraphicsDrawImageRectRect($aGraphic[0], $aBmp[1], _
            $iHistX, 0, $iHistW, $iH, _ ;[Src XYWH]
            0, 0, $iShiftDist, $iH) ; [Dst XYWH]

    ;Here we scale the path(points) to the size of the graph X increment and Y range
    _GDIPlus_MatrixSetElements($hMatrixHT, $iIncrement, 0, 0, (($iH - 1) / $g_SSLG[$iIdx][$gSSLG_iY_Range]), $iShiftDist, 0)

    If $bFill And _GDIPlus_PathGetPointCount($hPath) > 0 Then
        ;Create 3 lines to enclose desired fill area -- If $iY_Min was used it'd fill top of graph instead
        Local $iY_F = $g_SSLG[$iIdx][$gSSLG_iY_Max]
        Static Local $tPoints = DllStructCreate("float[" & 3 * 2 & "]")
        DllStructSetData($tPoints, 1, $iSamples, 1)
        DllStructSetData($tPoints, 1, $iY_Last, 2)
        DllStructSetData($tPoints, 1, $iSamples, 3)
        DllStructSetData($tPoints, 1, $iY_F, 4)
        DllStructSetData($tPoints, 1, -1, 5)
        DllStructSetData($tPoints, 1, $iY_F, 6)
        DllCall($__g_hGDIPDll, "int", "GdipAddPathLine2", "handle", $hPath, "struct*", $tPoints, "int", 3)
        ;_GDIPlus_PathSetFillMode ($hPath, 0)

        _GDIPlus_MatrixSetElements($hMatrixHT, $iIncrement, 0, 0, (($iH - 1) / $g_SSLG[$iIdx][$gSSLG_iY_Range]), $iShiftDist, 0)
        _GDIPlus_PathTransform($hPath, $hMatrixHT)
        _GDIPlus_GraphicsFillPath($aGraphic[0], $hPath, $g_SSLG[$iIdx][$gSSLG_hBrushLine])
        _GDIPlus_GraphicsDrawPath($aGraphic[0], $hPath, $g_SSLG[$iIdx][$gSSLG_hPen])
    Else
        _GDIPlus_MatrixSetElements($hMatrixHT, $iIncrement, 0, 0, (($iH - 1) / $g_SSLG[$iIdx][$gSSLG_iY_Range]), $iShiftDist, 0)
        _GDIPlus_PathTransform($hPath, $hMatrixHT)
        _GDIPlus_GraphicsDrawPath($aGraphic[0], $hPath, $g_SSLG[$iIdx][$gSSLG_hPen])
    EndIf
    ;_GDIPlus_PathReset($hPath) (Now Handled in AddSample)

    If $g_SSLG[$iIdx][$gSSLG_hPenGrid] Then
        _GDIPlus_PathAddPath($g_SSLG[$iIdx][$gSSLG_hPathGridV], $g_SSLG[$iIdx][$gSSLG_hPathGridH], False)
        _GDIPlus_PathTransform($g_SSLG[$iIdx][$gSSLG_hPathGridV], $hMatrixHT)
        _GDIPlus_GraphicsDrawPath($aGraphic[0], $g_SSLG[$iIdx][$gSSLG_hPathGridV], $g_SSLG[$iIdx][$gSSLG_hPenGrid])
        ;_GDIPlus_PathReset($g_SSLG[$iIdx][$gSSLG_hPathGridV]) (Now Handled in AddSample)
    EndIf

    If $g_SSLG[$iIdx][$gSSLG_hPenSamp] Then ;Place a line at the end of each sample period
        _GDIPlus_GraphicsDrawLine($aGraphic[0], $iW - 1, 0, $iW - 1, $iH, $g_SSLG[$iIdx][$gSSLG_hPenSamp])
    EndIf

    __SSLG_GraphicsDraw($iIdx, $aBmp[0], $iW, $iH) ;Copy back to ctrl

    ;Clear Next Bitmap
    _GDIPlus_GraphicsFillRect($aGraphic[1], 0, 0, $iW, $iH, $g_SSLG[$iIdx][$gSSLG_hBrushBkgnd])

    $g_SSLG[$iIdx][$gSSLG_iY_Last] = $iY_Last ;Release mutex
    Return SetError($iErr, $iSamples, 1)

EndFunc   ;==>_SSLG_UpdateGraph

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetSampleIndicator
; Description ...: Sets Line Color and line size of sample update line
; Syntax.........: _SSLG_SetSetSampleIndicator($iIdx, $iARGB, $iPenSize)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;                  $iLineColor - Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB) [Default] = False Removes Indicator
;                  $iPenSize - Size of pen [Default] = 2
;                  $iPenStyle  - GDIP_DASHSTYLE [Default] = $GDIP_DASHSTYLESOLID
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
;                               - 2 Error setting pen color
; Author ........: Bilgus
; Remarks .......: none
; ===============================================================================================================================
Func _SSLG_SetSetSampleIndicator($idSSLG, $iLineColor = False, $iPenSize = Default, $iPenStyle = Default)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    If $iPenStyle = Default Then $iPenStyle = $GDIP_DASHSTYLESOLID

    If $iLineColor = False Then
        _GDIPlus_PenDispose($g_SSLG[$iIdx][$gSSLG_hPenSamp])
        $g_SSLG[$iIdx][$gSSLG_hPenSamp] = False
        Return 1
    EndIf

    If $iPenStyle = Default Then $iPenStyle = $GDIP_DASHSTYLEDOT

    $iLineColor = __SSLG_RGB_ARGB($iLineColor)
    If Not $g_SSLG[$iIdx][$gSSLG_hPenSamp] And $iPenSize = Default Then $iPenSize = 1

    If $iPenSize <> Default Then
        _GDIPlus_PenDispose($g_SSLG[$iIdx][$gSSLG_hPenSamp])
        $g_SSLG[$iIdx][$gSSLG_hPenSamp] = _GDIPlus_PenCreate($iLineColor, $iPenSize)
    Else
        _GDIPlus_PenSetColor($g_SSLG[$iIdx][$gSSLG_hPenSamp], $iLineColor)
        If @error Then Return SetError(2, @extended, 0)
    EndIf
    _GDIPlus_PenSetDashStyle($g_SSLG[$iIdx][$gSSLG_hPenSamp], $iPenStyle)

    Return 1

EndFunc   ;==>_SSLG_SetSetSampleIndicator

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetGrid
; Description ...: Sets Grid color x/y interval and line size of Grid
; Syntax.........: _SSLG_SetGrid($iIdx, $iX_c, $iY_c, $iARGB, $iPenSize)
; Parameters ....: $idSSLG     - Control ID returned from _SSLG_CreateGraph()
;                  $iX_c = 0   - X axis grid lines (vertical) [Default] = 0 --  Removes Vertical
;                  $iY_c = 0   - Y axis grid lines (hoizontal) [Default] = 0 -- Removes Horizontal
;                  $iGridColor - Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB)
;                  $iPenSize   - Size of pen [Default] = 2
;                  $iPenStyle  - GDIP_DASHSTYLE [Default] =
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
;                               - 2 Error setting pen color
; Author ........: Bilgus
; Remarks .......: none
; ===============================================================================================================================
Func _SSLG_SetGrid($idSSLG, $iX_c, $iY_c, $iGridColor = Default, $iPenSize = Default, $iPenStyle = Default)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)
    Local $aRect = $g_SSLG[$iIdx][$gSSLG_aRect]
    Local $iH = $aRect[3]

    If $iGridColor = Default Then $iGridColor = 0xFFA0A0A0
    If $iPenSize = Default Then $iPenSize = 1
    If $iPenStyle = Default Then $iPenStyle = $GDIP_DASHSTYLESOLID

    $iGridColor = __SSLG_RGB_ARGB($iGridColor)

    Local $hPath_Grid = $g_SSLG[$iIdx][$gSSLG_hPathGridH]

    _GDIPlus_PathReset($hPath_Grid)

    _GDIPlus_PenDispose($g_SSLG[$iIdx][$gSSLG_hPenGrid])
    $g_SSLG[$iIdx][$gSSLG_hPenGrid] = False

    ;NOTE: notice the negative (-) sign on the Step value -- without it Step = 0 hangs

    For $j = $iH - 1 To 0 Step -Int($iY_c)
        _GDIPlus_PathStartFigure($hPath_Grid)
        _GDIPlus_PathAddLine($hPath_Grid, -1, $j, $g_SSLG[$iIdx][$gSSLG_iIncrements] - 1, $j)
        _GDIPlus_PathCloseFigure($hPath_Grid)
    Next

    $g_SSLG[$iIdx][$gSSLG_iX_Inc] = Int($iX_c)
    If $iX_c <> 0 Or $iY_c <> 0 Then
        $g_SSLG[$iIdx][$gSSLG_hPenGrid] = _GDIPlus_PenCreate($iGridColor, $iPenSize)
        _GDIPlus_PenSetDashStyle($g_SSLG[$iIdx][$gSSLG_hPenGrid], $iPenStyle)
    EndIf

    _SSLG_ClearGraph($idSSLG)

    Return 1

EndFunc   ;==>_SSLG_SetGrid

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetLine
; Description ...: Sets Line Color and line size of Graph
; Syntax.........: _SSLG_SetLineColor($iIdx, $iARGB)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;                  $iLineColor - Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB)
;                  $iPenSize - Size of pen [Default] = 2
;                  $iFillColor - Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB) [Default] = $iLineColor
;                  $hBrush - you may also supply your own brush to fill area under plotted line (deleted on exit by SSLG)
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
;                               - 2 Error setting pen color
; Author ........: Bilgus
; Remarks .......: none
; ===============================================================================================================================
Func _SSLG_SetLine($idSSLG, $iLineColor, $iPenSize = Default, $iFillColor = Default, $hBrush = Default)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    $iLineColor = __SSLG_RGB_ARGB($iLineColor)
    If $iFillColor = Default Then $iFillColor = $iLineColor
    $iFillColor = __SSLG_RGB_ARGB($iFillColor)

    _GDIPlus_BrushDispose($g_SSLG[$iIdx][$gSSLG_hBrushLine])
    If $hBrush = Default Then
        $g_SSLG[$iIdx][$gSSLG_hBrushLine] = _GDIPlus_BrushCreateSolid($iFillColor)
    Else
        $g_SSLG[$iIdx][$gSSLG_hBrushLine] = $hBrush
    EndIf

    If Not $g_SSLG[$iIdx][$gSSLG_hPen] And $iPenSize = Default Then $iPenSize = 2
    If $iPenSize <> Default Then
        _GDIPlus_PenDispose($g_SSLG[$iIdx][$gSSLG_hPen])
        $g_SSLG[$iIdx][$gSSLG_hPen] = _GDIPlus_PenCreate($iLineColor, $iPenSize)
    EndIf

    GUICtrlSetColor($idSSLG, BitAND(BitNOT(0xFF000000), $iLineColor))
    _GDIPlus_PenSetColor($g_SSLG[$iIdx][$gSSLG_hPen], $iLineColor)
    If @error Then Return SetError(2, @extended, 0)

    Return 1

EndFunc   ;==>_SSLG_SetLine

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetSmoothingMode
; Description ...: Sets the graph object rendering quality

; Syntax.........: _SSLG_SetSmoothingMode($iIdx, $iSmooth = 2)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;                  $iSmooth - Smoothing mode
;                         0 - Smoothing is not applied
;                         1 - Smoothing is applied using an 8 X 4 box filter
;                         2 - Smoothing is applied using an 8 X 8 box filter [Default]
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
; Author ........: Beege
; Remarks .......: none
; ===============================================================================================================================
Func _SSLG_SetSmoothingMode($idSSLG, $iSmooth = 0)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    _GDIPlus_GraphicsSetSmoothingMode($g_SSLG[$iIdx][$gSSLG_hGrBuf_0], $iSmooth)
    _GDIPlus_GraphicsSetSmoothingMode($g_SSLG[$iIdx][$gSSLG_hGrBuf_1], $iSmooth)

    Return 1

EndFunc   ;==>_SSLG_SetSmoothingMode

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_ClearGraph
; Description ...: Clears all data from Graph
; Syntax.........: _SSLG_ClearGraph($iIdx)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
; Author ........: Bilgus
; Remarks .......: none
; ===============================================================================================================================
Func _SSLG_ClearGraph($idSSLG)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    If $g_SSLG[$iIdx][$gSSLG_bNeedResize] Then 
        if __SSLG_GraphicsSetup($iIdx) = 0 Then Return
    EndIf

    $g_SSLG[$iIdx][$gSSLG_iY_Last] = Default ;Used as Mutex

    ;Need to clear both buffers we dont keep track which is current
    _GDIPlus_GraphicsClear($g_SSLG[$iIdx][$gSSLG_hGrBuf_0], $g_SSLG[$iIdx][$gSSLG_iBackColor]) ;
    _GDIPlus_GraphicsClear($g_SSLG[$iIdx][$gSSLG_hGrBuf_1], $g_SSLG[$iIdx][$gSSLG_iBackColor]) ;

    _GDIPlus_PathReset($g_SSLG[$iIdx][$gSSLG_hPathGridV])
    _GDIPlus_PathReset($g_SSLG[$iIdx][$gSSLG_hPathLine])

    $g_SSLG[$iIdx][$gSSLG_iSamplesTotal] = 0
    $g_SSLG[$iIdx][$gSSLG_iSamples] = 0

    If $g_SSLG[$iIdx][$gSSLG_hPenGrid] Then
        ;Add blank samples to draw grid
        For $i = 0 To $g_SSLG[$iIdx][$gSSLG_iIncrements] - 1
            _SSLG_AddSample($idSSLG, Default)
        Next
        $g_SSLG[$iIdx][$gSSLG_iY_Last] = False
        _SSLG_UpdateGraph($idSSLG, False, False, True)
    EndIf

    $g_SSLG[$iIdx][$gSSLG_iY_Last] = False

    Return 1

EndFunc   ;==>_SSLG_ClearGraph

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetResizeGraph
; Description ...: Resize Graph after call to GUICtrlSetPos
; Syntax.........: _SSLG_ResizeGraph($iIdx)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
; Author ........: Bilgus
; Remarks .......: Only sets flag -- resize will occur on next ClearGraph or UpdateGraph
; ===============================================================================================================================
Func _SSLG_SetResizeGraph($idSSLG)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)
    $g_SSLG[$iIdx][$gSSLG_bNeedResize] = True

    Return 1

EndFunc   ;==>_SSLG_SetResizeGraph

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetYRange
; Description ...: Sets the Minimum and Maximum Y Values
; Syntax.........: _SSLG_SetYRange($iIdx, $iY_Min, $iY_Max)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;   $iY_Min     - Minimum Y Value
;   $iY_Max     - Maximum Y Value
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
; Author ........: Beege
; Remarks .......: User should most likly want to ClearGraph after changing Y Range
; ===============================================================================================================================
Func _SSLG_SetYRange($idSSLG, $iY_Min, $iY_Max)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)
    $g_SSLG[$iIdx][$gSSLG_iY_Min] = $iY_Min
    $g_SSLG[$iIdx][$gSSLG_iY_Max] = $iY_Max
    $g_SSLG[$iIdx][$gSSLG_iY_Range] = Abs($iY_Max - $iY_Min)
    
    Return 1

EndFunc   ;==>_SSLG_SetYRange

; #FUNCTION# ====================================================================================================================
; Name...........: _SSLG_SetBackGroundColor
; Description ...: Sets Graph BackGround Color
; Syntax.........: _SSLG_SetBackGroundColor($iIdx, $iARGB = 0xFF000000)
; Parameters ....: $idSSLG - Control ID returned from _SSLG_CreateGraph()
;   $iARGB      - Alpha, Red, Green and Blue Hex Value. (0xAARRGGBB). Default = Black
; Return values .: Success  - 1
;   Failure     - 0 and sets @ERROR:
;                               - 1 Invalid iIndex
; Author ........: Beege
; Remarks .......: All previous data will be cleared
; ===============================================================================================================================
Func _SSLG_SetBackGroundColor($idSSLG, $iARGB = 0xFF000000)
    Local $iIdx = __SSLG_LookupHandle($idSSLG)
    If $iIdx > $g_SSLG[0][0] Then Return SetError(1, @extended, 0)

    $g_SSLG[$iIdx][$gSSLG_iBackColor] = __SSLG_RGB_ARGB($iARGB)
    _GDIPlus_BrushDispose($g_SSLG[$iIdx][$gSSLG_hBrushBkgnd])
    $g_SSLG[$iIdx][$gSSLG_hBrushBkgnd] = _GDIPlus_BrushCreateSolid($g_SSLG[$iIdx][$gSSLG_iBackColor])
    _SSLG_ClearGraph($iIdx)

    Return 1

EndFunc   ;==>_SSLG_SetBackGroundColor
#EndRegion Public Functions

#Region Internal Functions
; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __SSLG_RGB_ARGB
; Description ...: Convert RBG color to ARGB color
; Syntax.........:
; Parameters ....:
; Return values .:
; Author ........: Bilgus
; Remarks .......:
; ===============================================================================================================================
Func __SSLG_RGB_ARGB($iARGB)
    If BitAND($iARGB, 0xFF000000) = 0 Then
        $iARGB = BitOR($iARGB, 0xFF000000)
    EndIf

    Return $iARGB

EndFunc   ;==>__SSLG_RGB_ARGB

; #INTERNAL_USE_ONLY# ====================================================================================================================
; Name...........: __SSLG_GraphicsSetup
; Description ...: Internal function -- (Re)Creates all the graphics objects
; Syntax.........: __SSLG_GraphicsSetup($iIdx)
; Parameters ....: $iIdx - INTERNAL Graph ID
; Return values .: N/A
;   Failure     - 0 and sets @ERROR:
;                               - 2 No change
; Author ........: Bilgus
; Remarks .......: All previous data will be cleared, Graphics objects destroyed, Re-created if $bReInit = True
; ===============================================================================================================================
Func __SSLG_GraphicsSetup($iIdx)
    $g_SSLG[$iIdx][$gSSLG_bNeedResize] = False

    Local $aRectOld = $g_SSLG[$iIdx][$gSSLG_aRect]
    Local $aRect = WinGetPos($g_SSLG[$iIdx][$gSSLG_hCtrl])

    If $g_SSLG[$iIdx][$gSSLG_hBitmap_0] And $aRect[2] = $aRectOld[2] And $aRect[3] = $aRectOld[3] Then
        ;ConsoleWrite("Resize not needed")
        Return SetError(2, @extended, 0)
    EndIf
    $g_SSLG[$iIdx][$gSSLG_aRect] = $aRect
    Local $iW = $aRect[2]
    Local $iH = $aRect[3]

    ;ConsoleWrite("New SZ: (" & $aRect[0] & ", " & $aRect[1] & "), (" & $iW & ", " & $iH & ")" & @CRLF)
    Local $hHBMP, $hGrBuf0, $hGrBuf1, $hBitmap0, $hBitmap1
    If $g_SSLG[$iIdx][$gSSLG_hBitmap_0] Then
        $hHBMP = $g_SSLG[$iIdx][$gSSLG_hHBitmapGDI_]
        $hGrBuf0 = $g_SSLG[$iIdx][$gSSLG_hGrBuf_0]
        $hBitmap0 = $g_SSLG[$iIdx][$gSSLG_hBitmap_0]
        $hGrBuf1 = $g_SSLG[$iIdx][$gSSLG_hGrBuf_1]
        $hBitmap1 = $g_SSLG[$iIdx][$gSSLG_hBitmap_1]
        $g_SSLG[$iIdx][$gSSLG_iIncrementSize] = -1
    EndIf


    Local $hGraphic = _GDIPlus_GraphicsCreateFromHWND($g_SSLG[$iIdx][$gSSLG_hCtrl])
    Local $hBitmap = _GDIPlus_BitmapCreateFromGraphics($iW, $iH, $hGraphic)
    
    ;$GDIP_PXF32PARGB (Pre-multiplied) appears to be the most efficient format as far as GDI+ is concerned
    $g_SSLG[$iIdx][$gSSLG_hBitmap_0] = _GDIPlus_BitmapCloneArea($hBitmap, 0, 0, $iW, $iH, $GDIP_PXF32PARGB)
    $g_SSLG[$iIdx][$gSSLG_hGrBuf_0] = _GDIPlus_ImageGetGraphicsContext($g_SSLG[$iIdx][$gSSLG_hBitmap_0])

    $g_SSLG[$iIdx][$gSSLG_hBitmap_1] = _GDIPlus_BitmapCloneArea($hBitmap, 0, 0, $iW, $iH, $GDIP_PXF32PARGB)
    $g_SSLG[$iIdx][$gSSLG_hGrBuf_1] = _GDIPlus_ImageGetGraphicsContext($g_SSLG[$iIdx][$gSSLG_hBitmap_1])

    $g_SSLG[$iIdx][$gSSLG_hHBitmapGDI_] = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hBitmap)

    $g_SSLG[$iIdx][$gSSLG_iIncrementSize] = Ceiling($iW / ($g_SSLG[$iIdx][$gSSLG_iIncrements] + 1))
    $g_SSLG[$iIdx][$gSSLG_iIncrements] = Int($iW / $g_SSLG[$iIdx][$gSSLG_iIncrementSize])

    _GDIPlus_BitmapDispose($hBitmap)
    _GDIPlus_GraphicsDispose($hGraphic)

    Local $aResult = DllCall("user32.dll", "hwnd", "GetParent", "hwnd", $g_SSLG[$iIdx][$gSSLG_hCtrl])
    If Not @error Then _WinAPI_RedrawWindow($aResult[0])

    _WinAPI_DeleteObject($hHBMP)
    _GDIPlus_GraphicsDispose($hGrBuf0)
    _GDIPlus_BitmapDispose($hBitmap0)
    _GDIPlus_GraphicsDispose($hGrBuf1)
    _GDIPlus_BitmapDispose($hBitmap1)

    Return 1

EndFunc   ;==>__SSLG_GraphicsSetup

; #INTERNAL_USE_ONLY# ====================================================================================================================
; Name...........: __SSLG_GraphicsDraw
; Description ...: Internal function -- BitBlt back to hDC
; Syntax.........: __SSLG_GraphicsDraw($iIdx)
; Parameters ....: $iIdx - INTERNAL Graph ID
;                  $iW - Width
;                  $iH - Height
; Return values .: N/A
; Author ........: Bilgus
; Remarks .......: Copy the GDI+ bitmap to a HBITMAP blit to the screen
; ===============================================================================================================================
Func __SSLG_GraphicsDraw($iIdx, $hBitmap, $iW, $iH)
    Local $hObjOld, $hDC_Src, $hDC_Dst, $hHBitmap, $hGraphic

    ;$hBitmap = GDI+ Bitmap [SRC]
    $hHBitmap = $g_SSLG[$iIdx][$gSSLG_hHBitmapGDI_] ;Standard GDI Bitmap [DEST]

    ;DC where we can select the Standard GDI Bitmap
    $hDC_Src = $g_SSLG[$iIdx][$gSSLG_hDCBuf]
    $hObjOld = _WinAPI_SelectObject($hDC_Src, $hHBitmap)

    $hGraphic = _GDIPlus_GraphicsCreateFromHDC($hDC_Src)
    _GDIPlus_GraphicsSetSmoothingMode($hGraphic, 2)
    _GDIPlus_GraphicsDrawImageRect($hGraphic, $hBitmap, 0, 0, $iW, $iH)
    _GDIPlus_GraphicsDispose($hGraphic)

    $hDC_Dst = _WinAPI_GetDC($g_SSLG[$iIdx][$gSSLG_hCtrl])
    _WinAPI_BitBlt($hDC_Dst, 0, 0, $iW, $iH, $hDC_Src, 0, 0, $SRCCOPY) ;Copy back to ctrl
    _WinAPI_ReleaseDC($g_SSLG[$iIdx][$gSSLG_hCtrl], $hDC_Dst)

    _WinAPI_SelectObject($hDC_Src, $hObjOld) ;Put old object back

EndFunc   ;==>__SSLG_GraphicsDraw

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __SSLG_LookupHandle
; Description ...: Converts controlID or Handles to internal graph index
; Syntax.........:
; Parameters ....:
; Return values .:
; Author ........: Bilgus
; Remarks .......:
; ===============================================================================================================================
Func __SSLG_LookupHandle($idSSLG, $bForce = True)
    Static Local $idSSLG_Last = -1
    Static Local $hSSLG_Last = 0
    Local Const $GUI_DISABLE = 128
    If $bForce Or Not BitAND(GUICtrlGetState($idSSLG), $GUI_DISABLE) Then
        If $idSSLG = $idSSLG_Last Then Return $hSSLG_Last ;Shortcut
        $idSSLG = Hex($idSSLG)
        Local $oDict = $g_SSLG[0][$gSSLG_DICT]

        If $oDict.Exists($idSSLG) Then
            $idSSLG_Last = $idSSLG
            $hSSLG_Last = $oDict.Item($idSSLG)
            Return $hSSLG_Last
        EndIf

        ConsoleWriteError("Graph ControlID: " & $idSSLG & " does not exist" & @CRLF)
        For $vKey In $oDict
            ConsoleWriteError("[" & $vKey & "] = " & $oDict.Item($vKey) & @CRLF)
        Next
    EndIf

    Return SetError(1, @extended, $g_SSLG[0][0] + 1)

EndFunc   ;==>__SSLG_LookupHandle

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __SSLG_Exit()
; Description ...: Cleans up stored objects
; Syntax.........:
; Parameters ....:
; Return values .:
; Author ........:
; Remarks .......:
; ===============================================================================================================================
Func __SSLG_Exit()
    If $g_SSLG[0][0] Then
        For $i = 1 To $g_SSLG[0][0]
            $g_SSLG[$i][$gSSLG_iY_Last] = Default ;Mutex

            _WinAPI_DeleteObject($g_SSLG[$i][$gSSLG_hHBitmapGDI_])

            _GDIPlus_GraphicsDispose($g_SSLG[$i][$gSSLG_hGrBuf_1])
            _GDIPlus_BitmapDispose($g_SSLG[$i][$gSSLG_hBitmap_1])

            _GDIPlus_GraphicsDispose($g_SSLG[$i][$gSSLG_hGrBuf_0])
            _GDIPlus_BitmapDispose($g_SSLG[$i][$gSSLG_hBitmap_0])

            _GDIPlus_MatrixDispose($g_SSLG[$i][$gSSLG_hMatrixHT])

            _GDIPlus_PathDispose($g_SSLG[$i][$gSSLG_hPathLine])
            _GDIPlus_PathDispose($g_SSLG[$i][$gSSLG_hPathGridV])
            _GDIPlus_PathDispose($g_SSLG[$i][$gSSLG_hPathGridH])

            _GDIPlus_PenDispose($g_SSLG[$i][$gSSLG_hPenGrid])
            _GDIPlus_PenDispose($g_SSLG[$i][$gSSLG_hPenSamp])
            _GDIPlus_PenDispose($g_SSLG[$i][$gSSLG_hPen])

            _GDIPlus_BrushDispose($g_SSLG[$i][$gSSLG_hBrushLine])
            _GDIPlus_BrushDispose($g_SSLG[$i][$gSSLG_hBrushBkgnd])
            _WinAPI_DeleteDC($g_SSLG[$i][$gSSLG_hDCBuf])
        Next
    EndIf

    _GDIPlus_Shutdown()

EndFunc   ;==>__SSLG_Exit
#EndRegion Internal Functions

 

Updated:

Previous Downloads [38 / 38/ 0]

SSLG.au3 waveform.au3 Peak.au3

Edited by Bilgus
Add screenshot, Update Code, Update Code, Update Code
Link to comment
Share on other sites

Since I have this extra post here I'll go into some of the details

As you recall (or maybe you have no idea) Begee's original code only updated one point on each graph redraw

This lead to some pretty high CPU usage if you wanted to add lots of points

My idea was to allow adding multiple points before redrawing the graph.......

Spoiler

First Try:

The first try I just encoded all the new points as 4byte chunks Concatenated in a binary string

Then on redraw decode these new points and display them, this worked decently but was still a little slow

 

Next Idea:

My next idea was to use a graphics path and just add points at every sample (X, Y) (and the grid lines too)

(X is the incremented sample position, Y the value position)

(0, Y) (1, Y) (2, Y) (3, Y) (4, Y)

This worked VERY well and also allowed me to get rid of the image shifting

Bummer:

 I couldnt find a way to QUICKLY & easily discard the portion of the path that had scrolled off screen

(except to walk the points and copy the ones I wanted to save to a new path)

as a result CPU usage started going UP and UP over time ( since more and more points needed translation)

 

Final Idea:

So clearly a graphics path is an excellent way to do this

Rather than continually adding to it instead I put the scrolling code back in but this time I only save the path till the redraw occurs

On GraphUpdate

  • The old graph is scrolled by (incrementSz * #Samples) and copied to the backbuffer
  • The now blank right portion gets the background copied into the empty space
    • (if enabled) the grid gets copied here too
  • The new points are translated in the X direction (scaled as a function of (IncrementSz * SamplePos) 
    • I wanted to do this with Y as well but I seem to get a rotation instead of scaling :(
  • The path gets reset back to empty and the Sample Count set to 0
    • Speaking of Sample Count GDI+ has a function to return number of points in a path (BUT it sometimes returns 0???)
  • After translation the path is written to the backbuffer
    • (if enabled) a vertical line is also placed at the right most portion of the graph to show distance between graph updates
  • Finally the new image is copied back to the graph

As I mentioned above I also return a real control Id from the create function

This is accomplished by creating a label control and drawing on that instead of the main hWnd

Having a label control underneath allows a few things

  • Click Events
  • Colored background
  • Control Disable
  • Real Control IDs
  • Message displayed on disable
  • Should also allow resizing the control though I didn't add this functionality as I have no need

The only problem is how do we associate the control ID with the graph instance

Here I used the Scripting.Dictionary object as a simple associative array the Key = Hex(controlID) Value = graph instance

Note: setting the backcolor of the control doesn't change the graph background only the disabled background

 

Oh hey if you look close at the code there is even a 'mutex' in there

I noticed if the graph updates while in the add sample code the points go to the wrong sample when it returns

 

 

 

Edited by Bilgus
updates
Link to comment
Share on other sites

Code In first post updated

  • Grids are now scaled to proper size 
  • Got rid of CloneBitmap on every graphics update (far less page faults now)
  • Allow use of ARGB for colors or RGB
  • Faster redraw (~10-20 ms) 
  • Y coords are now scaled using GDI+
  • Option to fill bottom portion of graph - Fixed scaling issue
Edited by Bilgus
Link to comment
Share on other sites

I noticed that my control flickers a lot more than his original 

I removed the BitBlt calls in favor of the GDI+ DrawImage so I could get rid of creating a new HBitmap each update

Looks to me like BitBlt does a better job

I'll upload a version fixing this issue and still without creating a new HBitmap in a day or two

Why am I trying to keep from creating a new bitmap each update you ask?

-- PageFaults --

While they are soft pagefaults it still means the program is churning memory so I want to try and decrease as much as possible

The other advantage is lower memory and processor usage

 

Oh I also have added the ability to resize the control now

It just deletes the old buffers and recreates them but it works :)

Link to comment
Share on other sites

SSLG code has been updated

Turns out BitBlt is faster than _GDIPlus_DrawImage even after you do an extra copy to a GDI HBitmap

Weird, Never would have guessed...

  • BitBlt used for final drawing to control
  • Added ability to resize the graph on window resize (try it in the example)
  • SSLG is much more well behaved after this update
  • There are now two buffers that get swapped every other graph update this gets us a free copy of the last image
  • Grid lines resize automatically on control resize
  • All sample scaling now handled with graphics path

GDI+ has a lot of heap allocated memory behind the scenes about the only thing that still has a lot of allocations in SSLG

is when you enable bCompress for the old samples otherwise you should see very few page faults

Link to comment
Share on other sites

Well I added another example peak.au3 it uses the Windows sound AudioMeterInfo.GetPeakValue code and displays it in a SSLG graph

See for Trancex's original code: https://www.autoitscript.com/forum/topic/142523-master-loudnessvolume-via-peak-meter-windows-7-64-bit/?tab=comments#comment-1003940

 

Unfortunately I found a few more bugs which are (fortunately) now fixed

  • AddSample did not check if UpdateGraph was currently running causing the graph to be cleared
  • If Grid was enabled SetResizeGraph did not draw new gridlines after the resize
  • Couple of error numbers were changed to match between AddSample and UpdateGraph
  • Increments are now recalculated after IncrementsSize is calculated

 

 

Edited by Bilgus
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...