Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Recursively extracting sprites
#1
I was working on a way to extract individual sprites (or images) from a sprite sheet. Most sprite sheets found on the Internet are not neatly organized in same sized rectangular areas across the sheet.

At first I tried to design a line follower that would trace around sprites to find their area. That was like herding cats.

It then occurred to me that a flood fill like recursive routine may work. It does! The code below is a quick hack to show the concept in action. You'll need the image to see the code in action.

There's still some issues to work out but I thought someone might like to play with the code as is.

Code: (Select All)
' Parse a sprite sheet concept
'
' Issues to overcome:
' - Individual pixels are seen as separate sprites (the last 5 sprites in image)
'   Somehow the code needs to intelligently decide that these pixels belong to a sprite
'

TYPE XY
    x AS INTEGER
    y AS INTEGER
END TYPE

DIM Min AS XY '                    minimum x,y values seen
DIM Max AS XY '                    maximum x,y values seen
DIM Background AS _UNSIGNED LONG ' background color to use as border
DIM Pixel AS _UNSIGNED LONG '      pixel color
DIM SourceSheet AS LONG '          source image containing sprites
DIM TargetSheet AS LONG '          target image to write found sprites to
DIM x AS INTEGER '                 horizontal coordinate counter
DIM y AS INTEGER '                 vertical coordinate counter

SourceSheet = _LOADIMAGE("demon.png", 32) '                              load source sprite sheet
TargetSheet = _NEWIMAGE(_WIDTH(SourceSheet), _HEIGHT(SourceSheet), 32) ' create target sprite sheet
SCREEN _NEWIMAGE(781, 720, 32) '                                         create view screen
CLS
Background = _RGB32(255, 0, 255) '                                       background border color (bright magenta)
_SOURCE SourceSheet '                                                    get pixel data from source image

y = 0 '     reset vertical counter
DO
    x = 0 ' reset horizontal counter
    DO

        ' scan right until a pixel is found

        Pixel = POINT(x, y) '          get pixel color
        IF Pixel <> Background THEN '  is this pixel part of a sprite?

            ' A pixel has been found

            Min.x = x ' set starting point of min/max x,y values seen
            Max.x = x
            Min.y = y
            Max.y = y

            GrabSprite x, y, SourceSheet, TargetSheet, Background ' recursively get sprite

            ' Now the sprite has been written to the target image and the min/max x,y coordinates are known
            ' Grab the written sprite as you wish and then remove it from the target sheet

            ' This nethod would work just as well if for instance the mouse pointer was used to click
            ' anywhere inside of a sprite to extract it.

            ' Quick code to show the concept in action

            _DEST 0
            _PUTIMAGE (0, 0), SourceSheet
            _PUTIMAGE (0, 360), TargetSheet
            LOCATE 1, 1
            PRINT Max.x - Min.x, Max.y - Min.y ' current sprite dimensions
            _LIMIT 5 ' slow down to see progress
        END IF
        x = x + 1
    LOOP UNTIL x = _WIDTH(SourceSheet)
    y = y + 1
LOOP UNTIL y = _HEIGHT(SourceSheet)



SUB GrabSprite (x AS INTEGER, y AS INTEGER, s AS LONG, t AS LONG, Border AS _UNSIGNED LONG)

    ' Recursively grabs an image within the border color specified (proof of concept)
    ' (Based on flood fill)

    ' x,y - pixel coordinate
    ' s   - source image
    ' t   - target image
    ' b   - border (background) color

    SHARED Min AS XY
    SHARED Max AS XY
    DIM Pixel AS _UNSIGNED LONG

    IF x = -1 OR y = -1 OR x = _WIDTH(s) OR y = _HEIGHT(s) THEN EXIT SUB ' leave if x,y outside of source image
    _SOURCE s '                             get from source image
    Pixel = POINT(x, y) '                   get pixel color
    IF Pixel = Border THEN EXIT SUB '       leave if pixel is border color
    _DEST t '                               draw on target image
    PSET (x, y), Pixel '                    copy pixel to target image
    _DEST s '                               draw on source image
    PSET (x, y), Border '                   remove pixel from source image

    MinMax x, y, Min, Max '                 get x and y extremes seen

    GrabSprite x - 1, y, s, t, Border '     examine surrounding pixels
    GrabSprite x + 1, y, s, t, Border
    GrabSprite x, y - 1, s, t, Border
    GrabSprite x, y + 1, s, t, Border
    GrabSprite x - 1, y - 1, s, t, Border
    GrabSprite x - 1, y + 1, s, t, Border
    GrabSprite x + 1, y - 1, s, t, Border
    GrabSprite x + 1, y + 1, s, t, Border

