From: Bee on
I am making good progress with a picturebox lasso thanks to Mike.
But I am stuck on the last issue.
So far I can
(1) lasso an area in a picturebox
(2) create a sprite and mask
(3) show the spite and mask as a moveable sprite on the picturebox
(4) move the sprite and mask as desired using the mouse
(5) "insert" the lassoed area into a picturebox making it part of the picture.

But ...
I have tried using a StretchBlt routine to flip the image.
I use this routine for other non-sprite images and it works properly.
I flip both the sprite and mask picturebox holders.
Yes, the image does get flipped; however, there is a black area near he
edges that apears. Seems that pat of the "mask" does not get "flipped".
So, is there a way to flip while creating the sprite and mask or a proper
way to flip afte the sprite and image are created?
I think I need to flip during the sprite and mask creation.
I did attempt to use the Sprite and Mask creation routine to do the flip
using BitBlt.
I looked over BitBlt and did not find a way according to my MSDN lib.


From: Mike Williams on
"Bee" <Bee(a)discussions.microsoft.com> wrote in message
news:330737F0-686D-46E9-8BD4-D1851EE81567(a)microsoft.com...

> I have tried using a StretchBlt routine to flip the image.
> I use this routine for other non-sprite images and it works
> properly. I flip both the sprite and mask picturebox holders.
> Yes, the image does get flipped; however, there is a black
> area near he edges that apears. Seems that pat of the
> "mask" does not get "flipped".

StretchBlt should work fine for accurately flipping an image (whether it is
the image of your sprite or your mask), and it shouldn't even matter which
StretchBltMode you use as long as you are not actually stretching (reducing
or increasing) the size of the image in the flip, which I assume you are
not, although having said that it would probably make sense to use
STRETCH_DELETESCANS (Stretch Mode 3) anyway even though you are peforming
just a simple flip.

> So, is there a way to flip while creating the sprite and mask or
> a proper way to flip afte the sprite and image are created?
> I think I need to flip during the sprite and mask creation.

Flipping as part of the creation process make much more sense than flipping
during an actual sprite movement routine, because StretchBlt typically takes
up to three or four times as long as blitting when StretchBlt is being asked
to either flip or resize the image. So if you flip during the creation of
the sprite and mask then you pay that time penalty only once, and you can
then use the standard Bitblt in the actual sprite movement routines.

> however, there is a black area near the edges that
> apears. Seems that pat of the "mask" does not
> get "flipped".

The StretchBlt flip should not cause that to happen, especially since you
are performing a simple flip and you are not actually resizing the image as
well, as long as you have specified the coordinates and sizes correctly in
both cases. Mind you, having said that, there might be some problems of that
nature if you are using the original version of the example code I posted,
which uses regions for some of its tasks, especially if you have modified it
in various ways for your own purposes.

That code was just a starting example and it used a region method simply
because it was the first thing that came into my head when answering your
original question on this topic some time ago. I thought that I had
mentioned that shortly afterwards and had posted some further example code
which does not use regions, but I can't seem to find is now. Do you remember
seeing that post? If not then I must have forgotten to post it, althoughy I
did mean to do so and I thought I had done.

Using CreatePolygonRgn to create an irregular region does not produce
exactly the same shape as does drawing a filled area using the Polygon
function, even if you are using exactly the same array of POINTAPI data to
perform both tasks. The shape drawn by Polygon will be very slightly larger
than the shape created by CreatePolygonRgn, although it is about a pixel
larger in a slightly irregular fashion. That is because the various region
functions in general behave slightly differently than do the various drawing
methods, especially in relation to the rightmost and bottommost end points
of the line segments.

Here's some example code which does not use regions in the creation of the
hand drawn area, or anywhere else, and which allows you to manually draw an
irregular shape using the mouse. When you release the mouse button the code
closes the shape (if it is not already closed) and it creates a sprite and a
mask of that shape and positions it at its initial display position, drawing
a "marching ants" outline around the sprite so that you can easily see it on
the main picture. You can then drag the sprite around the screen using the
mouse. Clicking anywhere other than on the sprite itself then drops the
sprite at its last position, allowing you to draw another shape.

Pressing the Ctrl key on the keyboard toggles between "normal" and "flip"
mode (which in this example applies to the next sprite you draw and not to
any sprite you may already be dragging around, although of course that kind
of code could be added if you wish). In flip mode the sprite and mask are
created as normal when you release the mouse button after drawing, but both
the sprite image and the mask image are then flipped in the x direction
(using StretchBlt) before being plotted on the display.

I've added the "flip the sprite" code rather hurriedly in response to this
latest post of yours, and so I haven't yet had time to flip the "marching
ants" outline as well (which requires a little more thought), so in flip
mode the marching ants outline does not correctly surround the shape of the
sprite, but you should nevertheless be able to see that StretchBlt does in
fact perform the flip accurately, both in the case of the sprite and he
mask.

The code is rather "rambling" and it could really do with a re-write in a
more logical fashion, but it should be enough to help you with your sprite
flipping problem. Start a new VB project and place four PictureBoxes and one
Timer Control on the Form and then name the PictureBoxes picMain, picSprite,
picMask and picBackBuffer (I've chosen a separate backbuffer for this
example, from which I can pull the small sections to clear the old sprite
area, although there are various other ways of doing it of course). Paste in
the following code and change the hard coded picture path to a picture that
exists on your own system.

Mike

Option Explicit
Private Declare Function StretchBlt Lib "gdi32" _
(ByVal hdc As Long, _
ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, _
ByVal hSrcDC As Long, _
ByVal xSrc As Long, ByVal ySrc As Long, _
ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, _
ByVal dwRop As Long) As Long
Private Declare Function SetStretchBltMode Lib "gdi32" _
(ByVal hdc As Long, ByVal nStretchMode As Long) As Long
Private Declare Function Polygon Lib "gdi32" _
(ByVal hdc As Long, lpPoint As POINTAPI, _
ByVal nCount As Long) As Long
Private Declare Function BitBlt Lib "gdi32" _
(ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, _
ByVal hSrcDC As Long, ByVal xSrc As Long, _
ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function SetViewportOrgEx Lib "gdi32" _
(ByVal hdc As Long, ByVal nX As Long, _
ByVal nY As Long, lpPoint As POINTAPI) As Long
Private Declare Function SetPolyFillMode Lib "gdi32" _
(ByVal hdc As Long, ByVal nPolyFillMode As Long) As Long
Private Const COLORONCOLOR = 3
Private Const ALTERNATE As Long = 1
Private Const WINDING As Long = 2
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type POINTAPI
x As Long
y As Long
End Type
Private d1() As POINTAPI, lastCoord As Long
Private mainWidth As Long, mainHeight As Long
Private xMin As Long, xMax As Long, yMin As Long, yMax As Long
Private SpriteX As Long, SpriteY As Long
Private spriteWide As Long, spriteHigh As Long
Private SpriteDisplayed As Boolean, Drawing As Boolean
Private MouseInSprite As Boolean, Dragging As Boolean
Private offsetX As Long, offsetY As Long
Private flipX As Boolean

Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyControl Then
flipX = Not flipX
DisplayFlipStatus
End If
End Sub

Private Sub DisplayFlipStatus()
If flipX Then
Me.Caption = "Next selection will be flipped"
Else
Me.Caption = "Next selection will NOT be flipped"
End If
Me.Caption = Me.Caption & " (Press Ctrl to change)"
End Sub

Private Sub Form_Load()
Dim s1 As String
Me.WindowState = vbMaximized
Me.Show
s1 = "c:\temp\jessica1.jpg"
InitPicBox picMain, True
InitPicBox picBackBuffer, False
InitPicBox picSprite, False
InitPicBox picMask, False
picMain.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
picBackBuffer.Move 0, 0, picMain.Width, picMain.Height
mainWidth = picMain.ScaleWidth
mainHeight = picMain.ScaleHeight
picMain.PaintPicture LoadPicture(s1), 0, 0, _
mainWidth, mainHeight
BitBlt picBackBuffer.hdc, 0, 0, mainWidth, mainHeight, _
picMain.hdc, 0, 0, vbSrcCopy
picMask.BackColor = vbWhite
picMask.FillColor = vbBlack
picMask.FillStyle = vbFSSolid
SetStretchBltMode picMain.hdc, COLORONCOLOR
DisplayFlipStatus
Me.KeyPreview = True
Timer1.Interval = 200
Timer1.Enabled = True
End Sub

Private Sub InitPicBox(p1 As PictureBox, SetVisible As Boolean)
p1.Visible = SetVisible
p1.BorderStyle = vbBSNone
p1.ScaleMode = vbPixels
p1.AutoRedraw = True
End Sub

Private Sub picMain_DblClick()
' A double click inside the area of a sprite indicates
' that user has finished dragging and wants to drop the
' sprite where it is.
If MouseInSprite Then
DropSprite SpriteX, SpriteY
End If
End Sub

Private Sub picmain_MouseDown(Button As Integer, _
Shift As Integer, x As Single, y As Single)
'If Button <> vbLeftButton Then Exit Sub
If MouseInSprite Then
' User wants to start dragging sprite
offsetX = SpriteX - x
offsetY = SpriteY - y
Dragging = True
'picMain.DrawStyle = vbDot
Else
' A click outside the sprite indicates user has finished
' dragging and possibly also wants to start drawing a new
' selection area, so we need to drop the sprite at its
' current location and then set up the conditions for
' a new drawing
If Button = vbLeftButton Then
' drop sprite at current position
DropSprite SpriteX, SpriteY
' start a new drawing
Drawing = True
picMain.DrawMode = vbNop
picMain.DrawStyle = vbSolid
picMain.PSet (x, y)
picMain.DrawMode = vbInvert
ReDim d1(0 To 100)
lastCoord = 0
d1(lastCoord).x = x
d1(lastCoord).y = y
xMin = x: xMax = x
yMin = y: yMax = y
Else
' cancel the operation (remove sprite from picMain)
DropSprite -spriteWide, -spriteHigh
End If
End If
End Sub

Private Sub picmain_MouseMove(Button As Integer, _
Shift As Integer, x As Single, y As Single)
If x >= mainWidth Then x = mainWidth - 1
If y >= mainHeight Then y = mainHeight - 1
If x < 0 Then x = 0
If y < 0 Then y = 0
If Drawing Then
If x > xMax Then xMax = x
If x < xMin Then xMin = x
If y > yMax Then yMax = y
If y < yMin Then yMin = y
picMain.Line -(x, y)
lastCoord = lastCoord + 1
If lastCoord > UBound(d1) Then
ReDim Preserve d1(0 To lastCoord + 100)
End If
d1(lastCoord).x = x
d1(lastCoord).y = y
End If
If Dragging And Button = vbLeftButton Then
DisplaySprite CLng(x) + offsetX, CLng(y) + offsetY, True
End If
MouseInSprite = SpriteDisplayed And _
(picMask.Point(x - SpriteX, y - SpriteY) = 0)
If MouseInSprite Then
If picMain.MousePointer <> 15 Then
picMain.MousePointer = 15
End If
Else
If picMain.MousePointer <> 0 Then
picMain.MousePointer = 0
End If
End If
End Sub

Private Sub picmain_MouseUp(Button As Integer, _
Shift As Integer, x As Single, y As Single)
If Button <> vbLeftButton Then Exit Sub
If Drawing Then
' close the shape and create the sprite and mask
picMain.Line -(d1(0).x, d1(0).y)
spriteWide = xMax - xMin + 1
spriteHigh = yMax - yMin + 1
If lastCoord > 0 Then
CreateAndDisplaySprite
End If
Drawing = False
End If
End Sub

Private Sub MakeSpriteAndMask()
Dim oldOrg As POINTAPI
picSprite.Width = Me.ScaleX(spriteWide, vbPixels, Me.ScaleMode)
picSprite.Height = Me.ScaleY(spriteHigh, vbPixels, Me.ScaleMode)
Me.ScaleMode = vbPixels ' just for test purposes for the following line
' create black on white picMask
picMask.Width = picSprite.Width
picMask.Height = picSprite.Height
picMask.Cls ' clear mask to white
' Draw black on white mask to picMask.
' First offset the origin of picMask so that we
' can use the same array of point data to draw
' a copy of the polygon at the top left of the
' Mask picbox.
SetViewportOrgEx picMask.hdc, -xMin, -yMin, oldOrg
' Then draw the polygon
SetPolyFillMode picMask.hdc, WINDING
Polygon picMask.hdc, d1(0), lastCoord + 1
' Then set the origin back to normal, otherwise VB
' will become very confused when we use any native
' VB methods on the picturebox.
SetViewportOrgEx picMask.hdc, oldOrg.x, oldOrg.y, oldOrg
' blit the full rectangle of the selection to picSprite
BitBlt picSprite.hdc, 0, 0, spriteWide, _
spriteHigh, picMain.hdc, _
xMin, yMin, vbSrcCopy
' Now OR the mask to it to create a standard
' "image on white" sprite image
BitBlt picSprite.hdc, 0, 0, spriteWide, _
spriteHigh, picMask.hdc, _
0, 0, vbSrcPaint
If flipX = True Then
' flip both sprite and mask
StretchBlt picMask.hdc, spriteWide, 0, -spriteWide, _
spriteHigh, picMask.hdc, _
0, 0, spriteWide, spriteHigh, vbSrcCopy
StretchBlt picSprite.hdc, spriteWide, 0, -spriteWide, _
spriteHigh, picSprite.hdc, _
0, 0, spriteWide, spriteHigh, vbSrcCopy
End If
End Sub

Private Sub CreateAndDisplaySprite()
' temporarily erase vbinvert drawn polygon by redrawing
Polygon picMain.hdc, d1(0), lastCoord + 1
' don't bother with extremely small sprites
If spriteWide > 2 And spriteHigh > 2 Then
' call the routine to make the sprite and mask
MakeSpriteAndMask
SpriteX = xMin: SpriteY = yMin ' initial sprite position
DisplaySprite SpriteX, SpriteY, True ' (True = include outline)
SpriteDisplayed = True
End If
End Sub

Private Sub DisplaySprite(x As Long, y As Long, AddOutline As Boolean)
' The method used here eliminates flicker when dragging
' or animating the sprite by having the display PictureBox
' Autoredraw True. There are other ways of doing it if you
' are running a very slow machine at a very high display
' resolution.
Dim orgOld As POINTAPI
' first redraw original background at old sprite position
BitBlt picMain.hdc, SpriteX, SpriteY, _
spriteWide, spriteHigh, picBackBuffer.hdc, _
SpriteX, SpriteY, vbSrcCopy
' now transparently draw the sprite at its new position
BitBlt picMain.hdc, x, y, spriteWide, spriteHigh, _
picMask.hdc, 0, 0, vbMergePaint
BitBlt picMain.hdc, x, y, spriteWide, spriteHigh, _
picSprite.hdc, 0, 0, vbSrcAnd
If AddOutline Then
SetViewportOrgEx picMain.hdc, x - xMin, y - yMin, orgOld
picMain.DrawStyle = vbDot
Polygon picMain.hdc, d1(0), lastCoord + 1
SetViewportOrgEx picMain.hdc, orgOld.x, orgOld.y, orgOld
picMain.DrawStyle = vbSolid
End If
picMain.Refresh
' update SpriteX and SpriteY variables to new coordinates
SpriteX = x
SpriteY = y
End Sub

Private Sub DropSprite(x As Long, y As Long)
DisplaySprite x, y, False ' display without outline
' copy modified main image to buffer
BitBlt picBackBuffer.hdc, 0, 0, mainWidth, mainHeight, _
picMain.hdc, 0, 0, vbSrcCopy
SpriteDisplayed = False
MouseInSprite = False
Dragging = False
If picMain.MousePointer <> 0 Then
picMain.MousePointer = 0
End If
End Sub

Private Sub Timer1_Timer()
' draw "marching ants" outline by repeatedly drawing
' a solid/invert polygon over an initially drawn
' dotted/invert polygon.
Dim orgOld As POINTAPI
If SpriteDisplayed Then
' setting Autoredraw to False whilst drawing the Polygon
' means we don't need to issue a PicBox.Refresh in order
' for the drawing to be seen, saving time overall, whilst
' maintaing Autoredraw True for other purposes
picMain.AutoRedraw = False
' offset viewport so we can use the same original polygon
' data array at any other position we wish
SetViewportOrgEx picMain.hdc, SpriteX - xMin, _
SpriteY - yMin, orgOld
Polygon picMain.hdc, d1(0), lastCoord + 1
' set viewport back to normal or VB will become confused
SetViewportOrgEx picMain.hdc, orgOld.x, orgOld.y, orgOld
picMain.AutoRedraw = True
End If
End Sub


From: Bee on
That is a lot to study, but I will.
I really enjoy just learning new things.
Thanks for all your help.

My newsreader is messed up again (works, then stops working compaining that
the ISP has rejected me just after successfullly posting a few minutes
earlier) and MS communities is not working so I am not sure where this post
will wind up.


"Bee" wrote:

> I am making good progress with a picturebox lasso thanks to Mike.
> But I am stuck on the last issue.
> So far I can
> (1) lasso an area in a picturebox
> (2) create a sprite and mask
> (3) show the spite and mask as a moveable sprite on the picturebox
> (4) move the sprite and mask as desired using the mouse
> (5) "insert" the lassoed area into a picturebox making it part of the picture.
>
> But ...
> I have tried using a StretchBlt routine to flip the image.
> I use this routine for other non-sprite images and it works properly.
> I flip both the sprite and mask picturebox holders.
> Yes, the image does get flipped; however, there is a black area near he
> edges that apears. Seems that pat of the "mask" does not get "flipped".
> So, is there a way to flip while creating the sprite and mask or a proper
> way to flip afte the sprite and image are created?
> I think I need to flip during the sprite and mask creation.
> I did attempt to use the Sprite and Mask creation routine to do the flip
> using BitBlt.
> I looked over BitBlt and did not find a way according to my MSDN lib.
>
>
From: Bee on
Seems to have some code missing Mike.
Studying what I have.

"Bee" wrote:

> I am making good progress with a picturebox lasso thanks to Mike.
> But I am stuck on the last issue.
> So far I can
> (1) lasso an area in a picturebox
> (2) create a sprite and mask
> (3) show the spite and mask as a moveable sprite on the picturebox
> (4) move the sprite and mask as desired using the mouse
> (5) "insert" the lassoed area into a picturebox making it part of the picture.
>
> But ...
> I have tried using a StretchBlt routine to flip the image.
> I use this routine for other non-sprite images and it works properly.
> I flip both the sprite and mask picturebox holders.
> Yes, the image does get flipped; however, there is a black area near he
> edges that apears. Seems that pat of the "mask" does not get "flipped".
> So, is there a way to flip while creating the sprite and mask or a proper
> way to flip afte the sprite and image are created?
> I think I need to flip during the sprite and mask creation.
> I did attempt to use the Sprite and Mask creation routine to do the flip
> using BitBlt.
> I looked over BitBlt and did not find a way according to my MSDN lib.
>
>
From: Mike Williams on
"Bee" <Bee(a)discussions.microsoft.com> wrote in message
news:1963717C-1E7A-4EAD-97E7-E2CBD4F5842D(a)microsoft.com...

> Seems to have some code missing Mike.
> Studying what I have . . .
> [Lots of stuff snipped]

I'm not sure what you mean, Bee? What code are you saying is missing? Or
have I misunderstood what you have said, either now or in your previous
posts? The example code I posted does all of the things you mention,
including the facility to correctly and accurately flip the sprite and mask
images (using StretchBlt) at the time you create it. All you need to do is
create a new VB Project and place a Timer and four PictureBoxes on the Form,
naming them as described in my previous post, and then paste in the example
code I posted, changing the hard coded picture path/name to a picture that
exists on your own system.

When you run the code you should be able to use the mouse to manually draw
an irregular selection area and when you release the mouse button that area
becomes a sprite, with a "marching ants" outline so that you can see it (if
it were not for the marching ants outline then you would not initially be
able to see the sprite because it is initially placed directly over the area
of the background picture from which it was created). Then you should be
able to click anywhere within the sprite and drag it around, repeatedly
moving it as many times as you wish before finally clicking an area of the
main picture other than the sprite itself, which will cause the sprite to be
"dropped" at its current location and thereafter become part of the
background image. You can then manually create another sprite, using the
same procedure. Also, the Ctrl key on the keyboard toggles between "normal
sprite" and "flipped sprite (x flip)", with the current state reported in
the Form's caption bar. If the current state is "flipped" (before you start
manually drawing an irregular selection area) then the resultant sprite when
you release the mouse button to create it will be a "flipped" sprite (x
flipped in the example code). Is that not what you wanted? Or is that not
what you meant by "flipped"?

Mike