[Solved] - Indexed Palette Swaps / Color Cycling

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
Semicolon
Prole
Posts: 3
Joined: Tue Apr 04, 2017 5:34 pm

[Solved] - Indexed Palette Swaps / Color Cycling

Post by Semicolon » Wed Apr 05, 2017 12:24 am

Problem has been solved, please see this post: https://love2d.org/forums/viewtopic.php ... 38#p211438

---

I have an indexed png sprite. I would like to recolor that sprite in real time using a separate palette image.
Here's an example of what I'm trying to accomplish:
My sprite: Image My palette (scaled up): Image
Image


This seemed like a good place to use a shader, so I set about searching for one, or at least some guidelines for writing one.
Eventually, that brought me to this link:

https://www.khronos.org/opengl/wiki/Com ... d_textures

A paletted texture sounded like exactly what I was going for, so I attempted to convert their shader into something I could use in LÖVE.
This is what I came up with:

Code: Select all

//Fragment shader
uniform sampler2D ColorTable; // 16 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

vec4 effect( vec4 color, Image MyIndexTexture, vec2 TexCoord0, vec2 screen_coords ){
	//What color do we want to index?
	vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
	//Do a dependency texture read
	vec4 texel = texture2D(ColorTable, myindex.xy);
	return texel;   //Output the color
}
Unfortunately, when I attempted to draw my sprite using this shader, this was my result:
Image

It's close, but some groups of colors are being set to a single color.
For example, the orange, yellow, pink and white hues on the original sprite are all being set to pink on the modified sprite.
Any help to fix this (or perhaps a different shader with a different methodology for accomplishing the same thing) would be greatly appreciated.

Things I have tried:
  • Turning on gamma-correct rendering (just makes it worse)

    Using a 256 x 1 palette (still converts multiple colors to the same color)

    Using ImageData:mapPixel() and pre-rendering the sprites (useful, but too slow for real-time)

Finally, here is a simple .love illustrating the problem.
Attachments
paletteShader.love
(2.51 KiB) Downloaded 84 times
Last edited by Semicolon on Wed Apr 05, 2017 6:18 pm, edited 1 time in total.

User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Indexed Palette Swaps / Color Cycling

Post by raidho36 » Wed Apr 05, 2017 1:30 am

You'll notice that you sample your color by X and Y components, i.e. Red and Green channels values mapped to 0-1 range. Starting there, you need at least 2d color map texture. Realistically you will use all 3 colors so you really need 3d color map texture.

To elaborate on the way it works:
First, it samples the color from your texture as normal. Then it (implicitly) converts that color to texture coordinates, so that red becomes X, green becomes Y and blue becomes Z. Using specially cratfted colormap texture, that goes from black to red on X axis, from black to green on Y axis, and from black to blue on Z axis (that's an identity map, it doesn't alter the color), you can use these previously obtained coordinates to sample new color from that texture.

So you need to create a 3d texture such that in coordinates R, G, B of input color as X, Y, Z values, there's an output color. E.g. if you want to change teal to magenta, in coordinates 0, 1, 1 (teal's color values) there's magenta color. Since you're going for low color depth, you can somehow encode input color such that each R, G, B combination on the input corresponds to exactly one X coordinate on the output. Then you can create such texture that has output color in that X coordinate, and use a 1d texture for that.

In general, as long as you're not doing rainbowy real time effects, I suggest simply creating altered color sprites by mapping pixel colors offline.

User avatar
Jasoco
Inner party member
Posts: 3651
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Indexed Palette Swaps / Color Cycling

Post by Jasoco » Wed Apr 05, 2017 5:19 am

I did something like this, but much simpler a few months ago when I was working on a sort of GameBoy Link's Awakening style engine. I was trying to make it as genuine as possible so I wrote a shader that would take a palette of fixed colors and apply them to 4-color greyscale images. Basically the four shades of grey would be replaced with the four chosen colors. I could expand it if I need to to do more than 4 colors, but it worked well.

It probably wouldn't help at all since it's much simpler than what you want to do but I'll post the shader anyway...

Code: Select all

nesShader = love.graphics.newShader[[
  extern vec4 color1;
  extern vec4 color2;
  extern vec4 color3;
  extern vec4 color4;

  vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
    vec4 pixel = Texel(texture, texture_coords );//This is the current pixel color
    if (pixel.a == 0.0) { return vec4(0.0,0.0,0.0,0.0); }
    if (pixel.r < 0.25) { return vec4(color4.r/255,color4.g/255,color4.b/255,color4.a/255); }
    else if (pixel.r < 0.5) { return vec4(color3.r/255,color3.g/255,color3.b/255,color3.a/255); }
    else if (pixel.r < 0.75) { return vec4(color2.r/255,color2.g/255,color2.b/255,color2.a/255); }
    else { return vec4(color1.r/255,color1.g/255,color1.b/255,color1.a/255); }
    return pixel;
  }
]]
You'd have to send each color separately every frame. But it worked fine.

User avatar
Semicolon
Prole
Posts: 3
Joined: Tue Apr 04, 2017 5:34 pm

Re: Indexed Palette Swaps / Color Cycling

Post by Semicolon » Wed Apr 05, 2017 6:17 pm

@raidho36: Aaah, spot on. Everything makes sense now. Thanks!

@Jasoco: Cool! That looks perfect for a game-boy palette-ifier. Out of curiosity, why would one need to send the color information every frame? Would it be possible to send the palette information only when the palette changes?

Thanks for the help guys, marking as solved. I've attached an updated version of the .love below with proper palette swapping.
(Use the left/right arrow keys to change palettes)
Attachments
properPaletteSwapper.love
(3.01 KiB) Downloaded 196 times

User avatar
Jasoco
Inner party member
Posts: 3651
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Indexed Palette Swaps / Color Cycling

Post by Jasoco » Wed Apr 05, 2017 9:55 pm

Semicolon wrote:
Wed Apr 05, 2017 6:17 pm
@Jasoco: Cool! That looks perfect for a game-boy palette-ifier. Out of curiosity, why would one need to send the color information every frame? Would it be possible to send the palette information only when the palette changes?
I wouldn't know how to do it. Since the shader is being called for every single image (In my case, every single 8x8 pixel sub-tile with 4 sub-tiles per 16x16 tile and a whole bunch of them per frame for the map and entities.) I wouldn't know of a way to store that information in the shader unless you were to load the shader separately for every single tile instead of reusing the same one. Can you even do that? Would it take a lot of memory to have the same shader duplicated for every single sprite?

Just for fun, here it was in action:

MasterLee
Party member
Posts: 141
Joined: Tue Mar 07, 2017 4:03 pm
Contact:

Re: [Solved] - Indexed Palette Swaps / Color Cycling

Post by MasterLee » Thu Apr 06, 2017 10:07 am

I did only the palette effect the following way:

Code: Select all

[[
    extern vec4 palette[4];
    vec4 effect(vec4 color,Image texture,vec2 texture_coords,vec2 pixel_coords)
    {
      return palette[int(Texel(texture,texture_coords).r*4)];
    }
  ]]
It is much shorter and uses less if. In my map the pixel data is premultilplied by 64(=256/number of colors)). That is the reason why r multiplied by 4.

User avatar
D0NM
Party member
Posts: 235
Joined: Mon Feb 08, 2016 10:35 am
Location: Zabuyaki
Contact:

Re: [Solved] - Indexed Palette Swaps / Color Cycling

Post by D0NM » Thu Apr 06, 2017 11:38 am

well... in our open source http://www.zabuyaki.com
I swap only certain colours:

Code: Select all

local sh_swap_colors = [[
        extern number n = 1;
        extern vec4 colors[16];
        extern vec4 newColors[16];
        vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
            vec4 pixel = Texel(texture, texture_coords );//This is the current pixel color
            for (int i = 0; i < n; i++) {
                if(pixel == colors[i])
                    return newColors[i] * color;
            }
            return pixel * color;
        }   ]]

local function swapColors(colors_default, alternate_colors)
    local shader = love.graphics.newShader(sh_swap_colors)
    shader:send("n", #alternate_colors)
    alternate_colors[#alternate_colors+1] = {} --TODO: Remove on fix of Love2D 0.10.2 shaders send bug
    colors_default[#colors_default+1] = {} --Love2D 0.10.2 shaders send bug workaround
    shader:sendColor("colors", unpack(colors_default))
    shader:sendColor("newColors", unpack(alternate_colors))
    alternate_colors[#alternate_colors] = nil --Love2D 0.10.2 shaders send bug workaround
    colors_default[#colors_default] = nil --Love2D 0.10.2 shaders send bug workaround
    return shader
end
ofc, it works for any images. The indexed palette is not needed here.
Our LÖVE Gamedev blog Zabuyaki (an open source retro beat 'em up game). Twitter: @Zabuyaki.
:joker: LÖVE & Lua Video Lessons in Russian / Видео уроки по LÖVE и Lua :joker:

Post Reply

Who is online

Users browsing this forum: No registered users and 20 guests