Skip to content

Instantly share code, notes, and snippets.

@1bardesign
Created November 13, 2018 00:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save 1bardesign/68c34f9b772c8b10372abde78d429553 to your computer and use it in GitHub Desktop.
Save 1bardesign/68c34f9b772c8b10372abde78d429553 to your computer and use it in GitHub Desktop.
recolour.lua
--[[
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 = love.graphics.newImage(recoloured)
--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))
end
--round v to nearest whole
function math.round(v)
return math.floor(v + 0.5)
end
--(required bitops)
local bit = require("bit")
local band, bor, lshift = bit.band, 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
end
--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 )
end
--[[
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)
--load
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
end
--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)
end
--(keep alpha the same)
end
return r, g, b, a
end)
end
return data
end
return recolour
@1bardesign
Copy link
Author

Has not been tested as-gisted, but the core recolour function is used in a functional game for multi-layer palette remapping, so I'm quite confident in the core algorithm 👍

@1bardesign
Copy link
Author

1bardesign commented Nov 13, 2018

Example tiles and palette file for brbl.

tiles
palette

NOT for redistribution, all rights reserved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment