Skip to content

Instantly share code, notes, and snippets.

Forked from 1bardesign/main.lua
Created November 13, 2018 00:32
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
example use
local recolour = require("recolour")
--we want to recolour this asset image
local to_recolour = love.image.newImageData("path/to/image.png")
--using this palette image
local palette = love.image.newImageData("path/to/palette.png")
--using this palette (first non-source palette)
local palette_number = 1
--get the recoloured image data
local recoloured = recolour(to_recolour, palette, palette_number)
--load into an image so we can render it
local image =
--now free to render it as normal using lg.draw or whatever!
(math helpers required)
(recommend having stuff like this in some central math.lua)
--clamp v to range [lo, hi]
function math.clamp(v, lo, hi)
return math.max(lo, math.min(v, hi))
--round v to nearest whole
function math.round(v)
return math.floor(v + 0.5)
--(required bitops)
local bit = require("bit")
local band, bor, lshift =, bit.bor, bit.lshift
--rgb hex -> r g b a (full alpha)
function math.getColorRGBHex(rgb)
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
local a = 1.0
return r, g, b, a
--r g b -> rgb hex (no alpha stored)
function math.colorToRGBHex(r, g, b)
local br = lshift(band(0xff, r * 255), 16)
local bg = lshift(band(0xff, g * 255), 8)
local bb = lshift(band(0xff, b * 255), 0)
return bor( br, bg, bb )
recolouring an asset based on an input palette
asset_data imagedata to recolour (in "source colours")
palette_data imagedata of palette
organised in vertical columns
first column = "input" palette
other columns = "output" palettes
palette_number which palette to use (0 for no recolour)
any colours not found in the palette will NOT be recoloured
this allows recolouring hair and skin and armour as separate
passes, without needing a super complicated palette
local function recolour(asset_data, palette_data, palette_number)
local data = asset_data:clone()
--recolour only if needed
palette_number = math.round(math.clamp(palette_number, 0, palette_data:getWidth() - 1))
if palette_number ~= 0 then
--build mapping
local pixel_mapping = {}
for i = 0, palette_data:getHeight() - 1 do
local in_rgb = math.colorToRGBHex(palette_data:getPixel(0, i))
local out_rgb = math.colorToRGBHex(palette_data:getPixel(palette_number, i))
pixel_mapping[in_rgb] = out_rgb
--apply mapping
data:mapPixel(function(x, y, r, g, b, a)
--(don't need to remap totally transparent pixels)
if a ~= 0 then
local mapped = pixel_mapping[math.colorToRGBHex(r, g, b)]
if mapped ~= nil then
r, g, b = math.getColorRGBHex(mapped)
--(keep alpha the same)
return r, g, b, a
return data
return recolour
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment