Fast ScanRegion (now very fast)

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Thanks for your post, it found a bug in GrabRegion. It's fixed now, here's the latest:

Code: Select all

ProcedureDLL GrabRegion(ImageID, transcolor) ; HBITMAP ImageID, COLORREF transcolor 

  ;======================================================= 
  ;                                                      = 
  ;      Very fast bitmap -> region creator              = 
  ;                                                      = 
  ;      By netmaestro                                   = 
  ;                                                      = 
  ;      Contributors: eesau, nico, flype                = 
  ;                                                      = 
  ;      June 26, 2007                                   = 
  ;                                                      = 
  ;======================================================= 
  
  Structure RECTARRAY
    rect.RECT[0] 
  EndStructure 

  GetObject_(ImageID, SizeOf(BITMAP), @bmp.BITMAP) 
  
  Protected width       = bmp\bmWidth 
  Protected height      = bmp\bmHeight 
  Protected hVisibleRgn = CreateRectRgn_(0, 0, width, height) 
  
  BmiInfo.BITMAPINFOHEADER 
  With BmiInfo 
    \biSize         = SizeOf(BITMAPINFOHEADER) 
    \biWidth        = width 
    \biHeight       = -height 
    \biPlanes       = 1 
    \biBitCount     = 32 
    \biCompression  = #BI_RGB 
  EndWith    
  
  rowbytes =  SizeOf(LONG)*width 

  *ColorBits = AllocateMemory(rowbytes*height) 
  
  hDC   = GetWindowDC_(#Null) 
  iRes  = GetDIBits_(hDC, ImageID, 0, height, *ColorBits, @bmiInfo, #DIB_RGB_COLORS) 
  ReleaseDC_(#Null, hDC) 
  
  Structure_Max=(width*height*16)+SizeOf(RGNDATAHEADER) 
  *Buffer.RGNDATAHEADER=AllocateMemory(Structure_Max) 
  *rd.RECTARRAY=*Buffer+SizeOf(RGNDATAHEADER) 

  rectcount = 0 
  For y=0 To height-1 
    pxcount=0 
    For x=0 To rowbytes-1 Step 4 
      *px.LONG = *ColorBits + rowbytes * y + x 
      If *px\l = transcolor  
        transcount = 1 : firsttrans = pxcount 
        x+SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x 
        While *px\l = transcolor And x <= rowbytes-1
          transcount+1 : pxcount+1 : x+SizeOf(LONG) 
          *px = *ColorBits + rowbytes * y + x 
        Wend 
        x-SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x 
        *rd\rect[rectcount]\left   = firsttrans            
        *rd\rect[rectcount]\top    = y                     
        *rd\rect[rectcount]\right  = firsttrans+transcount 
        *rd\rect[rectcount]\bottom = y+1      
        rectcount+1 
      EndIf 
      pxcount+1 
    Next 
  Next 
  
  With *Buffer 
    \dwSize         = SizeOf(RGNDATAHEADER) 
    \iType          = #RDH_RECTANGLES 
    \nCount         = rectcount 
    \nRgnSize       = rectcount * SizeOf(RECT) 
    \rcBound\left   = 0
    \rcBound\top    = 0
    \rcBound\right  = width
    \rcBound\bottom = height
  EndWith 
  
  RegionSize=SizeOf(RGNDATAHEADER)+(rectcount * SizeOf(RECT)) 
  hTransparentRgn = ExtCreateRegion_(0, RegionSize, *Buffer) 
  CombineRgn_(hVisibleRgn, hVisibleRgn, hTransparentRgn, #RGN_XOR) 
    
  FreeMemory(*Buffer) 
  FreeMemory(*ColorBits) 
  DeleteObject_(hTransparentRgn) 
  
  ProcedureReturn hVisibleRgn 
  
EndProcedure 
BERESHEIT
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

I really love this code. (no use for it yet myself though) And pondered how to improve it, I could not think of anything except one two things:

The source is now EnableExplicit compliant

I added several error checks (PSDK used as reference to make sure proper checks was done, knowing MS the return codes tend to vary :)

Multimedia Timer with 1ms period used for speed test, best timing without having to worry about QueryPerformanceCounter and Thread Affinity on multi cores etc.

Code: Select all

EnableExplicit

ProcedureDLL GrabRegion(ImageID, transcolor) ; HBITMAP ImageID, COLORREF transcolor

  ;=======================================================
  ;                                                      =
  ;      Very fast bitmap -> region creator              =
  ;                                                      =
  ;      By netmaestro                                   =
  ;                                                      =
  ;      Contributors: eesau, nico, flype                =
  ;                                                      =
  ;      June 26, 2007                                   =
  ;                                                      =
  ;=======================================================
 
  Structure RECTARRAY
    rect.RECT[0]
  EndStructure

  Protected bmp.BITMAP,width.l,height.l,hVisibleRgn.l,result.l=#False
  Protected BmiInfo.BITMAPINFOHEADER,rowbytes.l,*ColorBits,hDC.l,iRes.l,Structure_Max.l
  Protected *Buffer.RGNDATAHEADER,*rd.RECTARRAY,rectcount.l,y.l,x.l,pxcount.l,*px.LONG
  Protected transcount.l,firsttrans.l,regionSize.l,hTransparentRgn.l


  If GetObject_(ImageID, SizeOf(BITMAP), @bmp.BITMAP) And bmp

    width = bmp\bmWidth
    height = bmp\bmHeight
    hVisibleRgn=CreateRectRgn_(0, 0, width, height)
    If hVisibleRgn
     
      With BmiInfo
        \biSize         = SizeOf(BITMAPINFOHEADER)
        \biWidth        = width
        \biHeight       = -height
        \biPlanes       = 1
        \biBitCount     = 32
        \biCompression  = #BI_RGB
      EndWith   
     
      rowbytes =  SizeOf(LONG)*width
    
      *ColorBits = AllocateMemory(rowbytes*height)
      If *ColorBits
      
        hDC   = GetWindowDC_(#Null)
        If hDC
          iRes  = GetDIBits_(hDC, ImageID, 0, height, *ColorBits, @bmiInfo, #DIB_RGB_COLORS)
          If iRes
            ReleaseDC_(#Null, hDC)
            Structure_Max=(width*height*16)+SizeOf(RGNDATAHEADER)
            *Buffer=AllocateMemory(Structure_Max)
            If *Buffer
              *rd=*Buffer+SizeOf(RGNDATAHEADER)
            
              rectcount = 0
              For y=0 To height-1
                pxcount=0
                For x=0 To rowbytes-1 Step 4
                  *px = *ColorBits + rowbytes * y + x
                  If *px\l = transcolor 
                    transcount = 1 : firsttrans = pxcount
                    x+SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x
                    While *px\l = transcolor And x <= rowbytes-1
                      transcount+1 : pxcount+1 : x+SizeOf(LONG)
                      *px = *ColorBits + rowbytes * y + x
                    Wend
                    x-SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x
                    *rd\rect[rectcount]\left   = firsttrans           
                    *rd\rect[rectcount]\top    = y                     
                    *rd\rect[rectcount]\right  = firsttrans+transcount
                    *rd\rect[rectcount]\bottom = y+1     
                    rectcount+1
                  EndIf
                  pxcount+1
                Next
              Next
             
              With *Buffer
                \dwSize         = SizeOf(RGNDATAHEADER)
                \iType          = #RDH_RECTANGLES
                \nCount         = rectcount
                \nRgnSize       = rectcount * SizeOf(RECT)
                \rcBound\left   = 0
                \rcBound\top    = 0
                \rcBound\right  = width
                \rcBound\bottom = height
              EndWith
              RegionSize=SizeOf(RGNDATAHEADER)+(rectcount * SizeOf(RECT))
              hTransparentRgn = ExtCreateRegion_(0, RegionSize, *Buffer)
              If hTransparentRgn
                If CombineRgn_(hVisibleRgn, hVisibleRgn, hTransparentRgn, #RGN_XOR)
                  result=hVisibleRgn
                EndIf
              EndIf
              DeleteObject_(hTransparentRgn)
            EndIf
            FreeMemory(*Buffer)
          EndIf
        EndIf
        FreeMemory(*ColorBits)
      EndIf
    EndIf
  EndIf
  ProcedureReturn hVisibleRgn
EndProcedure 

Define start.l,ending.l,image.l,region.l

timeBeginPeriod_(1)
LoadImage(0, "girl.bmp")
OpenWindow(0,0,0,ImageWidth(0),ImageHeight(0),"", #PB_Window_ScreenCentered|#PB_Window_BorderLess)
CreateGadgetList(WindowID(0))
ImageGadget(0,0,0,0,0,ImageID(0))
start = timeGetTime_()
region=GrabRegion(ImageID(image),#White)
ending = timeGetTime_()
If region
 SetWindowRgn_(WindowID(0), region, #True)
 MessageRequester("Created Region in:", Str(ending-start)+" milliseconds!",$C0)
Else
 MessageRequester("Failed to create Region:", Str(ending-start)+" milliseconds wasted!",$C0)
EndIf
Repeat
  WaitWindowEvent()
Until GetAsyncKeyState_(#VK_SPACE) & 32768
timeEndPeriod_(1)
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Very good, thanks for help. I just had to make a couple of tweaks, here is the latest:

Code: Select all

ProcedureDLL GrabRegion(ImageID, transcolor) ; HBITMAP ImageID, COLORREF transcolor 

  ;======================================================= 
  ;                                                      = 
  ;      Very fast bitmap -> region creator              = 
  ;                                                      = 
  ;      Version 1.0 Release                             = 
  ;                                                      = 
  ;      By netmaestro                                   = 
  ;                                                      = 
  ;      Contributors: eesau, nico, flype, rescator      = 
  ;                                                      = 
  ;      June 26, 2007                                   = 
  ;                                                      = 
  ;======================================================= 
  
  Structure RECTARRAY 
    rect.RECT[0] 
  EndStructure 

  Protected bmp.BITMAP,width.l,height.l,hVisibleRgn.l,combineresult.l=0, returnvalue.l = 0 
  Protected BmiInfo.BITMAPINFOHEADER,rowbytes.l,*ColorBits,hDC.l,iRes.l,Structure_Max.l 
  Protected *Buffer.RGNDATAHEADER,*rd.RECTARRAY,rectcount.l,y.l,x.l,pxcount.l,*px.LONG 
  Protected transcount.l,firsttrans.l,regionSize.l,hTransparentRgn.l 

  If GetObject_(ImageID, SizeOf(BITMAP), @bmp.BITMAP) And bmp 
    width = bmp\bmWidth 
    height = bmp\bmHeight 
    hVisibleRgn=CreateRectRgn_(0, 0, width, height) 
    If hVisibleRgn 
      With BmiInfo 
        \biSize         = SizeOf(BITMAPINFOHEADER) 
        \biWidth        = width 
        \biHeight       = -height 
        \biPlanes       = 1 
        \biBitCount     = 32 
        \biCompression  = #BI_RGB 
      EndWith    
      rowbytes =  SizeOf(LONG)*width 
      *ColorBits = AllocateMemory(rowbytes*height) 
      If *ColorBits 
        hDC   = GetWindowDC_(#Null) 
        If hDC 
          iRes  = GetDIBits_(hDC, ImageID, 0, height, *ColorBits, @bmiInfo, #DIB_RGB_COLORS) 
          If iRes 
            ReleaseDC_(#Null, hDC) 
            Structure_Max=(width*height*16)+SizeOf(RGNDATAHEADER) 
            *Buffer=AllocateMemory(Structure_Max) 
            If *Buffer 
              *rd=*Buffer+SizeOf(RGNDATAHEADER) 
              rectcount = 0 
              For y=0 To height-1 
                pxcount=0 
                For x=0 To rowbytes-1 Step 4 
                  *px = *ColorBits + rowbytes * y + x 
                  If *px\l = transcolor 
                    transcount = 1 : firsttrans = pxcount 
                    x+SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x 
                    While *px\l = transcolor And x <= rowbytes-1 
                      transcount+1 : pxcount+1 : x+SizeOf(LONG) 
                      *px = *ColorBits + rowbytes * y + x 
                    Wend 
                    x-SizeOf(LONG) : *px.LONG = *ColorBits + rowbytes * y + x 
                    With *rd\rect[rectcount]
                      \left   = firsttrans            
                      \top    = y                      
                      \right  = firsttrans+transcount 
                      \bottom = y+1      
                    EndWith
                    rectcount+1 
                  EndIf 
                  pxcount+1 
                Next 
              Next 
              With *Buffer 
                \dwSize         = SizeOf(RGNDATAHEADER) 
                \iType          = #RDH_RECTANGLES 
                \nCount         = rectcount 
                \nRgnSize       = rectcount * SizeOf(RECT) 
                \rcBound\left   = 0 
                \rcBound\top    = 0 
                \rcBound\right  = width 
                \rcBound\bottom = height 
              EndWith 
              RegionSize=SizeOf(RGNDATAHEADER)+(rectcount * SizeOf(RECT)) 
              hTransparentRgn = ExtCreateRegion_(0, RegionSize, *Buffer) 
              If hTransparentRgn 
                combineresult = CombineRgn_(hVisibleRgn, hVisibleRgn, hTransparentRgn, #RGN_XOR) 
                If combineresult = #SIMPLEREGION Or combineresult = #COMPLEXREGION 
                  returnvalue = hVisibleRgn 
                Else 
                  returnvalue = 0 
                EndIf 
                DeleteObject_(hTransparentRgn) 
              EndIf 
              FreeMemory(*Buffer) 
            EndIf 
          EndIf 
        EndIf 
        FreeMemory(*ColorBits) 
      EndIf 
    EndIf 
    DeleteObject_(bmp) 
  EndIf 
  ProcedureReturn returnvalue 
EndProcedure 
BERESHEIT
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I've ran my own humble optimizations on this. Unfortunately, I also formatted it to what I'm used seeing. I apologize for that.

Code: Select all

ProcedureDLL.l TicksHQ() 
  Static maxfreq.q 
  Protected t.q 
  If maxfreq=0 
    QueryPerformanceFrequency_(@maxfreq) 
    maxfreq=maxfreq/1000 
  EndIf 
  QueryPerformanceCounter_(@t) 
  ProcedureReturn t/maxfreq 
EndProcedure 


Structure REGIONDATA 
  rdh.RGNDATAHEADER 
  Buffer.l[100000] 
EndStructure 

Structure RECTARRAY 
   Rect.RECT[0] 
EndStructure 

ProcedureDLL GrabRegion(ImageID, transcolor) ; HBITMAP ImageID, COLORREF transcolor 
   
   ;======================================================= 
   ;                                                      = 
   ;      Very fast bitmap -> region creator              = 
   ;                                                      = 
   ;      Version 1.0 Release                             = 
   ;                                                      = 
   ;      By netmaestro                                   = 
   ;                                                      = 
   ;      Contributors: eesau, nico, flype, rescator      = 
   ;                                                      = 
   ;      June 26, 2007                                   = 
   ;                                                      = 
   ;======================================================= 
   
   Protected bmp.BITMAP,width.l,height.l,hVisibleRgn.l,combineresult.l=0, ReturnValue.l = 0 
   Protected BmiInfo.BITMAPINFOHEADER,rowbytes.l,*ColorBits,hdc.l,iRes.l,Structure_Max.l 
   Protected *Buffer.RGNDATAHEADER,*rd.RECTARRAY,rectcount.l,y.l,x.l,pxcount.l,*px.Long 
   Protected transcount.l,firsttrans.l,RegionSize.l,hTransparentRgn.l 
   Protected yMul.l
   ;
   If GetObject_(ImageID, SizeOf(BITMAP), @bmp.BITMAP) And bmp 
      ;
      width = bmp\bmWidth : height = bmp\bmHeight 
      ;
      hVisibleRgn=CreateRectRgn_(0, 0, width, height) 
      ;
      If hVisibleRgn
         ;
         With BmiInfo 
            \biSize         = SizeOf(BITMAPINFOHEADER) 
            \biWidth        = width 
            \biHeight       = -height 
            \biPlanes       = 1 
            \biBitCount     = 32 
            \biCompression  = #BI_RGB 
         EndWith
         ;
         rowbytes =  SizeOf(Long) * width 
         ;
         *ColorBits = AllocateMemory(rowbytes * height) 
         ;
         If *ColorBits 
            ;
            hdc   = GetWindowDC_(#Null) 
            ;
            If hdc 
               ;
               iRes  = GetDIBits_(hdc, ImageID, 0, height, *ColorBits, @BmiInfo, #DIB_RGB_COLORS) 
               ;
               If iRes 
                  ;
                  ReleaseDC_(#Null, hdc) 
                  ;
                  Structure_Max = (width * height << 4) + SizeOf(RGNDATAHEADER) 
                  ;
                  *Buffer = AllocateMemory(Structure_Max)
                  ;
                  If *Buffer 
                     ;
                     *rd = *Buffer + SizeOf(RGNDATAHEADER) 
                     ;
                     y = 0 : rectcount = 0 : yMul = 0
                     ;
                     While y < height
                        ;
                        x = 0 : pxcount=0
                        ;
                        While x < rowbytes
                           ;
                           *px = *ColorBits + yMul + x 
                           ;
                           If *px\l = transcolor 
                              ;
                              transcount = 1 : firsttrans = pxcount 
                              ;
                              x + SizeOf(Long)
                              ;
                              While *px\l = transcolor And x < rowbytes
                                 x + SizeOf(Long) : *px = *ColorBits + yMul + x : transcount + 1 : pxcount + 1
                              Wend 
                              ;
                              x - SizeOf(Long)
                              ;
                              With *rd\Rect[rectcount] 
                                 \Left   = firsttrans
                                 \Top    = y
                                 \Right  = firsttrans + transcount
                                 \Bottom = y + 1
                              EndWith 
                              ;
                              rectcount + 1
                              ;
                           EndIf 
                           ;
                           pxcount + 1 : x + 4
                           ;
                        Wend
                        ;
                        y + 1 : yMul + rowbytes
                        ;
                     Wend
                     ;
                     With *Buffer 
                        \dwSize         = SizeOf(RGNDATAHEADER) 
                        \iType          = #RDH_RECTANGLES 
                        \nCount         = rectcount 
                        \nRgnSize       = rectcount * SizeOf(RECT) 
                        \rcBound\Left   = 0 
                        \rcBound\Top    = 0 
                        \rcBound\Right  = width 
                        \rcBound\Bottom = height 
                     EndWith
                     ;
                     RegionSize = SizeOf(RGNDATAHEADER) + (rectcount * SizeOf(RECT)) 
                     ;
                     hTransparentRgn = ExtCreateRegion_(0, RegionSize, *Buffer) 
                     ;
                     If hTransparentRgn
                        ;
                        combineresult = CombineRgn_(hVisibleRgn, hVisibleRgn, hTransparentRgn, #RGN_XOR) 
                        ;
                        If combineresult = #SIMPLEREGION Or combineresult = #COMPLEXREGION 
                           ReturnValue = hVisibleRgn 
                        Else 
                           ReturnValue = 0 
                        EndIf 
                        ;
                        DeleteObject_(hTransparentRgn) 
                        ;
                     EndIf 
                     ;
                     FreeMemory(*Buffer) 
                     ;
                  EndIf 
                  ;
               EndIf 
               ;
            EndIf 
            ;
            FreeMemory(*ColorBits) 
            ;
         EndIf 
         ;
      EndIf 
      ;
      DeleteObject_(bmp) 
      ;
   EndIf 
   ;
   ProcedureReturn ReturnValue 
   ;
EndProcedure 

LoadImage(0, "c:\temp\girl.bmp") 

OpenWindow(0,0,0,ImageWidth(0),ImageHeight(0),"", #PB_Window_ScreenCentered|#PB_Window_BorderLess|#PB_Window_Invisible) 
CreateGadgetList(WindowID(0)) 
ImageGadget(0,0,0,0,0,ImageID(0)) 

Delay(100)
start = TicksHQ() 
  region = GrabRegion(ImageID(0), #White) 
ending = TicksHQ() 

SetWindowRgn_(WindowID(0), region, #True) 
HideWindow(0,0) 
MessageRequester("Created Region in:", Str(ending-start)+" milliseconds",$C0) 
Repeat 
  WaitWindowEvent() 
Until GetAsyncKeyState_(#VK_SPACE) & 32768 
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Post by Mistrel »

This is really great, netmaestro. Is there any chance you could get it working with 8-bit transparency?
Post Reply