Skip to content

Instantly share code, notes, and snippets.

@kaeza
Last active March 9, 2022 00:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaeza/e88d6e8c868aa1ce81a3be50ea404afc to your computer and use it in GitHub Desktop.
Save kaeza/e88d6e8c868aa1ce81a3be50ea404afc to your computer and use it in GitHub Desktop.
Minetest - texutil.lua
---
-- Work with textures as if they were objects.
--
-- All methods sanitize its arguments and perform any kind of
-- escaping needed.
--
-- A simple example:
--
-- local tex = texutil.new("foo.png"):colorize("#FF0000", 255)
-- tex:add(texutil.new("bar.png"):colorize("#0000FF", 255))
-- print(tex)
-- --> (foo.png^[colorize:#FF0000FF:255^(bar.png^[colorize:#0000FFFF:255))
--
-- @module texutil
local M = { }
local F = string.format
---
-- Table representing a texture.
--
-- Integer indices are sub-textures.
local texture = { }
texture.__index = texture
-- INTERNAL
-- Escapes top-level characters that the texture parser
-- considers "special".
-- Returns escaped string.
local function escape(x)
return (x:gsub("([%[%]\\^%(%)])", "\\%1"))
end
-- INTERNAL
-- Adds an item and returns self.
function addraw(self, x)
self[#self+1] = x
return self
end
---
-- Adds sub-textures to this texture.
--
-- @tparam string|texture ... Textures to add.
-- @treturn texture This object.
function texture:add(...)
local t = { n=select("#", ...), ... }
for i = 1, t.n do
local x = t[i]
if type(x) == "string" then
x = escape(x)
end
addraw(self, x)
end
return self
end
-- TODO
local named_colors = { }
-- INTERNAL
-- Convert a string of hex digits to a number.
local function X(x)
return tonumber(x, 16)
end
-- INTERNAL
-- Parse a color into separate components.
-- Returns `r`, `g`, `b`, `a`.
-- Returns nil plus error message on error.
local function parse_color(x)
if type(x) == "string" then
-- #RRGGBBAA
local r, g, b, a
r, g, b, a = x:match("^#(%x%x)(%x%x)(%x%x)(%x%x)$")
if r then
return X(r), X(g), X(b), X(a)
end
-- #RRGGBB
r, g, b = x:match("^#(%x%x)(%x%x)(%x%x)$")
if r then
return X(r), X(g), X(b), 255
end
local c
-- namedcolor[#alpha]
c, a = x:match("^([A-Za-z0-9]+)#(%x%x)$")
if not c then
c, a = x, 255
else
a = X(a)
end
c = named_colors[c]
if c then
r, g, b = unpack(c)
return r, g, b, a
end
elseif type(x) == "number" then
-- 0xAARRGGBB
local a, r, g, b
a = math.floor(x/0x1000000)%0x100
r = math.floor(x/0x10000)%0x100
g = math.floor(x/0x100)%0x100
b = math.floor(x)%0x100
return r, g, b, a
end
return nil, "invalid color"
end
-- INTERNAL
-- Normalizes color argument and returns the normalized form
-- as a string in the format `#RRGGBBAA`.
local function norm_color(x)
local r, g, b, a = parse_color(x)
if not r then return nil, g end
return ("#%02X%02X%02X%02X"):format(r, g, b, a)
end
---
-- Colorizes this texture.
--
-- @tparam string|number color Base color. If it's a number, it must
-- be in the `0xRRGGBBAA` format. If it's a string, it must have the
-- format `#RRGGBBAA` or `#RRGGBB`.
-- @tparam ?string|number ratio Blend ratio. If it's a number, it must
-- be an integer in the range 0 to 255, inclusive, where 0 means no
-- colorization takes place, and 255 means the texture is fully
-- colorized.
-- @treturn texture This object.
function texture:colorize(color, ratio)
color = assert(norm_color(color))
if type(ratio) == "number" then
ratio = ":"..math.max(0, math.min(255, math.floor(ratio)))
elseif ratio == "alpha" then
ratio = ":alpha"
else
ratio = ""
end
return addraw(self, "[colorize:"..color..ratio)
end
---
-- Creates a new sub-texture combining the one or more textures.
--
-- @tparam number w Width of new texture.
-- @tparam number h Height of new texture.
-- @tparam table textures List of textures to combine. It must be
-- an array where each item is a table `{x, y, tex}`, where `x` and
-- `y` are the coordinates where the texture is placed (relative
-- to the new texture's origin), and `tex` is the texture to add.
-- `tex` may be a string or another `texture` object.
-- @treturn texture This object.
function texture:combine(w, h, textures)
local t = { }
for i, tt in ipairs(textures) do
local x, y, texture = unpack(tt)
t[i] = x..","..y.."="..texture:gsub(":", "\\:")
end
return addraw(self, "[combine:"..w.."x"..h..":"..table.concat(t, ":"))
end
---
-- Resizes this texture, scaling the contents.
--
-- @tparam number w New width.
-- @tparam number h New height.
-- @treturn texture This object.
function texture:resize(w, h)
return addraw(self, "[resize:"..w.."x"..h)
end
---
-- Returns a string representation of this object.
--
-- @function texture:tostring
-- @treturn string A string.
-- @see texture:__tostring
texture.tostring = tostring
---
-- Returns a string representation of this object.
--
-- The string representation is the texture string that should be
-- used to get the texture represented by this object.
--
-- This metamethod is called by the built-in `tostring` function.
--
-- @treturn string A string.
function texture:__tostring()
local t = { }
for i, v in ipairs(self) do
t[i] = tostring(v)
end
return "("..table.concat(t, "^")..")"
end
---
-- Create a new texture object.
--
-- @tparam ?string|texture ... Initial sub-textures.
-- @treturn texture A new texture object.
function M.new(...)
local self = setmetatable({ }, texture)
self:add(...)
return self
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment