Getting Handle to a region [GDI with VB]

Getting Handle to a region [GDI with VB]

Post by Ashwin » Sat, 05 Jul 2003 19:45:16



Hi All,

I'm working on a VB application that includes a small ms paint like program.
I'm giving the users the option to load bitmaps (in a picturebox) and edit
the bitmaps (draw lines, rectangles, fill etc.).

For filling an enclosed area, I'm using ExtFloodFill method.  I'm stuck with
a few things:

My questions are as follows:

1.  Is it possible to get the handle of the region that gets painted by the
ExtFloodFill method?  if yes then how? Alternatively, How can I get a handle
of the region a point (x1, y1) belongs to?

2.  How can I determine whether two points (x1, y1) and (x1, y2) are in the
same region?  i.e. enclosed by the same boundary. The boundary can be of any
irregular shape.

Any help with this would be appreciated.

Thanks in advance.

Regards,
Ashwini

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Sun, 06 Jul 2003 00:15:02


Quote:> I'm working on a VB application that includes a small ms paint like
program.
> I'm giving the users the option to load bitmaps (in a picturebox) and edit
> the bitmaps (draw lines, rectangles, fill etc.).

> For filling an enclosed area, I'm using ExtFloodFill method.  I'm stuck
with
> a few things:

> My questions are as follows:

> 1.  Is it possible to get the handle of the region that gets painted by
the
> ExtFloodFill method?  if yes then how? Alternatively, How can I get a
handle
> of the region a point (x1, y1) belongs to?

[Ext]FloodFill() is raster based, that is it draws pixels onto a surface -
It has no knowledge of regions.  If your shapes are made up of regions then
you can work out which you've clicked in by calling PtInRgn() for each, but
there's no way (At least using the GDI) to get the region of the area to the
first edges of multiple intersecting regions.  If you're working with simple
shapes (Convex only) then you can probably get away with calling
CombineRgn() with the RGN_AND flag on all the regions that are hit-tested as
true at the point you clicked on but the same would not always work for
concave shapes.

Quote:> 2.  How can I determine whether two points (x1, y1) and (x1, y2) are in
the
> same region?  i.e. enclosed by the same boundary. The boundary can be of
any
> irregular shape.

Again, PtInRgn() will help you here.
Hope this helps,

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by dolthar » Sun, 06 Jul 2003 03:06:53


I remember a sample (probably from vbAccelerator or vbThunder) that
makes a odd shaped form with a fish bitmap as a shape mask

He was using CreateRectRgn and CombineRgn to built the final Rgn to be
used for the form shape.

Maybe you can take a look at this sample.

Here a the list of site i was used to visit when i saw this sample:
  vbAccelerator.com
  vbThunder.com
  mvps.org/vbnet (Randy's one)
  vb2themax.com (Francesco)

if someone got more clues about this sample, tell us.

I'm not a GURU nor an EXPERT or something like that.
I'm just a passionnate programmer.

Doltharz

On Fri, 4 Jul 2003 16:15:02 +0100, "Mike D Sutton"


>> I'm working on a VB application that includes a small ms paint like
>program.
>> I'm giving the users the option to load bitmaps (in a picturebox) and edit
>> the bitmaps (draw lines, rectangles, fill etc.).

>> For filling an enclosed area, I'm using ExtFloodFill method.  I'm stuck
>with
>> a few things:

>> My questions are as follows:

>> 1.  Is it possible to get the handle of the region that gets painted by
>the
>> ExtFloodFill method?  if yes then how? Alternatively, How can I get a
>handle
>> of the region a point (x1, y1) belongs to?

>[Ext]FloodFill() is raster based, that is it draws pixels onto a surface -
>It has no knowledge of regions.  If your shapes are made up of regions then
>you can work out which you've clicked in by calling PtInRgn() for each, but
>there's no way (At least using the GDI) to get the region of the area to the
>first edges of multiple intersecting regions.  If you're working with simple
>shapes (Convex only) then you can probably get away with calling
>CombineRgn() with the RGN_AND flag on all the regions that are hit-tested as
>true at the point you clicked on but the same would not always work for
>concave shapes.

>> 2.  How can I determine whether two points (x1, y1) and (x1, y2) are in
>the
>> same region?  i.e. enclosed by the same boundary. The boundary can be of
>any
>> irregular shape.

>Again, PtInRgn() will help you here.
>Hope this helps,

>    Mike

> - Microsoft Visual Basic MVP -

>WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Sun, 06 Jul 2003 04:23:28


Quote:> I remember a sample (probably from vbAccelerator or vbThunder) that
> makes a odd shaped form with a fish bitmap as a shape mask

> He was using CreateRectRgn and CombineRgn to built the final Rgn to be
> used for the form shape.

The sample was from vbAccelerator (At least I know there is one there,
couldn't comment on the other sites), but the problem with that method was
that it's using any occurrence of a single colour only rather than a single
_area_ of that colour.  As such, if the colour existed anywhere in the
original image it would assume that that area had been filled too and give
an incorrect result.

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by dolthar » Sun, 06 Jul 2003 05:23:51


Mike,

One way that get to my humble mind is a recurcive method which i'm
sure is TOTALLY unefficient, can it be adapted and optimized to be
acceptable?

Maybe stack approach could be lot faster but it remains a "stack"
method which can become very very memory consuming.

I just remember old "koala painter" on commodore C=64 that seems to
use kinda method. When i was kid,  we were building large maze and
staring at it while floodfill makes it way throught it. Good old
memories.

I'm not a GURU nor an EXPERT or something like that.
I'm just a passionnate programmer.

Doltharz

On Fri, 4 Jul 2003 19:23:28 +0000 (UTC), "Mike D Sutton"


>> I remember a sample (probably from vbAccelerator or vbThunder) that
>> makes a odd shaped form with a fish bitmap as a shape mask

>> He was using CreateRectRgn and CombineRgn to built the final Rgn to be
>> used for the form shape.

>The sample was from vbAccelerator (At least I know there is one there,
>couldn't comment on the other sites), but the problem with that method was
>that it's using any occurrence of a single colour only rather than a single
>_area_ of that colour.  As such, if the colour existed anywhere in the
>original image it would assume that that area had been filled too and give
>an incorrect result.

>    Mike

> - Microsoft Visual Basic MVP -

>WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Sun, 06 Jul 2003 07:44:37


Quote:> One way that get to my humble mind is a recurcive method which i'm
> sure is TOTALLY unefficient, can it be adapted and optimized to be
> acceptable?

> Maybe stack approach could be lot faster but it remains a "stack"
> method which can become very very memory consuming.

> I just remember old "koala painter" on commodore C=64 that seems to
> use kinda method. When i was kid,  we were building large maze and
> staring at it while floodfill makes it way throught it. Good old
> memories.

Yep, this same problem came up a few years ago, when both I and another
developer wrote custom flood fill routines from scratch using an internal
stack based approach.  You can find that thread here:
http://groups.google.co.uk/groups?th=9ffcf5f84c76bb81
Both methods could be optimised further and offloading the mathematics to a
C++ DLL would further increase performance, but for a basic solution they're
both useable (Mine is currently offline, but available at request until the
site goes back online.)
The problem with both of these two methods is that they're still raster
based methods where as what was required is a region based method.  It
should be possible to short circuit a bespoke flood fill routine to store
scanlines rather than write pixel data, then combine those scans as single
pixels runs to create a complex region.  It is however a lot of work to get
all this working, especially if performance is an issue and may very well be
beyond the scope of the original problem.

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Rocky Clar » Sun, 06 Jul 2003 22:27:02


I've given a solution to this in microsoft.public.vb.controls. The code
works flawlessly, but I consider it to be somewhat slow. If anyone can take
a look at the code and come up with a way to speed it up, I would greatly
appreciate it.

My current performance test results are as follows:

250 x 250 bitmap:    average time = 94 ms (0.094 seconds)
1024 x 768 bitmap:    average time = 1250 ms (1.250 seconds)
The above tests were performed on a P4-1.8 GHz machine.

I'm just not satisfied with those results!

The code simply makes a copy of the image and then performs the flood fill
on the original image. It then compares the two images to find the different
colored area and creates a region from that area. This is where the
performance is lost, since it walks through the images pixel by pixel,
comparing the colors (Note: This is not using GetPixel(). It downloads both
images into byte arrays using GetDIBits() and then steps through the bytes)

Can anyone come up with a faster way to compare the two images?

Here is the link to the code:
http://kath-rock.com/ZipFiles/BmpRgn.zip

Rocky



> > One way that get to my humble mind is a recurcive method which i'm
> > sure is TOTALLY unefficient, can it be adapted and optimized to be
> > acceptable?

> > Maybe stack approach could be lot faster but it remains a "stack"
> > method which can become very very memory consuming.

> > I just remember old "koala painter" on commodore C=64 that seems to
> > use kinda method. When i was kid,  we were building large maze and
> > staring at it while floodfill makes it way throught it. Good old
> > memories.

> Yep, this same problem came up a few years ago, when both I and another
> developer wrote custom flood fill routines from scratch using an internal
> stack based approach.  You can find that thread here:
> http://groups.google.co.uk/groups?th=9ffcf5f84c76bb81
> Both methods could be optimised further and offloading the mathematics to
a
> C++ DLL would further increase performance, but for a basic solution
they're
> both useable (Mine is currently offline, but available at request until
the
> site goes back online.)
> The problem with both of these two methods is that they're still raster
> based methods where as what was required is a region based method.  It
> should be possible to short circuit a bespoke flood fill routine to store
> scanlines rather than write pixel data, then combine those scans as single
> pixels runs to create a complex region.  It is however a lot of work to
get
> all this working, especially if performance is an issue and may very well
be
> beyond the scope of the original problem.

>     Mike

>  - Microsoft Visual Basic MVP -

> WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Mon, 07 Jul 2003 00:16:45


Rocky,

Quote:> I've given a solution to this in microsoft.public.vb.controls. The code
> works flawlessly, but I consider it to be somewhat slow. If anyone can
take
> a look at the code and come up with a way to speed it up, I would greatly
> appreciate it.

> My current performance test results are as follows:

> 250 x 250 bitmap:    average time = 94 ms (0.094 seconds)
> 1024 x 768 bitmap:    average time = 1250 ms (1.250 seconds)
> The above tests were performed on a P4-1.8 GHz machine.

> I'm just not satisfied with those results!

> The code simply makes a copy of the image and then performs the flood fill
> on the original image. It then compares the two images to find the
different
> colored area and creates a region from that area. This is where the
> performance is lost, since it walks through the images pixel by pixel,
> comparing the colors (Note: This is not using GetPixel(). It downloads
both
> images into byte arrays using GetDIBits() and then steps through the

bytes)

I've not had a look at the code for performance optimisations, but
unfortunately the routine is flawed since you can theoretically fill with
the same colour as is already there - This routine would not recognise that
unfortunately..  There's lots of methods that will work in certain
situations (Such as the region combining method for convex shapes), however
for a generic method for use in a painting application these can't be relied
on.

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Rocky Clar » Mon, 07 Jul 2003 01:10:51


Thanks for the reply, Mike.

I've already considered the same color situation and it can be remedied by
checking the current color and calling ExtFloodFill with a different color,
if needed before calling my routine. This will not be seen by the user if
AutoRedraw = True and Refresh is not called between calls. Actually, for
most paint programs, the drawing is done off-screen and then stretched to
the current scale on-screen, so the double flood fill would not be of
consequence.

Anyhow, I found another way to improve the performance and it's a fairly
significant performance increase. I realized that I didn't need to Create a
DC and bitmap and Blit a copy of the image. I just needed to cal GetDIBits()
before and after my flood fill, using my same two arrays for the bits. It
cut my last times in half.

250 x 250 bitmap:    average time = 49 ms (0.049 seconds)
1024 x 768 bitmap:    average time = 598 ms (0.598 seconds)
The above tests were performed on a P4-1.8 GHz machine.

This is much closer to satisfactory, but I'd still like some other
viewpoints.

The updated code is here:
http://kath-rock.com/ZipFiles/BmpRgn.zip

Rocky



> Rocky,

> > I've given a solution to this in microsoft.public.vb.controls. The code
> > works flawlessly, but I consider it to be somewhat slow. If anyone can
> take
> > a look at the code and come up with a way to speed it up, I would
greatly
> > appreciate it.

> > My current performance test results are as follows:

> > 250 x 250 bitmap:    average time = 94 ms (0.094 seconds)
> > 1024 x 768 bitmap:    average time = 1250 ms (1.250 seconds)
> > The above tests were performed on a P4-1.8 GHz machine.

> > I'm just not satisfied with those results!

> > The code simply makes a copy of the image and then performs the flood
fill
> > on the original image. It then compares the two images to find the
> different
> > colored area and creates a region from that area. This is where the
> > performance is lost, since it walks through the images pixel by pixel,
> > comparing the colors (Note: This is not using GetPixel(). It downloads
> both
> > images into byte arrays using GetDIBits() and then steps through the
> bytes)

> I've not had a look at the code for performance optimisations, but
> unfortunately the routine is flawed since you can theoretically fill with
> the same colour as is already there - This routine would not recognise
that
> unfortunately..  There's lots of methods that will work in certain
> situations (Such as the region combining method for convex shapes),
however
> for a generic method for use in a painting application these can't be
relied
> on.

>     Mike

>  - Microsoft Visual Basic MVP -

> WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Mon, 07 Jul 2003 02:34:22


Hi Rocky,

Quote:> I've already considered the same color situation and it can be remedied by
> checking the current color and calling ExtFloodFill with a different
color,
> if needed before calling my routine. This will not be seen by the user if
> AutoRedraw = True and Refresh is not called between calls. Actually, for
> most paint programs, the drawing is done off-screen and then stretched to
> the current scale on-screen, so the double flood fill would not be of
> consequence.

Interesting, nice solution too.  I'd put that into the existing code though
as a default, you can't really expect the user to check to see if the colour
already exists there before clicking, and trying to check the area
beforehand would be a nightmare.
I had a look at the code and I've got a few comments if you're still looking
for them.
First up, you don't need to create and select a new Bitmap into the DC to
get the current bitmap handle, just call GetCurrentObject() and pass in
OBJ_BITMAP.
You're always forcing it (GetDIBits()) to read at 24-bit which will make it
have to perform a costly bit-depth conversion for you when working at a
screen depth other than 24-bit (All but the oldest graphics cards here won't
even run at 24-bit any more!)  Granted this gives you an easier job in the
code since you only have to code for one bit-depth, but it's unnecessary
overhead when working at 32-bit for example.  I'd suggest you first query
the existing bit depth and if it's either 24 or 32 bit you use that bit
depth - In this way you can still code a generic routine (Just step either 3
or 4 bytes per pixel) and you bypass the bitdepth conversion.  If it's not
true colour then a conversion to 24-bit will most likely be a lot faster
than trying to shift the paletted/high colour data about.
In this case it doesn't really matter, but in a generic case you could test
to see if you had been passed a DIB then just point an array at the data
rather than calling GetDIBits() - I'd strongly suggest wrapping all this
until into a DIB class before doing that though since it gets very messy
very quickly.
Do you need two BMI structures, looks like they have the same information in
or am I missing something you're doing with them?  You could also convert
the API calls to just take a BMIH structure rather than the full BMI which
would shorten the code itself down, no performance gain here though that I'm
aware of.
Another option to speed up the routine is to always work at 32-bit instead
and read the bitmap data into an array of Long's, this will mean you can
simply do one comparison per pixel "If (Src = Dst) Then ..." rather than "If
((Src.Red = Dst.Red) And (Src.Green = Dst.Green) And (Src.Blue = Dst.Blue))
Then ..." which should increase performance a little I'd imagine.
Taking out the error handling will also speed the code up, as long as you're
careful about checking your inputs and validating object handles then you
shouldn't need this anyway and it adds additional overhead to the code.
Finally the obvious two, checking all the advanced compile options will give
you probably a 25% speed increase since you're working a lot in arrays, and
compiling it to an EXE will greatly outperform the IDE.
Hope this helps,

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Rocky Clar » Mon, 07 Jul 2003 03:55:25


Mike,

First off, I'd like to say thank you. This is exactly what I needed. That's
why I ask in these groups (and also why I don't mind helping out with
answers for others).

I've taken your advise:
(1) Converted to 32 bit longs for the data. Eliminates the need to check for
DWord-alilgned scanlines and greatly improves performance in the loops by
only testing one long against another (instead of three bytes against 3
others).
(2) Now using GetCurrentObject() API. (And I thought I knew them all. <g>)
(3) Removed the second BMI structure. (Don't know why I ever had it.)
(4) This version handles having the same color fill by inverting the fill
color when needed. When this is the case, it restores the flood color by
using FillRgn() on the region it just created. This was neccessary, since
ExtFloodFill would have colored the region AND any adjacent areas that now
possibly match the inverted color.

My previous test results were:
250 x 250 bitmap:    average time = 49 ms (0.049 seconds)
1024 x 768 bitmap:    average time = 598 ms (0.598 seconds)

My latest test results are:
250 x 250 bitmap:    average time = 24 ms (0.024 seconds)
1024 x 768 bitmap:    average time = 294 ms (0.294 seconds)

The above tests were performed on a P4-1.8 GHz machine.

Now I'd say that's quite an improvement!

If anyone would like this latest code, download it here:
http://Kath-Rock.com/ZipFiles/BmpRgn.zip

Thanks again,
Rocky



> Hi Rocky,

> > I've already considered the same color situation and it can be remedied
by
> > checking the current color and calling ExtFloodFill with a different
> color,
> > if needed before calling my routine. This will not be seen by the user
if
> > AutoRedraw = True and Refresh is not called between calls. Actually, for
> > most paint programs, the drawing is done off-screen and then stretched
to
> > the current scale on-screen, so the double flood fill would not be of
> > consequence.

> Interesting, nice solution too.  I'd put that into the existing code
though
> as a default, you can't really expect the user to check to see if the
colour
> already exists there before clicking, and trying to check the area
> beforehand would be a nightmare.
> I had a look at the code and I've got a few comments if you're still
looking
> for them.
> First up, you don't need to create and select a new Bitmap into the DC to
> get the current bitmap handle, just call GetCurrentObject() and pass in
> OBJ_BITMAP.
> You're always forcing it (GetDIBits()) to read at 24-bit which will make
it
> have to perform a costly bit-depth conversion for you when working at a
> screen depth other than 24-bit (All but the oldest graphics cards here
won't
> even run at 24-bit any more!)  Granted this gives you an easier job in the
> code since you only have to code for one bit-depth, but it's unnecessary
> overhead when working at 32-bit for example.  I'd suggest you first query
> the existing bit depth and if it's either 24 or 32 bit you use that bit
> depth - In this way you can still code a generic routine (Just step either
3
> or 4 bytes per pixel) and you bypass the bitdepth conversion.  If it's not
> true colour then a conversion to 24-bit will most likely be a lot faster
> than trying to shift the paletted/high colour data about.
> In this case it doesn't really matter, but in a generic case you could
test
> to see if you had been passed a DIB then just point an array at the data
> rather than calling GetDIBits() - I'd strongly suggest wrapping all this
> until into a DIB class before doing that though since it gets very messy
> very quickly.
> Do you need two BMI structures, looks like they have the same information
in
> or am I missing something you're doing with them?  You could also convert
> the API calls to just take a BMIH structure rather than the full BMI which
> would shorten the code itself down, no performance gain here though that
I'm
> aware of.
> Another option to speed up the routine is to always work at 32-bit instead
> and read the bitmap data into an array of Long's, this will mean you can
> simply do one comparison per pixel "If (Src = Dst) Then ..." rather than
"If
> ((Src.Red = Dst.Red) And (Src.Green = Dst.Green) And (Src.Blue =
Dst.Blue))
> Then ..." which should increase performance a little I'd imagine.
> Taking out the error handling will also speed the code up, as long as
you're
> careful about checking your inputs and validating object handles then you
> shouldn't need this anyway and it adds additional overhead to the code.
> Finally the obvious two, checking all the advanced compile options will
give
> you probably a 25% speed increase since you're working a lot in arrays,
and
> compiling it to an EXE will greatly outperform the IDE.
> Hope this helps,

>     Mike

>  - Microsoft Visual Basic MVP -

> WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Mon, 07 Jul 2003 08:47:30


Quote:> First off, I'd like to say thank you. This is exactly what I needed.
That's
> why I ask in these groups (and also why I don't mind helping out with
> answers for others).

<snip>

I ported this to a C++ DLL just for fun (Yeah, like I have a life!.. ;) and
got a slight speed improvement over the VB version - I get an average of
~5ms here with a P4 1.4 Ghz 1GB RDRAM over the ~8ms of the VB version.
You can find the DLL and source here:
Http://www.mvps.org/EDais/TempFiles/EDBmpRgn_Bin.zip
Http://www.mvps.org/EDais/TempFiles/EDBmpRgn_Src.zip
The declare for the DLL is:

'***
Public Declare Function FloodFillGetRgn3 Lib "EDBmpRgn.dll" Alias _
    "FloodFillGetRgn" (ByVal hDC As Long, ByVal X As Long, _
    ByVal Y As Long, ByVal lFloodColor As Long) As Long
'***

The nice side effect of using a DLL for this kind of thing is that you get
fully compiled code right in the IDE.
I used a slightly faster region creation method in the DLL too (I've left
the old one in these too for comparison), ported back to VB it would be
something like this:

'***
Dim InRun As Boolean

' ...

' Create a null region to start with
hRgn = CreateRectRgn(0, 0, 0, 0)

For lY = 0 To lHeight - 1
    lIdx = lY * lWidth
    lPosY = (lHeight - 1) - lY

    For lX = 0 To lWidth - 1
        If (laBefore(lIdx) = laAfter(lIdx)) Then
            If (InRun) Then ' Write this run
                ' Create a temp region from the row of pixels.
                hTmpRgn = CreateRectRgn(lStartX, lPosY, lX, lPosY + 1)

                ' Remove the temp region from the main region.
                Call CombineRgn(hRgn, hRgn, hTmpRgn, RGN_OR)

                ' Get rid of the temp region
                Call DeleteObject(hTmpRgn)

                InRun = False
            End If
        Else
            If (Not InRun) Then
                lStartX = lX
                InRun = True
            End If
        End If

        ' Next pixel
        lIdx = lIdx + 1
    Next lX

    If (InRun) Then ' Write final run
        ' Create a temp region from the row of pixels.
        hTmpRgn = CreateRectRgn(lStartX, lPosY, lWidth, lPosY + 1)

        ' Remove the temp region from the main region.
        Call CombineRgn(hRgn, hRgn, hTmpRgn, RGN_OR)

        ' Get rid of the temp region
        Call DeleteObject(hTmpRgn)

        InRun = False
    End If
Next lY
'***

I'm sure you could most likely squeeze a little more performance out of it
still if you really had too, I'd expect using ExtCreateRegion() and building
up the region rectangle's array would be faster than multiple CombineRgn()'s
for a start.
One more problem with this though is if the user fill's with 0x808080 over
0x808080, you could put in a test for this one though, can't think of any
more odd little problems like this offhand.
Hope this helps,

    Mike

 - Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Mike D Sutto » Mon, 07 Jul 2003 10:24:53


> First off, I'd like to say thank you. This is exactly what I needed.
That's
> why I ask in these groups (and also why I don't mind helping out with
> answers for others).

<snip>

Ok one last one before bed *g*
Dump this in a new class called "clsAPIRgn":

'***
Option Explicit

Private Declare Function ExtCreateRegion Lib "gdi32" _
    (ByRef lpXform As Any, ByVal nCount As Long, ByRef lpRgnData As Any) As
Long
Private Declare Sub RtlMoveMemory Lib "kernel32" _
    (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Type RectAPI ' 16 bytes
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type RGNDataHeader ' 32 bytes
    dwSize As Long
    iType As Long
    nCount As Long
    nRgnSize As Long
    rcBound As RectAPI
End Type

Private Type RGNData ' 32 + (nCount * 16)
    rdh As RGNDataHeader
    Buffer() As Long
End Type

Dim RgnInf As RGNData
Dim DataSize As Long
Dim ArrPos As Long

Private Const RDH_RECTANGLES As Long = &H1

' How many rectangles to create in one 'chunk'
' The class will allocate an extra (StepSize * 16 bytes) each chunk
(Including starting run)
Private Const StepSize As Long = 10

Public Sub ClearRects()
    ReDim RgnInf.Buffer(0) As Long
    DataSize = 0
    ArrPos = 0

    With RgnInf.rdh
        ' These properties stay the same
        .dwSize = Len(RgnInf.rdh)
        .iType = RDH_RECTANGLES

        ' Initialise to null bounds rectangle
        With .rcBound
            .Left = &H7FFFFFFF
            .Top = &H7FFFFFFF
            .Right = &H80000000
            .Bottom = &H80000000
        End With
    End With
End Sub

Public Sub AddRect(ByVal inLeft As Long, ByVal inTop As Long, _
    ByVal inRight As Long, ByVal inBottom As Long)
    If (ArrPos <= DataSize) Then
        DataSize = DataSize + (StepSize * 4)
        ReDim Preserve RgnInf.Buffer(DataSize - 1) As Long
    End If

    With RgnInf ' Add this rectangle
        .Buffer(ArrPos) = inLeft
        .Buffer(ArrPos + 1) = inTop
        .Buffer(ArrPos + 2) = inRight
        .Buffer(ArrPos + 3) = inBottom

        With .rdh.rcBound ' Bounds check this rectangle
            If (inLeft < .Left) Then .Left = inLeft
            If (inRight > .Right) Then .Right = inRight
            If (inTop < .Top) Then .Top = inTop
            If (inBottom > .Bottom) Then .Bottom = inBottom
        End With
    End With

    ArrPos = ArrPos + 4
End Sub

Public Function CreateRegion() As Long
    Dim WriteData() As Long
    Dim BufSize As Long

    ' Nothing to do!
    If (DataSize <= 0) Then Exit Function

    With RgnInf.rdh ' Fill in header sizes
        .nCount = ArrPos \ 4
        .nRgnSize = .nCount * Len(.rcBound)
    End With

    ' Get buffer size and scale output array
    BufSize = RgnInf.rdh.dwSize + RgnInf.rdh.nRgnSize
    ReDim WriteData((BufSize \ 4) - 1) As Long

    ' Copy the header and data to output array
    Call RtlMoveMemory(WriteData(0), RgnInf.rdh, Len(RgnInf.rdh))
    Call RtlMoveMemory(WriteData(Len(RgnInf.rdh) \ 4), RgnInf.Buffer(0),
RgnInf.rdh.nRgnSize)

    ' Create region from internal data
    CreateRegion = ExtCreateRegion(ByVal 0&, BufSize, WriteData(0))
End Function

Private Sub Class_Initialize()
    Call ClearRects
End Sub
'***

The region creation code then gets compacted right down and ends up looking
like this:

'***
Dim RgnObj As clsAPIRgn

...

Set RgnObj = New clsAPIRgn

For lY = 0 To lHeight - 1
    lIdx = lY * lWidth
    lPosY = (lHeight - 1) - lY
    InRun = False

    For lX = 0 To lWidth - 1
        If (laBefore(lIdx) = laAfter(lIdx)) Then
            If (InRun) Then ' Write this run
                Call RgnObj.AddRect(lStartX, lPosY, lX, lPosY + 1)
                InRun = False
            End If
        Else
            If (Not InRun) Then
                lStartX = lX
                InRun = True
            End If
        End If

        ' Next pixel
        lIdx = lIdx + 1
    Next lX

    ' Write final run
    If (InRun) Then Call RgnObj.AddRect(lStartX, lPosY, lWidth, lPosY + 1)
Next lY

hRgn = RgnObj.CreateRegion()

Set RgnObj = Nothing
'***

I got about the same performance with this so tried re-writing the class to
avoid the re-buffering to fixed array and VB's often slow structures:

'***
Option Explicit

Private Declare Function ExtCreateRegion Lib "gdi32" _
    (ByRef lpXform As Any, ByVal nCount As Long, ByRef lpRgnData As Any) As
Long
Private Declare Sub RtlMoveMemory Lib "kernel32" _
    (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Dim RgnData() As Long
Dim DataSize As Long
Dim ArrPos As Long

Private Const RDH_RECTANGLES As Long = &H1
Private Const StepSize As Long = 10
Private Const HeaderOffset As Long = 32 \ 4

' Map structure offsets to constants to maintain some code readability
Private Const RGNDataHeader_dwSize As Long = 0
Private Const RGNDataHeader_iType As Long = 1
Private Const RGNDataHeader_nCount As Long = 2
Private Const RGNDataHeader_nRgnSize As Long = 3
Private Const RGNDataHeader_rcBound_Left As Long = 4
Private Const RGNDataHeader_rcBound_Top As Long = 5
Private Const RGNDataHeader_rcBound_Right As Long = 6
Private Const RGNDataHeader_rcBound_Bottom As Long = 7

Public Sub ClearRects()
    ReDim RgnData(HeaderOffset - 1) As Long
    DataSize = HeaderOffset
    ArrPos = HeaderOffset

    ' These properties stay the same
    RgnData(RGNDataHeader_dwSize) = 32 ' sizeof(RGNDataHeader)
    RgnData(RGNDataHeader_iType) = RDH_RECTANGLES

    ' Initialise to null bounds rectangle
    RgnData(RGNDataHeader_rcBound_Left) = &H7FFFFFFF
    RgnData(RGNDataHeader_rcBound_Top) = &H7FFFFFFF
    RgnData(RGNDataHeader_rcBound_Right) = &H80000000
    RgnData(RGNDataHeader_rcBound_Bottom) = &H80000000
End Sub

Public Sub AddRect(ByVal inLeft As Long, ByVal inTop As Long, _
    ByVal inRight As Long, ByVal inBottom As Long)
    If (ArrPos <= DataSize) Then
        DataSize = DataSize + (StepSize * 4)
        ReDim Preserve RgnData(DataSize - 1) As Long
    End If

    ' Add this rectangle
    RgnData(ArrPos) = inLeft
    RgnData(ArrPos + 1) = inTop
    RgnData(ArrPos + 2) = inRight
    RgnData(ArrPos + 3) = inBottom

    ' Bounds check this rectangle
    If (inLeft < RgnData(RGNDataHeader_rcBound_Left)) Then _
        RgnData(RGNDataHeader_rcBound_Left) = inLeft
    If (inRight > RgnData(RGNDataHeader_rcBound_Right)) Then _
        RgnData(RGNDataHeader_rcBound_Right) = inRight
    If (inTop < RgnData(RGNDataHeader_rcBound_Top)) Then _
        RgnData(RGNDataHeader_rcBound_Top) = inTop
    If (inBottom > RgnData(RGNDataHeader_rcBound_Bottom)) Then _
        RgnData(RGNDataHeader_rcBound_Bottom) = inBottom

    ArrPos = ArrPos + 4
End Sub

Public Function CreateRegion() As Long
    Dim WriteData() As Long
    Dim BufSize As Long

    ' Nothing to do!
    If (DataSize <= HeaderOffset) Then Exit Function

    ' Fill in header sizes
    RgnData(RGNDataHeader_nCount) = (ArrPos - HeaderOffset) \ 4
    RgnData(RGNDataHeader_nRgnSize) = _
        RgnData(RGNDataHeader_nCount) * 16 ' sizeof(RECT)

    CreateRegion = ExtCreateRegion(ByVal 0&, RgnData(RGNDataHeader_dwSize) +
_
        RgnData(RGNDataHeader_nRgnSize), RgnData(0))
End Function

Private Sub Class_Initialize()
    Call ClearRects
End Sub
'***

Completely unreadable, but _very_ fast.
This effectively bypasses the time to create the region so all the only
thing that takes the time is walking the pixel buffers which as we've seen
if about as fast as it can get.
There's probably odd optimisations you can make here (C++ is also faster at
walking arrays, I'm not porting the class there though for the time being!)
and there but it's not going to get a lot faster than that IMO.
Hope this helps,

    Mike

 - Microsoft Visual Basic MVP -
E-Mail: ED...@mvps.org
WWW: Http://www.mvps.org/EDais/

 
 
 

Getting Handle to a region [GDI with VB]

Post by Rocky Clar » Tue, 08 Jul 2003 03:23:20


Mike,

You ARE the man!

FYI:  Hex(&H808080 Xor &HFFFFFF) = &H7F7F7F, so there's no problem using the
inverse color for the flood fill.

I took it even a step further. I dumped the entire thing into an ActiveX
DLL, including the function to find the FloodFill region, now called
CreateFloodRgn (By the way, this function no longer performs the FloodFill,
it just returns the region). I also added another function CreateMaskRgn,
which creates a masked region for any color specified that exists in the
bitmap. This would create transparency by using SetWindowRgn on the bitmap's
container window. It can also be used for many other things, such as
SelectClipRgn for complex drawing masks.

My previous best test results were:
250 x 250 bitmap:    average time = 24 ms (0.024 seconds)
1024 x 768 bitmap:    average time = 294 ms (0.294 seconds)

My latest test results are:
(Uncompiled DLL running in VB IDE)
250 x 250 bitmap:    average time = 19 ms (0.019 seconds)
1024 x 768 bitmap:    average time = 231 ms (0.231 seconds)

(Compiled DLL)
250 x 250 bitmap:    average time = 3 ms (0.003 seconds)
1024 x 768 bitmap:    average time = 34 ms (0.034 seconds)

The above tests were performed on a P4-1.8 GHz machine.

Now these are the results I was looking for!

The entire source for the DLL and the Demo is here:
http://kath-rock.com/ZipFiles/BmpRgn.zip

Thanks again,
Rocky

"Mike D Sutton" <Mike.Sut...@btclick.com> wrote in message
news:uOv3o11QDHA.712@TK2MSFTNGP12.phx.gbl...

> > First off, I'd like to say thank you. This is exactly what I needed.
> That's
> > why I ask in these groups (and also why I don't mind helping out with
> > answers for others).
> <snip>

> Ok one last one before bed *g*
> Dump this in a new class called "clsAPIRgn":

> '***
> Option Explicit

> Private Declare Function ExtCreateRegion Lib "gdi32" _
>     (ByRef lpXform As Any, ByVal nCount As Long, ByRef lpRgnData As Any)
As
> Long
> Private Declare Sub RtlMoveMemory Lib "kernel32" _
>     (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

> Private Type RectAPI ' 16 bytes
>     Left As Long
>     Top As Long
>     Right As Long
>     Bottom As Long
> End Type

> Private Type RGNDataHeader ' 32 bytes
>     dwSize As Long
>     iType As Long
>     nCount As Long
>     nRgnSize As Long
>     rcBound As RectAPI
> End Type

> Private Type RGNData ' 32 + (nCount * 16)
>     rdh As RGNDataHeader
>     Buffer() As Long
> End Type

> Dim RgnInf As RGNData
> Dim DataSize As Long
> Dim ArrPos As Long

> Private Const RDH_RECTANGLES As Long = &H1

> ' How many rectangles to create in one 'chunk'
> ' The class will allocate an extra (StepSize * 16 bytes) each chunk
> (Including starting run)
> Private Const StepSize As Long = 10

> Public Sub ClearRects()
>     ReDim RgnInf.Buffer(0) As Long
>     DataSize = 0
>     ArrPos = 0

>     With RgnInf.rdh
>         ' These properties stay the same
>         .dwSize = Len(RgnInf.rdh)
>         .iType = RDH_RECTANGLES

>         ' Initialise to null bounds rectangle
>         With .rcBound
>             .Left = &H7FFFFFFF
>             .Top = &H7FFFFFFF
>             .Right = &H80000000
>             .Bottom = &H80000000
>         End With
>     End With
> End Sub

> Public Sub AddRect(ByVal inLeft As Long, ByVal inTop As Long, _
>     ByVal inRight As Long, ByVal inBottom As Long)
>     If (ArrPos <= DataSize) Then
>         DataSize = DataSize + (StepSize * 4)
>         ReDim Preserve RgnInf.Buffer(DataSize - 1) As Long
>     End If

>     With RgnInf ' Add this rectangle
>         .Buffer(ArrPos) = inLeft
>         .Buffer(ArrPos + 1) = inTop
>         .Buffer(ArrPos + 2) = inRight
>         .Buffer(ArrPos + 3) = inBottom

>         With .rdh.rcBound ' Bounds check this rectangle
>             If (inLeft < .Left) Then .Left = inLeft
>             If (inRight > .Right) Then .Right = inRight
>             If (inTop < .Top) Then .Top = inTop
>             If (inBottom > .Bottom) Then .Bottom = inBottom
>         End With
>     End With

>     ArrPos = ArrPos + 4
> End Sub

> Public Function CreateRegion() As Long
>     Dim WriteData() As Long
>     Dim BufSize As Long

>     ' Nothing to do!
>     If (DataSize <= 0) Then Exit Function

>     With RgnInf.rdh ' Fill in header sizes
>         .nCount = ArrPos \ 4
>         .nRgnSize = .nCount * Len(.rcBound)
>     End With

>     ' Get buffer size and scale output array
>     BufSize = RgnInf.rdh.dwSize + RgnInf.rdh.nRgnSize
>     ReDim WriteData((BufSize \ 4) - 1) As Long

>     ' Copy the header and data to output array
>     Call RtlMoveMemory(WriteData(0), RgnInf.rdh, Len(RgnInf.rdh))
>     Call RtlMoveMemory(WriteData(Len(RgnInf.rdh) \ 4), RgnInf.Buffer(0),
> RgnInf.rdh.nRgnSize)

>     ' Create region from internal data
>     CreateRegion = ExtCreateRegion(ByVal 0&, BufSize, WriteData(0))
> End Function

> Private Sub Class_Initialize()
>     Call ClearRects
> End Sub
> '***

> The region creation code then gets compacted right down and ends up
looking
> like this:

> '***
> Dim RgnObj As clsAPIRgn

> ...

> Set RgnObj = New clsAPIRgn

> For lY = 0 To lHeight - 1
>     lIdx = lY * lWidth
>     lPosY = (lHeight - 1) - lY
>     InRun = False

>     For lX = 0 To lWidth - 1
>         If (laBefore(lIdx) = laAfter(lIdx)) Then
>             If (InRun) Then ' Write this run
>                 Call RgnObj.AddRect(lStartX, lPosY, lX, lPosY + 1)
>                 InRun = False
>             End If
>         Else
>             If (Not InRun) Then
>                 lStartX = lX
>                 InRun = True
>             End If
>         End If

>         ' Next pixel
>         lIdx = lIdx + 1
>     Next lX

>     ' Write final run
>     If (InRun) Then Call RgnObj.AddRect(lStartX, lPosY, lWidth, lPosY + 1)
> Next lY

> hRgn = RgnObj.CreateRegion()

> Set RgnObj = Nothing
> '***

> I got about the same performance with this so tried re-writing the class
to
> avoid the re-buffering to fixed array and VB's often slow structures:

> '***
> Option Explicit

> Private Declare Function ExtCreateRegion Lib "gdi32" _
>     (ByRef lpXform As Any, ByVal nCount As Long, ByRef lpRgnData As Any)
As
> Long
> Private Declare Sub RtlMoveMemory Lib "kernel32" _
>     (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

> Dim RgnData() As Long
> Dim DataSize As Long
> Dim ArrPos As Long

> Private Const RDH_RECTANGLES As Long = &H1
> Private Const StepSize As Long = 10
> Private Const HeaderOffset As Long = 32 \ 4

> ' Map structure offsets to constants to maintain some code readability
> Private Const RGNDataHeader_dwSize As Long = 0
> Private Const RGNDataHeader_iType As Long = 1
> Private Const RGNDataHeader_nCount As Long = 2
> Private Const RGNDataHeader_nRgnSize As Long = 3
> Private Const RGNDataHeader_rcBound_Left As Long = 4
> Private Const RGNDataHeader_rcBound_Top As Long = 5
> Private Const RGNDataHeader_rcBound_Right As Long = 6
> Private Const RGNDataHeader_rcBound_Bottom As Long = 7

> Public Sub ClearRects()
>     ReDim RgnData(HeaderOffset - 1) As Long
>     DataSize = HeaderOffset
>     ArrPos = HeaderOffset

>     ' These properties stay the same
>     RgnData(RGNDataHeader_dwSize) = 32 ' sizeof(RGNDataHeader)
>     RgnData(RGNDataHeader_iType) = RDH_RECTANGLES

>     ' Initialise to null bounds rectangle
>     RgnData(RGNDataHeader_rcBound_Left) = &H7FFFFFFF
>     RgnData(RGNDataHeader_rcBound_Top) = &H7FFFFFFF
>     RgnData(RGNDataHeader_rcBound_Right) = &H80000000
>     RgnData(RGNDataHeader_rcBound_Bottom) = &H80000000
> End Sub

> Public Sub AddRect(ByVal inLeft As Long, ByVal inTop As Long, _
>     ByVal inRight As Long, ByVal inBottom As Long)
>     If (ArrPos <= DataSize) Then
>         DataSize = DataSize + (StepSize * 4)
>         ReDim Preserve RgnData(DataSize - 1) As Long
>     End If

>     ' Add this rectangle
>     RgnData(ArrPos) = inLeft
>     RgnData(ArrPos + 1) = inTop
>     RgnData(ArrPos + 2) = inRight
>     RgnData(ArrPos + 3) = inBottom

>     ' Bounds check this rectangle
>     If (inLeft < RgnData(RGNDataHeader_rcBound_Left)) Then _
>         RgnData(RGNDataHeader_rcBound_Left) = inLeft
>     If (inRight > RgnData(RGNDataHeader_rcBound_Right)) Then _
>         RgnData(RGNDataHeader_rcBound_Right) = inRight
>     If (inTop < RgnData(RGNDataHeader_rcBound_Top)) Then _
>         RgnData(RGNDataHeader_rcBound_Top) = inTop
>     If (inBottom > RgnData(RGNDataHeader_rcBound_Bottom)) Then _
>         RgnData(RGNDataHeader_rcBound_Bottom) = inBottom

>     ArrPos = ArrPos + 4
> End Sub

> Public Function CreateRegion() As Long
>     Dim WriteData() As Long
>     Dim BufSize As Long

>     ' Nothing to do!
>     If (DataSize <= HeaderOffset) Then Exit Function

>     ' Fill in header sizes
>     RgnData(RGNDataHeader_nCount) = (ArrPos - HeaderOffset) \ 4
>     RgnData(RGNDataHeader_nRgnSize) = _
>         RgnData(RGNDataHeader_nCount) * 16 ' sizeof(RECT)

>     CreateRegion = ExtCreateRegion(ByVal 0&, RgnData(RGNDataHeader_dwSize)
+
> _
>         RgnData(RGNDataHeader_nRgnSize), RgnData(0))
> End Function

> Private Sub Class_Initialize()
>     Call ClearRects
> End Sub
> '***

> Completely unreadable, but _very_ fast.
> This effectively bypasses the time to create the region so all the only
> thing that takes the time is walking the pixel buffers which as we've seen
> if about as fast as it can get.
> There's probably odd optimisations you can make here (C++ is also faster
at
> walking arrays, I'm not porting the class there though for the time
being!)
> and there but it's not going to get a lot faster than that IMO.
> Hope this helps,

>     Mike

>  - Microsoft Visual Basic MVP -
> E-Mail: ED...@mvps.org
> WWW: Http://www.mvps.org/EDais/

 
 
 

1. how to get region info from handle by Windows GDI

Hello,

Does anyone know how to get the border information of a REGION object
given its handle in Windows 95?
I know there are some APIs in Windows GDI, (GetRgnBox().GetClipBox(),..)
But those API can only return the rectangle information which includes the
region. I want to get the EXACT border for non-rectangle region, say an ellipse
for a region created by CreateEllipticRgn().

Thanks in advance and looking forward to replies.

Lichao
--

  Lichao Tan

2. Fast video card

3. D3D and GDI Regions

4. Greyscale scanners?

5. Loosing GDI Resources with regions

6. Low GDI Resources using WinAPI Functions in VB App

7. VB Api GDI WinNt

8. VB 5.0 application uses excessive GDI Memory under Windows 95

9. GDI Function Calls in VB 5.0 CCE

10. Releasing Region Handles

11. How to get the handle of the icon on a VB form