Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Webcam Pong v0.01
#11
Don't let your dreams be dreams.
-Shia TheBeef

You need to download 
.h   framecallback.h (Size: 355 bytes / Downloads: 8)
Code: (Select All)
Option _Explicit
'$Console
'------------------------------------------------------------------------------------------------------------------------------
'Capture Driver Constants
Const WM_CAP_START = &H0400
Const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
Const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
Const WM_CAP_EDIT_COPY = WM_CAP_START + 30
Const WM_CAP_SET_SCALE = WM_CAP_START + 53
Const WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
Const WM_CAP_SET_PREVIEW = WM_CAP_START + 50
Const WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42
Const WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61
Const WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25
Const WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44
Const WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45
Const WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41
Const WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5
Const CF_DIB = &H0008

Const FPS = 30 'Not recommended to set this higher than frame rate of webcam/video device

Type BITMAPINFOHEADER
    As _Unsigned Long biSize
    As Long biWidth, biHeight
    As _Unsigned Integer biPlanes, biBitCount
    As _Unsigned Long biCompression, biSizeImage
    As Long biXPelsPerMeter, biYPelsPerMeter
    As _Unsigned Long biClrUsed, biClrImportant
End Type

Type BITMAPINFO
    As BITMAPINFOHEADER bmiHeader
End Type

Declare Dynamic Library "Avicap32"
    Function CreateCaptureWindow& Alias "capCreateCaptureWindowA" (ByVal lpszWindowName As _Offset, ByVal dwStyle As _Unsigned Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hwndParent As _Offset, ByVal nId As Long)
End Declare

Declare CustomType Library
    Sub SendMessage Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Function SendMessage& Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Sub DestroyWindow (ByVal hWnd As _Offset)
    Function GetAsyncKeyState% (ByVal vKey As Long)
    Function mmioStringToFOURCC& (sz As String, ByVal uFlags As _Unsigned Long)
    Function OpenClipboard& (ByVal hWndNewOwner As _Offset)
    Function GetClipboardData%& (ByVal uFormat As _Unsigned Long)
    Sub CloseClipboard ()
    Function GlobalLock%& (ByVal hMem As _Offset)
    Function GlobalUnlock& (ByVal hMem As _Offset)

    Function LoadLibrary%& (lpLibFileName As String)
    Function GetProcAddress%& (ByVal hModule As _Offset, lpProcName As String)
End Declare

Declare Library ".\internal\c\c_compiler\include\vfw"
End Declare

Declare Library "framecallback"
End Declare

Dim Shared As _Unsigned Long vidWidth, vidHeight
vidWidth = 640
vidHeight = 480

Screen _NewImage(vidWidth, vidHeight, 32)

_ScreenMove _Middle

Dim As String captureWinText: captureWinText = "Webcam API Test - Child" + Chr$(0)
Dim As _Offset childID


Dim As _Offset childWin: childWin = CreateCaptureWindow(_Offset(captureWinText), 0, 0, 0, vidWidth, vidHeight, _WindowHandle, childID)
_Title "Webcam API Test - Parent"
SetupDriver childWin, _TRUE 'change _FALSE to _TRUE to use default settings

Print "Previewing... Press Space bar to stop"
Print "Press escape to kill the window"
Dim Shared As _MEM frame
Do
    'GetAsyncKeyState is necessary because the capture window steals keyboard focus
    If GetAsyncKeyState(&H20) <> 0 Then Exit Do
    If GetAsyncKeyState(&H1B) <> 0 Then
        KillDriver childWin
        End
    End If
    GrabFrame childWin
    Screen frame.IMAGE
    _Display
    _Limit FPS
Loop
Print "Disconnecting Driver"
KillDriver childWin

_MemFree frame
Sleep