END SUB


SUB MinMax (x AS INTEGER, y AS INTEGER, min AS XY, max AS XY)

    IF x < min.x THEN
        min.x = x
    ELSEIF x > max.x THEN
        max.x = x
    END IF
    IF y < min.y THEN
        min.y = y
    ELSEIF y > max.y THEN
        max.y = y
    END IF

END SUB


Attached Files Image(s)
   
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Reply
#2
Makes sense. If this was SCREEN 0, I would approach it using the SCREEN function to do as follows:

1) Start at the top row and for across the screen, one column at a time until the end of the screen. If a pixel is filled, jump out of the loop and continue down to the next row until the function can go completely across the screen without encountering a filled pixel. This is the bottom row just below the bottom of all images in a row.

2) Start at the first column of the first row and proceed down to the bottom row of the sprite discovered in the step above. If no pixel is encountered, jump to the next column to the left. When a pixel is encountered, load the first of four variables as true, and proceed on until a full downward pass is made with no pixels encountered. You now have the first and last columns occupied by the first sprite.

3) Repeat step two from the last column scanned, finding the first and last columns of the second sprite.

4) Take half the width between the first two variables, and half the width between the second two variables. Measure between those two results to get the width between the centers of all sprites in a line and the starting point.

5) start from the first column and measure as far as the width results above to find and pixels in the first sprite. Multiply this amount by the sprite number on the line desired, and start from there to retrieve that sprite.

Code: (Select All)

Print "                                           "
Print "   ******    *******   *******   *******   "
Print "   *     *   *            *      *         "
Print "   *     *   *            *      *         "
Print "   ******    *****        *      ****      "
Print "   *         *            *      *         "
Print "   *         *            *      *         "
Print "   *         *******      *      *******   "
Print "                                           "

Locate 1, 1
Do
    Do Until Pos(0) = _Width
        If Screen(CsrLin, Pos(0)) <> 32 Then Exit Do
        Locate , Pos(0) + 1
    Loop
    y1 = CsrLin: x1 = Pos(0)
    If pixel = 0 Then If x1 < _Width Then pixel = -1: start_row = CsrLin
    Locate CsrLin + 1, 1
Loop Until y1 > 1 And x1 = _Width And pixel = -1 Or CsrLin = _Height
If pixel And x1 = _Width Then sprite_height = CsrLin - start_row - 1

col = 0
Do
    col = col + 1: pixel = 0
    Locate start_row, col
    For i = 1 To sprite_height
        If Screen(CsrLin, col) <> 32 Then
            pixel = -1
            If sprite_1_start_col = 0 Then sprite_1_start_col = col: Exit For
        End If
        Locate CsrLin + 1, col
    Next
Loop Until sprite_1_start_col And pixel = 0 Or col = _Width
If col < _Width Then
    sprite_1_end_col = col - 1
End If

Do
    col = col + 1: pixel = 0
    Locate start_row, col
    For i = 1 To sprite_height
        If Screen(CsrLin, col) <> 32 Then
            pixel = -1
            If sprite_2_start_col = 0 Then sprite_2_start_col = col: Exit For
        End If
        Locate CsrLin + 1, col
    Next
Loop Until sprite_2_start_col And pixel = 0 Or col = _Width
If col < _Width Then
    sprite_2_end_col = col - 1
End If

fac_1 = sprite_1_start_col / 2
fac_2 = (sprite_2_start_col + (sprite_2_end_col - sprite_2_start_col) / 2) - (sprite_1_start_col + (sprite_1_end_col - sprite_1_start_col) / 2)

Do
    View Print: Locate 12, 1: Print Space$(_Width);
    Locate 12, 1
    Input "Select to retrieve a sprite from sprite sheet (1 - 4): "; sel
    View Print 13 To _Height: Cls 2
    x = fac_1 + (sel - 1) * fac_2
    y = start_row

    For i = start_row To start_row + sprite_height
        For j = x To x + fac_2
            If Screen(i, j) <> 32 Then Locate 15 + i - start_row, 4 + j - ((sel - 1) * fac_2): Print "*";
        Next
    Next
Loop

Not completely stress tested and note if you took out a full column in any sprite, it would fail. With sprites like that you would have to increase the loop sensitivity to account for a few columns or rows of completely missing pixels.

Pete
Reply
#3
Pete,

In line 55 instead of '/ 2' try '-1'. If 1 is pressed it displays a P in asterisks as printed above. Dividing by two leaves a three asterisks wide display. May be this is what you are aiming for.
Reply
#4
(03-03-2024, 03:21 PM)GareBear Wrote: Pete,

In line 55 instead of '/ 2' try '-1'. If 1 is pressed it displays a P in asterisks as printed above. Dividing by two leaves a three asterisks wide display. May be this is what you are aiming for.
Aiming for? Angry 

I'll have you know I always hit what I'm aiming for. Now if you will excuse me, the bathroom floor is all wet in places, again... and why do my slippers smell funny? Huh 

No worries, darn forum formatting. In the process of trying to get a copy and paste of the strings to the forum right and by manipulating the text in the strings to accomplish that, there was a space missing, now fixed, which made the "sprite sheet" uneven. It works now.

THANKS!

Pete
Reply
#5
@TerryRitchie I don't know if you picked a good sheet to illustrate problem?

I had no problem blocking it out from the pixel dimensions given in file properties:
Code: (Select All)
' Terry's sprite sheet demon.png is 781 X 360
' there are 11 images across 781 / 11 = 71 exactly
' thereare 6 images down 360 / 6 = 60
' Hence each image should be 71 X 60

' Let's check that!

Screen _NewImage(800, 600, 32)
_ScreenMove 240, 70

D& = _LoadImage("demon.png", 32)
_PutImage (0, 0), D&, 0 ' OK (finally?)

'For x = 0 To 780 Step 71
'    Line (x, 0)-(x, 359),
'Next
'For y = 0 To 359 Step 60
'    Line (0, y)-(780, y)
'Next
' looks good
Sleep
Dim i&(0 To 57)
For j = 0 To 57
    col = j Mod 11
    row = Int(j / 11)
    i&(j) = _NewImage(71, 60, 32)
    _PutImage , 0, i&(j), (col * 71, row * 60)-Step(70, 59)
Next
'Cls
For j = 0 To 57
    Cls
    _PutImage (0, 0), i&(j), 0
    _PrintString (22, 70), Str$(j)
    Sleep
Next
b = b + ...
Reply
#6
Correct, this sheet I prepared many years ago by hand. It was just handy to show the routine worked.

Preparing sheets by hand though rarely ever gets the exact results that replicate their intended motion. Most sprite sheets that are not aligned assume the sprites use the center point as a reference to display therefore each sprite's dimensions are different for the same character.

I'm writing code to take this into account and prepare and write sprite sheets accordingly. Each sprite will be encoded with it's actual dimensions when read. I'm working on a sprite editor now to help with this.
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Reply
#7
What inspired that sprite sheet and... did you get a signed release from your mother-in-law to use it?

Good luck with the editor project. I don't do graphics, but I do get a kick how the different graphic statements can be used in a more efficient manner than text coding.

Pete
Reply
#8
(03-03-2024, 08:18 PM)Pete Wrote: What inspired that sprite sheet and... did you get a signed release from your mother-in-law to use it?

Good luck with the editor project. I don't do graphics, but I do get a kick how the different graphic statements can be used in a more efficient manner than text coding.

Pete

I was working on a game called BeDoomed years ago, a cross between BeJeweled and Doom. I gave up because my mother-in-law wouldn't relinquish the rights.


Attached Files Image(s)
   
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Reply
#9
Big Grin Big Grin Big Grin
Shoot first and shoot people who ask questions, later.
Reply
#10
OK @TerryRitchie work your magic on this altered test sheet:
   

My first use of _SaveImage from v3.12 Smile
b = b + ...
Reply




Users browsing this thread: 5 Guest(s)