Reading all or some on screen pixels

Just starting out? Need help? Post your questions and find answers here.
Nituvious
Addict
Addict
Posts: 1029
Joined: Sat Jul 11, 2009 4:57 am
Location: United States

Reading all or some on screen pixels

Post by Nituvious »

I'm trying to read pixels using GetDIBits() and BltBit() at a reasonably fast speed(faster than getpixel()) but I can't seem to get it working.

I'm having issues getting the area I want to search into memory to read through. Can anyone offer some insight? Here's my last failed attempt...

Code: Select all

dc = GetDC_(0)
ndc = CreateCompatibleDC_(dc)

*buffer = AllocateMemory(400*400)

bmi.BITMAPINFO
bmi\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER) 
bmi\bmiHeader\biWidth = 400
bmi\bmiHeader\biHeight = 400
bmi\bmiHeader\biPlanes = 1 
bmi\bmiHeader\biBitCount = 32 
bmi\bmiHeader\biCompression = #BI_RGB 

Debug GetDIBits_(dc, ndc, 0, 400, *buffer, bmi, #DIB_RGB_COLORS)

For x = 0 To 30
	Debug PeekL(*buffer + x)
Next x
▓▓▓▓▓▒▒▒▒▒░░░░░
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Reading all or some on screen pixels

Post by netmaestro »

Your 400*400 should have been 1600*400, as each pixel is an RGBQUAD. This looks reasonable to me as GetDIBits needs an hBitmap as source:

Code: Select all

#CAPTUREBLT = $40000000 ; Necessary for BitBlt to get layered windows if present on screen

dc = CreateDC_("DISPLAY",0,0,0)
ndc = CreateCompatibleDC_(0)

bmi.BITMAPINFO
bmi\bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
bmi\bmiHeader\biWidth    = 400
bmi\bmiHeader\biHeight   = -400 ; Otherwise colors in the buffer will be backwards, "bottom-up" which we don't want
bmi\bmiHeader\biPlanes   = 1 
bmi\bmiHeader\biBitCount = 32 
bmi\bmiHeader\biCompression = #BI_RGB 

hImage = CreateDIBSection_(ndc, bmi, #DIB_RGB_COLORS, @*pvBits, 0, 0)
If hImage
  DeleteObject_(SelectObject_(ndc, hImage))
  BitBlt_(ndc, 0,0,400,400,dc,0,0,#SRCCOPY|#CAPTUREBLT)
  DeleteDC_(dc)
  DeleteDC_(ndc)
  *buffer=PeekI(@*pvBits)
  For x = 0 To 30*SizeOf(RGBQUAD) Step 4 ; Look at the first 30 colors in scanline 0
    Debug PeekL(*buffer+x)&$FFFFFF       ; ignoring the alpha byte here; GetPixel would return this value
  Next x
EndIf
Thank you very much Nituvious, now my postcount isn't 6502 now :cry: I'm not a cool old processor anymore

[edit] Removed redundant GetObject_() line
Last edited by netmaestro on Wed Sep 18, 2013 5:49 am, edited 1 time in total.
BERESHEIT
User avatar
Lord
Addict
Addict
Posts: 902
Joined: Tue May 26, 2009 2:11 pm

Re: Reading all or some on screen pixels

Post by Lord »

netmaestro wrote:...
Thank you very much Nituvious, now my postcount isn't 6502 now :cry: I'm not a cool old processor anymore
But maybe you become cool again when reaching 68000 posts? :mrgreen:
Image
User avatar
NicknameFJ
User
User
Posts: 90
Joined: Tue Mar 17, 2009 6:36 pm
Location: Germany

Re: Reading all or some on screen pixels

Post by NicknameFJ »

You become cool again when reaching 6510 posts.

NicknameFJ
PS: Sorry for my weird english, but english is not my native language.



Image
Nituvious
Addict
Addict
Posts: 1029
Joined: Sat Jul 11, 2009 4:57 am
Location: United States

Re: Reading all or some on screen pixels

Post by Nituvious »

netmaestro wrote:Your 400*400 should have been 1600*400, as each pixel is an RGBQUAD. This looks reasonable to me as GetDIBits needs an hBitmap as source:

Code: Select all

#CAPTUREBLT = $40000000 ; Necessary for BitBlt to get layered windows if present on screen

dc = CreateDC_("DISPLAY",0,0,0)
ndc = CreateCompatibleDC_(0)

bmi.BITMAPINFO
bmi\bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
bmi\bmiHeader\biWidth    = 400
bmi\bmiHeader\biHeight   = -400 ; Otherwise colors in the buffer will be backwards, "bottom-up" which we don't want
bmi\bmiHeader\biPlanes   = 1 
bmi\bmiHeader\biBitCount = 32 
bmi\bmiHeader\biCompression = #BI_RGB 

hImage = CreateDIBSection_(ndc, bmi, #DIB_RGB_COLORS, @*pvBits, 0, 0)
If hImage
  GetObject_(himage, SizeOf(BITMAP), @bmp.BITMAP)
  DeleteObject_(SelectObject_(ndc, hImage))
  BitBlt_(ndc, 0,0,400,400,dc,0,0,#SRCCOPY|#CAPTUREBLT)
  DeleteDC_(dc)
  DeleteDC_(ndc)
  *buffer=PeekI(@*pvBits)
  For x = 0 To 30*SizeOf(RGBQUAD) Step 4 ; Look at the first 30 colors in scanline 0
    Debug PeekL(*buffer+x)&$FFFFFF       ; ignoring the alpha byte here; GetPixel would return this value
  Next x
EndIf
Thank you very much Nituvious, now my postcount isn't 6502 now :cry: I'm not a cool old processor anymore
Thanks :) That's a lot more complicated than I expected it to be.
And as other have said, you'll be cool again soon :lol:
▓▓▓▓▓▒▒▒▒▒░░░░░
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: Reading all or some on screen pixels

Post by charvista »

Very interesting and useful code. Thank you.

I am nuts when it goes about APIs, but I was wondering if it is not more accurate to set the Step to SizeOf(RGBQUAD) instead of fixed 4? (because SizeOf(RGBQUAD) is 4)

Code: Select all

For x=0 To 30*SizeOf(RGBQUAD) Step SizeOf(RGBQUAD)
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Reading all or some on screen pixels

Post by netmaestro »

...set the Step to SizeOf(RGBQUAD) instead of fixed 4?..
Sure, better for self-doc-ing code too. Just rushed. I often write Until EventID=16 for window events too when knocking a test up quickly but I never post code with that in it.
BERESHEIT
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: Reading all or some on screen pixels

Post by charvista »

netmaestro wrote:Sure, better for self-doc-ing code too. Just rushed.
Ah, OK. I just asked that to be sure it was the parallel jump, and not something else.
Thanks for your answer, it helps to increase your postcount :mrgreen:
I created a procedure that loads all screen pixels in a two-dimensional array so they can be read in the conventional way Screen(x,y).

Code: Select all

#CAPTUREBLT = $40000000 ; Necessary for BitBlt to get layered windows if present on screen



Procedure zGrabScreen(Array Screen.i(2))
    
    ExamineDesktops()
    W.i=DesktopWidth(0); Desktop resolution width
    H.i=DesktopHeight(0); Desktop resolution height
    
    Dim Screen.i(W,H)
    
    dc = CreateDC_("DISPLAY",0,0,0)
    ndc = CreateCompatibleDC_(0)
    
    bmi.BITMAPINFO
    bmi\bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER)
    bmi\bmiHeader\biWidth    = W
    bmi\bmiHeader\biHeight   = -H ; Negative, otherwise colors in the buffer will be backwards, "bottom-up" which we don't want
    bmi\bmiHeader\biPlanes   = 1
    bmi\bmiHeader\biBitCount = 32
    bmi\bmiHeader\biCompression = #BI_RGB
    
    hImage=CreateDIBSection_(ndc,bmi,#DIB_RGB_COLORS,@*pvBits,0,0)
    If hImage
        GetObject_(himage,SizeOf(BITMAP),@bmp.BITMAP)
        DeleteObject_(SelectObject_(ndc,hImage))
        BitBlt_(ndc,0,0,W,H,dc,0,0,#SRCCOPY|#CAPTUREBLT)
        DeleteDC_(dc)
        DeleteDC_(ndc)
        *buffer=PeekI(@*pvBits)
        For C=0 To (W*H-1)*SizeOf(RGBQUAD) Step SizeOf(RGBQUAD) ; Look at the first 30 colors in scanline 0
            Screen(Int(Mod(C/SizeOf(RGBQUAD),W)),Int(C/SizeOf(RGBQUAD)/W))=PeekL(*buffer+C)&$FFFFFF; ignoring the alpha byte here; GetPixel would return this value
            ;           If Mod(C/SizeOf(RGBQUAD),W)=0
            ;               Debug Int(C/SizeOf(RGBQUAD)/W)
            ;           EndIf
            ;Debug PeekL(*buffer+C)&$FFFFFF   ; ignoring the alpha byte here; GetPixel would return this value
        Next C
    EndIf
EndProcedure

Dim Screen.i(0,0)
zGrabScreen(Screen())

For i=0 To ArraySize(Screen(),2)-1
    For j=0 To ArraySize(Screen(),1)-1
        Debug "@("+Str(j)+","+Str(i)+")="+Str(Screen(j,i))
    Next
Next
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Reading all or some on screen pixels

Post by netmaestro »

I just noticed I have a GetObject_() in there that doesn't do anything. I was going to use the bmBits member from it for a pointer to the colorbits but I ended up using the pointer from CreateDIBSection and never did use it. It can be safely removed.
BERESHEIT
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Reading all or some on screen pixels

Post by netmaestro »

I did a speed test on this today, capturing my whole screen. Here is the code:

Code: Select all


;*************** TURN OFF THE DEBUGGER! ****************

Procedure.d TicksHQ(init=0)
  Static freq.q, lastcount.q
  
  If init
    QueryPerformanceFrequency_(@freq.q)
    QueryPerformanceCounter_(@lastcount.q)
    lastcount/(freq/1000)
  Else
    QueryPerformanceCounter_(@thiscount.q)
    thiscount/(freq/1000)
    result.d = (thiscount-lastcount)
    lastcount=thiscount
    ProcedureReturn result
  EndIf
  
EndProcedure

#CAPTUREBLT = $40000000 ; Necessary for BitBlt to get layered windows if present on screen
ExamineDesktops()
width=DesktopWidth(0)
height = DesktopHeight(0)

tickshq(1)

dc = CreateDC_("DISPLAY",0,0,0)
ndc = CreateCompatibleDC_(0)

bmi.BITMAPINFO
bmi\bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
bmi\bmiHeader\biWidth    = width
bmi\bmiHeader\biHeight   = -height ; Otherwise colors in the buffer will be backwards, "bottom-up" which we don't want
bmi\bmiHeader\biPlanes   = 1 
bmi\bmiHeader\biBitCount = 32 
bmi\bmiHeader\biCompression = #BI_RGB 

hImage = CreateDIBSection_(ndc, bmi, #DIB_RGB_COLORS, @*buffer1, 0, 0)
If hImage
  DeleteObject_(SelectObject_(ndc, hImage))
  BitBlt_(ndc, 0,0,width,height,dc,0,0,#SRCCOPY|#CAPTUREBLT)
  DeleteDC_(dc)
  DeleteDC_(ndc)
EndIf

result1 = TicksHQ()

*buffer2 = AllocateMemory(width*SizeOf(RGBQUAD)*height)
*readptr.long = *buffer2

dc = CreateDC_("DISPLAY",0,0,0)
For j=0 To height-1
  For i=0 To width-1
    *readptr\l = GetPixel_(dc, i, j)
    *readptr + SizeOf(Long)
  Next
Next
DeleteDC_(dc)

result2 = TicksHQ()

; Verify that both methods worked and show the result

CreateImage(0,width,height,24)
StartDrawing(ImageOutput(0))
*readptr.Long = *buffer1
For j=0 To height-1
  For i=0 To width-1
    Plot(i,j, RGB(Blue(*readptr\l), Green(*readptr\l), Red(*readptr\l)))
    *readptr+SizeOf(Long)
  Next
Next
StopDrawing()
ResizeImage(0,400,300)

CreateImage(1,width,height,24)
StartDrawing(ImageOutput(1))

*readptr.Long = *buffer2
For j=0 To height-1
  For i=0 To width-1
    Plot(i,j, RGB(Red(*readptr\l), Green(*readptr\l), Blue(*readptr\l)))
    *readptr+SizeOf(Long)
  Next
Next
StopDrawing()
ResizeImage(1,400,300)

OpenWindow(0,0,0,820,340,"",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
ImageGadget(0,0,0,0,0,ImageID(0))
ImageGadget(1,420,0,0,0,ImageID(1))
TextGadget(10,0,310,300,20,"BitBlt: "+result1+" ms",#SS_CENTER)
TextGadget(11,420,310,300,20,"GetPixel: "+result2+" ms",#SS_CENTER)

Repeat:Until WaitWindowEvent() = #PB_Event_CloseWindow
While I was prepared for a speed improvement by moving to BitBlt from GetPixel, this was my (rather shocking) result:

Image

Read 'em and weep, GetPixel. You've just been owned!
BERESHEIT
Post Reply