Sprite Graphics

Share and discuss all facets of DKC ROM hacking...

Sprite Graphics

Postby Cyclone » November 15th, 2022, 9:22 pm

Hey all, as far as I know the sprites are uncompressed right?

Well I want to extract them. I want to find the banana sprite location in the rom for starters as I think that is only one tile.
Where is it stored in the rom?

Thanks in advance.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 16th, 2022, 12:20 am

Cyclone wrote:Hey all, as far as I know the sprites are uncompressed right?

Well I want to extract them. I want to find the banana sprite location in the rom for starters as I think that is only one tile.
Where is it stored in the rom?

Thanks in advance.


Oof. That is a loaded question. First, do you understand bitplanes?

The banana is actually multiple sprites, since bananas spin. Animations are just a series of still images.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 16th, 2022, 1:58 am

Yes, I think i understand bitplanes. And yes I know what you are saying...

I just want to edit the sprite manually in a hex editor. So I need to know the location of the sprite graphics.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 16th, 2022, 10:31 pm

Cyclone wrote:I just want to edit the sprite manually in a hex editor. So I need to know the location of the sprite graphics.


Look, it is way more nuanced than that. There are a ton of things you'd have to understand first. First are Sprite Headers. The DKC series has a unique header format. Then is the whole Sprite array thing. There is a 32-bit-wide array (4 bytes per index) that holds absolute pointers of each image. This gives the address of each image. Then, there is the hitbox array. Mattrizzle did I forget anything?

You can check out this program if you want to find the exact address of the bananas viewtopic.php?f=37&t=2505

Mattrizzle heavily goes into sprite format in another post here on the atlas.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby WesternTanager794 » November 17th, 2022, 5:07 am

In DKC resource editor you can export sprites, and in the infamous RainbowS editor you can import them. I may not be able to help an enormous amount, but I’ll look into it! :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby Cyclone » November 17th, 2022, 8:16 pm

Right now I’m just trying to understand how to read the gfx data and nothing else. I think I found where the first frame of the banana sprite is stored. Now I just need to understand how to read the data in the hex editor.

I found this which may be useful that Simion posted.
viewtopic.php?f=38&t=1167&p=21466&hilit=Sprite+format#p21467
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 18th, 2022, 3:16 am

Cyclone wrote:Right now I’m just trying to understand how to read the gfx data and nothing else. I think I found where the first frame of the banana sprite is stored. Now I just need to understand how to read the data in the hex editor.


That's what I asked at first, if you understood bitplanes. Bitplanes are how the SNES does graphics. This is a bit tough to understand.
Code: Select all
VRAM 8x8 Pixel Tile Data (BG and OBJ)
Each 8x8 tile occupies 16, 32, or 64 bytes (for 4, 16, or 256 colors). BG tiles can be 4/16/256 colors (depending on BG Mode), OBJs are always 16 color.
  Color Bits (Planes)     Upper Row ........... Lower Row
  Plane 0 stored in bytes 00h,02h,04h,06h,08h,0Ah,0Ch,0Eh ;\for 4/16/256 colors
  Plane 1 stored in bytes 01h,03h,05h,07h,09h,0Bh,0Dh,0Fh ;/
  Plane 2 stored in bytes 10h,12h,14h,16h,18h,1Ah,1Ch,1Eh ;\for 16/256 colors
  Plane 3 stored in bytes 11h,13h,15h,17h,19h,1Bh,1Dh,1Fh ;/
  Plane 4 stored in bytes 20h,22h,24h,26h,28h,2Ah,2Ch,2Eh ;\
  Plane 5 stored in bytes 21h,23h,25h,27h,29h,2Bh,2Dh,2Fh ; for 256 colors
  Plane 6 stored in bytes 30h,32h,34h,36h,38h,3Ah,3Ch,3Eh ;
  Plane 7 stored in bytes 31h,33h,35h,37h,39h,3Bh,3Dh,3Fh ;/
  In each byte, bit7 is left-most, bit0 is right-most.
  Plane 0 is the LSB of color number.



Cyclone wrote:I found this which may be useful that Simion posted.
viewtopic.php?f=38&t=1167&p=21466&hilit=Sprite+format#p21467


That is good for the majority of Graphics, but not all. That post is for 4bpp (16 color). Some Graphics use 2bpp (4 color) or even 8bpp (256 color). I haven't studied that method closely, but I have no reason to think it is wrong. Simion is a legend!
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Kingizor » November 18th, 2022, 5:07 am

The idea is that bitplane data is decoded into a value that is an index corresponding to a colour in the associated palette. The SNES palette (CGRAM) is a block of data containing 512 colours. Sprite and background tiles have some extra data that tells the SNES what section of CGRAM to use.

2bpp, 4bpp and 8bpp determine how many bits each decoded value has. For 2 bits, we have a range of 0-3, for 4 bits we have 0-15 and for 8 bits we have 0-255. SNES Sprites are always 4bpp. Background tiles can be 2bpp, 4bpp or 8bpp depending on the background mode. There are a few other background formats but most of them aren't used in the DKC games.

Individual tiles are always 8x8 pixels, so a single tile contains 64 pixels worth of data. Sprites are typically made up of many tiles!

So we have:
· palette data (contains the colours)
· tilemap data (which palette block to use)
· tileset data (bitplane data, which colours to use in the block)

And that's everything needed to decode and construct a tile. (you won't need tilemap data if you know the palette used by a particular tile)


Here is a brief summary for the bitplane format itself:

It's easier to think it through with 2bpp first, as 4bpp and 8bpp are the same but with more blocks of data. The nth 16-bit word contains data for the nth row of pixels. So 1st word contains data for 1st row, 8th word contains data for the 8th row.

Each 16-bit word in the bitplane format consists of 2 bytes containing 2 bits for each pixel in a given row.
Code: Select all
76543210 - byte #1 in a given word
76543210 - byte #2 in a given word

So if we had a 2bpp bitplane and the first word was C7 34, we could decode that as:
Code: Select all
C7 = 11000111   (1)
34 = 00110100   (2)
   = 11220311

So the 8 pixel values in the nth row would be 1,1,2,2,0,3,1,1. We'd then look those up those in the palette to get the matching colour.

4bpp and 8bpp work the same, they just contain additional blocks of words after the first block of words.
Code: Select all
2bpp = 1 block  of 8 words
4bpp = 2 blocks of 8 words
8bpp = 4 blocks of 8 words

If the data were 4bpp, the only difference from 2bpp would be that there is another block of 8 words after the first block, so 16 words (32 bytes) altogether.

If the corresponding word in the second block was 56 39, we'd get:
Code: Select all
C7 = 11000111   (1)
34 = 00110100   (2)
56 = 01010110   (4)
39 = 00111001   (8)
   = 15AE8759

So 1,5,10,14,8,7,5,9 as values for each pixel in this row.

It's a finicky thing and it can take a while to click, especially if you're not used to bits and binary logic. Some of the tutorials I went through years ago about this were nothing short of atrocious. There was one that was so bad it felt like I was unlearning all of this by just reading it.

If you don't have the palette data handy, you can scale the decoded values to produce a greyscale image. It wouldn't contain any colour, but it would still be legible.

Spoiler!
The NES stores bitplanes in a more natural way due to using an 8-bit bus. For 2bpp there are 8 bytes, then another 8 bytes.

The 16-bit fetches here are due to how the SNES PPU works. All data in VRAM is 16-bit and during rendering the PPU is only concerned about the current row, so it needs that data to be adjacent. If it used the same format as NES it would take twice as many fetches, and it's already so busy that every single step is already occupied. The Game Boy uses the same bitplane format, so presumably it'd work somewhat similarly.

Lots of interesting things going on!
Trailblazer
Bananas received 77
Posts: 247
Joined: 2010

Re: Sprite Graphics

Postby WesternTanager794 » November 18th, 2022, 5:36 am

The way the SNES stores graphics is confusing. Did they make it that way to keep people from tinkering with the system? Like with creating their own games? :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby rainbowsprinklez » November 18th, 2022, 6:57 am

Cyclone, this is what finally 'clicked' for me in that regard. https://sneslab.net/wiki/Graphics_Format#4bpp
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Kingizor » November 18th, 2022, 7:09 am

WesternTanager794 wrote:The way the SNES stores graphics is confusing. Did they make it that way to keep people from tinkering with the system? Like with creating their own games? :parry:


There could be a lot of reasons, and we can only really speculate.

Most capabilities of the SNES PPU are extensions of the functionality present on the NES PPU. When the NES was designed in the early 1980s the cost of different components would have been an extremely important factor.

A cartridge had to contain physical memory to hold the ROM, so they would factor that in. A memory chip that holds 64KB would be far more expensive than one that could hold 32KB, so having a graphics format that doesn't waste any space can make a lot of difference.

For the NES in particular it was generally expected early on that one chip would contain the game's program code and another would contain the graphics data.

The bitplane format is just an efficient and plain way of storing graphics data. and can be decoded quite easily by a program or an electronic circuit. The format is finicky, but it's by no means a secret.

When SNES came around they probably thought why not extend and build upon what we already have instead of doing something completely different?

The only thing intended to prevent people from developing their own games on NES or SNES is the CIC lockout chip. There is one present on the console itself and a corresponding chip on each cartridge. They talk to each other when the console turns on, it's kind of a special handshake. If the handshake doesn't happen properly the CIC on the console doesn't allow any further access to the contents of the cartridge. There are workarounds for that today, but for a long time it ensured that no-one could manufacture cartridges without going through Nintendo.

There are a number of communities out there full of interested individuals that research and develop games and tools for these old consoles.
Trailblazer
Bananas received 77
Posts: 247
Joined: 2010

Re: Sprite Graphics

Postby VideoViking » November 18th, 2022, 9:16 am

As well as how-to videos. Here's one I found not too long ago:

https://www.youtube.com/playlist?list=P ... mHezHaEUsK

Be warned, the lessons in this playlist are very verbose. And I believe it'll be the same for every other assembly tutorial that's out there.
Treasure Hunter
Bananas received 41
Posts: 329
Joined: 2009

Re: Sprite Graphics

Postby WesternTanager794 » November 18th, 2022, 9:16 am

Thanks for the guide Kingizor! I'll try to figure out bitplanes soon!
And also thanks for the link VideoViking! I'll study up on that!
:parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby Cyclone » November 18th, 2022, 3:27 pm

Kingizor wrote:Individual tiles are always 8x8 pixels, so a single tile contains 64 pixels worth of data. Sprites are typically made up of many tiles!

So we have:
· palette data (contains the colours)
· tilemap data (which palette block to use)
· tileset data (bitplane data, which colours to use in the block)

And that's everything needed to decode and construct a tile. (you won't need tilemap data if you know the palette used by a particular tile)


hmm where is this tileset data? specifically the tileset for the banana sprite (just want to learn).
I found the header for it... see screenshot. RainbowSprinklez editor says something about Tile Addresses but changing those did nothing.
See screenshot of my HEX editor where I found the header Data.

Awesome editor by the way mr Sprinklez!

Thanks everyone for the help and patience. I know i'm slow to understand. :oops:
Attachments
SNESGFXHeader.png
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby WesternTanager794 » November 18th, 2022, 3:57 pm

I'll start looking in a second, Cyclone! :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby Cyclone » November 18th, 2022, 7:10 pm

Ok I think I know what those tile addresses do. They just shift pixels around.
I found the address of the letter k sprite :k:

I played around with hex editor. All I can seem to do is make the pixels in the tiles black or transparent…

How can I for example make it a solid yellow square? Or something easy to change by changing bitplane values
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 18th, 2022, 10:50 pm

Cyclone wrote:I played around with hex editor. All I can seem to do is make the pixels in the tiles black or transparent…

How can I for example make it a solid yellow square? Or something easy to change by changing bitplane values


That tool will reflect changes you make in rom. Therefore, one thing I like to do when I want to understand is making the changes I want and Save As another copy. That way I could file compare in a hex editor (I use HxD for this) and see exactly what bytes change. This is a powerful technique!
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 18th, 2022, 11:42 pm

I’m not sure if I understand. Can someone show me an example on changing the letter k to something else.
For example make each sprite that makes the letter k a different colour.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 1:59 am

by the way I tested your sprite editor and it does not work. I tried changing the pallet and the sprite using the tile editor.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 2:16 am

I was able to change the Pallet ok using your RainbowZ Editor but the sprite editor menu is grayed out...
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 19th, 2022, 3:58 am

Cyclone wrote:I was able to change the Pallet ok using your RainbowZ Editor but the sprite editor menu is grayed out...


You are using it wrong. There is nothing wrong.
Image
You should be using the tile editor if you want to edit tiles.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 7:40 am

Sorry user error! I pressed apply thinking it would write to the rom and not the write button. :oops:

Here is the simplest thing I could think of to try to understand.
The heart that Candy kisses DK is a single 8x8 tile.
I used the sprite editor to colour it in a solid dark red square.

I am comparing (as you suggested using the HxD hex editor) the files to try to figure out where the sprite data starts and ends. See screenshots.

SO now i am trying to figure out where it starts and ends and what format its in (Planar / Intertwined, 4BPP?)
Attachments
compareB.png
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby WesternTanager794 » November 19th, 2022, 7:55 am

Thanks! I've been looking for a decent hex editor! I haven't been looking very hard, though. :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby rainbowsprinklez » November 19th, 2022, 9:04 am

Cyclone wrote:Sorry user error! I pressed apply thinking it would write to the rom and not the write button. :oops:


Rainbow on another post wrote:'Write' always writes to rom, where 'Apply' makes changes on a program level.


Seriously, no big deal. Ok, you are ALMOST right. From my notes:
Code: Select all
Sprite Header
Byte 0 is number of 2x2 chars
Byte 1 is number of  1x1 chars in group 1
Byte 2 is relative position of first 1x1 char of group 1
Byte 3 is number of 1x1 chars in group 2
Byte 4 is position of group 2
Byte 5 is number of chars in dma group 1
Byte 6 is where to place dma group 2 (0 if none)
Byte 7 is number of chars in dma group 2 (0 if none)

These 8 bytes are followed by 2 bytes each representing bytes 0, 1, and 3 of the header. Those 2 bytes are the X and Y coordinate of each graphic.


That info is available SOMEWHERE on the atlas. While it is not perfect, it is good enough for this. So you navigated past the main header. You need to skip 2 more bytes. Then sprites are ALWAYS in 4bpp. The tile/char is the next 0x20 bytes.

Next 2 lines of ROM are:
Code: Select all
00 ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 ff
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00



With bpp, you focus on 2 bitplanes per 0x10 bytes. Every other byte is the same bitplane. So for 00 ff 00 ff, you look at it and 00 focuses on bit 0, then ff on bit 1. Altogether it's like
Code: Select all
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3

Each byte in the bitplane focuses on 1 row of the char. So ff means that bit is on for every pixel. Consider 1 nybble in hex. That has 4 bits. A byte has 8 bits. So 8 bytes means 1 for each row. 8 bytes is only for 1 bit. 4bpp = 4 bits * 8 bytes. Sorry if that was confusing. Kingizor could likely clarify
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 12:44 pm

ok I got it working but there is one issue. See screenshot. There is a difference in the pallet index and what I actually get when I run the game.
Attachments
notes.png
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 1:04 pm

I don't know if this is user error but I keep getting a green square when I select index 8. Again see screenshot.
Attachments
green_square.png
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 19th, 2022, 1:39 pm

Cyclone wrote:I don't know if this is user error but I keep getting a green square when I select index 8. Again see screenshot.


User error. You are using the wrong palette.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 1:48 pm

what is the palette name for the Red heart then? I was using the default. And I didn't see it in the drop down list.
Thanks.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 19th, 2022, 2:02 pm

Cyclone wrote:what is the palette name for the Red heart then? I was using the default. And I didn't see it in the drop down list.
Thanks.


It's complicated. I believe the heart palette resides in the bg palette bank. There was never any reason to individually split the palettes by entity because things are constant there.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 19th, 2022, 2:08 pm

That's really odd. I wonder why RARE made an exception just for that sprite.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 19th, 2022, 2:11 pm

Cyclone wrote:That's really odd. I wonder why RARE made an exception just for that sprite.


Not odd. They didn't. Cranky and Funky do the same thing. Think about it. It makes sense.
Instead of 8 16 color palettes, they use 1 256 color palette

WE might see those as separate things. Candy, the heart, etc. Rare saw it as one SCREEN. Or at least, I think they did. The data supports that conclusion

Tldr; there's this thing called color RAM. See this image:
Image
The bottom half are all sprite palettes. Any sprite can access any row of the bottom 8.The top 8 are reserved for BG, and typically any 8x8 char has access to only one of the top 8 rows. The lower 8 (for sprites) section changes in levels as new sprites use them. In other words, the environment constantly changes. In contrast, Candy has 0 risk of things changing. If you try and change Diddy's shirt color to purple, it will change in level but not places like Cranky and I presume here. Since Diddy's palette isn't used here. Another copy exists. Afaik, the other games are better about this
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby WesternTanager794 » November 20th, 2022, 3:28 am

Confusing! That’s an interesting mystery! :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby Cyclone » November 20th, 2022, 6:38 am

Yes but it makes sense.

Any ideas on a single tile sprite with a proper palette?
I still need to play around with something simple until I get the hang of things.

Thanks!

Edit. What palette is used for the heart? I mean how do I know what colours are available for it?
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby rainbowsprinklez » November 20th, 2022, 7:21 am

Cyclone wrote:Yes but it makes sense.

Any ideas on a single tile sprite with a proper palette?
I still need to play around with something simple until I get the hang of things.

Thanks!

I know of no tile that is composed of 1 char. Perhaps a good one to look at is KONG letters. That one is a 16x16 tile composed of 4 chars
Image

Cyclone wrote:Edit. What palette is used for the heart? I mean how do I know what colours are available for it?


Tldr; you don't. You guess and check. You could use Mesen to find which row is used and use that to aid guess and check. We could delve into that, but it doesn't seem worth in my opinion
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 20th, 2022, 7:26 am