Sub SetupDriver (hwnd As _Offset, defaultSource As _Byte)
    Dim As _Offset libload: libload = LoadLibrary(Command$(0))
    Dim As _Offset myCallback: myCallback = GetProcAddress(libload, "CapVideoCallback")
    Dim As BITMAPINFO format
    'End
    SendMessage hwnd, WM_CAP_DRIVER_CONNECT, 0, 0
    SendMessage hwnd, WM_CAP_SET_SCALE, -1, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEWRATE, 1000 / FPS, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEW, -1, 0

    If defaultSource = _FALSE Then
        SendMessage hwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0 'PICK YUY2!!!
        SendMessage hwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0
    End If
    Dim As _Unsigned Long formatSize: formatSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If Len(format) <> formatSize Then
        KillDriver hwnd
        Print "Wrong size"
        Print formatSize, Len(format)
        End
    End If

    If SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, formatSize, _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Couldn't get format"
        End
    End If
    'bmi.bmiHeader.biWidth = vidWidth
    'bmi.bmiHeader.biHeight = vidHeight
    format.bmiHeader.biBitCount = 16 'yuy2 format
    format.bmiHeader.biCompression = mmioStringToFOURCC("YUY2" + Chr$(0), &H00000010) 'MUST BE YUY2 FORMAT
    If SendMessage(hwnd, WM_CAP_SET_VIDEOFORMAT, formatSize, _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Failed to set video format"
        End
    End If
    SendMessage hwnd, WM_CAP_GET_VIDEOFORMAT, 0, _Offset(format)
    SendMessage hwnd, WM_CAP_SET_CALLBACK_FRAME, 0, myCallback
End Sub

Sub KillDriver (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0
    DestroyWindow hwnd
End Sub

Sub SaveBMP (hwnd As _Offset, filename As String)
    filename = filename + Chr$(0)
    SendMessage hwnd, WM_CAP_FILE_SAVEDIB, 0, _Offset(filename)
End Sub

Sub GrabFrame (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_GRAB_FRAME_NOSTOP, 0, 0
End Sub

Sub GrabFrameToClipboard (hwnd As _Offset, image As _MEM)
    Dim As _Unsigned Long frameSize: frameSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        KillDriver hwnd
        End
    End If
    SendMessage hwnd, WM_CAP_EDIT_COPY, 0, 0
    If OpenClipboard(0) = 0 Then
        Print "Couldn't open clipboard"
        KillDriver hwnd
        End
    End If
    Dim As _Offset hDIB: hDIB = GetClipboardData(CF_DIB)
    If hDIB = 0 Then
        Print "Failed to retrieve bitmap from clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    Dim As _Offset pDIB: pDIB = GlobalLock(hDIB)
    If pDIB = 0 Then
        Print "Failed to lock the clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    CloseClipboard
    Dim As _MEM p: p = _Mem(pDIB, 4)
    Dim As _Unsigned Long biSize: biSize = _MemGet(p, p.OFFSET, _Unsigned Long)
    Dim As _MEM pBI: pBI = _Mem(pDIB, biSize)
    Dim As BITMAPINFO bi
    _MemGet pBI, pBI.OFFSET, bi
    _MemFree pBI
    _MemFree p
    Dim As Long bytesPerPixel: bytesPerPixel = (bi.bmiHeader.biBitCount + 7) \ 8
    Dim As Long stride: stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    Dim As _MEM m: m = _Mem(pDIB + Len(bi), bi.bmiHeader.biSizeImage)
    Dim As Long y, x, t
    Dim As _Unsigned _Byte pixel24(0 To 2)
    Dim As _Unsigned _Byte pixel32(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned Long q
    Dim As Long i: i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i


    If bi.bmiHeader.biHeight > 0 Then
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, Abs(bi.bmiHeader.biHeight) - 1 - y), q
            Next x
        Next y
    Else
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, y), q
            Next
        Next
    End If
    _Dest 0
    image = _MemImage(i)
    Dim As Long a: a = GlobalUnlock(pDIB)
End Sub

Function CapVideoCallback%& (hWnd As _Offset, lpVHdr As _Offset)
    Type VIDEOHDR
        As _Offset lpData
        As _Unsigned Long dwBufferLength, dwBytesUsed, dwTimeCaptured
        As String * 4 padding1
        As _Unsigned _Offset dwUser
        As _Unsigned Long dwFlags
        As String * 4 padding2
        As _Offset dwReserved1, dwReserved2, dwReserved3, dwReserved4
    End Type
    Dim As VIDEOHDR vhdr
    Dim As _MEM pVhdr: pVhdr = _Mem(lpVHdr, Len(vhdr))
    _MemGet pVhdr, pVhdr.OFFSET, vhdr
    _MemFree pVhdr

    Dim As _MEM lpData: lpData = _Mem(vhdr.lpData, vhdr.dwBufferLength)
    If _MemExists(frame) Then _MemFree frame
    Dim As Long bitsPerPixel: bitsPerPixel = 16
    Dim As Long bytesPerPixel: bytesPerPixel = 2
    Dim As Long stride: stride = vidWidth * 2
    Dim As Long y, x
    Dim As _Unsigned _Byte yuy2(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned _Integer64 converted
    Dim As _Unsigned _Byte r, g, b
    Dim As _Offset pixelOffset
    Dim As Long i: i = _NewImage(vidWidth, vidHeight, 32)
    _Dest i
    For y = 0 To vidHeight - 1
        pScanLine = lpData.OFFSET + (y * stride)
        For x = 0 To vidWidth - 2 Step 2
            pixelOffset = pScanLine + (x * 2)
            _MemGet lpData, pixelOffset, yuy2()
            converted = ConvertYUY2toRGB(yuy2())
            PSet (x, y), converted And &HFFFFFFFF
            PSet (x + 1, y), _ShR(converted, 32) And &HFFFFFFFF
        Next
    Next
    _Dest 0
    frame = _MemImage(i)
End Function

Function ConvertYUY2toRGB~&& (yuy2() As _Unsigned _Byte) '(y1 As _Byte, u As _Byte, y2 As _Byte, v As _Byte)
    Dim As _Unsigned _Byte r1, g1, b1, r2, g2, b2
    ' Convert first pixel (Y1)
    r1 = yuy2(0) + 1.13983 * (yuy2(3) - 128)
    g1 = yuy2(0) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128)
    b1 = yuy2(0) + 0.58060 * (yuy2(1) - 128)

    ' Convert second pixel (Y2)
    r2 = yuy2(2) + 1.13983 * (yuy2(3) - 128)
    g2 = yuy2(2) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128)
    b2 = yuy2(2) + 0.58060 * (yuy2(1) - 128)

    ConvertYUY2toRGB = _RGB32(r1, g1, b1) Or _ShL(_RGB32(r2, g2, b2), 32)
End Function

The noticing will continue
Reply
#12
(Today, 06:17 AM)SpriggsySpriggs Wrote:
(Today, 04:11 AM)madscijr Wrote: One thing I was gonna ask was, how might those two dialogs that open at the beginning be auto-filled or otherwise hidden? Windows API functions are so mysterious!
You gotta toggle that _TRUE/_FALSE flag in the SetupDriver call, my dude.

Oooh I gotta try that!

BTW when you say "my dude" like that, in my mind I'm picturing the character Downward Dog from the Dog Man book Fetch 22, good stuff! :p  LoL


[Image: dogman-fetch22-downwarddog-you-were-trippin.png]
Reply
#13
Pssssst https://qb64phoenix.com/forum/showthread...8#pid32368
The noticing will continue
Reply
#14
(10 hours ago)SpriggsySpriggs Wrote: Pssssst https://qb64phoenix.com/forum/showthread...8#pid32368
Yes - soon as we're back to teh 'puter!
Reply
#15
There is a bit of a bug with some calculation. I was only able to get 640 x 480 resolution to work, which probably means I'm not calculating something right. Nonetheless, you should be able to at least see it. I'll keep working on getting it to be compatible with all resolutions.
The noticing will continue
Reply
#16
Fixed the bug. Phew.

Code: (Select All)
Option _Explicit
'$Console
'------------------------------------------------------------------------------------------------------------------------------
'Capture Driver Constants
Const WM_CAP_START = &H0400
Const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
Const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
Const WM_CAP_EDIT_COPY = WM_CAP_START + 30
Const WM_CAP_SET_SCALE = WM_CAP_START + 53
Const WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
Const WM_CAP_SET_PREVIEW = WM_CAP_START + 50
Const WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42
Const WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61
Const WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25
Const WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44
Const WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45
Const WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41
Const WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5
Const CF_DIB = &H0008

Const FPS = 30 'Not recommended to set this higher than frame rate of webcam/video device

Type BITMAPINFOHEADER
    As _Unsigned Long biSize
    As Long biWidth, biHeight
    As _Unsigned Integer biPlanes, biBitCount
    As _Unsigned Long biCompression, biSizeImage
    As Long biXPelsPerMeter, biYPelsPerMeter
    As _Unsigned Long biClrUsed, biClrImportant
End Type

Type BITMAPINFO
    As BITMAPINFOHEADER bmiHeader
End Type

Declare Dynamic Library "Avicap32"
    Function CreateCaptureWindow& Alias "capCreateCaptureWindowA" (ByVal lpszWindowName As _Offset, ByVal dwStyle As _Unsigned Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hwndParent As _Offset, ByVal nId As Long)
End Declare

Declare CustomType Library
    Sub SendMessage Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Function SendMessage& Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Sub DestroyWindow (ByVal hWnd As _Offset)
    Function GetAsyncKeyState% (ByVal vKey As Long)
    Function mmioStringToFOURCC& (sz As String, ByVal uFlags As _Unsigned Long)
    Function OpenClipboard& (ByVal hWndNewOwner As _Offset)
    Function GetClipboardData%& (ByVal uFormat As _Unsigned Long)
    Sub CloseClipboard ()
    Function GlobalLock%& (ByVal hMem As _Offset)
    Function GlobalUnlock& (ByVal hMem As _Offset)

    Function LoadLibrary%& (lpLibFileName As String)
    Function GetProcAddress%& (ByVal hModule As _Offset, lpProcName As String)
End Declare

Declare Library ".\internal\c\c_compiler\include\vfw"
End Declare

Declare Library "framecallback"
End Declare

Dim Shared As _Unsigned Long vidWidth, vidHeight
vidWidth = 640
vidHeight = 480

Screen _NewImage(vidWidth, vidHeight, 32)

_ScreenMove _Middle

Dim As String captureWinText: captureWinText = "Webcam API Test - Child" + Chr$(0)
Dim As _Offset childID


Dim As _Offset childWin: childWin = CreateCaptureWindow(_Offset(captureWinText), 0, 0, 0, vidWidth, vidHeight, _WindowHandle, childID)
_Title "Webcam API Test - Parent"
SetupDriver childWin, _TRUE 'change _FALSE to _TRUE to use default settings

Print "Previewing... Press Space bar to stop"
Print "Press escape to kill the window"
Dim Shared As _MEM frame
Do
    'GetAsyncKeyState is necessary because the capture window steals keyboard focus
    If GetAsyncKeyState(&H20) <> 0 Then Exit Do
    If GetAsyncKeyState(&H1B) <> 0 Then
        KillDriver childWin
        End
    End If
    GrabFrame childWin
    Screen frame.IMAGE
    _Display
    _Limit FPS
Loop
Dim As _MEM clippedFrame
GrabFrameToClipboard childWin, clippedFrame
Print "Disconnecting Driver"
KillDriver childWin

_MemFree frame
_MemFree clippedFrame

Sleep

Sub SetupDriver (hwnd As _Offset, defaultSource As _Byte)
    Dim As _Offset libload: libload = LoadLibrary(Command$(0))
    Dim As _Offset myCallback: myCallback = GetProcAddress(libload, "CapVideoCallback")
    Dim As BITMAPINFO format
    'End
    SendMessage hwnd, WM_CAP_DRIVER_CONNECT, 0, 0
    SendMessage hwnd, WM_CAP_SET_SCALE, -1, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEWRATE, 1000 / FPS, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEW, -1, 0

    Dim As _Unsigned Long formatSize: formatSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If Len(format) <> formatSize Then
        KillDriver hwnd
        Print "Wrong size"
        Print formatSize, Len(format)
        End
    End If

    If SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Couldn't get format"
        End
    End If
    format.bmiHeader.biSize = Len(format)
    format.bmiHeader.biWidth = vidWidth
    format.bmiHeader.biHeight = vidHeight
    format.bmiHeader.biPlanes = 1
    format.bmiHeader.biBitCount = 16 'yuy2 format
    format.bmiHeader.biSizeImage = vidWidth * vidHeight * 2
    format.bmiHeader.biCompression = mmioStringToFOURCC("YUY2" + Chr$(0), &H00000010) 'MUST BE YUY2 FORMAT
    If SendMessage(hwnd, WM_CAP_SET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Failed to set video format"
        End
    End If
    If defaultSource = _FALSE Then
        SendMessage hwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0 'PICK YUY2!!!
        SendMessage hwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0
    End If
    SendMessage hwnd, WM_CAP_GET_VIDEOFORMAT, 0, _Offset(format)
    SendMessage hwnd, WM_CAP_SET_CALLBACK_FRAME, 0, myCallback
End Sub

Sub KillDriver (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0
    DestroyWindow hwnd
End Sub

Sub SaveBMP (hwnd As _Offset, filename As String)
    filename = filename + Chr$(0)
    SendMessage hwnd, WM_CAP_FILE_SAVEDIB, 0, _Offset(filename)
End Sub

Sub GrabFrame (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_GRAB_FRAME_NOSTOP, 0, 0
End Sub

Sub GrabFrameToClipboard (hwnd As _Offset, image As _MEM)
    Dim As _Unsigned Long frameSize: frameSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        KillDriver hwnd
        End
    End If
    SendMessage hwnd, WM_CAP_EDIT_COPY, 0, 0
    If OpenClipboard(0) = 0 Then
        Print "Couldn't open clipboard"
        KillDriver hwnd
        End
    End If
    Dim As _Offset hDIB: hDIB = GetClipboardData(CF_DIB)
    If hDIB = 0 Then
        Print "Failed to retrieve bitmap from clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    Dim As _Offset pDIB: pDIB = GlobalLock(hDIB)
    If pDIB = 0 Then
        Print "Failed to lock the clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    CloseClipboard
    Dim As _MEM p: p = _Mem(pDIB, 4)
    Dim As _Unsigned Long biSize: biSize = _MemGet(p, p.OFFSET, _Unsigned Long)
    Dim As _MEM pBI: pBI = _Mem(pDIB, biSize)
    Dim As BITMAPINFO bi
    _MemGet pBI, pBI.OFFSET, bi
    _MemFree pBI
    _MemFree p
    Dim As Long bytesPerPixel: bytesPerPixel = (bi.bmiHeader.biBitCount + 7) \ 8
    Dim As Long stride: stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    Dim As _MEM m: m = _Mem(pDIB + Len(bi), bi.bmiHeader.biSizeImage)
    Dim As Long y, x, t
    Dim As _Unsigned _Byte pixel24(0 To 2)
    Dim As _Unsigned _Byte pixel32(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned Long q
    Dim As Long i: i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i


    If bi.bmiHeader.biHeight > 0 Then
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, Abs(bi.bmiHeader.biHeight) - 1 - y), q
            Next x
        Next y
    Else
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, y), q
            Next
        Next
    End If
    _Dest 0
    image = _MemImage(i)
    Dim As Long a: a = GlobalUnlock(pDIB)
End Sub

Function CapVideoCallback%& (hWnd As _Offset, lpVHdr As _Offset)
    Type VIDEOHDR
        As _Offset lpData
        As _Unsigned Long dwBufferLength, dwBytesUsed, dwTimeCaptured
        As String * 4 padding1
        As _Unsigned _Offset dwUser
        As _Unsigned Long dwFlags
        As String * 4 padding2
        As _Offset dwReserved1, dwReserved2, dwReserved3, dwReserved4
    End Type
    Dim As VIDEOHDR vhdr
    Dim As _MEM pVhdr: pVhdr = _Mem(lpVHdr, Len(vhdr))
    _MemGet pVhdr, pVhdr.OFFSET, vhdr
    _MemFree pVhdr
    Dim As _Unsigned Long frameSize: frameSize = SendMessage(hWnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        'KillDriver hWnd
        'End
    End If
    Dim As BITMAPINFO bi
    SendMessage hWnd, WM_CAP_GET_VIDEOFORMAT, frameSize, _Offset(bi)
    'Print bi.bmiHeader.biHeight
    Dim As _MEM lpData: lpData = _Mem(vhdr.lpData, vhdr.dwBufferLength)
    If _MemExists(frame) Then _MemFree frame
    Dim As Long bitsPerPixel: bitsPerPixel = bi.bmiHeader.biBitCount
    Dim As Long bytesPerPixel: bytesPerPixel = (bitsPerPixel + 7) \ 8
    Dim As _Unsigned Long stride: stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    Dim As Long y, x
    Dim As _Unsigned _Byte yuy2(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned _Integer64 converted
    Dim As _Unsigned _Byte r, g, b
    Dim As _Offset pixelOffset
    Dim As Long i: i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i
    For y = 0 To bi.bmiHeader.biHeight - 1
        pScanLine = lpData.OFFSET + (y * stride)
        For x = 0 To bi.bmiHeader.biWidth - bytesPerPixel Step bytesPerPixel
            pixelOffset = pScanLine + (x * bytesPerPixel)
            _MemGet lpData, pixelOffset, yuy2()
            converted = ConvertYUY2toRGB(yuy2())
            PSet (x, y), converted And &HFFFFFFFF
            PSet (x + 1, y), _ShR(converted, 32) And &HFFFFFFFF
        Next
    Next
    _Dest 0
    frame = _MemImage(i)
End Function

Function ConvertYUY2toRGB~&& (yuy2() As _Unsigned _Byte) '(y1 As _Byte, u As _Byte, y2 As _Byte, v As _Byte)
    Dim As _Unsigned _Byte r1, g1, b1, r2, g2, b2
    ' Convert first pixel (Y1)
    r1 = _Clamp(yuy2(0) + 1.13983 * (yuy2(3) - 128), 0, 255)
    g1 = _Clamp(yuy2(0) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128), 0, 255)
    b1 = _Clamp(yuy2(0) + 0.58060 * (yuy2(1) - 128), 0, 255)

    ' Convert second pixel (Y2)
    r2 = _Clamp(yuy2(2) + 1.13983 * (yuy2(3) - 128), 0, 255)
    g2 = _Clamp(yuy2(2) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128), 0, 255)
    b2 = _Clamp(yuy2(2) + 0.58060 * (yuy2(1) - 128), 0, 255)

    ConvertYUY2toRGB = _RGB32(r1, g1, b1) Or _ShL(_RGB32(r2, g2, b2), 32)
End Function
   


Attached Files
.h   framecallback.h (Size: 355 bytes / Downloads: 4)
The noticing will continue
Reply
#17
(10 hours ago)SpriggsySpriggs Wrote: You need to download framecallback.h (Size: 355 bytes / Downloads: 0)
(10 hours ago)SpriggsySpriggs Wrote: There is a bit of a bug with some calculation. I was only able to get 640 x 480 resolution to work, which probably means I'm not calculating something right. Nonetheless, you should be able to at least see it. I'll keep working on getting it to be compatible with all resolutions.
(9 hours ago)SpriggsySpriggs Wrote: Fixed the bug. Phew.
Big improvement - thank you! I merged your changes into my code (no small feat, LoL) and it works!

And so, here is Webcam Pong v0.04 which no longer opens those pesky dialogs, thanks to @SpriggsySpriggs:

Code: (Select All)
' Webcam Pong by Madscijr (v0.04)
' Thanks to SpriggsySpriggs for figuring out how to program the webcam.

' HOW TO RUN:
' Download "framecallback.h" from
'     https://qb64phoenix.com/forum/attachment.php?aid=4150
' and place it in the same folder as the source code.

' TODO:
' * Auto-fill & hide the Video Format & Video Source forms that open in the beginning
' * Enable clipboard while the program is running?
' * Enable program to lose and regain focus gracefully?

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' USB Camera? Reply #26 > Help Me! > Code and Stuff > QB64 Rising > QB64 Phoenix Edition
' https://qb64phoenix.com/forum/showthread.php?tid=1163&pid=32337#pid32337
' From: SpriggsySpriggs, Professional Noticer
' Date: 2/26/2025 2:48 PM
' Now you can export straight to memory, mi amigo.
' It should be noted that it has to export to clipboard first and then load to memory.
' So your clipboard will contain the image as well. But that's the nature of the beast.
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' USB Camera? Reply #28 > Help Me! > Code and Stuff > QB64 Rising > QB64 Phoenix Edition
' https://qb64phoenix.com/forum/showthread.php?tid=1163&pid=32339#pid32339
' From: SpriggsySpriggs, Professional Noticer
' Date: 2/26/2025 4:01 PM
' Q: I wonder if there's a way to change camera resolution at the API or software level?
' A: YES! If you want to change the settings first, use this code:
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' Webcam Pong v0.01 > Reply #11 - Webcam Pong v0.01 > Works in Progress > Code and Stuff > QB64 Rising > QB64 Phoenix Edition
' https://qb64phoenix.com/forum/showthread.php?tid=3497&pid=32368#pid32368
' From: SpriggsySpriggs, Professional Noticer
' Date: 2/28/2025 9:12 AM
' Don't let your dreams be dreams.
' -Shia TheBeef
' You need to download framecallback.h (Size: 355 bytes / Downloads: 0)
' https://qb64phoenix.com/forum/attachment.php?aid=4150
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' Webcam Pong v0.01 > Reply #16 - Webcam Pong v0.01 > Works in Progress > Code and Stuff > QB64 Rising > QB64 Phoenix Edition
' https://qb64phoenix.com/forum/showthread.php?tid=3497&pid=32373#pid32373
' From: SpriggsySpriggs, Professional Noticer
' Date: 2/28/2025 8:55 AM
' Fixed the bug. Phew.
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

Option _Explicit
_Title "Webcam API Test - Parent"

' ================================================================================================================================================================
' CONSTANTS
' ================================================================================================================================================================
Const FPS = 30 ' Not recommended to set this higher than frame rate of webcam/video device

Const MOVEMENT_THRESHOLD = 160

Const CAMERA_WIDTH = 320
Const CAMERA_HEIGHT = 240
Const DETECT_WIDTH = 40 ' # pixels from right/left edges of screen to scan for movement

Const WS_VISIBLE = &H10000000 '  The window is initially visible.
Const WS_MAXIMIZE = &H1000000 ' The window is initially maximized.
Const WS_MINIMIZE = &H20000000 ' The window is initially minimized.

Const CF_DIB = &H0008

' Capture Driver Constants
Const WM_CAP_START = &H0400
Const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
Const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
Const WM_CAP_EDIT_COPY = WM_CAP_START + 30
Const WM_CAP_SET_SCALE = WM_CAP_START + 53
Const WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
Const WM_CAP_SET_PREVIEW = WM_CAP_START + 50
Const WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42
Const WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61
Const WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25
Const WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44
Const WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45
Const WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41
Const WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5
Const CF_DIB = &H0008

Const LONG_MIN = -2147483648
Const LONG_MAX = 2147483647

' ================================================================================================================================================================
' UDTs
' ================================================================================================================================================================
Type BITMAPINFOHEADER
    As _Unsigned Long biSize
    As Long biWidth, biHeight
    As _Unsigned Integer biPlanes, biBitCount
    As _Unsigned Long biCompression, biSizeImage
    As Long biXPelsPerMeter, biYPelsPerMeter
    As _Unsigned Long biClrUsed, biClrImportant
End Type

Type BITMAPINFO
    As BITMAPINFOHEADER bmiHeader
End Type

' HOLDS DISPLAY LAYERS (ALL NEED _FREEIMAGE WHEN PROGRAM ENDS)
Type GraphicsType
    imgBackground As Long ' background image
    imgCamera As Long
    imgWalls As Long
    imgStaticText As Long '
    imgDynamicText As Long
    imgSprites As Long
End Type ' GraphicsType

' HOLDS VALUES FOR OPERATING WEBCAM
Type WebcamType
    captureWinText As String
    childID As _Offset
    videoWidth As _Unsigned Long
    videoHeight As _Unsigned Long
    childWin As _Offset
    'm As _MEM
    frame As _MEM
    CaptureWindowStyle As _Unsigned Long
End Type ' WebcamType

' HOLDS PLAYER COORDINATES AND INFO
Type PlayerType
    color As _Unsigned Long
    x As Long
    y As Long
    width As Integer
    height As Integer
    score As Integer
    Visible As Integer
End Type ' PlayerType

' HOLDS BALL COORDINATES AND INFO
Type BallType
    color As _Unsigned Long
    size As Integer
    x As Long
    y As Long
    dx As Integer
    dy As Integer
    Visible As Integer
End Type ' BallType

' HOLDS RGB VALUES FOR A PIXEL
Type PixelType
    r As Long
    g As Long
    b As Long
End Type ' PixelType

' ================================================================================================================================================================
' API DEFINITIONS
' ================================================================================================================================================================
Declare CustomType Library
    Sub SendMessage Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Function SendMessage& Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Sub DestroyWindow (ByVal hWnd As _Offset)
    Function GetAsyncKeyState% (ByVal vKey As Long)
    Function mmioStringToFOURCC& (sz As String, ByVal uFlags As _Unsigned Long)
    Function OpenClipboard& (ByVal hWndNewOwner As _Offset)
    Function GetClipboardData%& (ByVal uFormat As _Unsigned Long)
    Sub CloseClipboard ()
    Function GlobalLock%& (ByVal hMem As _Offset)
    Function GlobalUnlock& (ByVal hMem As _Offset)
    Function LoadLibrary%& (lpLibFileName As String)
    Function GetProcAddress%& (ByVal hModule As _Offset, lpProcName As String)
End Declare

Declare Dynamic Library "Avicap32"
    Function CreateCaptureWindow& Alias "capCreateCaptureWindowA" (ByVal lpszWindowName As _Offset, ByVal dwStyle As _Unsigned Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hwndParent As _Offset, ByVal nId As Long)
End Declare

Declare Library ".\internal\c\c_compiler\include\vfw"
End Declare

Declare Library "framecallback"
End Declare

' ================================================================================================================================================================
' GLOBAL VARIABLES
' ================================================================================================================================================================
' BASIC PROGRAM METADATA
Dim Shared m_ProgramPath$: m_ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
Dim Shared m_ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)

' GLOBAL VARIABLES TO TRACK ERROR STATE
Dim Shared m_ErrorMessage As String: m_ErrorMessage = ""
'Dim Shared m_RoutineName As String: m_RoutineName = ""
'Dim Shared m_LastStatement As String: m_LastStatement = ""

' GLOBAL VARIABLES FOR SCREEN
Dim Shared m_iScreenWidth As Long: m_iScreenWidth = 1024 ' _DesktopWidth
Dim Shared m_iScreenHeight As Long: m_iScreenHeight = 768 ' _DesktopHeight
Dim Shared m_iTextRows As Long: m_iTextRows = (m_iScreenHeight / _FontHeight)
Dim Shared m_iTextCols As Long: m_iTextCols = (m_iScreenWidth / _FontWidth)
Dim Shared PLAYER_HEIGHT As Long: PLAYER_HEIGHT = 32
Dim Shared MIN_Y As Long: MIN_Y = 44 + 1
Dim Shared MAX_Y As Long: MAX_Y = (m_iScreenHeight - 54) - (PLAYER_HEIGHT + 1)

' GLOBAL VARIABLES FOR WEBCAM
Dim Shared WebCam As WebcamType

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' EXECUTION STARTS HERE!
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' START THE MAIN ROUTINE
main

' FINISH
Print m_ProgramName$ + " finished."

'End
System ' return control to the operating system

' ================================================================================================================================================================
' ROUTINES
' ================================================================================================================================================================

' /////////////////////////////////////////////////////////////////////////////
' MAIN PROGRAM

Sub main
    Dim Graph1 As GraphicsType
    ReDim arrPlayer(1 To 2) As PlayerType ' 1 = left, 2 = right
    ReDim arrComparePixels(CAMERA_WIDTH, CAMERA_HEIGHT) As PixelType
    Dim Ball1 As BallType
    Dim x, y As Integer
    Dim iValue As Integer
    Dim IsCalibrating As Integer
    Dim ShowCamera As Integer
   
    ' INIT SCREEN
    Screen _NewImage(m_iScreenWidth, m_iScreenHeight, 32)
    _ScreenMove 0, 0
   
    ' Show initial detailed instructions
    Cls
    Print "Webcam Pong, a 'computer vision' experiment, v0.01"
    Print "by madscijr, webcam code by spriggsyspriggs"
    Print
    Print "At 'Video Format' popup, values should say:"
    Print "    Resolution                        : 320x240"
    Print "    Pixel Depth (bits) and Compression: YUY2"
    Print "    Size (bytes)                      : 153600"
    Print "Do not change these values, just click [OK]"
    Print
    Print "At 'Video Source' popup, values should say:"
    Print "    Select a Video Device: {your camera name}"
    Print "Click [OK]"
    Print
    Print "1 window should open:"
    Print "    Webcam API Test - Parent"
    Print
    Print "And this window hidden (maybe visible in Task Manager):"
    Print "    Webcam API Test - Child"
    Print
    Print "Point webcam at an empty room or down at a blank sheet of paper,"
    Print "away from you and your friends, and anything that's moving."
    Print "Press Enter to 'calibrate', where program memorizes the room."
    Print "Enter also toggles the camera picture on and off,"
    Print "with the camera on, you can see yourself moving around."
    Print "Try moving something up and down along the left half of the screen,"
    Print "that controls the left paddle. Right half of screen = right paddle."
    Print
    Print "Per SpriggsySpriggs:"
    Print "DO NOT GO CLICKING ALL WILLY-NILLY ON THE PROGRAM WINDOW,"
    Print "ESPECIALLY IF YOUR CURSOR HAS THE LOADING CIRCLE/HOURGLASS."
    Print "For some reason, this will cause it to freeze and crash."
    Print "Why? 'cause Microsoft, that's why."
    Print
    Print "Also note that this version has the following QUIRKS while running:"
    Print "* clipboard is not available in other programs"
    Print "* when changing focus to another program, to switch back, you must minimize the other program for this to be visible again"
    Print
    Print "Press any key to start..."
    Sleep
    _KeyClear: '_DELAY 1 ' clear keyboard buffer

    ' CLEAR THE SCREEN
    _Dest 0: Cls , cEmpty
    _Display ' update screen with changes & wait for next update

    ' INITIALIZE SCREEN
    InitGraphicLayers Graph1
    DrawInstructionsLayer Graph1

    ' SET LOWER CAMERA RESOLUTION
    WebCam.videoWidth = CAMERA_WIDTH
    WebCam.videoHeight = CAMERA_HEIGHT

    ' CREATE CAPTURE WINDOW (HIDDEN)
    WebCam.captureWinText = "Webcam API Test - Child" + Chr$(0)
    WebCam.CaptureWindowStyle = WS_MINIMIZE ' WS_VISIBLE
    'WebCam.childWin = CreateCaptureWindow(_Offset(WebCam.captureWinText), WebCam.CaptureWindowStyle, 0, 0, WebCam.videoWidth, WebCam.videoHeight, _WindowHandle, WebCam.childID)
    WebCam.childWin = CreateCaptureWindow(_Offset(WebCam.captureWinText), 0, 0, 0, WebCam.videoWidth, WebCam.videoHeight, _WindowHandle, WebCam.childID)

    ' Initialize the driver
    'SetupDriver WebCam.childWin, WebCam.videoWidth, WebCam.videoHeight, _FALSE ' change _FALSE to _TRUE to use default settings
    SetupDriver WebCam.childWin, WebCam.videoWidth, WebCam.videoHeight, _TRUE ' change _FALSE to _TRUE to use default settings
   
    ' INIT PLAYERS
    arrPlayer(1).color = cRed
    arrPlayer(1).x = 64
    arrPlayer(1).y = m_iScreenHeight / 2
    arrPlayer(1).width = 8
    arrPlayer(1).height = 32
    arrPlayer(1).score = 0
    arrPlayer(1).Visible = _FALSE
   
    arrPlayer(2).color = cBlue
    arrPlayer(2).x = m_iScreenWidth - 64
    arrPlayer(2).y = m_iScreenHeight / 2
    arrPlayer(2).width = 8
    arrPlayer(2).height = 32
    arrPlayer(2).score = 0
    arrPlayer(2).Visible = _FALSE
   
    ' INIT BALL
    Ball1.color = cWhite
    Ball1.size = 8
    Ball1.x = m_iScreenWidth / 2
    Ball1.y = m_iScreenHeight / 2
    Ball1.Visible = _FALSE
   
    ' RANDOM BALL DIRECTION
    Randomize Timer
    If RandomNumber%(1, 2) = 1 Then
        Ball1.dx = -1
    Else
        Ball1.dx = 1
    End If
    iValue = RandomNumber%(1, 3)
    If iValue = 1 Then
        Ball1.dy = -1
    Else
        If iValue = 2 Then
            Ball1.dy = 0
        Else
            Ball1.dy = 1
        End If
    End If
   
    ' Capture a single frame to window WebCam.childWin
    IsCalibrating = _TRUE
    ShowCamera = _TRUE
    Do
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' READ KEYBOARD
        ' GetAsyncKeyState is necessary because the capture window steals keyboard focus
       
        ' ENTER = RECALIBRATE + SHOW CAMERA FRAME
        ' PRESSING  ENTER AGAIN HIDES CAMERA FRAME
        If GetAsyncKeyState(&H0D) <> 0 Then ' user pressed Enter
            If ShowCamera = _FALSE Then
                ShowCamera = _TRUE
                IsCalibrating = _TRUE
            Else
                ShowCamera = _FALSE
            End If
        End If
       
        ' ESC = QUIT
        If GetAsyncKeyState(&H1B) <> 0 Then ' user pressed Escape
            KillDriver WebCam.childWin
            Exit Do
        End If
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' CAPTURE FRAME WITH WEBCAM
       
        ' Get still picture from camera to frame
        GrabFrame WebCam.childWin
       
        '' Copy frame from WebCam.childWin from clipboard to image in memory
        'GrabFrameToClipboard WebCam.childWin, WebCam.frame
       
        ' Remember background (to compare to detect movement)
        If IsCalibrating = _TRUE Then
            SaveFrame WebCam.frame.IMAGE, arrComparePixels()
            IsCalibrating = _FALSE
        End If
       
        ' Detect movement with the camera (computer vision)
        DetectMovement WebCam.frame.IMAGE, arrComparePixels(), arrPlayer()
       
        ' Display the image
        DrawCameraLayer Graph1, WebCam.frame.IMAGE, ShowCamera
        'Screen WebCam.frame.IMAGE
       
        ' Draw walls layer
        DrawWallsLayer Graph1
       
        ' Draw sprites layer
        DrawSpritesLayer Graph1, arrPlayer(), Ball1
       
        ' Draw
        DrawDynamicTextLayer Graph1, arrPlayer(), ShowCamera
       
        ' COPY LAYERS TO SCREEN AND UPDATE DISPLAY
        RenderScreen Graph1
       
        ' Keep at steady frames per second
        _Limit FPS
    Loop
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay
    Cls
   
    ' Disconnect driver
    Print "Disconnecting driver"
    KillDriver WebCam.childWin
   
    ' Free memory
    Print "Freeing memory"
    _MemFree WebCam.frame
   
    ' Free images
    Print "Freeing images"
    FreeImage Graph1.imgBackground
    FreeImage Graph1.imgCamera
    FreeImage Graph1.imgWalls
    FreeImage Graph1.imgStaticText
    FreeImage Graph1.imgDynamicText
    FreeImage Graph1.imgSprites
   
    '' RESUME NORMAL ERROR TRAPPING
    'On Error GoTo 0
   
End Sub ' main

' /////////////////////////////////////////////////////////////////////////////
' COMPARE PIXELS IN CAMERA FRAME AGAINST WHAT IT SAW AT BEGINNING

Sub DetectMovement (imgCamera As Long, arrComparePixels() As PixelType, arrPlayer() As PlayerType)
    Dim iPlayer As Integer
    Dim x As Integer
    Dim y As Integer
    Dim c&
    Dim dr&, dg&, db&
   
    ' LOOK FOR PLAYER 1 IN LEFT HALF OF CAMERA FRAME
    iPlayer = 1
    arrPlayer(iPlayer).Visible = _FALSE
    _Source imgCamera
    'For x = (CAMERA_WIDTH - 1) To (CAMERA_WIDTH / 2) Step -1
    For x = (CAMERA_WIDTH - 1) To (CAMERA_WIDTH - 1) - DETECT_WIDTH Step -1
        For y = 1 To (CAMERA_HEIGHT - 1)
            ' COMPARE AGAINST ORIGINAL PIXEL
            c& = Point(x, y)
            dr& = Abs(arrComparePixels(x, y).r - _Red32(c&))
            dg& = Abs(arrComparePixels(x, y).g - _Green32(c&))
            db& = Abs(arrComparePixels(x, y).b - _Blue32(c&))
            '_Alpha32(c&)
           
            ' DID IT CHANGE ENOUGH?
            If (dr& + dg& + db&) > MOVEMENT_THRESHOLD Then
                'arrPlayer(iPlayer).x = x
                arrPlayer(iPlayer).y = y * (m_iScreenHeight / CAMERA_HEIGHT)
               
                'NO:
                '' Y has to be reversed (up is down & down is up)
                'arrPlayer(iPlayer).y = m_iScreenHeight - (y * (m_iScreenHeight / CAMERA_HEIGHT))
               
                ' Keep within boundaries
                If arrPlayer(iPlayer).y < MIN_Y Then
                    arrPlayer(iPlayer).y = MIN_Y
                ElseIf arrPlayer(iPlayer).y > MAX_Y Then
                    arrPlayer(iPlayer).y = MAX_Y
                End If
               
                ' Done looking
                arrPlayer(iPlayer).Visible = _TRUE
                Exit For
            End If
        Next y
        If arrPlayer(iPlayer).Visible = _TRUE Then
            Exit For
        End If
    Next x
   
    ' LOOK FOR PLAYER 2 IN RIGHT HALF OF CAMERA FRAME
    iPlayer = 2
    arrPlayer(iPlayer).Visible = _FALSE
    _Source imgCamera
    'For x = 1 To (CAMERA_WIDTH / 2)
    For x = 1 To DETECT_WIDTH
        For y = 1 To (CAMERA_HEIGHT - 1)
            ' COMPARE AGAINST ORIGINAL PIXEL
            c& = Point(x, y)
            dr& = Abs(arrComparePixels(x, y).r - _Red32(c&))
            dg& = Abs(arrComparePixels(x, y).g - _Green32(c&))
            db& = Abs(arrComparePixels(x, y).b - _Blue32(c&))
            '_Alpha32(c&)
           
            ' DID IT CHANGE ENOUGH?
            If (dr& + dg& + db&) > MOVEMENT_THRESHOLD Then
                'arrPlayer(iPlayer).x = x
                arrPlayer(iPlayer).y = y * (m_iScreenHeight / CAMERA_HEIGHT)
               
                'NO:
                '' Y has to be reversed (up is down & down is up)
                'arrPlayer(iPlayer).y = m_iScreenHeight - (y * (m_iScreenHeight / CAMERA_HEIGHT))
               
                ' Keep within boundaries
                If arrPlayer(iPlayer).y < MIN_Y Then
                    arrPlayer(iPlayer).y = MIN_Y
                ElseIf arrPlayer(iPlayer).y > MAX_Y Then
                    arrPlayer(iPlayer).y = MAX_Y
                End If
               
                ' Done looking
                arrPlayer(iPlayer).Visible = _TRUE
                Exit For
            End If
        Next y
        If arrPlayer(iPlayer).Visible = _TRUE Then
            Exit For
        End If
    Next x
   
End Sub ' DetectMovement

' /////////////////////////////////////////////////////////////////////////////
' SAVE RGB OF EACH PIXEL CAMERA SEES BEFORE PLAYERS USE HANDS/LASERS

Sub SaveFrame (imgCamera As Long, arrComparePixels() As PixelType)
    Dim x As Integer
    Dim y As Integer
    Dim c&, r&, g&, b&
    'Dim a&
   
    _Source imgCamera
    For y = 1 To CAMERA_HEIGHT
        For x = 1 To CAMERA_WIDTH
            c& = Point(x, y)
            arrComparePixels(x, y).r = _Red32(c&)
            arrComparePixels(x, y).g = _Green32(c&)
            arrComparePixels(x, y).b = _Blue32(c&)
            'arrComparePixels(x, y).a = _Alpha32(c&)
        Next x
    Next y
End Sub ' SaveFrame

' /////////////////////////////////////////////////////////////////////////////

Sub InitGraphicLayers (Graph1 As GraphicsType)
    ' BACKGROUND = SOLID BLACK
    InitImage Graph1.imgBackground, m_iScreenWidth, m_iScreenHeight, cBlack
   
    ' CAMERA LAYER
    InitImage Graph1.imgCamera, m_iScreenWidth, m_iScreenHeight, cEmpty
   
    ' WALLS LAYER
    InitImage Graph1.imgWalls, m_iScreenWidth, m_iScreenHeight, cEmpty
   
    ' STATIC TEXT (TITLE, INSTRUCTIONS, ETC.)
    InitImage Graph1.imgStaticText, m_iScreenWidth, m_iScreenHeight, cEmpty
   
    ' CONSTANTLY UPDATED TEXT (SCORE, LIVES, ETC.)
    InitImage Graph1.imgDynamicText, m_iScreenWidth, m_iScreenHeight, cEmpty
   
    ' HOLDS SPRITES
    InitImage Graph1.imgSprites, m_iScreenWidth, m_iScreenHeight, cEmpty
End Sub ' InitGraphicLayers

' /////////////////////////////////////////////////////////////////////////////

Sub InitImage (ThisImage&, iWidth&, iHeight&, bgColor~&)
    If ThisImage& = -1 Or ThisImage& = 0 Then
        ThisImage& = _NewImage(iWidth&, iHeight&, 32)
        _Dest ThisImage&: Cls , bgColor~&
    End If
End Sub ' InitImage

' /////////////////////////////////////////////////////////////////////////////

Sub FreeImage (ThisImage&)
    If ThisImage& < -1 Or ThisImage& > 0 Then _FreeImage ThisImage&
End Sub ' FreeImage

' /////////////////////////////////////////////////////////////////////////////

Sub DrawCameraLayer (Graph1 As GraphicsType, imgCamera As Long, ShowCamera As Integer)
    Dim dx1, dx2, dy1, dy2 As Integer
   
    _Dest Graph1.imgCamera: Cls , cEmpty
    If ShowCamera = _TRUE Then
        ' Camera image is flipped horizontally (like looking in a mirror)
        '_PutImage ((m_iScreenWidth - CAMERA_WIDTH) / 2, (m_iScreenHeight - CAMERA_HEIGHT) / 2), imgCamera
       
        ' Per: https://qb64phoenix.com/qb64wiki/index.php/PUTIMAGE
        ' To flip the image on the x axis, swap the dx coordinate values:
        ' e.g., _PUTIMAGE (dx2, dy1)-(dx1, dy2), source_handle, dest_handle
        dx1 = (m_iScreenWidth - CAMERA_WIDTH) / 2
        dy1 = (m_iScreenHeight - CAMERA_HEIGHT) / 2
        dx2 = dx1 + CAMERA_WIDTH
        dy2 = dy1 + CAMERA_HEIGHT
        _PutImage (dx2, dy1)-(dx1, dy2), imgCamera, Graph1.imgCamera
       
    End If
End Sub ' DrawCameraLayer

' /////////////////////////////////////////////////////////////////////////////
' DRAW THE INSTRUCTIONS LAYER

Sub DrawInstructionsLayer (Graph1 As GraphicsType)
    Dim sMessage As String
    Dim iCol As Integer
   
    _Dest Graph1.imgStaticText: Cls , cEmpty
   
    ' ROW 1: TITLE
    'Color cWhite, cEmpty
    sMessage = "Webcam computer vision Pong"
    iCol = (m_iTextCols / 2) - (Len(sMessage) / 2) ' CENTERED TEXT
    PrintTitle 1, iCol, sMessage
   
    ' ROW 47: INSTRUCTIONS
    iCol = 1
    sMessage = "POINT WEBCAM AT EMPTY ROOM OR WHITE SHEET OF PAPER"
    'iCol = (((m_iTextCols / 3) / 2) - (Len(sMessage) / 2)) ' LEFT THIRD
    Color cBlack, cWhite
    Locate 47, iCol: Print sMessage;
   
    ' ROW 48: INSTRUCTIONS
    iCol = 1
    sMessage = "PRESS ENTER TO CALIBRATE + SHOW/HIDE CAMERA"
    'iCol = (((m_iTextCols / 3) / 2) - (Len(sMessage) / 2)) ' LEFT THIRD
    Color cBlack, cLime
    Locate 48, iCol: Print sMessage;
   
    iCol = iCol + Len(sMessage) + 2
    sMessage = "MOVE HAND UP & DOWN ALONG LEFT AND RIGHT EDGES"
    'iCol = ((m_iTextCols / 2) - (Len(sMessage) / 2)) ' MIDDLE THIRD
    Color cWhite, cRed
    Locate 48, iCol: Print sMessage;
   
    iCol = iCol + Len(sMessage) + 2
    sMessage = "PRESS [ESC] TO EXIT"
    'iCol = (m_iTextCols - ((m_iTextCols / 3) / 2)) - (Len(sMessage) / 2) ' RIGHT THIRD
    Color cWhite, cBlue
    Locate 48, iCol: Print sMessage;
   
End Sub ' DrawInstructionsLayer

' /////////////////////////////////////////////////////////////////////////////
' DRAW NON-MOVING STUFF

Sub DrawWallsLayer (Graph1 As GraphicsType)
    Dim iLoop As Integer
    Dim x1, y1, x2, y2 As Integer
   
    ' CLEAR LAYER
    _Dest Graph1.imgWalls: Cls , cEmpty
   
    ' DRAW WALLS
    x1 = 1: y1 = 40: x2 = m_iScreenWidth: y2 = 44
    Line (x1, y1)-(x2, y2), cWhite, BF
    x1 = 1: y1 = m_iScreenHeight - 54: x2 = m_iScreenWidth: y2 = m_iScreenHeight - 50
    Line (x1, y1)-(x2, y2), cWhite, BF
   
    ' DRAW THE NET
    For iLoop = 44 To (m_iScreenHeight - 48) Step 20
        Line ((m_iScreenWidth / 2) - 2, iLoop)-((m_iScreenWidth / 2) + 2, iLoop + 10), cWhite, BF
    Next iLoop
End Sub ' DrawWallsLayer

' /////////////////////////////////////////////////////////////////////////////
' DRAW THE SCORE LAYER
' TEXT SCREEN AT 1024 x  768 = 128 x 48 (m_iTextCols x m_iTextRows)

Sub DrawDynamicTextLayer (Graph1 As GraphicsType, arrPlayer() As PlayerType, ShowCamera As Integer)
    Dim y
   
    _Dest Graph1.imgDynamicText: Cls , cEmpty
   
    ' SHOW SCORES
    Color cCyan, cEmpty: Locate 2, 32
    Print _Trim$(Str$(arrPlayer(1).score))
   
    Color cLightPink, cEmpty: Locate 2, 96
    Print _Trim$(Str$(arrPlayer(2).score))
   
    ' SHOW POSITIONS
    If arrPlayer(1).Visible = _TRUE Then
        Color cCyan, cEmpty: Locate 37, 32
        Print "Player 1: x=" + _Trim$(Str$(arrPlayer(1).x)) + " y=" + _Trim$(Str$(arrPlayer(1).y))
    End If
    If arrPlayer(2).Visible = _TRUE Then
        Color cLightPink, cEmpty: Locate 37, 96
        Print "Player 2: x=" + _Trim$(Str$(arrPlayer(2).x)) + " y=" + _Trim$(Str$(arrPlayer(2).y))
    End If
   
    ' SHOW OTHER
    If ShowCamera = _TRUE Then
        'y = ( ( (m_iScreenHeight - CAMERA_HEIGHT) / 2 ) / _FontHeight) - _FontHeight
        'color cBlack, cYellow : Locate y, 60
        Color cBlack, cYellow: Locate 16, 60
        Print "ShowCamera = _TRUE"
    End If
End Sub ' DrawDynamicTextLayer

' /////////////////////////////////////////////////////////////////////////////
' DRAW SPRITES (PLAYERS + BALL)

Sub DrawSpritesLayer (Graph1 As GraphicsType, arrPlayer() As PlayerType, Ball1 As BallType)
    _Dest Graph1.imgSprites: Cls , cEmpty
   
    ' If players are visible draw a box at the locations
    If arrPlayer(1).Visible = _TRUE Then
        DrawRectSolid arrPlayer(1).x, arrPlayer(1).y, arrPlayer(1).width, arrPlayer(1).height, arrPlayer(1).color
    End If
    If arrPlayer(2).Visible = _TRUE Then
        DrawRectSolid arrPlayer(2).x, arrPlayer(2).y, arrPlayer(2).width, arrPlayer(2).height, arrPlayer(2).color
    End If
   
    ' If ball is visible, draw it
    If Ball1.Visible = _TRUE Then
        DrawRectSolid Ball1.x, Ball1.y, Ball1.size, Ball1.size, Ball1.color
    End If
End Sub ' DrawSpritesLayer

' /////////////////////////////////////////////////////////////////////////////
' COPY LAYERS TO SCREEN AND UPDATE DISPLAY

Sub RenderScreen (Graph1 As GraphicsType)
    ' CLEAR THE SCREEN
    _Dest 0: Cls , cEmpty
   
    ' Add the background
    _PutImage , Graph1.imgBackground, 0
   
    ' Add the camera image
    _PutImage , Graph1.imgCamera, 0
   
    ' Add the walls
    _PutImage , Graph1.imgWalls, 0
   
    ' Add the instructions
    _PutImage , Graph1.imgStaticText, 0
   
    ' Add the score
    _PutImage , Graph1.imgDynamicText, 0
   
    ' Add the sprites
    _PutImage , Graph1.imgSprites, 0
   
    ' update screen with changes
    _Display
End Sub ' RenderScreen

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE (SOLID)

Sub DrawRectSolid (iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + iSizeW, iY + iSizeH), fgColor, BF ' Draw a solid rectangle
End Sub ' DrawRectSolid

' /////////////////////////////////////////////////////////////////////////////

Sub GrabFrame (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_GRAB_FRAME_NOSTOP, 0, 0
End Sub ' GrabFrame

' /////////////////////////////////////////////////////////////////////////////

Sub GrabFrameToClipboard (hwnd As _Offset, image As _MEM)
    Dim As _Unsigned Long frameSize
    Dim As _Offset hDIB
    Dim As _Offset pDIB
    Dim As _MEM p
    Dim As _Unsigned Long biSize
    Dim As _MEM pBI
    Dim As BITMAPINFO bi
    Dim As Long bytesPerPixel
    Dim As Long stride
    Dim As _MEM m
    Dim As Long y, x, t
    Dim As _Unsigned _Byte pixel24(0 To 2)
    Dim As _Unsigned _Byte pixel32(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned Long q
    Dim As Long i
    Dim As Long a
   
    frameSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        KillDriver hwnd
        End
    End If
   
    SendMessage hwnd, WM_CAP_EDIT_COPY, 0, 0
   
    If OpenClipboard(0) = 0 Then
        Print "Couldn't open clipboard"
        KillDriver hwnd
        End
    End If

    hDIB = GetClipboardData(CF_DIB)
    If hDIB = 0 Then
        Print "Failed to retrieve bitmap from clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If

    pDIB = GlobalLock(hDIB)
    If pDIB = 0 Then
        Print "Failed to lock the clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
   
    CloseClipboard
   
    'Print "Frame retrieved"
   
    p = _Mem(pDIB, 4)
    biSize = _MemGet(p, p.OFFSET, _Unsigned Long)
    pBI = _Mem(pDIB, biSize)
    'Print pBI.SIZE
   
    _MemGet pBI, pBI.OFFSET, bi
    _MemFree pBI
    _MemFree p
    'Print bi.bmiHeader.biWidth
    'Print bi.bmiHeader.biHeight
    'Print bi.bmiHeader.biBitCount
   
    bytesPerPixel = (bi.bmiHeader.biBitCount + 7) \ 8
    'Print bytesPerPixel
   
    stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    'Print stride
    'Print bi.bmiHeader.biSizeImage
   
    m = _Mem(pDIB + Len(bi), bi.bmiHeader.biSizeImage)
   
    i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i
   
    If bi.bmiHeader.biHeight > 0 Then
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, Abs(bi.bmiHeader.biHeight) - 1 - y), q
            Next x
        Next y
    Else
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, y), q
            Next
        Next
    End If
   
    _Dest 0
    image = _MemImage(i)
    a = GlobalUnlock(pDIB)
End Sub ' GrabFrameToClipboard

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if value OriginalString$ is numeric.

' Re: Does a Is Number function exist in QB64?
' https://www.qb64.org/forum/index.php?topic=896.15

' Version 2 by madscijr
' Returns TRUE (-1) if string is an integer, FALSE (0) if not

' Version 1 by MWheatley
' Reply #18 on: January 01, 2019, 11:24:30 AM
' returns 1 if string is an integer, 0 if not

Function IsNumber% (OriginalString$)
    Dim bResult%: bResult% = _FALSE
    Dim iLoop%
    Dim TestString$
    'Dim bNegative%
    Dim iDecimalCount%
    Dim sNextChar$

    'THEY SHOULD TRIM OUTSIDE THE FUNCTION!
    'TestString$ = _TRIM$(OriginalString$)

    If Len(OriginalString$) > 0 Then
        TestString$ = ""
        If Left$(OriginalString$, 1) = "+" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = FALSE
        ElseIf Left$(OriginalString$, 1) = "-" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = TRUE
        Else
            TestString$ = OriginalString$
            'bNegative% = FALSE
        End If
        If Len(TestString$) > 0 Then
            bResult% = _TRUE
            iDecimalCount% = 0
            For iLoop% = 1 To Len(TestString$)
                sNextChar$ = Mid$(TestString$, iLoop%, 1)
                If sNextChar$ = "." Then
                    iDecimalCount% = iDecimalCount% + 1
                    If iDecimalCount% > 1 Then
                        ' TOO MANY DECIMAL POINTS, INVALID!
                        bResult% = _FALSE
                        Exit For
                    End If
                ElseIf Asc(sNextChar$) < 48 Or Asc(sNextChar$) > 57 Then
                    ' NOT A NUMERAL OR A DECIMAL, INVALID!
                    bResult% = _FALSE
                    Exit For
                End If
            Next iLoop%
        End If
    End If
    IsNumber% = bResult%
End Function ' IsNumber%

' /////////////////////////////////////////////////////////////////////////////

Sub KillDriver (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0
    DestroyWindow hwnd
End Sub ' KillDriver

' /////////////////////////////////////////////////////////////////////////////
' Does a _PrintString at the specified row+column.
' iRow and iCol are 0-based.
' See also: PrintString1

Sub PrintString (iRow As Integer, iCol As Integer, MyString As String)
    Dim iX As Integer
    Dim iY As Integer
    iX = _FontWidth * iCol
    iY = _FontHeight * iRow ' (iRow + 1)
    _PrintString (iX, iY), MyString
End Sub ' PrintString

' /////////////////////////////////////////////////////////////////////////////
' Print sMessage in fancy alternating colors
' at row RowNum, column StartCol

Sub PrintTitle (RowNum As Integer, StartCol As Integer, sMessage As String)
    Dim ColNum As Integer
    Dim iLoop As Integer
    Dim fgColor~&
    ColNum = StartCol
    For iLoop = 1 To Len(sMessage)
        If fgColor~& = cBlue Then fgColor~& = cRed Else fgColor~& = cBlue
        Color cWhite, fgColor~&
        Locate RowNum, ColNum: Print Mid$(sMessage, iLoop, 1);
        'PrintString RowNum, ColNum, mid$(sMessage, iLoop, 1)
        ColNum = ColNum + 1
    Next iLoop
End Sub ' PrintTitle

' /////////////////////////////////////////////////////////////////////////////
' Generate random value between Min and Max inclusive.

' Note: random-number generator should be initialized with
'       InitializeRandom or Randomize Timer

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%

    '' SET RANDOM SEED
    ''Randomize ' Initialize random-number generator.
    'Randomize Timer

    NumSpread% = (Max% - Min%) + 1
    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
End Function ' RandomNumber%

' /////////////////////////////////////////////////////////////////////////////

Sub SaveBMP (hwnd As _Offset, filename As String)
    filename = filename + Chr$(0)
    SendMessage hwnd, WM_CAP_FILE_SAVEDIB, 0, _Offset(filename)
End Sub ' SaveBMP

' /////////////////////////////////////////////////////////////////////////////

Sub SetupDriver (hwnd As _Offset, videoWidth As _Unsigned Long, videoHeight As _Unsigned Long, defaultSource As _Byte)
    Dim As _Offset libload
    Dim As _Offset myCallback
    Dim As BITMAPINFO format
    Dim As _Unsigned Long formatSize
   
    libload = LoadLibrary(Command$(0))
    myCallback = GetProcAddress(libload, "CapVideoCallback")
   
    SendMessage hwnd, WM_CAP_DRIVER_CONNECT, 0, 0
    SendMessage hwnd, WM_CAP_SET_SCALE, -1, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEWRATE, 1000 / FPS, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEW, -1, 0
   
    If defaultSource = _FALSE Then
        SendMessage hwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0 ' PICK YUY2!!!
        SendMessage hwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0
    End If
   
    formatSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If Len(format) <> formatSize Then
        KillDriver hwnd
        Print "Wrong size"
        Print formatSize, Len(format)
        End
    End If
   
    If SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Couldn't get format"
        End
    End If
   
    format.bmiHeader.biSize = Len(format)
    format.bmiHeader.biWidth = videoWidth
    format.bmiHeader.biHeight = videoHeight
    format.bmiHeader.biPlanes = 1
    format.bmiHeader.biBitCount = 16 ' yuy2 format
    format.bmiHeader.biSizeImage = videoWidth * videoHeight * 2
    format.bmiHeader.biCompression = mmioStringToFOURCC("YUY2" + Chr$(0), &H00000010) ' MUST BE YUY2 FORMAT
   
    If SendMessage(hwnd, WM_CAP_SET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Failed to set video format"
        End
    End If
   
    SendMessage hwnd, WM_CAP_GET_VIDEOFORMAT, 0, _Offset(format)
    SendMessage hwnd, WM_CAP_SET_CALLBACK_FRAME, 0, myCallback
End Sub ' SetupDriver

' /////////////////////////////////////////////////////////////////////////////

Function CapVideoCallback%& (hWnd As _Offset, lpVHdr As _Offset)
    Type VIDEOHDR
        As _Offset lpData
        As _Unsigned Long dwBufferLength, dwBytesUsed, dwTimeCaptured
        As String * 4 padding1
        As _Unsigned _Offset dwUser
        As _Unsigned Long dwFlags
        As String * 4 padding2
        As _Offset dwReserved1, dwReserved2, dwReserved3, dwReserved4
    End Type

    Dim As VIDEOHDR vhdr
    Dim As _MEM pVhdr
    Dim As _Unsigned Long frameSize
    Dim As BITMAPINFO bi
    Dim As _MEM lpData
    Dim As Long bitsPerPixel
    Dim As Long bytesPerPixel
    Dim As _Unsigned Long stride
    Dim As Long y, x
    Dim As _Unsigned _Byte yuy2(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned _Integer64 converted
    Dim As _Unsigned _Byte r, g, b
    Dim As _Offset pixelOffset
    Dim As Long i

    pVhdr = _Mem(lpVHdr, Len(vhdr))
    _MemGet pVhdr, pVhdr.OFFSET, vhdr
    _MemFree pVhdr

    frameSize = SendMessage(hWnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        'KillDriver hWnd
        'End
    End If

    SendMessage hWnd, WM_CAP_GET_VIDEOFORMAT, frameSize, _Offset(bi)
    'Print bi.bmiHeader.biHeight

    lpData = _Mem(vhdr.lpData, vhdr.dwBufferLength)
    If _MemExists(WebCam.frame) Then _MemFree WebCam.frame

    bitsPerPixel = bi.bmiHeader.biBitCount
    bytesPerPixel = (bitsPerPixel + 7) \ 8
    stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8

    i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i
    For y = 0 To bi.bmiHeader.biHeight - 1
        pScanLine = lpData.OFFSET + (y * stride)
        For x = 0 To bi.bmiHeader.biWidth - bytesPerPixel Step bytesPerPixel
            pixelOffset = pScanLine + (x * bytesPerPixel)
            _MemGet lpData, pixelOffset, yuy2()
            converted = ConvertYUY2toRGB(yuy2())
            PSet (x, y), converted And &HFFFFFFFF
            PSet (x + 1, y), _ShR(converted, 32) And &HFFFFFFFF
        Next x
    Next y

    _Dest 0
    WebCam.frame = _MemImage(i)
End Function ' CapVideoCallback%&

' /////////////////////////////////////////////////////////////////////////////

Function ConvertYUY2toRGB~&& (yuy2() As _Unsigned _Byte) '(y1 As _Byte, u As _Byte, y2 As _Byte, v As _Byte)
    Dim As _Unsigned _Byte r1, g1, b1, r2, g2, b2
   
    ' Convert first pixel (Y1)
    r1 = yuy2(0) + 1.13983 * (yuy2(3) - 128)
    g1 = yuy2(0) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128)
    b1 = yuy2(0) + 0.58060 * (yuy2(1) - 128)

    ' Convert second pixel (Y2)
    r2 = yuy2(2) + 1.13983 * (yuy2(3) - 128)
    g2 = yuy2(2) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128)
    b2 = yuy2(2) + 0.58060 * (yuy2(1) - 128)

    ConvertYUY2toRGB = _RGB32(r1, g1, b1) Or _ShL(_RGB32(r2, g2, b2), 32)
End Function ' ConvertYUY2toRGB~&&

' /////////////////////////////////////////////////////////////////////////////

' ################################################################################################################################################################
' BEGIN COLOR FUNCTIONS #COLOR
' ################################################################################################################################################################

Function cRed~& ()
    cRed = _RGB32(255, 0, 0)
End Function

Function cOrangeRed~& ()
    cOrangeRed = _RGB32(255, 69, 0)
End Function ' cOrangeRed~&

Function cDarkOrange~& ()
    cDarkOrange = _RGB32(255, 140, 0)
End Function ' cDarkOrange~&

Function cOrange~& ()
    cOrange = _RGB32(255, 165, 0)
End Function ' cOrange~&

Function cGold~& ()
    cGold = _RGB32(255, 215, 0)
End Function ' cGold~&

Function cYellow~& ()
    cYellow = _RGB32(255, 255, 0)
End Function ' cYellow~&

' LONG-HAIRED FRIENDS OF JESUS OR NOT,
' THIS IS NOT YELLOW ENOUGH (TOO CLOSE TO LIME)
' TO USE FOR OUR COMPLEX RAINBOW SEQUENCE:
Function cChartreuse~& ()
    cChartreuse = _RGB32(127, 255, 0)
End Function ' cChartreuse~&

' WE SUBSTITUTE THIS CSS3 COLOR FOR INBETWEEN LIME AND YELLOW:
Function cOliveDrab1~& ()
    cOliveDrab1 = _RGB32(192, 255, 62)
End Function ' cOliveDrab1~&

Function cLime~& ()
    cLime = _RGB32(0, 255, 0)
End Function ' cLime~&

Function cMediumSpringGreen~& ()
    cMediumSpringGreen = _RGB32(0, 250, 154)
End Function ' cMediumSpringGreen~&

' ADDED THIS FOR THE GAUGE COLOR:
Function cSpringGreen~& ()
    cSpringGreen = _RGB32(0, 255, 160)
End Function ' cSpringGreen~&

Function cCyan~& ()
    cCyan = _RGB32(0, 255, 255)
End Function ' cCyan~&

Function cDeepSkyBlue~& ()
    cDeepSkyBlue = _RGB32(0, 191, 255)
End Function ' cDeepSkyBlue~&

Function cDodgerBlue~& ()
    cDodgerBlue = _RGB32(30, 144, 255)
End Function ' cDodgerBlue~&

Function cSeaBlue~& ()
    cSeaBlue = _RGB32(0, 64, 255)
End Function ' cSeaBlue~&

Function cBlue~& ()
    cBlue = _RGB32(0, 0, 255)
End Function ' cBlue~&

Function cBluePurple~& ()
    cBluePurple = _RGB32(64, 0, 255)
End Function ' cBluePurple~&

Function cDeepPurple~& ()
    cDeepPurple = _RGB32(96, 0, 255)
End Function ' cDeepPurple~&

Function cPurple~& ()
    cPurple = _RGB32(128, 0, 255)
End Function ' cPurple~&

Function cPurpleRed~& ()
    cPurpleRed = _RGB32(128, 0, 192)
End Function ' cPurpleRed~&

Function cDarkRed~& ()
    cDarkRed = _RGB32(160, 0, 64)
End Function ' cDarkRed~&

Function cBrickRed~& ()
    cBrickRed = _RGB32(192, 0, 32)
End Function ' cBrickRed~&

Function cDarkGreen~& ()
    cDarkGreen = _RGB32(0, 100, 0)
End Function ' cDarkGreen~&

Function cGreen~& ()
    cGreen = _RGB32(0, 128, 0)
End Function ' cGreen~&

Function cOliveDrab~& ()
    cOliveDrab = _RGB32(107, 142, 35)
End Function ' cOliveDrab~&

Function cLightPink~& ()
    cLightPink = _RGB32(255, 182, 193)
End Function ' cLightPink~&

Function cHotPink~& ()
    cHotPink = _RGB32(255, 105, 180)
End Function ' cHotPink~&

Function cDeepPink~& ()
    cDeepPink = _RGB32(255, 20, 147)
End Function ' cDeepPink~&

Function cMagenta~& ()
    cMagenta = _RGB32(255, 0, 255)
End Function ' cMagenta~&

Function cBlack~& ()
    cBlack = _RGB32(0, 0, 0)
End Function ' cBlack~&

Function cDimGray~& ()
    cDimGray = _RGB32(105, 105, 105)
End Function ' cDimGray~&

Function cGray~& ()
    cGray = _RGB32(128, 128, 128)
End Function ' cGray~&

Function cDarkGray~& ()
    cDarkGray = _RGB32(169, 169, 169)
End Function ' cDarkGray~&

Function cSilver~& ()
    cSilver = _RGB32(192, 192, 192)
End Function ' cSilver~&

Function cLightGray~& ()
    cLightGray = _RGB32(211, 211, 211)
End Function ' cLightGray~&

Function cGainsboro~& ()
    cGainsboro = _RGB32(220, 220, 220)
End Function ' cGainsboro~&

Function cWhiteSmoke~& ()
    cWhiteSmoke = _RGB32(245, 245, 245)
End Function ' cWhiteSmoke~&

Function cWhite~& ()
    cWhite = _RGB32(255, 255, 255)
End Function ' cWhite~&

Function cDarkBrown~& ()
    cDarkBrown = _RGB32(128, 64, 0)
End Function ' cDarkBrown~&

Function cLightBrown~& ()
    cLightBrown = _RGB32(196, 96, 0)
End Function ' cLightBrown~&

Function cKhaki~& ()
    cKhaki = _RGB32(240, 230, 140)
End Function ' cKhaki~&

Function cEmpty~& ()
    cEmpty = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ################################################################################################################################################################
' END COLOR FUNCTIONS @COLOR
' ################################################################################################################################################################


Attached Files
.h   framecallback.h (Size: 355 bytes / Downloads: 2)
Reply
#18
Ha! And some more changes! I added the ability to adjust the saturation level. Why? Because I can! Now you can have greyscale images as well as full color OR oversaturated images.
Also, I left the code for copying the frame to clipboard but commented out the call because we can just copy it using _ClipboardImage anyways.
Code: (Select All)
Option _Explicit
'$Console
'------------------------------------------------------------------------------------------------------------------------------
'Capture Driver Constants
Const WM_CAP_START = &H0400
Const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
Const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
Const WM_CAP_EDIT_COPY = WM_CAP_START + 30
Const WM_CAP_SET_SCALE = WM_CAP_START + 53
Const WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
Const WM_CAP_SET_PREVIEW = WM_CAP_START + 50
Const WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42
Const WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61
Const WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25
Const WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44
Const WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45
Const WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41
Const WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5
Const CF_DIB = &H0008

Const FPS = 30 'Not recommended to set this higher than frame rate of webcam/video device

Const SATURATION_LEVEL = 0.00 '0.00 for greyscale, 1.00 for normal, and >1.00 for oversaturation
Const VIDWIDTH = 320
Const VIDHEIGHT = 240

Type BITMAPINFOHEADER
    As _Unsigned Long biSize
    As Long biWidth, biHeight
    As _Unsigned Integer biPlanes, biBitCount
    As _Unsigned Long biCompression, biSizeImage
    As Long biXPelsPerMeter, biYPelsPerMeter
    As _Unsigned Long biClrUsed, biClrImportant
End Type

Type BITMAPINFO
    As BITMAPINFOHEADER bmiHeader
End Type

Declare Dynamic Library "Avicap32"
    Function CreateCaptureWindow& Alias "capCreateCaptureWindowA" (ByVal lpszWindowName As _Offset, ByVal dwStyle As _Unsigned Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hwndParent As _Offset, ByVal nId As Long)
End Declare

Declare CustomType Library
    Sub SendMessage Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Function SendMessage& Alias "SendMessageA" (ByVal hWnd As _Offset, ByVal Msg As _Unsigned Long, ByVal wParam As _Offset, ByVal lParam As _Offset)
    Sub DestroyWindow (ByVal hWnd As _Offset)
    Function GetAsyncKeyState% (ByVal vKey As Long)
    Function mmioStringToFOURCC& (sz As String, ByVal uFlags As _Unsigned Long)
    Function OpenClipboard& (ByVal hWndNewOwner As _Offset)
    Function GetClipboardData%& (ByVal uFormat As _Unsigned Long)
    Sub CloseClipboard ()
    Function GlobalLock%& (ByVal hMem As _Offset)
    Function GlobalUnlock& (ByVal hMem As _Offset)
    Function LoadLibrary%& (lpLibFileName As String)
    Function GetProcAddress%& (ByVal hModule As _Offset, lpProcName As String)
End Declare

Declare Library ".\internal\c\c_compiler\include\vfw"
End Declare

Declare Library "framecallback"
End Declare

Screen _NewImage(VIDWIDTH, VIDHEIGHT, 32)

_ScreenMove _Middle

Dim As String captureWinText: captureWinText = "Webcam API Test - Child" + Chr$(0)
Dim As _Offset childID


Dim As _Offset childWin: childWin = CreateCaptureWindow(_Offset(captureWinText), 0, 0, 0, VIDWIDTH, VIDHEIGHT, _WindowHandle, childID)
_Title "Webcam API Test - Parent"
SetupDriver childWin, _TRUE 'change _FALSE to _TRUE to use default settings

Print "Previewing... Press Space bar to stop"
Print "Press escape to kill the window"
Dim Shared As _MEM frame
Do
    'GetAsyncKeyState is necessary because the capture window steals keyboard focus
    If GetAsyncKeyState(&H20) <> 0 Then Exit Do
    If GetAsyncKeyState(&H1B) <> 0 Then
        KillDriver childWin
        End
    End If
    GrabFrame childWin
    Screen frame.IMAGE
    _Display
    _Limit FPS
Loop
Dim As _MEM clippedFrame
'GrabFrameToClipboard childWin, clippedFrame
_ClipboardImage = frame.IMAGE
Print "Disconnecting Driver"
KillDriver childWin

_MemFree frame
'_MemFree clippedFrame

Sleep

Sub SetupDriver (hwnd As _Offset, defaultSource As _Byte)
    Dim As _Offset libload: libload = LoadLibrary(Command$(0))
    Dim As _Offset myCallback: myCallback = GetProcAddress(libload, "CapVideoCallback")
    Dim As BITMAPINFO format
    'End
    SendMessage hwnd, WM_CAP_DRIVER_CONNECT, 0, 0
    SendMessage hwnd, WM_CAP_SET_SCALE, -1, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEWRATE, 1000 / FPS, 0
    SendMessage hwnd, WM_CAP_SET_PREVIEW, -1, 0

    Dim As _Unsigned Long formatSize: formatSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If Len(format) <> formatSize Then
        KillDriver hwnd
        Print "Wrong size"
        Print formatSize, Len(format)
        End
    End If

    If SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Couldn't get format"
        End
    End If
    format.bmiHeader.biSize = Len(format)
    format.bmiHeader.biWidth = VIDWIDTH
    format.bmiHeader.biHeight = VIDHEIGHT
    format.bmiHeader.biPlanes = 1
    format.bmiHeader.biBitCount = 16 'yuy2 format
    format.bmiHeader.biSizeImage = VIDWIDTH * VIDHEIGHT * 2
    format.bmiHeader.biCompression = mmioStringToFOURCC("YUY2" + Chr$(0), &H00000010) 'MUST BE YUY2 FORMAT
    If SendMessage(hwnd, WM_CAP_SET_VIDEOFORMAT, Len(format), _Offset(format)) = 0 Then
        KillDriver hwnd
        Print "Failed to set video format"
        End
    End If
    If defaultSource = _FALSE Then
        SendMessage hwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0 'PICK YUY2!!!
        SendMessage hwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0
    End If
    SendMessage hwnd, WM_CAP_GET_VIDEOFORMAT, 0, _Offset(format)
    SendMessage hwnd, WM_CAP_SET_CALLBACK_FRAME, 0, myCallback
End Sub

Sub KillDriver (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0
    DestroyWindow hwnd
End Sub

Sub SaveBMP (hwnd As _Offset, filename As String)
    filename = filename + Chr$(0)
    SendMessage hwnd, WM_CAP_FILE_SAVEDIB, 0, _Offset(filename)
End Sub

Sub GrabFrame (hwnd As _Offset)
    SendMessage hwnd, WM_CAP_GRAB_FRAME_NOSTOP, 0, 0
End Sub

Sub GrabFrameToClipboard (hwnd As _Offset, image As _MEM)
    Dim As _Unsigned Long frameSize: frameSize = SendMessage(hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        KillDriver hwnd
        End
    End If
    SendMessage hwnd, WM_CAP_EDIT_COPY, 0, 0
    If OpenClipboard(0) = 0 Then
        Print "Couldn't open clipboard"
        KillDriver hwnd
        End
    End If
    Dim As _Offset hDIB: hDIB = GetClipboardData(CF_DIB)
    If hDIB = 0 Then
        Print "Failed to retrieve bitmap from clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    Dim As _Offset pDIB: pDIB = GlobalLock(hDIB)
    If pDIB = 0 Then
        Print "Failed to lock the clipboard"
        CloseClipboard
        KillDriver hwnd
        End
    End If
    CloseClipboard
    Dim As _MEM p: p = _Mem(pDIB, 4)
    Dim As _Unsigned Long biSize: biSize = _MemGet(p, p.OFFSET, _Unsigned Long)
    Dim As _MEM pBI: pBI = _Mem(pDIB, biSize)
    Dim As BITMAPINFO bi
    _MemGet pBI, pBI.OFFSET, bi
    _MemFree pBI
    _MemFree p
    Dim As Long bytesPerPixel: bytesPerPixel = (bi.bmiHeader.biBitCount + 7) \ 8
    Dim As Long stride: stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    Dim As _MEM m: m = _Mem(pDIB + Len(bi), bi.bmiHeader.biSizeImage)
    Dim As Long y, x, t
    Dim As _Unsigned _Byte pixel24(0 To 2)
    Dim As _Unsigned _Byte pixel32(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned Long q
    Dim As Long i: i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i


    If bi.bmiHeader.biHeight > 0 Then
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, Abs(bi.bmiHeader.biHeight) - 1 - y), q
            Next x
        Next y
    Else
        For y = 0 To Abs(bi.bmiHeader.biHeight) - 1
            pScanLine = m.OFFSET + (y * stride)
            For x = 0 To bi.bmiHeader.biWidth - 1
                If bi.bmiHeader.biBitCount = 24 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel24()
                    q = _RGB32(pixel24(2), pixel24(1), pixel24(0))
                ElseIf bi.bmiHeader.biBitCount = 32 Then
                    _MemGet m, pScanLine + (x * bytesPerPixel), pixel32()
                    q = _RGBA32(pixel32(2), pixel32(1), pixel32(0), pixel32(3))
                Else
                    q = _RGB32(255, 255, 255)
                End If
                PSet (x, y), q
            Next
        Next
    End If
    _Dest 0
    image = _MemImage(i)
    Dim As Long a: a = GlobalUnlock(pDIB)
End Sub

Function CapVideoCallback%& (hWnd As _Offset, lpVHdr As _Offset)
    Type VIDEOHDR
        As _Offset lpData
        As _Unsigned Long dwBufferLength, dwBytesUsed, dwTimeCaptured
        As String * 4 padding1
        As _Unsigned _Offset dwUser
        As _Unsigned Long dwFlags
        As String * 4 padding2
        As _Offset dwReserved1, dwReserved2, dwReserved3, dwReserved4
    End Type
    Dim As VIDEOHDR vhdr
    Dim As _MEM pVhdr: pVhdr = _Mem(lpVHdr, Len(vhdr))
    _MemGet pVhdr, pVhdr.OFFSET, vhdr
    _MemFree pVhdr
    Dim As _Unsigned Long frameSize: frameSize = SendMessage(hWnd, WM_CAP_GET_VIDEOFORMAT, 0, 0)
    If frameSize = 0 Then
        Print "Failed to get video format size"
        'KillDriver hWnd
        'End
    End If
    Dim As BITMAPINFO bi
    SendMessage hWnd, WM_CAP_GET_VIDEOFORMAT, frameSize, _Offset(bi)
    'Print bi.bmiHeader.biHeight
    Dim As _MEM lpData: lpData = _Mem(vhdr.lpData, vhdr.dwBufferLength)
    If _MemExists(frame) Then _MemFree frame
    Dim As Long bitsPerPixel: bitsPerPixel = bi.bmiHeader.biBitCount
    Dim As Long bytesPerPixel: bytesPerPixel = (bitsPerPixel + 7) \ 8
    Dim As _Unsigned Long stride: stride = (((bi.bmiHeader.biWidth * bi.bmiHeader.biBitCount) + 31) And Not 31) \ 8
    Dim As Long y, x
    Dim As _Unsigned _Byte yuy2(0 To 3)
    Dim As _Offset pScanLine
    Dim As _Unsigned _Integer64 converted
    Dim As _Unsigned _Byte r, g, b
    Dim As _Offset pixelOffset
    Dim As Long i: i = _NewImage(bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, 32)
    _Dest i
    For y = 0 To bi.bmiHeader.biHeight - 1
        pScanLine = lpData.OFFSET + (y * stride)
        For x = 0 To bi.bmiHeader.biWidth - bytesPerPixel Step bytesPerPixel
            pixelOffset = pScanLine + (x * bytesPerPixel)
            _MemGet lpData, pixelOffset, yuy2()
            converted = ConvertYUY2toRGB(yuy2())
            PSet (x, y), converted And &HFFFFFFFF
            PSet (x + 1, y), _ShR(converted, 32) And &HFFFFFFFF
        Next
    Next
    _Dest 0
    frame = _MemImage(i)
End Function

Function ConvertYUY2toRGB~&& (yuy2() As _Unsigned _Byte) '(y1 As _Byte, u As _Byte, y2 As _Byte, v As _Byte)
    Dim As _Unsigned _Byte r1, g1, b1, r2, g2, b2
    ' Convert first pixel (Y1)

    r1 = _Clamp(yuy2(0) + 1.13983 * (yuy2(3) - 128) * SATURATION_LEVEL, 0, 255)
    g1 = _Clamp(yuy2(0) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128) * SATURATION_LEVEL, 0, 255)
    b1 = _Clamp(yuy2(0) + 0.58060 * (yuy2(1) - 128) * SATURATION_LEVEL, 0, 255)

    ' Convert second pixel (Y2)
    r2 = _Clamp(yuy2(2) + 1.13983 * (yuy2(3) - 128) * SATURATION_LEVEL, 0, 255)
    g2 = _Clamp(yuy2(2) - 0.39465 * (yuy2(1) - 128) - 0.714 * (yuy2(3) - 128) * SATURATION_LEVEL, 0, 255)
    b2 = _Clamp(yuy2(2) + 0.58060 * (yuy2(1) - 128) * SATURATION_LEVEL, 0, 255)

    ConvertYUY2toRGB = _RGB32(r1, g1, b1) Or _ShL(_RGB32(r2, g2, b2), 32)
End Function
   


Attached Files
.h   framecallback.h (Size: 355 bytes / Downloads: 3)
The noticing will continue
Reply
#19
(8 hours ago)SpriggsySpriggs Wrote: Ha! And some more changes! I added the ability to adjust the saturation level. Why? Because I can! Now you can have greyscale images as well as full color OR oversaturated images.
Also, I left the code for copying the frame to clipboard but commented out the call because we can just copy it using _ClipboardImage anyways.

Nice! Those might come in handy to make it easier to detect movement with the camera.
Can we drop the quality down to 8-bit color?
Doing some of this from the API might speed up the processing by eliminating calculations where we're analyzing a frame pixel-by-pixel.

However, just being able to process a frame as an image, pixel-by-pixel, opens up some fun possibilities!
Want to see yourself in color ASCII characters? No problem! Or inverse video or other funky effects that used to be built into camcorders back in the day. You could even emulate green screen from software!
Though I'm not sure how we'd grab input from 2 simultaneous webcam video sources, or maybe the background could come from a video file that plays and the code superimposes your greenscreen video over it, frame by frame? I know we can write images to an image file, I wonder how might we write these altered image frames back to a video file? Hmmm...
Reply
#20
Quote:I wonder how might we write these altered image frames back to a video file? Hmm...

We'd probably need to learn video codecs. I can probably convert my stored frames into an AVI format, which would be easiest as it is uncompressed and lossless.

Quote:Though I'm not sure how we'd grab input from 2 simultaneous webcam video sources

Ehhhhh... I don't know, man.

Quote:You could even emulate green screen from software!

Yeah, you could.

Quote:Can we drop the quality down to 8-bit color?

16 bits per pixel is what we need. YUY2 records as 4 bytes for 2 pixels, shared, which is native colorspace of the camera. I reckon you could always display only 8 bits if you wanted to, although I don't know much about doing that. But we need to record at this particular colorspace and bit.
The noticing will continue
Reply




Users browsing this thread: 7 Guest(s)