Converting 2bpp tiles to 8bpp image

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

Moderator: Moderators

Post Reply
User avatar
DragonDePlatino
Posts: 94
Joined: Mon Oct 20, 2014 1:50 pm

Converting 2bpp tiles to 8bpp image

Post by DragonDePlatino »

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: Select all

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: Select all

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:
  • Is there a faster, less-verbose way to do this in C? All of this casting, bitmasking and multiplication feels unecessary.
  • 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?
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Converting 2bpp tiles to 8bpp image

Post by rainwarrior »

You can do the bitmasking in a for-loop by treating the tile bytes as shift registers. Something like:

Code: Select all

// 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.
Last edited by rainwarrior on Sat Mar 11, 2017 3:08 pm, edited 3 times in total.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Converting 2bpp tiles to 8bpp image

Post by lidnariq »

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
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Converting 2bpp tiles to 8bpp image

Post by rainwarrior »

Alternative to my previous illustration, you don't even have to use them like shift registers if you just use indexed bit shifts:

Code: Select all

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.
User avatar
DragonDePlatino
Posts: 94
Joined: Mon Oct 20, 2014 1:50 pm

Re: Converting 2bpp tiles to 8bpp image

Post by DragonDePlatino »

@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.
martin123
Posts: 3
Joined: Sun Apr 02, 2017 12:56 am

Re: Converting 2bpp tiles to 8bpp image

Post by martin123 »

Is there any other way to do so because i m getting invalid adjustment error!
HyperthymesiaRemedies
Post Reply