Converting 2bpp tiles to 8bpp image

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Converting 2bpp tiles to 8bpp image
by on (#190935)
I'm writing a ROM-editing tool in C and need to extract some 8x8 tiles from CHR-ROM. First, I want to convert 2pp NES tiles to indexed 8bpp images. Second, I want to render them to an OpenGL canvas so the user can edit them. I haven't learned a lick of OpenGL yet but I just managed to wrap my head around the conversion part. This is my code:
Code:
typedef unsigned char byte;

/* Get 16 bytes from somewhere in CHR-ROM. */
byte* data = rom_data(0x040010, 0x10);

/* 8bpp image that will hold tile. */
byte image[64];

int pixel = 0;
byte* low_bit = data;
byte* high_bit = data + 8;
int row = 0;
while(pixel < 64) {
   image[pixel++] = (bool)(low_bit[row] & 0b10000000) + (bool)(high_bit[row] & 0b10000000) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b01000000) + (bool)(high_bit[row] & 0b01000000) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00100000) + (bool)(high_bit[row] & 0b00100000) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00010000) + (bool)(high_bit[row] & 0b00010000) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00001000) + (bool)(high_bit[row] & 0b00001000) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00000100) + (bool)(high_bit[row] & 0b00000100) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00000010) + (bool)(high_bit[row] & 0b00000010) * 2;
   image[pixel++] = (bool)(low_bit[row] & 0b00000001) + (bool)(high_bit[row] & 0b00000001) * 2;
   ++row;
}

/* Print out 8bpp image pixel-by-pixel to validate. */
for (int y = 0; y < 8; ++y) {
   for (int x = 0; x < 8; ++x) {
      printf("%d ", image[x + y * 8]);
   }
   putchar('\n');
}


So this is my input and output:
Image
Code:
0 0 0 0 0 0 0 0
0 3 3 3 1 1 1 0
0 3 3 3 1 1 0 0
0 3 3 3 1 0 0 0
0 2 2 2 0 0 0 0
0 2 2 0 0 0 0 0
0 2 0 0 0 0 0 0
0 0 0 0 0 0 0 0

I have two questions:
  1. Is there a faster, less-verbose way to do this in C? All of this casting, bitmasking and multiplication feels unecessary.
  2. Is it even possible to display indexed 8bpp images in OpenGL? Will that be hardware-accelerated? Or will I need to use a shader and 24bpp image?
Re: Converting 2bpp tiles to 8bpp image
by on (#190939)
You can do the bitmasking in a for-loop by treating the tile bytes as shift registers. Something like:
Code:
// tile[16] input 16 byte CHR tile data
// output[8][8] output image

for (y=0; y<8; ++y)
{
   a = tile[y+0];
   b = tile[y+8];
   for (x=0; x<8; ++x)
   {
      output[y][x] = ((b & 0x80) >> 6) | ((a & 0x80) >> 7);
      a <<= 1;
      b <<= 1;
   }
}

An aggressive optimizing compiler might even be able to fully unroll this.

Edit: was off by 1 on the shifts.
Re: Converting 2bpp tiles to 8bpp image
by on (#190941)
DragonDePlatino wrote:
Is there a faster, less-verbose way to do this in C? All of this casting, bitmasking and multiplication feels unecessary.
It's verbose because you're unrolling your loop.
Compare it to my chr2pgm...

Also you might find something useful in the bit twiddling hacks
Re: Converting 2bpp tiles to 8bpp image
by on (#190943)
Alternative to my previous illustration, you don't even have to use them like shift registers if you just use indexed bit shifts:
Code:
for (y=0; y<8; ++y)
{
   a = tile[y+0];
   b = tile[y+8];
   for  (x=0; x<8; ++x)
   {
      output[y][x] = (((b >> (7-x)) & 1) << 1) | ((a >> (7-x)) & 1);
   }
}

This is probably even easier for a compiler to optimize.

Edit: I was curious so I stuck both examples into the godbolt compiler explorer. I'm kinda surprised, but GCC unrolls the shift-register approach better? Maybe? The practical difference might be marginal, though.
Re: Converting 2bpp tiles to 8bpp image
by on (#190950)
@rainwarrior
Great answer. It took me a minute to digest it (and I had to rotate your output 90 degrees) but it's a very elegant solution. And your hunch was correct. The shift-register approach is approximately 4% faster. Thanks for answering my first question.

@lidnariq
Yikes. I actually came across that standford page while looking for a solution. There's some good stuff in there but it's an overwhelming amount of information! This chr2pgm is a good reference, though.
Re: Converting 2bpp tiles to 8bpp image
by on (#193076)
Is there any other way to do so because i m getting invalid adjustment error!
HyperthymesiaRemedies