albin-johansson / centurion

A modern C++ wrapper library for SDL2 in order to improve type-safety, memory safety and overall ease-of-use.
MIT License
305 stars 29 forks source link

change palette of surface with indexed pixel format #108

Closed gxm11 closed 3 years ago

gxm11 commented 3 years ago

Hi,

I know the way to change the colors of a surface. For example, convert the surface to grayscale:

gray = (red * 9 + green * 18 + blue  * 5) / 32;
red = green = blue = gray;

Just take the pointer from surface.data(), and change pixels one by one. But it costs a lot of time for large size. I thought a new idea that to use indexed pixel format like:

  1. convert the pixel format of surface into index8
  2. get the palette of new format
  3. change the palette

Since the library don't have the function for palette of surface. I take the palette of the surface by surface.get()->format->palette and the colors by surface.get()->format->palette->colors.

However, it doesn't work. I print the colors in the palette, all of them are shown as (255, 255, 255, 255). If I leave the palette unchanged, nothing changed to the surface. But if I set all the colors to (255, 255, 255, 255), every untransparent pixels of the surface change to white (255, 255, 255, 255). It seem that error happens when read palette colors while the writing is correct. Or, maybe the step1-2-3 is not a valid choice for processing colors?

If the step1-2-3 is the valid choice, I still have 3 more questions:

  1. will the conversion between RGBA to indexed pixels cost too much time? (Since it needs to find the most 256 popular colors)
  2. Can I directly modify the palette of the cen::texture or the SDL_Texture, if their pixel format are set to index8.
  3. Can a texture with indexed pixel format be a render target? In the software PhotoShop, a figure with indexed pixel format cannot be edit until you convert it into RGBA format.
albin-johansson commented 3 years ago

Hi!

This is a very technical issue not strictly related to the Centurion library. I would recommend that you file the questions regarding pixel formats in the SDL discussion forums. However, I tried to answer some of your inquiries.

Since the library don't have the function for palette of surface. I take the palette of the surface by surface.get()->format->palette and the colors by surface.get()->format->palette->colors.

It does, see cen::surface::format_info(). Additionally, cen::surface::with_format() might be of interest for you.

However, it doesn't work. I print the colors in the palette, all of them are shown as (255, 255, 255, 255).

See the following comment from the SDL header where SDL_PixelFormat lives for an explanation to why you see no change when editing the palette.

https://github.com/libsdl-org/SDL/blob/ddfd21a6784f533bdd04b7e0dd61a0f400eee96e/include/SDL_pixels.h#L322

will the conversion between RGBA to indexed pixels cost too much time? (Since it needs to find the most 256 popular colors)

Very hard for me to decide, you'll have to measure and determine that yourself, unfortunately.

Can I directly modify the palette of the cen::texture or the SDL_Texture, if their pixel format are set to index8.

It's hard to directly modify textures, unlike surfaces, regardless of the pixel format.

Can a texture with indexed pixel format be a render target? In the software PhotoShop, a figure with indexed pixel format cannot be edit until you convert it into RGBA format.

I'm not aware of any reason that such textures wouldn't be.

gxm11 commented 3 years ago

I found this code can generate the grayscale of some texture.

// renderer.set_target(some_texture);
auto capture = renderer.capture(window.get_pixel_format());
auto gray = cen::surface({capture.width(), capture.height()}, cen::pixel_format::index8);
auto palette = gray.get()->format->palette;
for (int i = 0; i < 256; i++)
 {
       palette->colors[i].r = i;
       palette->colors[i].g = i;
       palette->colors[i].b = i;
}
SDL_BlitSurface(capture.get(), nullptr, gray.get(), nullptr);

Instead, run auto gray = capture.convert(cen::pixel_format::index8); only generate a figure with everywhere white (255, 255, 255, 255).

Maybe the palette need setup manually or read from a file. SDL didn't generate the palette.

albin-johansson commented 3 years ago

You cannot edit the palette manually, as I mentioned previously. That is why the code doesn't work.

gxm11 commented 3 years ago

I tests that code and it works. The pointer to the palette cannot be changed, which might not means colors in palette cannot be changed. That code counld be a trick to genrerate grayscale of one surface. But I decide not to use it since indexed pixel format is outmoded. Perhaps, shader is the morden way for processing pixels.

Much thanks for your help, I think I still need to learn more :smile

albin-johansson commented 3 years ago

There's nothing in the language that prevents you from editing members in the SDL_PixelFormat struct. However, this is what the SDL documentation says.

/**
 *  \note Everything in the pixel format structure is read-only.
 */
typedef struct SDL_PixelFormat
{
    Uint32 format;
    SDL_Palette *palette;
    Uint8 BitsPerPixel;
    Uint8 BytesPerPixel;
    Uint8 padding[2];
    Uint32 Rmask;
    Uint32 Gmask;
    Uint32 Bmask;
    Uint32 Amask;
    Uint8 Rloss;
    Uint8 Gloss;
    Uint8 Bloss;
    Uint8 Aloss;
    Uint8 Rshift;
    Uint8 Gshift;
    Uint8 Bshift;
    Uint8 Ashift;
    int refcount;
    struct SDL_PixelFormat *next;
} SDL_PixelFormat;

Anyways, I hope my responses were of some use. 😄