Cool thanks dude for your advice.
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Cyclone » November 26th, 2022, 4:02 pm

Hi, just wondering how to convert each row(the index values) of a sprite's graphics into individual pixels that I can write to a bitmap image. I am using C++.


Also Simion notes say the color data is planar. I thought it was intertwined... Is he wrong?
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Kingizor » November 27th, 2022, 2:38 am

Try to get a bit more familiar with binary and using bitwise operators to manipulate binary numbers. Remember that all integers can be represented as a sequence of bits.

Bitwise operators are fundamental for computers so almost all programming languages have some means of using them. Languages derived from C will typically use:

Code: Select all
Bitwise AND:  & (check)
Bitwise  OR:  | (set)
Bitwise XOR:  ^ (toggle)
Bitwise NOT:  ~ (negate)
 Left Shift: <<
Right Shift: >>
Logical NOT:  ! (useful for reducing single bits)


It's customary to use truth tables to describe the effects of bitwise operators but they can be a bit confusing if you're new to them.

A value from the top row with a value from the left column gives the result where they meet.

Code: Select all
AND: If A and B are set, then C is set
             |~~~|~~~|~~~|
             |AND| 0 | 1 |
0 & 0 = 0    |~~~|~~~|~~~|
0 & 1 = 0    | 0 | 0 | 0 |
1 & 0 = 0    |~~~|~~~|~~~|
1 & 1 = 1    | 1 | 0 | 1 |
             |~~~|~~~|~~~|

 OR: If A  or B  is set, then C is set
             |~~~|~~~|~~~|
             | OR| 0 | 1 |
0 & 0 = 0    |~~~|~~~|~~~|
0 & 1 = 1    | 0 | 0 | 1 |
1 & 0 = 1    |~~~|~~~|~~~|
1 & 1 = 1    | 1 | 1 | 1 |
             |~~~|~~~|~~~|

XOR: If one of A and B is set, then C is set.
             |~~~|~~~|~~~|
             |XOR| 0 | 1 |
0 & 0 = 0    |~~~|~~~|~~~|
0 & 1 = 1    | 0 | 0 | 1 |
1 & 0 = 1    |~~~|~~~|~~~|
1 & 1 = 0    | 1 | 1 | 0 |
             |~~~|~~~|~~~|


Bitwise NOT flips every bit in a given value. It's typically used to clear bits:

C++ has had the binary prefix 0b for a while, and the hexadecimal prefix 0x has been there since the beginning. The following statements are equivalent:

Code: Select all
int a = 0b1111 & ~01100; // a = 0b0011
int a = 0xF    & ~0xC  ; // a = 0x3
int a = 15     & ~12   ; // a = 3


Shifting typically means moving the bits to the left or to the right.

Code: Select all
int a = 0b1100 >> 2; // a = 0b0011
int a = 0xB    >> 2; // a = 0x3
int a = 11     >> 2; // a = 3

int a = 0b1100 << 2; // a = 0b110000
int a = 0xC    << 2; // a = 0x30
int a = 12     << 2; // a = 48


The logical NOT operator isn't a bitwise operator, but it can be useful for extracting single bits quickly. The way it works is any "true" value (i.e. non-zero) becomes 0, and any non-true value (i.e. zero) becomes 1.

Code: Select all
int a = !5;        // a = 0
int a = !12345678; // a = 0
int a = !0;        // a = 1

int a = !!5;        // a = 1
int a = !!12345678; // a = 1
int a = !!0;        // a = 0

int a = !!(0xFF & 0x80); // a = 1


For extracting values from bitplanes, there are a few approaches. Using bitwise AND and shifting the result in a loop would be one option. Another option would be to use bitwise AND and the logical NOT operator to extract set bits and combine them with smaller shifts.



Intertwined refers to the structure of the bitplane that's been described already. All sprites and most background tiles on the SNES use that format.

Spoiler!
"interleaved" is more common here than "intertwined". The "twin" in the latter would make me think of two threads of data, like a DNA strand. Perhaps call that an interleaved pair? Interleaved doesn't have a number associated with it which is more suitable for bitplanes which we can think of as having eight threads of data, one for each column all packed into a single byte.


Planar tiles are used in mode7. You can think of them as 8bpp values that have already been decoded.

The goal is to decode interleaved tiles into planar values. Simion is doing the opposite direction in that post, planar to interleaved, so he refers to them as planar first.

Remember, tiles themselves don't have any colour information bundlded with them, so it can get a bit confusing to refer to the decoded values as colours. Simion's post calls them palette indexes which is very suitable. I'm quite accustomed to calling them "shades" which is probably worse.
Trailblazer
Bananas received 77
Posts: 247
Joined: 2010


Re: Sprite Graphics

Postby rainbowsprinklez » November 27th, 2022, 1:11 pm

Kingizor wrote:"interleaved" is more common here than "intertwined". The "twin" in the latter would make me think of two threads of data, like a DNA strand. Perhaps call that an interleaved pair? Interleaved doesn't have a number associated with it which is more suitable for bitplanes which we can think of as having eight threads of data, one for each column all packed into a single byte.



How does interleaved not involve a number? It starts with the word 'int' which is a number :P
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 27th, 2022, 3:52 pm

@ Kingizor
I followed you advice and came up with this code.
Is there a better more efficient way?

Code: Select all
int main()
{

    int ROW0 = 0b00011000;; //to store number
    int ROW1 = 0b00111100;
    int ROW2 = 0b01111110;
    int ROW3 = 0b11011011;
    int ROW4 = 0b11111111;
    int ROW5 = 0b00100100;
    int ROW6 = 0b01011010;
    int ROW7 = 0b10000001;
    int N = 7; //to store bit
    int NB = 7;
    int NC = 7;
    int ND = 7;
    int NE = 7;
    int NF = 7;
    int NG = 7;
    int NH = 7;
    int c = 0;
   
    BYTE* buf = new BYTE[8 * 5];
   

    for (int j = 0; j < 8; j++) // Row 7
    {
        if (ROW7 & (1 << NH))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        NH--;
    }



    for (int j = 0; j < 8; j++) // Row 6
    {
        if (ROW6 & (1 << NG))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        NG--;
    }


    for (int j = 0; j < 8; j++) // Row 5
    {
        if (ROW5 & (1 << NF))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        NF--;
    }

    for (int j = 0; j < 8; j++) // Row 4
    {
        if (ROW4 & (1 << NE))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        NE--;
    }


    for (int j = 0; j < 8; j++) // Row 3
    {
        if (ROW3 & (1 << ND))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        ND--;
    }


    for (int j = 0; j < 8; j++) // Row 2
    {
        if (ROW2 & (1 << NC))
        {
            buf[c + 0] = (BYTE)0;
            buf[c + 1] = (BYTE)0;
            buf[c + 2] = (BYTE)0;


        }
        else
        {
            buf[c + 0] = (BYTE)255;
            buf[c + 1] = (BYTE)255;
            buf[c + 2] = (BYTE)255;
        }
        c += 3;
        NC--;
    }



     for (int j = 0; j < 8; j++) // Row 1
        {
         if (ROW1 & (1 << N))
            {
                buf[c + 0] = (BYTE)0;
                buf[c + 1] = (BYTE)0;
                buf[c + 2] = (BYTE)0;


            }
            else
            {
                buf[c + 0] = (BYTE)255;
                buf[c + 1] = (BYTE)255;
                buf[c + 2] = (BYTE)255;
            }
            c += 3;
            N--;
        }

     for (int j = 0; j < 8; j++)  // Row 0
     {
         if (ROW0 & (1 << NB))
         {
             buf[c + 0] = (BYTE)0;
             buf[c + 1] = (BYTE)0;
             buf[c + 2] = (BYTE)0;


         }
         else
         {
             buf[c + 0] = (BYTE)255;
             buf[c + 1] = (BYTE)255;
             buf[c + 2] = (BYTE)255;
         }
         c += 3;
         NB--;
     }
   
   


    SaveBitmapToFile((BYTE*)buf,
        8,
        8,
        24,
        0,
        "C:\\Users\\Chris\\Desktop\\bluesquare.bmp");

    delete[] buf;



The output is this image.

bluesquare.png
bluesquare.png (166 Bytes) Viewed 32675 times
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Kingizor » November 27th, 2022, 8:53 pm

That should work. Here are a few small suggestions:

You've got eight loops that are very similar. The first loop operates on the ROW7 and NH variables, the second on ROW6 and HG, and so on. If you put all your ROW7 variables into an array and your N# variables into another array you could loop over them with another loop, so you'd only need one copy of the current 'j' loop instead of eight of them. If the N# variables all start at 7 you might not even need all the separate variables for them. (brackets { } denote scope, so normal variables declared inside them are local to them)

The data so far consists of 1 bit per plane, and you're setting it to black (0) if a bit is set or white (255) if a bit is not set. When we're lacking colour information it's a bit more common to have set bits denote the lighter colours.

For 1bpp (0, 255) is a suitable range of colours when converting directly to RGB24. For arbitrary depths we might use something like (bits * (255 / ((1 << depth)-1))) to get a suitable shade of grey.

Code: Select all
1bpp: (bits * (255 / ((1 << 1)-1))) = { 0, 255 }
2bpp: (bits * (255 / ((1 << 2)-1))) = { 0, 85, 170, 255 }
4bpp: (bits * (255 / ((1 << 4)-1))) = { 0, 36, 72, 109, 145, 182, 218, 255}
8bpp: (bits * (255 / ((1 << 8)-1))) = { 0..255 }

For bitplanes consisting of multiple bits, we would have to stack extracted bits into a single decoded value before we can do something with them. That's should be your next step and shifting/OR/AND should be useful for that.
Trailblazer
Bananas received 77
Posts: 247
Joined: 2010

Re: Sprite Graphics

Postby WesternTanager794 » November 28th, 2022, 6:41 am

Like always! I’m learning from these posts! Thanks Kingizor! :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby rainbowsprinklez » November 28th, 2022, 6:44 am

Kingizor wrote:You've got eight loops that are very similar. The first loop operates on the ROW7 and NH variables, the second on ROW6 and HG, and so on. If you put all your ROW7 variables into an array and your N# variables into another array you could loop over them with another loop, so you'd only need one copy of the current 'j' loop instead of eight of them. If the N# variables all start at 7 you might not even need all the separate variables for them. (brackets { } denote scope, so normal variables declared inside them are local to them)


Another consideration for this is let's say you want to make 1 tweak. The way you have it, you would need to make that tweak in 8 places, rather than 1. Further, you would have to remember to make 8 changes. Trust me, when it's been a while, you'll likely forget you did it that way and get frustrating bugs cause you forgot.
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby WesternTanager794 » November 28th, 2022, 6:45 am

I noticed that as well. :parry:
Sage of Discovery
Bananas received 127
Posts: 2392
Joined: 2022

Re: Sprite Graphics

Postby Cyclone » November 28th, 2022, 1:37 pm

rainbowsprinklez wrote:Another consideration for this is let's say you want to make 1 tweak. The way you have it, you would need to make that tweak in 8 places, rather than 1. Further, you would have to remember to make 8 changes. Trust me, when it's been a while, you'll likely forget you did it that way and get frustrating bugs cause you forgot.


Exactly what I was thinking that's why i asked. I'm already forgetting some things...

Is this better? :thumbs:
Code: Select all
int main()
{
    int ROW[] = { 0b10000001 , 0b01011010, 0b00100100, 0b11111111, 0b11011011, 0b01111110, 0b00111100, 0b00011000 }; // Array to to store numbers


    int N = 7; //to store bit
    int c = 0;

    BYTE* buf = new BYTE[8 * 5];
   

    for (int p = 0; p < 8; p++)
    {
        for (int j = 0; j < 8; j++) // Row 6
        {
            if (ROW[p] & (1 << N))
            {
                buf[c + 0] = (BYTE)0;
                buf[c + 1] = (BYTE)0;
                buf[c + 2] = (BYTE)0;
            }
            else
            {
                buf[c + 0] = (BYTE)255;
                buf[c + 1] = (BYTE)255;
                buf[c + 2] = (BYTE)255;
            }
            c += 3;
            N--;
        }
        N = 7;
    }

SaveBitmapToFile((BYTE*)buf, 8, 8, 24, 0, "C:\\Users\\Chris\\Desktop\\1bppSprite.png");

delete[] buf;

return 0;

Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008


Re: Sprite Graphics

Postby rainbowsprinklez » November 28th, 2022, 3:36 pm

Cyclone wrote: :thumbs:
Code: Select all
int main()
{
    int ROW[] = { 0b10000001 , 0b01011010, 0b00100100, 0b11111111, 0b11011011, 0b01111110, 0b00111100, 0b00011000 }; // Array to to store numbers


    int N = 7; //to store bit
    int c = 0;

    BYTE* buf = new BYTE[8 * 5];
   

    for (int p = 0; p < 8; p++)
    {
        for (int j = 0; j < 8; j++) // Row 6
        {
            if (ROW[p] & (1 << N))
            {
                buf[c + 0] = (BYTE)0;
                buf[c + 1] = (BYTE)0;
                buf[c + 2] = (BYTE)0;
            }
            else
            {
                buf[c + 0] = (BYTE)255;
                buf[c + 1] = (BYTE)255;
                buf[c + 2] = (BYTE)255;
            }
            c += 3;
            N--;
        }
        N = 7;
    }

SaveBitmapToFile((BYTE*)buf, 8, 8, 24, 0, "C:\\Users\\Chris\\Desktop\\1bppSprite.png");

delete[] buf;

return 0;



Looks good to me! I noticed something of a code golf trick though... very minor, and I don't think it's an optimization! But 'N=7;' could go right after p++, like
Code: Select all
    for (int p = 0; p < 8; p++, N=7)
Saves a whopping 1 char, whoo!
Edit
Nvm! Not even!
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Re: Sprite Graphics

Postby Cyclone » November 28th, 2022, 6:08 pm

Quick programming question.

How do you check if a bit not set? I'm sure its something obvious. sorry.

Code: Select all
This checks if a bit is set → if (Bitplane0_ROW[p] & (1 << N)){ // do something.}
but i'm not sure how to check is a bit not set like this → if (! Bitplane0_ROW[p] & (1 << N)){// do something}



I got a 2bbp image working correctly. Now I need to get a 4bpp working. Below is the code. I know its horrible.
Link_Sprite_2bbp.png
Link_Sprite_2bbp.png (177 Bytes) Viewed 32568 times

Code: Select all
int main()
{
 

    int Bitplane0_ROW[] = { 0b01100110 , 0b11111111, 0b01011010, 0b01111110, 0b00000000, 0b10000001, 0b11111111, 0b01111110 }; // Array to to store numbers Last Row is first.
    int Bitplane1_ROW[] = {0b01111110, 0b11111111, 0b11111111, 0b11011011, 0b11111111, 0b01111110, 0b00000000, 0b00000000};

    int N = 7; //to store bit
    int c = 0;

    BYTE* buf = new BYTE[8 * 5];


    for (int p = 0; p < 8; p++)
    {
        for (int j = 0; j < 8; j++) // Row 6
        {
            if (Bitplane0_ROW[p] & (1 << N))
            {

                if (Bitplane1_ROW[p] & (1 << N))
                {
                    // Index 3
                    buf[c + 0] = (BYTE)154;
                    buf[c + 1] = (BYTE)194;
                    buf[c + 2] = (BYTE)237;
                }
                else
                {
                        // Index 1
                        buf[c + 0] = (BYTE)53;
                        buf[c + 1] = (BYTE)189;
                        buf[c + 2] = (BYTE)104;
                }
            }
            else if (Bitplane1_ROW[p] & (1 << N) )
            {

                if (Bitplane0_ROW[p] & (1 << N)){}
                else
                {   // Index 2
                    buf[c + 0] = (BYTE)59;
                    buf[c + 1] = (BYTE)85;
                    buf[c + 2] = (BYTE)142;
                }
            }
           
            else
            {
                // Index 0
                buf[c + 0] = (BYTE)255;
                buf[c + 1] = (BYTE)255;
                buf[c + 2] = (BYTE)255;

            }
            c += 3;
            N--;
        }
        N = 7;
    }

    SaveBitmapToFile((BYTE*)buf, 8, 8, 24, 0, "C:\\Users\\Chris\\Desktop\\bluesquare.png");

    delete[] buf;
    return 0;
}
Expedition Leader
Bananas received 559
Posts: 1211
Joined: 2008

Re: Sprite Graphics

Postby Kingizor » November 28th, 2022, 8:57 pm

Cyclone wrote:How do you check if a bit not set? I'm sure its something obvious. sorry.


If a bit is not set, the result of the expression would be zero. You can therefore check for that by comparing against zero:

Code: Select all
if ((a & bit) == 0) { ...
if (!(a & bit)) { ...

The extra parentheses can be helpful, but they're not strictly necessary in the first case. Some compilers will warn about it because of how easy it is to mix up bitwise AND (&) logical AND (&&) which have different behaviours.

Combining the bits with OR and shifting should be a bit tidier than if/else chains.
Trailblazer
Bananas received 77
Posts: 247
Joined: 2010

Re: Sprite Graphics

Postby rainbowsprinklez » November 29th, 2022, 1:00 am

Kingizor wrote:The extra parentheses can be helpful, but they're not strictly necessary in the first case. Some compilers will warn about it because of how easy it is to mix up bitwise AND (&) logical AND (&&) which have different behaviours.

Rather than remembering the precedence, I just always use parentheses! :) Unless I am code golfing. Then I use Google to quickly remind me of the rules :D Tldr bitwise has a weird order of operations. I think addition and subtraction are evaluated first! This is C#'s rules, but I BELIEVE the precedence applies here.
https://www.programiz.com/csharp-progra ... ociativity
Veteran Venturer
Bananas received 108
Posts: 568
Joined: 2016

Next

Return to ROM Hacking

Who is online

Users browsing this forum: No registered users and 3 guests

cron