Page 1 of 1

Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 4:53 am
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

Re: Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 6:07 am
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

Re: Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 9:42 am
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:

Re: Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 11:54 am
by NicknameFJ
You become cool again when reaching 6510 posts.

NicknameFJ

Re: Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 11:11 pm
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:

Re: Reading all or some on screen pixels

Posted: Tue Sep 17, 2013 11:40 pm
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)

Re: Reading all or some on screen pixels

Posted: Wed Sep 18, 2013 12:04 am
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.

Re: Reading all or some on screen pixels

Posted: Wed Sep 18, 2013 1:11 am
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

Re: Reading all or some on screen pixels

Posted: Wed Sep 18, 2013 2:04 am
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.

Re: Reading all or some on screen pixels

Posted: Wed Sep 18, 2013 7:22 am
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!