Compare commits
8 Commits
795edc18c1
...
5ab72c18d1
Author | SHA1 | Date | |
---|---|---|---|
5ab72c18d1 | |||
48091dcf98 | |||
962182f5c3 | |||
369dbb9d43 | |||
d096e4b4eb | |||
d08a003f31 | |||
1f1e8973c0 | |||
e0a7b52994 |
|
@ -1,8 +0,0 @@
|
|||
name: Lua Library
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Lua-Testing:
|
||||
uses: jmaa/workflows/.gitea/workflows/lua-testing.yaml@v6.19
|
||||
Static-Analysis:
|
||||
uses: jmaa/workflows/.gitea/workflows/lua-static-analysis.yaml@v6.19
|
29
.gitea/workflows/lua-library.yml
Normal file
29
.gitea/workflows/lua-library.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
# WARNING!
|
||||
# THIS IS AN AUTOGENERATED FILE!
|
||||
# MANUAL CHANGES CAN AND WILL BE OVERWRITTEN!
|
||||
|
||||
name: LÖVE/Lua Library
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore: ['README.md', '.gitignore', 'LICENSE', 'CONVENTIONS.md']
|
||||
|
||||
jobs:
|
||||
Lua-Testing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Lua
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y luajit
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: Run testing library
|
||||
run: luajit test/init.lua
|
||||
Static-Analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: Luacheck linter
|
||||
uses: https://github.com/lunarmodules/luacheck@v1.1.1
|
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
# WARNING!
|
||||
# THIS IS AN AUTOGENERATED FILE!
|
||||
# MANUAL CHANGES CAN AND WILL BE OVERWRITTEN!
|
||||
|
||||
# Löve: Exclude build items
|
||||
/bin/
|
||||
/lib/
|
||||
.love-cache/
|
||||
|
||||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# Misc (Image, MacOS, Backups) files
|
||||
*.psd
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Tools
|
||||
*.tiled-session
|
|
@ -1,6 +1,10 @@
|
|||
std = "max"
|
||||
-- WARNING!
|
||||
-- THIS IS AN AUTOGENERATED FILE!
|
||||
-- MANUAL CHANGES CAN AND WILL BE OVERWRITTEN!
|
||||
|
||||
std = "love+max"
|
||||
cache = true
|
||||
include_files = {"**/*.lua"}
|
||||
include_files = {"**.lua", "*.luacheckrc"}
|
||||
exclude_files = {}
|
||||
self = false
|
||||
max_line_length=false
|
||||
|
|
3
LICENSE
Normal file
3
LICENSE
Normal file
|
@ -0,0 +1,3 @@
|
|||
Copyright (c) 2019-2025 Jon Michael Aanes
|
||||
|
||||
All rights reserved.
|
24
README.md
24
README.md
|
@ -1,3 +1,23 @@
|
|||
# Palette Swap
|
||||
<!-- WARNING! -->
|
||||
<!-- THIS IS AN AUTOGENERATED FILE! -->
|
||||
<!-- MANUAL CHANGES CAN AND WILL BE OVERWRITTEN! -->
|
||||
|
||||
Utility library for creating palette swaps of LÖVE-based images.
|
||||
# Utility library for creating palette swaps of LÖVE-based images
|
||||
|
||||
## Dependencies
|
||||
|
||||
This project requires [PUC Lua 5.1](https://www.tecgraf.puc-rio.br/lua/mirror/versions.html#5.1) or [LuaJIT](https://luajit.org/luajit.html). Newer versions of PUC Lua are not supported.
|
||||
|
||||
This project does not have any library requirements 😎
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to submit pull requests. Please follow the [Code Conventions](CONVENTIONS.md) when doing so.
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Copyright (c) 2019-2025 Jon Michael Aanes
|
||||
|
||||
All rights reserved.
|
||||
```
|
||||
|
|
188
init.lua
188
init.lua
|
@ -1,185 +1,5 @@
|
|||
--- Utility library for creating palette swaps of LÖVE-based images.
|
||||
-- WARNING!
|
||||
-- THIS IS AN AUTOGENERATED FILE!
|
||||
-- MANUAL CHANGES CAN AND WILL BE OVERWRITTEN!
|
||||
|
||||
local _VERSION = '0.1.0'
|
||||
|
||||
local palette_swap = {
|
||||
_VERSION = _VERSION,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Main work functions
|
||||
|
||||
local function get_palette_of_image_data (data)
|
||||
assert(data)
|
||||
--
|
||||
local palette = {}
|
||||
local has_seen = {}
|
||||
--
|
||||
for x = 0, data:getWidth() - 1 do
|
||||
for y = 0, data:getHeight() - 1 do
|
||||
local r, g, b, a = data:getPixel(x, y)
|
||||
if not has_seen[r] then has_seen[r] = {} end
|
||||
if not has_seen[r][g] then has_seen[r][g] = {} end
|
||||
if not has_seen[r][g][b] then
|
||||
local color = {r,g,b,a}
|
||||
has_seen[r][g][b] = color
|
||||
palette[#palette+1] = color
|
||||
end
|
||||
end
|
||||
end
|
||||
--
|
||||
return palette
|
||||
end
|
||||
|
||||
local function change_palette_to_dim_tree (change_palette)
|
||||
assert(type(change_palette) == 'table')
|
||||
--
|
||||
local tree = {}
|
||||
--
|
||||
for color_from, color_to in pairs(change_palette) do
|
||||
assert(type(color_from) == 'table')
|
||||
assert(type(color_to) == 'table')
|
||||
--
|
||||
local r, g, b = unpack(color_from)
|
||||
if not tree[r] then tree[r] = {} end
|
||||
if not tree[r][g] then tree[r][g] = {} end
|
||||
--
|
||||
assert(not tree[r][g][b])
|
||||
tree[r][g][b] = color_to
|
||||
end
|
||||
--
|
||||
return tree
|
||||
end
|
||||
|
||||
local function apply_change_palette (data, change_palette)
|
||||
assert(data)
|
||||
assert(change_palette)
|
||||
--
|
||||
local change_tree = change_palette_to_dim_tree(change_palette)
|
||||
local function map (_x, _y, r, g, b, a)
|
||||
local c = change_tree[r][g][b]
|
||||
return c[1], c[2], c[3], a
|
||||
end
|
||||
--
|
||||
local new_data = data:clone()
|
||||
new_data:mapPixel(map)
|
||||
--
|
||||
return new_data
|
||||
end
|
||||
|
||||
local function change_palette_from_map (map_func, palette)
|
||||
local change_palette = {}
|
||||
for i = 1, #palette do
|
||||
local from = palette[i]
|
||||
local to = {[4] = 1, map_func(unpack(from))}
|
||||
change_palette[from] = to
|
||||
end
|
||||
return change_palette
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- API
|
||||
|
||||
palette_swap.get_palette_from_image_data = get_palette_of_image_data
|
||||
|
||||
local function concat_functions (first_fn, ...)
|
||||
--
|
||||
local funcs = {first_fn, ...}
|
||||
|
||||
-- Identity function.
|
||||
if #funcs == 0 then
|
||||
return function (...) return ... end
|
||||
|
||||
-- Only one function
|
||||
elseif #funcs <= 1 then
|
||||
return first_fn
|
||||
end
|
||||
|
||||
-- Generic
|
||||
return function (...)
|
||||
local val = {...}
|
||||
for i = 1, #funcs do
|
||||
val = {funcs[i](unpack(val))}
|
||||
end
|
||||
return unpack(val)
|
||||
end
|
||||
end
|
||||
|
||||
function palette_swap.apply_color_map_to_image_data (img_data, ...)
|
||||
assert(img_data)
|
||||
--
|
||||
local map_fn = concat_functions(...)
|
||||
--
|
||||
return apply_change_palette(img_data, change_palette_from_map(map_fn, get_palette_of_image_data(img_data)))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Example color maps
|
||||
|
||||
palette_swap.color_map = {}
|
||||
|
||||
function palette_swap.color_map.invert (r,g,b)
|
||||
return 1 - r, 1 - g, 1 - b
|
||||
end
|
||||
|
||||
local CHANNEL_ORDERS = {
|
||||
[0] = {'r', 'g', 'b'},
|
||||
[1] = {'g', 'b', 'r'},
|
||||
[2] = {'b', 'r', 'g'},
|
||||
[3] = {'r', 'b', 'g'},
|
||||
[4] = {'b', 'g', 'r'},
|
||||
[5] = {'g', 'r', 'b'},
|
||||
[6] = {'b', 'g', 'r'},
|
||||
[7] = {'b', 'g', 'r'}, -- TODO
|
||||
[8] = {'b', 'g', 'r'}, -- TODO
|
||||
[9] = {'r', 'g', 'b'}, -- TODO
|
||||
}
|
||||
|
||||
function palette_swap.color_map.switch_channel (mode)
|
||||
local channel_order = CHANNEL_ORDERS[mode]
|
||||
local func_s = ("return function (r, g, b) return %s, %s, %s end"):format(unpack(channel_order))
|
||||
return loadstring (func_s)()
|
||||
end
|
||||
|
||||
local function load_colors ()
|
||||
|
||||
-- Only need to import colors when needed
|
||||
local colors = require 'colors'
|
||||
assert(type(colors) == 'table')
|
||||
assert(type(colors.rgb1_to_rgb255) == 'function')
|
||||
assert(type(colors.rgb_to_hsl) == 'function')
|
||||
assert(type(colors.hsl_to_rgb1) == 'function')
|
||||
|
||||
function palette_swap.color_map.increase_saturation (amount)
|
||||
-- Create function
|
||||
return function (r, g, b)
|
||||
local hsl = colors.rgb_to_hsl(colors.rgb1_to_rgb255(r, g, b))
|
||||
--
|
||||
hsl[2] = math.min(1, math.max(0, hsl[2] + amount))
|
||||
--
|
||||
return unpack(colors.hsl_to_rgb1(hsl))
|
||||
end
|
||||
end
|
||||
|
||||
function palette_swap.color_map.rotate_hue (rad)
|
||||
return function (r, g, b)
|
||||
local hsl = colors.rgb_to_hsl(colors.rgb1_to_rgb255(r, g, b))
|
||||
--
|
||||
hsl[1] = (hsl[1] + rad) % 1
|
||||
--
|
||||
return unpack(colors.hsl_to_rgb1(hsl))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs {'rotate_hue', 'increase_saturation'} do
|
||||
palette_swap.color_map[key] = function (...)
|
||||
load_colors()
|
||||
return palette_swap.color_map[key](...)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Return
|
||||
|
||||
return palette_swap
|
||||
return require (((...) ~= 'init' and (...) .. '.' or '') .. 'palette-swap')
|
||||
|
|
187
palette-swap.lua
Normal file
187
palette-swap.lua
Normal file
|
@ -0,0 +1,187 @@
|
|||
--- Utility library for creating palette swaps of LÖVE-based images.
|
||||
--
|
||||
-- @author Jon Michael Aanes (jonjmaa@gmail.com)
|
||||
|
||||
local _VERSION = '0.1.1'
|
||||
|
||||
local palette_swap = {
|
||||
_VERSION = _VERSION,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Main work functions
|
||||
|
||||
local function get_palette_of_image_data (data)
|
||||
assert(data)
|
||||
--
|
||||
local palette = {}
|
||||
local has_seen = {}
|
||||
--
|
||||
for x = 0, data:getWidth() - 1 do
|
||||
for y = 0, data:getHeight() - 1 do
|
||||
local r, g, b, a = data:getPixel(x, y)
|
||||
if not has_seen[r] then has_seen[r] = {} end
|
||||
if not has_seen[r][g] then has_seen[r][g] = {} end
|
||||
if not has_seen[r][g][b] then
|
||||
local color = {r,g,b,a}
|
||||
has_seen[r][g][b] = color
|
||||
palette[#palette+1] = color
|
||||
end
|
||||
end
|
||||
end
|
||||
--
|
||||
return palette
|
||||
end
|
||||
|
||||
local function change_palette_to_dim_tree (change_palette)
|
||||
assert(type(change_palette) == 'table')
|
||||
--
|
||||
local tree = {}
|
||||
--
|
||||
for color_from, color_to in pairs(change_palette) do
|
||||
assert(type(color_from) == 'table')
|
||||
assert(type(color_to) == 'table')
|
||||
--
|
||||
local r, g, b = unpack(color_from)
|
||||
if not tree[r] then tree[r] = {} end
|
||||
if not tree[r][g] then tree[r][g] = {} end
|
||||
--
|
||||
assert(not tree[r][g][b])
|
||||
tree[r][g][b] = color_to
|
||||
end
|
||||
--
|
||||
return tree
|
||||
end
|
||||
|
||||
local function apply_change_palette (data, change_palette)
|
||||
assert(data)
|
||||
assert(change_palette)
|
||||
--
|
||||
local change_tree = change_palette_to_dim_tree(change_palette)
|
||||
local function map (_x, _y, r, g, b, a)
|
||||
local c = change_tree[r][g][b]
|
||||
return c[1], c[2], c[3], a
|
||||
end
|
||||
--
|
||||
local new_data = data:clone()
|
||||
new_data:mapPixel(map)
|
||||
--
|
||||
return new_data
|
||||
end
|
||||
|
||||
local function change_palette_from_map (map_func, palette)
|
||||
local change_palette = {}
|
||||
for i = 1, #palette do
|
||||
local from = palette[i]
|
||||
local to = {[4] = 1, map_func(unpack(from))}
|
||||
change_palette[from] = to
|
||||
end
|
||||
return change_palette
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- API
|
||||
|
||||
palette_swap.get_palette_from_image_data = get_palette_of_image_data
|
||||
|
||||
local function concat_functions (first_fn, ...)
|
||||
--
|
||||
local funcs = {first_fn, ...}
|
||||
|
||||
-- Identity function.
|
||||
if #funcs == 0 then
|
||||
return function (...) return ... end
|
||||
|
||||
-- Only one function
|
||||
elseif #funcs <= 1 then
|
||||
return first_fn
|
||||
end
|
||||
|
||||
-- Generic
|
||||
return function (...)
|
||||
local val = {...}
|
||||
for i = 1, #funcs do
|
||||
val = {funcs[i](unpack(val))}
|
||||
end
|
||||
return unpack(val)
|
||||
end
|
||||
end
|
||||
|
||||
function palette_swap.apply_color_map_to_image_data (img_data, ...)
|
||||
assert(img_data)
|
||||
--
|
||||
local map_fn = concat_functions(...)
|
||||
--
|
||||
return apply_change_palette(img_data, change_palette_from_map(map_fn, get_palette_of_image_data(img_data)))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Example color maps
|
||||
|
||||
palette_swap.color_map = {}
|
||||
|
||||
function palette_swap.color_map.invert (r,g,b)
|
||||
return 1 - r, 1 - g, 1 - b
|
||||
end
|
||||
|
||||
local CHANNEL_ORDERS = {
|
||||
[0] = {'r', 'g', 'b'},
|
||||
[1] = {'g', 'b', 'r'},
|
||||
[2] = {'b', 'r', 'g'},
|
||||
[3] = {'r', 'b', 'g'},
|
||||
[4] = {'b', 'g', 'r'},
|
||||
[5] = {'g', 'r', 'b'},
|
||||
[6] = {'b', 'g', 'r'},
|
||||
[7] = {'b', 'g', 'r'}, -- TODO
|
||||
[8] = {'b', 'g', 'r'}, -- TODO
|
||||
[9] = {'r', 'g', 'b'}, -- TODO
|
||||
}
|
||||
|
||||
function palette_swap.color_map.switch_channel (mode)
|
||||
local channel_order = CHANNEL_ORDERS[mode]
|
||||
local func_s = ("return function (r, g, b) return %s, %s, %s end"):format(unpack(channel_order))
|
||||
return loadstring (func_s)()
|
||||
end
|
||||
|
||||
local function load_colors ()
|
||||
|
||||
-- Only need to import colors when needed
|
||||
local colors = require 'colors'
|
||||
assert(type(colors) == 'table')
|
||||
assert(type(colors.rgb1_to_rgb255) == 'function')
|
||||
assert(type(colors.rgb_to_hsl) == 'function')
|
||||
assert(type(colors.hsl_to_rgb1) == 'function')
|
||||
|
||||
function palette_swap.color_map.increase_saturation (amount)
|
||||
-- Create function
|
||||
return function (r, g, b)
|
||||
local hsl = colors.rgb_to_hsl(colors.rgb1_to_rgb255(r, g, b))
|
||||
--
|
||||
hsl[2] = math.min(1, math.max(0, hsl[2] + amount))
|
||||
--
|
||||
return unpack(colors.hsl_to_rgb1(hsl))
|
||||
end
|
||||
end
|
||||
|
||||
function palette_swap.color_map.rotate_hue (rad)
|
||||
return function (r, g, b)
|
||||
local hsl = colors.rgb_to_hsl(colors.rgb1_to_rgb255(r, g, b))
|
||||
--
|
||||
hsl[1] = (hsl[1] + rad) % 1
|
||||
--
|
||||
return unpack(colors.hsl_to_rgb1(hsl))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs {'rotate_hue', 'increase_saturation'} do
|
||||
palette_swap.color_map[key] = function (...)
|
||||
load_colors()
|
||||
return palette_swap.color_map[key](...)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Return
|
||||
|
||||
return palette_swap
|
Loading…
Reference in New Issue
Block a user