Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Copying and pasting a non-rectangular area of screen
#1
Has anyone made a routine to copy a paste a non-rectangular area of the screen?  Or does QB64PE have a built-in way?  I'm working on a new drawing program and trying to add a feature where you can draw an enclosed area with the mouse, copy it, and paste it.  Making a circle area routine was easy, but having some difficulty with the any shape one.  I'm doing a polygon approach for the any shaped one, capture all the pixels inside the drawn polygon.   Wondered if someone here has already made such a routine that is working.

Here's the circle copy/paste that is working ok.

- Dav

Code: (Select All)

Screen _NewImage(1000, 700, 32)

For t = 1 To 7000
    Color _RGB(Rnd * 255, Rnd * 255, Rnd * 255)
    Print _Trim$(Str$(Int(Rnd * 10)));
Next

Circle (100, 100), 51, _RGB(255, 255, 255)
x& = GetCircle&(100, 100, 50)

Locate 2, 24:: Color _RGB(255, 255, 255)
Print "  Copied this area of the screen  "
_Delay 2

Cls

Do
    PutCircle Rnd * _Width, Rnd * _Height, x&, Rnd * 255
    _Limit 30
Loop Until InKey$ <> ""



Function GetCircle& (cx, cy, radius)

    If circleimg& <> 0 Then _FreeImage circleimg&

    circleimg& = _NewImage(radius * 2, radius * 2) '

    _Dest circleimg&

    For x = 0 To radius * 2
        For y = 0 To radius * 2
            If Sqr((x - radius) ^ 2 + (y - radius) ^ 2) <= radius Then
                clr& = Point(cx - radius + x, cy - radius + y)
                PSet (x, y), clr&
            End If
        Next
    Next

    GetCircle& = _CopyImage(circleimg&)
    _FreeImage circleimg&

    _Dest 0

End Function

Sub PutCircle (cx, cy, img&, alpha)

    If img& = 0 Then Exit Sub

    radius = _Width(img&) / 2

    _Source img&
    _Dest 0

    For x = 0 To radius * 2
        For y = 0 To radius * 2
            If Sqr((x - radius) ^ 2 + (y - radius) ^ 2) <= radius Then
                clr~& = Point(x, y)
                r = _Red32(clr~&)
                g = _Green32(clr~&)
                b = _Blue32(clr~&)
                PSet (cx + x, cy + y), _RGBA(r, g, b, alpha)
            End If
        Next
    Next

    _Source 0

End Sub

Find my programs here in Dav's QB64 Corner
Reply
#2
(09-25-2024, 12:26 PM)Dav Wrote: Has anyone made a routine to copy a paste a non-rectangular area of the screen?  Or does QB64PE have a built-in way?  I'm working on a new drawing program and trying to add a feature where you can draw an enclosed area with the mouse, copy it, and paste it.  Making a circle area routine was easy, but having some difficulty with the any shape one.  I'm doing a polygon approach for the any shaped one, capture all the pixels inside the drawn polygon.   Wondered if someone here has already made such a routine that is working.

Here's the circle copy/paste that is working ok.

- Dav

Code: (Select All)

Screen _NewImage(1000, 700, 32)

For t = 1 To 7000
    Color _RGB(Rnd * 255, Rnd * 255, Rnd * 255)
    Print _Trim$(Str$(Int(Rnd * 10)));
Next

Circle (100, 100), 51, _RGB(255, 255, 255)
x& = GetCircle&(100, 100, 50)

Locate 2, 24:: Color _RGB(255, 255, 255)
Print "  Copied this area of the screen  "
_Delay 2

Cls

Do
    PutCircle Rnd * _Width, Rnd * _Height, x&, Rnd * 255
    _Limit 30
Loop Until InKey$ <> ""



Function GetCircle& (cx, cy, radius)

    If circleimg& <> 0 Then _FreeImage circleimg&

    circleimg& = _NewImage(radius * 2, radius * 2) '

    _Dest circleimg&

    For x = 0 To radius * 2
        For y = 0 To radius * 2
            If Sqr((x - radius) ^ 2 + (y - radius) ^ 2) <= radius Then
                clr& = Point(cx - radius + x, cy - radius + y)
                PSet (x, y), clr&
            End If
        Next
    Next

    GetCircle& = _CopyImage(circleimg&)
    _FreeImage circleimg&

    _Dest 0

End Function

Sub PutCircle (cx, cy, img&, alpha)

    If img& = 0 Then Exit Sub

    radius = _Width(img&) / 2

    _Source img&
    _Dest 0

    For x = 0 To radius * 2
        For y = 0 To radius * 2
            If Sqr((x - radius) ^ 2 + (y - radius) ^ 2) <= radius Then
                clr~& = Point(x, y)
                r = _Red32(clr~&)
                g = _Green32(clr~&)
                b = _Blue32(clr~&)
                PSet (cx + x, cy + y), _RGBA(r, g, b, alpha)
            End If
        Next
    Next

    _Source 0

End Sub

       I don't have any code handy but I have a good idea how you must go about it.      Because of how Video Ram is layed out _PutImage & _GetImage only operate on rectangular images.  I believe you MUST be in 32 bit color mode to do this but what you have to do is do a _GetImage of what you want.     Then Mask out the shape with pixels that have an _Alpha (opacity) value of 0.    Then when you do the _PutImage the ALPHA=0 pixels will not be visible.      Depending on the shape the masking code will be different.    Your code is clever but I have the feeling that it might be to slow for animation.    (Maybe not though,  It is a bit amazing how fast modern computers are.   But my code mind set is stuck on slower systems)
Reply
#3
Yeah, I dealt with it. Finally, I used a transparent background, I selected the farthest coordinates from the array of points that bounded the polygon, thus obtaining a rectangular slice with a transparent background, and I sent that to the new handle of the new image with the _PutImage command. It's significantly faster than solving it point by point. I'll try to find it or write it right away.


Reply
#4
I agree with @ahenry3068 and @Petr. Doing it using alpha masks will be the fastest for 32-bpp images.
Reply
#5
Thank you all for the advice, that sounds like a good idea.  Will get working on it.

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#6
Hey, I needed to dust the cobwebs out of my brain anyway and this caught my eye (obviously I couldn't find the old source), so a bit of thinking and here's the output. Mark any shape, the condition is that you must connect the beginning of the line with the end (tolerance of 5 pixels from each other). After marking the selection of the image, the program will only show you what you marked with the selection.

Code: (Select All)

'copy/paste polygon image
'use mouse for inserting image

Image& = _ScreenImage
_Delay .5

Screen _NewImage(_DesktopWidth, _DesktopHeight, 32)
_FullScreen
Cls
Type Points
    As Integer x, y
End Type
ReDim P(20000) As Points

Do
    _PutImage , Image& 'place complete image to screen

    Locate 1
    Print "Use mouse, press left button and select any image area"



    While _MouseInput
    Wend
    MX = _MouseX
    MY = _MouseY
    LB = _MouseButton(1)


    Print LB, MX, MY, j, max


    If LB = -1 Then
        'insert coordinate to array if is left mouse button pressed
        If oMX <> MX Or oMY <> MY Then 'if is mouse moved to other position
            P(j).x = MX '              add new coordinates to array P
            P(j).y = MY
            oMX = MX '                  and memorize old coordinates
            oMY = MY
            j = j + 1
            max = 0
            i = UBound(P)
            If j = i Then '            if array P is full, add next 20000 records
                ReDim _Preserve P(i + 20000) As Points
                max = 0
            End If
        End If
    End If


    'draw selected area: find used indexes

    If max = 0 Then 'lock for search maximum once if now record is not added
        max = UBound(P)
        Do Until P(max).x > 0 Or P(max).y > 0
            If max > 0 Then max = max - 1 Else Exit Do
        Loop
    End If


    If max Then
        PReset (P(0).x, P(0).y)
        For D = 1 To max
            Line -(P(D).x, P(D).y)
        Next
    End If

    'control if area is completed
    If max > 10 Then
        If Abs(P(max).x - P(0).x) < 5 And Abs(P(max).y - P(0).y) < 5 Then
            Print "select complete!"

            'find maximal/minimal X and Y
            c = 0
            minX = _Width
            minY = _Height
            Do Until c = max
                If minX > P(c).x Then minX = P(c).x
                If maxX < P(c).x Then maxX = P(c).x
                If minY > P(c).y Then minY = P(c).y
                If maxY < P(c).y Then maxY = P(c).y
                c = c + 1
            Loop

            'copy full rectangle image area to new image
            copyImg& = _NewImage(maxX - minX, maxY - minY, 32)
            _PutImage (0, 0), Image&, copyImg&, (minX, minY)-(maxX, maxY)


            'create alpha mask for image
            Mask& = _NewImage(maxX - minX, maxY - minY, 32)
            _Dest Mask&
            Cls 'now is the background color black and alpha is full - set to 0,0,0,255

            For D = 1 To max
                Line (-minX + P(D - 1).x, -minY + P(D - 1).y)-(-minX + P(D).x, -minY + P(D).y), _RGB32(100)
            Next
            Line (-minX + P(0).x, -minY + P(0).y)-(-minX + P(max).x, -minY + P(max).y), _RGB32(100)

            Paint (_Width(Mask&) \ 2, _Height(Mask&) \ 2), _RGB32(100) 'colorize selected area
            _SetAlpha 0, _RGB32(100), Mask&


            'place mask image
            _PutImage , Mask&, copyImg&
            _FreeImage Mask&
            _Dest 0
            _Source 0
            'view output
            Cls
            _PutImage (0, 0), copyImg&
            _FreeImage copyImg&
            End


        End If
    End If

    _Limit 30
    _Display
Loop


[Image: output.png]

   


Reply
#7
The way I'd go about this would be to :

1) Make a _CopyImage of the existing screen.
2) Set _DontBlend on that CopyImage
3) When clicking the mouse, I'd draw a line from point A to point B in COLOR 0 (no alpha, no red, no green, no blue.)   Do this until you enclose the area you want to copy.  Keep the max top/left/bottom/right coordinates to make a box later.
4) PAINT that screen in COLOR 0, outside the box to keep.
5) Turn _Blend on for that CopyImage
6) You can now _PutImage that top/left/bottom/right box onto the main screen.
7) Freeimage that _CopyImage
Reply
#8
Thanks for the code, @Petr!  That looks good.  I have to leave home for the rest of the day now but will sure play with your code when getting back home.  Have a good day.

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#9
There's a bug, I'll look into fixing it after the meal. Specifically, line 101. That's bullshit, isn't it? This has to be replaced. We need to find for a specific Y coordinate two marginal X coordinates. And we are back to sorting numbers, mama mia...


Reply
#10
I've been messing around with it a bit, but I'm still not completely satisfied. Although it is possible to mark the shape of the letter C without error, there are still situations when the selection of the image is done incorrectly.

The original version took the center of the image as a fixed point at which the selection image is always present. That was wrong. This version will count all the points around the selection and then try to determine the point between the two boundaries where the color fill for the mask will be done. Well, sometimes is this operation wrong done. If anyone wants to add own version, please, I'd be happy to take a look.

Code: (Select All)

'copy/paste polygon image
'use mouse for inserting image

Image& = _ScreenImage
_Delay .5

Screen _NewImage(_DesktopWidth, _DesktopHeight, 32)
_FullScreen
Cls

Type Points
    As Integer x, y
End Type

Type LinePoints
    X As Integer
    Y As Integer
End Type
ReDim Shared LP(0) As LinePoints
ReDim nP(0) As Points


ReDim P(20000) As Points

Do
    _PutImage , Image& 'place complete image to screen

    Locate 1
    Print "Use mouse, press left button and select any image area"



    While _MouseInput
    Wend
    MX = _MouseX
    MY = _MouseY
    LB = _MouseButton(1)


    Print LB, MX, MY, j, max


    If LB = -1 Then
        'insert coordinate to array if is left mouse button pressed
        If oMX <> MX Or oMY <> MY Then 'if is mouse moved to other position
            P(j).x = MX '              add new coordinates to array P
            P(j).y = MY
            oMX = MX '                  and memorize old coordinates
            oMY = MY
            j = j + 1
            max = 0
            i = UBound(P)
            If j = i Then '            if array P is full, add next 20000 records
                ReDim _Preserve P(i + 20000) As Points
                max = 0
            End If
        End If
    End If


    'draw selected area: find used indexes

    If max = 0 Then 'lock for search maximum once if now record is not added
        max = UBound(P)
        Do Until P(max).x > 0 Or P(max).y > 0
            If max > 0 Then max = max - 1 Else Exit Do
        Loop
    End If


    If max Then
        PReset (P(0).x, P(0).y)
        For D = 1 To max
            Line -(P(D).x, P(D).y)
        Next
    End If

    'control if area is completed
    If max > 10 Then
        If Abs(P(max).x - P(0).x) < 5 And Abs(P(max).y - P(0).y) < 5 Then
            Print "select complete!"

            'find maximal/minimal X and Y
            c = 0
            minX = _Width
            minY = _Height
            Do Until c = max
                If minX > P(c).x Then minX = P(c).x
                If maxX < P(c).x Then maxX = P(c).x
                If minY > P(c).y Then minY = P(c).y
                If maxY < P(c).y Then maxY = P(c).y
                c = c + 1
            Loop

            'copy full rectangle image area to new image
            copyImg& = _NewImage(maxX - minX, maxY - minY, 32)
            _PutImage (0, 0), Image&, copyImg&, (minX, minY)-(maxX, maxY)


            'create alpha mask for image
            Mask& = _NewImage(maxX - minX, maxY - minY, 32)
            _Dest Mask&
            Cls 'now is the background color black and alpha is full - set to 0,0,0,255

            For D = 1 To max
                Line (-minX + P(D - 1).x, -minY + P(D - 1).y)-(-minX + P(D).x, -minY + P(D).y), _RGB32(100)
            Next
            Line (-minX + P(0).x, -minY + P(0).y)-(-minX + P(max).x, -minY + P(max).y), _RGB32(100)

            'Paint (_Width(Mask&) \ 2, _Height(Mask&) \ 2), _RGB32(100) 'colorize selected area
            'this is bad: if you select "C" character, program works badly, also:


            'every y has its own X, so I write out X for a specific y:

            'faulty reasoning. It's not like that. Some points are not in the field because they were drawn by the line statement.
            'therefore, the missing points must be counted and then only look for two neighbors, because otherwise it may happen that
            'you find two which, however, are not directly adjacent.
            'I name the new field with added points nP



            For FillnP = 0 To max - 1
                ReDim LP(0) As LinePoints
                GETPOINTS P(FillnP).x, P(FillnP).y, P(FillnP + 1).x, P(FillnP + 1).y, LP()
                UnP = UBound(nP)
                ReDim _Preserve nP(UnP + UBound(LP)) As Points
                For Fnp = 0 To UBound(LP) 'calculate all new points to field nP
                    nP(UnP + Fnp).x = LP(Fnp).X
                    nP(UnP + Fnp).y = LP(Fnp).Y
                Next
            Next
            ReDim LP(0) As LinePoints
            GETPOINTS P(0).x, P(0).y, P(max).x, P(max).y, LP()
            UnP = UBound(nP)
            ReDim _Preserve nP(UnP + UBound(LP)) As Points
            For Fnp = 0 To UBound(LP) 'calculate all new points to field nP
                nP(UnP + Fnp).x = LP(Fnp).X
                nP(UnP + Fnp).y = LP(Fnp).Y
            Next

            ReDim Xes(rec) As Integer 'array for X - points in one Y line

            For Sy = 0 To UBound(nP)
                MyY = nP(Sy).y

                rec = 0
                ReDim Xes(rec) As Integer

                For Sx = 0 To UBound(nP)
                    If nP(Sx).y = MyY Then
                        rec = rec + 1
                        ReDim _Preserve Xes(rec) As Integer
                        Xes(rec) = nP(Sx).x
                    End If
                Next Sx

                '_Dest 0
                '  Print "for Y = "; MyY; "have found "; rec; "X points";
                '  If rec Then
                '  For RList = 1 To rec
                '  Print Xes(RList)
                'Next
                'End If

                If rec = 2 Then
                    If Xes(1) < Xes(2) Then Swap Xes(1), Xes(2)
                    If Abs(Xes(1) - Xes(2)) > 10 Then
                        MyX = Xes(2) + (Xes(1) - Xes(2)) \ 2
                        '  _Dest 0
                        '  Print "calculated MyX "; MyX
                        ' _Display
                        ' Sleep
                        GoTo test
                    End If
                End If
                '  _Display
                '  Sleep
            Next Sy

            test:

            _Dest Mask&

            Paint (-minX + MyX, -minY + MyY), _RGB32(100) 'colorize selected area
            _SetAlpha 0, _RGB32(100), Mask&


            'place mask image
            _PutImage , Mask&, copyImg&
            _FreeImage Mask&
            _Dest 0
            _Source 0
            'view output
            Cls
            _PutImage (0, 0), copyImg&
            _FreeImage copyImg&
            End


        End If
    End If

    _Limit 30
    _Display
Loop

Sub GETPOINTS (x1, y1, x2, y2, A() As LinePoints)
    Dim lenght As Integer
    lenght = _Hypot(x1 - x2, y1 - y2) 'Fellippe Heitor show me using this great function.
    ReDim A(lenght) As LinePoints
    For fill = 0 To lenght
        If x1 > x2 Then A(fill).X = x1 - fill * ((x1 - x2) / lenght)
        If x1 < x2 Then A(fill).X = x1 + fill * ((x2 - x1) / lenght)
        If x1 = x2 Then A(fill).X = x1
        If y1 > y2 Then A(fill).Y = y1 - fill * ((y1 - y2) / lenght)
        If y1 < y2 Then A(fill).Y = y1 + fill * ((y2 - y1) / lenght)
        If y1 = y2 Then A(fill).Y = y1
    Next
End Sub


Reply




Users browsing this thread: 4 Guest(s)