CHR loading code (C++) don't quite understand CHR format...

A place for your artistic side. Discuss techniques and tools for pixel art on the NES, GBC, or similar platforms.

Moderator: Moderators

Post Reply
ittyBittyByte
Posts: 24
Joined: Sun Dec 18, 2016 1:11 pm

CHR loading code (C++) don't quite understand CHR format...

Post by ittyBittyByte » Sat Dec 31, 2016 1:00 pm

What I (think I) understand about the basic CHR format, not counting setting colors, for the standard 2BPP NES format, from hex viewing:
Each byte acts like a vertical bitflag, e.g. 0 = nothing there, 1 = pixel on 1st row, 2 = pixel on 2nd row, 3 = pixel on 1st and 2nd row etc...

And it is repeated for each color every 8(???) bytes. Something like that. But I'm NOT interested in color right now, I just want the shape to be right!

I am attempting to load CHR with this code:
Using the default rotation: 270.

Code: Select all

unsigned char _bit[9];
	_bit[0] = 0x00;
	_bit[1] = 0x01;
	_bit[2] = 0x02;
	_bit[3] = 0x04;
	_bit[4] = 0x08;
	_bit[5] = 0x10;
	_bit[6] = 0x20;
	_bit[7] = 0x40;
	_bit[8] = 0x80;

int _i = 0;

// ...

while (chr.read(&_b, 1)) {
	if (_i <= imgw) {

		std::cout << "#" << _i << ";\n";

		bool bPixel = false;

		for (int _ii=1; _ii<8; ++_ii) {
			bool bPixel = (_bit[_ii] &_b != 0);
			if (bPixel) {
				tile[_ii-1][_i][_b] = 255; // TESTING PURPOSES NO COLOR YET
			}
		}

		std::cout << "\n";

		_i = _i + 1;
	}
}
Works great. For the first row.
Otherwise I just get garbage that is very vaguely reminiscent of a messed up version of the tile.

Can someone clear up a few things on the CHR format? How should I really be loading this? And yes, I know my code is probably horribly written...

I really hope this is the right place to post it. CHR info is kind of scarce.

User avatar
Kasumi
Posts: 1289
Joined: Wed Apr 02, 2008 2:09 pm

Re: CHR loading code (C++) don't quite understand CHR format

Post by Kasumi » Sat Dec 31, 2016 1:15 pm

There's a page on the wiki that describes it pretty well, I think.

https://wiki.nesdev.com/w/index.php/PPU_pattern_tables

Each byte represents a row, each bit represents a column. Each tile is 16 bytes.

If a color for a pixel is %XY (two bits)
Bytes 0-7 are the low bit of the color (The Y bit above) for each row/column.
Bytes 8-15 are the high bit of the color (The X bit above).

If you have byte 0 as this: 0 1 0 0 1 1 1 1
If you have byte 8 as this: 0 0 0 1 0 1 0 1

The first row of the tile is: 00 01 00 10 01 11 01 11
or color 0, 1, 0, 2, 1, 3, 1, 3

Then you read byte 1 and and byte 9 to get the second row.
at byte 16, it's a new tile.
Edit:
My code looks a bit like this:

Code: Select all

int decode_chr(int byte1, int byte2, int andvalue){//andvalue is the bitmask
	if(byte1&andvalue){
		if(byte2&andvalue){
			return 3;
		}else{
			return 1;
		}
	}else{
		if(byte2&andvalue){
			return 2;
		}else{
			return 0;
		}
	}
}//Verfied

		for(int x = 0; x < 16; x++){
			for(int y = 0; y < 16; y++){
				bufferpos = x * 16+y*256;
				for(int row = 0; row < 8; row++){
					chrarray[x*8+0][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x80);
					chrarray[x*8+1][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x40);
					chrarray[x*8+2][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x20);
					chrarray[x*8+3][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x10);
					chrarray[x*8+4][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x08);
					chrarray[x*8+5][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x04);
					chrarray[x*8+6][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x02);
					chrarray[x*8+7][y*8+row] = decode_chr(buffer[bufferpos], buffer[bufferpos+8], 0x01);

					bufferpos++;
				}
				
			}
		}

It assumes the CHR is 256 tiles arranged in a grid of 16x16 tiles. Each tile is 16 bytes, so x (column) is multiplied by 16, y is multiplied by 256 (16 columns skips a row of tiles) to set bufferpos, which is the first byte of the tile at the X, Y position. Then it feeds the byte at that location, and the byte 8 bytes ahead to decode chr to get the color, which is placed in an array.

Edit2: I would name your variables things that aren't i and such.
Anyway, this isn't a clever way to do it, and I know it could be condensed. But it does work.

Edit3: Fixed a small error in the explanation.
Last edited by Kasumi on Sat Dec 31, 2016 2:01 pm, edited 3 times in total.

User avatar
koitsu
Posts: 4214
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: CHR loading code (C++) don't quite understand CHR format

Post by koitsu » Sat Dec 31, 2016 1:30 pm

What isn't immediately apparent (because it's hard to depict in ASCII -- but the very top of the wiki page linked there DOES explain it) is that the two "bit planes" are essentially "overlayed" (for lack of better term -- I like to think of them as OR'd when just referring to raw black and white (pixel on, pixel off)) which is what ends up making the colour range from 0-4 (2 bits per pixel, one from each "plane").

The numbers in the "Pixel Pattern" depict what colour to use, ranging from 0 to 3 (again: 2 bits per pixel).

The attribute table (not going to discuss that in depth right now, but just noting this for clarity) is what holds the "remaining" 2 bits, thus making the pixel colour essentially 4 bits, hence values 0 to 15.

Trust me: once you see it/get it, it'll suddenly click and make a whole ton of sense and you'll be thrilled.

User avatar
tokumaru
Posts: 11438
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: CHR loading code (C++) don't quite understand CHR format

Post by tokumaru » Sat Dec 31, 2016 2:04 pm

ittyBittyByte wrote:Each byte acts like a vertical bitflag, e.g. 0 = nothing there, 1 = pixel on 1st row, 2 = pixel on 2nd row, 3 = pixel on 1st and 2nd row etc...

And it is repeated for each color every 8(???) bytes.
Where did you get this information from? That doesn't sound anything like NES patterns. Maybe you were reading about the compression format used by an NES game and mistook it for the format the PPU itself uses? If that's the case, you don't have to worry about game-specific compression formats, because the games themselves will take care of decompressing them to the format the PPU expects.
CHR info is kind of scarce.
You probably won't find much information about it because the format is pretty straightforward and well documented. There's just not much to say about it.

ittyBittyByte
Posts: 24
Joined: Sun Dec 18, 2016 1:11 pm

Re: CHR loading code (C++) don't quite understand CHR format

Post by ittyBittyByte » Sat Dec 31, 2016 2:37 pm

I got that information by using a hex editor, messing in YY-CHR, and saving over and over. Because on a blank CHR when I set pixel 0,0 to color 1 the first byte became 01, when I set pixel 0,1 to color 1 the first byte became 2, and when I set 0,0 and 0,1 to color 1 it became 3. Then I figured out that setting only 0,8 to color 1 made the first byte the hex equivelant 127 (or maybe it was 128, dont remember). And setting all pixels from 0,0 to 0,8 in a column of pixels to color 1 gave me FF for the first byte. So I assumed it worked like some sort of bit flag thing. I knew it was probably wrong though...

zzo38
Posts: 1046
Joined: Mon Feb 07, 2011 12:46 pm

Re: CHR loading code (C++) don't quite understand CHR format

Post by zzo38 » Sat Dec 31, 2016 3:38 pm

This is part of a program I wrote which can do this. The value of "parameter" needs to be 8 for use with NES/Famicom.

Code: Select all

static int initpixel_F2(void) {
  if(!parameter) parameter=1;
  buf=calloc(2,curbyte=parameter);
  return !buf;
}

static int getpixel_F2(void) {
  int i;
  if(curbyte==parameter) {
    curbyte=rowpos=0;
    fread(buf,2,parameter,stdin);
  }
  i=buf[curbyte]&128?1:0;
  i|=buf[parameter+curbyte]&128?2:0;
  buf[curbyte]<<=1;
  buf[parameter+curbyte]<<=1;
  if(++rowpos==8) {
    rowpos=0;
    curbyte++;
  }
  return i;
}
[url=gopher://zzo38computer.org/].[/url]

ittyBittyByte
Posts: 24
Joined: Sun Dec 18, 2016 1:11 pm

Re: CHR loading code (C++) don't quite understand CHR format

Post by ittyBittyByte » Tue Jan 03, 2017 1:55 pm

Thanks guys, I got it to work by messing with Kasumi's code for 7 hours... :P

Post Reply