How do I use a color palette with a .PNG?

So I have a .png file, and I want to use a color palette with it.
Say I wanted to turn #ff0000#0000ff and #00ff00#ff0000.
What do I do to make the color palette work?
This is the code I have now, how should I rework it to make it compatible for a color palette?

SDL_Window* window = NULL;
SDL_Renderer* renderer;

SDL_Init(SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);

window = SDL_CreateWindow("Window", SDL_WINDOWPOS_UNDEFINED, 
    SDL_WINDOWPOS_UNDEFINED, 400, 400, 
    SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

SDL_Surface* img_surface = IMG_Load("image.png");
SDL_Texture* img_texture = SDL_CreateTextureFromSurface(renderer, img_surface);

SDL_RenderCopy(renderer, img_texture, NULL, NULL); 
SDL_RenderPresent(renderer); //updates the renderer

//other stuff

SDL_DestroyTexture(img_texture);
SDL_FreeSurface(img_surface);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

Assuming your source image is itself a paletted PNG (you can convert it to that format offline if necessary) I think you need to do something like this:

SDL_Surface* img_surface = IMG_Load("image.png");
SDL_SetPaletteColors(img_surface->format->palette, pal, first, ncols);
SDL_Texture* img_texture = SDL_CreateTextureFromSurface(renderer, img_surface);

where pal points to the color palette you want to apply to the image.

Do you know of any SDL2 function to do this automatically, or do I have to rely on an app or online converter? If so, are there any that you reccommend?

I’ve always used an online converter for this, for example this one. It’s described as a ‘PNG compressor’ but what it’s actually doing is converting to a paletted format.

1 Like

In GIMP you can do this by switching to indexed mode. Note that the palette can only have at most 256 colours so you might want to use the colorcube analysis tool to check how many colours the image have before doing this.

1 Like

I figured out how to do that right, but the exported PNG file did not work with the palette code. When I did this:

SDL_Surface* img_surface = IMG_Load("image.png");
SDL_Color colors[2];
colors[0] = (SDL_Color) {r: 255, g: 0, b: 0, a: 255};
colors[1] = (SDL_Color) {r: 0, g: 0, b: 255, a: 255};

SDL_SetPaletteColors(img_surface->format->palette, colors, 0, 2);
SDL_Texture* img_texture = SDL_CreateTextureFromSurface(renderer, img_surface);

The resulting texture looked identical to the PNG file.
Is there a different way I should export the PNG file?

Your code works for me when I test it on an “indexed image” with only two colours that I exported from GIMP.

Here is the image if you want to try: image.png.zip (3.5 KB)
(I zipped it because I wasn’t sure if Discourse would try to convert it if I uploaded the image file directly)


When you switched to “indexed mode”, did you generate an “optimal palette” or did you use a “custom palette”? In the latter case there might be more colours in the palette than in the image and if the first two colours are not present in the image then your code will not have an effect.

Make sure the loaded image actually has a palette by checking if img_surface->format->palette is not null.

if (img_surface->format->palette != NULL)
{
	printf("The image has a palette with %d colours.\n", img_surface->format->palette->ncolors);
}
else
{
	printf("The image has no palette.\n");
}

Just as a test, instead of using SDL_SetPaletteColors, try setting all the colours in the palette to random colours and see if it affects the image.

for (int i = 0; i < img_surface->format->palette->ncolors; ++i)
{
	img_surface->format->palette->colors[i] = (SDL_Color) {r: rand() & 0xFF, g: rand() & 0xFF, b: rand() & 0xFF, a: 255};
}

When I ran the test you did, it returned: The image has no palette.
I did the same as the image you provided, and the same thing happened.

When I ran that test, the program crashed, I believe because there was no palette.
Do you think that my platform I am compiling on could make a difference, if nothing else?

I use a pretty old version of SDL/SDL_image which probably uses libpng to load PNG files.

Apparently recent versions of SDL_image have started to use another library, stb_image, to load PNG files instead (at least by default).

2.6.0:
Added stb_image as the default backend for JPG and PNG images loading.
To use libpng and libjpg instead, configure using --disable-stb-image

I also found this which seems to suggest that stb_image removes the palette when loading the image.

Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.

I don’t know if this should be considered a bug in SDL_image. Apperently there is already an open issue about this so I don’t know if they’re planning to implement a patch for stb_image to restore the old behaviour or what…


An alternative to using palettes would be to loop through all of the pixel values to update the colours. It would be a bit more complicated to implement and probably slower but not necessarily a performance problem if the images are small and/or it’s not something you do very often. The calls to IMG_Load/SDL_CreateTextureFromSurface might very well still be the slower part.

Do you think it is a good idea to revert to an older version of SDL/SDL_image then?

No, not unless you know this issue is going to get fixed. It might be fine if this is just some temporary project that you’re going to throw away in a month or two, but otherwise I would personally not want to write code that I know is not going to work in the latest version.

Note that it’s still possible to use the latest version of SDL_image with libpng if you compile it yourself, but depending on how you distribute your game it might not be a solution unless you can always control the SDL_image that is being used with your game.

I ended up using a paletted BMP image (loaded with SDL_LoadBMP_RW()) as the most straightforward workaround. It’s bigger than the PNG would be of course, but it avoids any issues with SDL_image or stb_image stripping the palette.

And if you put the BMP into a .zip archive, it shouldn’t end up using much more space than a PNG

True, but in an ideal world I’d still like there to be a way to tell SDL_stbimage to import a PNG with minimum alteration, so a paletted PNG would end up as a paletted SDL_Surface.

Converting a paletted image into an RGBA image isn’t exactly ‘lossy’ but it’s a process that can’t be reversed. So if a lot of effort has gone into generating an optimised palette for that image, it’s lost.

But SDL_stbimage uses stb_image, which doesn’t support palettes (and I doubt STB is interested in adding palette support)

I know, but that doesn’t stop me hoping! I’d have no objection to SDL_stbimage using a custom (patched) version of stb_image.h,

That seems to work in the short term, but do you know how much more data BMP image will store compared to a PNG of the same size?
This is my code, for refrence:

SDL_RWops * rw = SDL_RWFromFile("image.bmp", "r");
SDL_Surface* img_surface = SDL_LoadBMP_RW(rw, 1);

SDL_Color colors[2];
colors[0] = (SDL_Color) {r: 255, g: 0, b: 0, a: 255};
colors[1] = (SDL_Color) {r: 0, g: 0, b: 255, a: 255};
SDL_SetPaletteColors(img_surface->format->palette, colors, 0, 2);
SDL_Texture* img_texture = SDL_CreateTextureFromSurface(renderer, img_surface);

//...

SDL_FreeSurface(img_surface);

Ignoring the header, a paletted BMP will be one-byte-per-pixel plus 1024 bytes for the palette itself. It’s impossible to say how big the equivalent paletted PNG would be because it depends on how compressible the image is. But as Daniel remarked, if you’re concerned about file size zip the BMP and unzip it on demand.

1 Like

I’m no expert on ZIP files, but for a large number or size of files, should I have to be worried about the time and memory it will take to unzip them? I assume that for one 4kb file it wont be much, but what about for multiple 100kb+files? (100kb unzipped)

unzipping 100kb takes no time at all…

Also note that you can add files to a zip archive uncompressed, in that case reading them from the zip file is about as fast as reading an file from the disk.
This especially makes sense for files that are compressed themselves and that won’t really get smaller by zips compression, like JPEGs, PNGs (unless they’re created with compression level 0), MP3s, OGGs, …

Furthermore, you should be able to read directly from the zip (maybe even using PhysicsFS which integrates well with SDL RWops), you usually do not extract the file from the ZIP to your harddisk and make the game read that.

In case of BMP from ZIP vs PNG from disk: ZIP uses the same compression as PNG (deflate), so decompressing takes about the same time.