Recursively extracting sprites - TerryRitchie - 03-03-2024
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
RE: Recursively extracting sprites - Pete - 03-03-2024
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
RE: Recursively extracting sprites - GareBear - 03-03-2024
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.
RE: Recursively extracting sprites - Pete - 03-03-2024
(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?
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?
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
RE: Recursively extracting sprites - bplus - 03-03-2024
@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
RE: Recursively extracting sprites - TerryRitchie - 03-03-2024
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.
RE: Recursively extracting sprites - Pete - 03-03-2024
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
RE: Recursively extracting sprites - TerryRitchie - 03-03-2024
(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.
RE: Recursively extracting sprites - Pete - 03-03-2024
RE: Recursively extracting sprites - bplus - 03-04-2024
OK @TerryRitchie work your magic on this altered test sheet:
My first use of _SaveImage from v3.12
|