Some circles to be used as example latter.

Palette Swap On Löve 2D

Without Using Shaders

Date:

Changing the color of an image can be a very useful way to reuse a resource without increasing the disk size of the game. It is possible to change the color of images using the capabilities of ImageData to access the pixels directly, either to read the colors or to change them.

Animation demonstrating what can be done with palette swapping.
Animation demonstrating what can be done with palette swapping.

The algorithm consists of loading our image into memory as an object of type ImageData, which we will not modify and will only use as an "map" to know which color we are supposed to use, which is why we will call it image_map. We also create an image of the same dimensions where we will write all the changes we make, and we will call this image_data 1

The image used as a map, note the different colors.
The image used as a map, note the different colors.

We need to load an image that contains the palettes to be used, which will be palette_data, where each horizontal strip one pixel high is a palette:

An image with several bands of different colors.
An image with several bands of different colors.

lua

image_map = love.image.newImageData('ima.png') image_data = love.image.newImageData( image_map:getWidth(), image_map:getHeight()) palette_data = love.image.newImageData('palette.png')

We must also calculate the maximum number of palettes (max_palettes) that we can use, which will be equal to the vertical size of the image where we store the palettes.

lua

max_palettes = palette_data:getHeight()

Now we create a table where we will see which color in the first row of the palette image corresponds to each column that makes up the first palette. We will call this table look_up_color_table. We will create a string that will serve as a id to quickly identify each color.

lua

look_up_color_table = {} local col = palette_data:getWidth() local i = 0 while i < col do local r,g,b,a = palette_data:getPixel(i,0) -- The id is created using a hexadecimal-like value -- El id es creado usando un valor similar al hexadecimal local id = ("%X_%X_%X_%X"):format( math.floor((o_r)*255), math.floor((o_g)*255), math.floor((o_b)*255), math.floor((o_a)*255) ) look_up_color_table[id] = i i=i+1 end

We set the palette we want to use and change the colors of image_data.

lua

use_palette = 1 changePalete()

What happens inside the function changePalete(), is that we call the mapPixel() method of the ImageData object. mapPixel() allows us to send a function that will be executed for each of the pixels that make up the object, in this case, the function with which we change the colors.

Then, since objects of type ImageData cannot be drawn, we create an image to be able to draw on the screen, and we tell it that we want it to be drawn sharply.

lua

function changePalete() image_data:mapPixel(changeColors) image = love.graphics.newImage(image_data) image:setFilter('nearest','nearest') end

Now, it is in the function changeColors() where everything happens.

Graphic description of the algorithm step by step.
Graphic description of the algorithm step by step.

First, we receive the coordinates (x,y) of the color we want to change, we look up what color it is in image_map with the method getPixel(x,y), then we transform that color into an id, with which we access the table look_up_color_table, which returns us a column number. With the number of use_palette which is equal to the row number, and already knowing the column, we take that color from our palette, and return it to be placed into the coordinates (x,y) in image_data

lua

function changeColors(x, y, r,g,b,a) local o_r,o_g,o_b,o_a = image_map:getPixel(x,y) local id = ("%X_%X_%X_%X"):format( math.floor((o_r)*255), math.floor((o_g)*255), math.floor((o_b)*255), math.floor((o_a)*255) ) --check if the color exist on the table if look_up_color_table[id] then --if exist, then get the color on the palette local col = look_up_color_table[id] local row = use_palette-1 return palette_data:getPixel(col,row) end --else, do nothing, return the original color return r, g, b, a end

Each time we change the palette of the image, we only need to set which is the corresponding row and change it.

lua

use_palette = 3 changePalete()

Note, a disadvantage of this method without shaders is the fact that changing the palettes, the larger the area to be changed, it will be exponentially slower

That would be all, you can download the file with the source code .zip right here and check it out for yourself. You can use the arrows to switch between palettes.

[1] When we create an object ImageData using love.image.newImageData(), all the RGBA values of the pixels are (0,0,0,0)