Hi folks,
I found myself in need of a quick way to put a text label in a sprite via Lua script. Couldn’t find the Insert Text command in the scripting API (see also). So I wanted to share this experiment.
I made some 3x5 matrices for the glyphs I wanted, pack them as a number, then stored them in a dictionary. For example, this is the ‘A’ glyph.
..123
1 X
2 X X
3 XXX
4 X X
5 X X
In binary, with the Xs replaced by 1s, the spaces replaced by 0s and the line breaks taken out, that’s
010101111101101
The dictionary looks like this, where the glyph is the key and the binary formation is the value. It includes a space, digits 0 through 9 and capital letters A through Z.
local glyphLut = {
[' '] = 0,
['0'] = 31599, -- 111101101101111
['1'] = 11415, -- 010110010010111
['2'] = 29671, -- 111001111100111
['3'] = 29647, -- 111001111001111
['4'] = 23497, -- 101101111001001
['5'] = 31183, -- 111100111001111
['6'] = 31215, -- 111100111101111
['7'] = 29257, -- 111001001001001
['8'] = 31727, -- 111101111101111
['9'] = 31689, -- 111101111001001
['A'] = 11245, -- 010101111101101
['B'] = 27566, -- 110101110101110
['C'] = 14627, -- 011100100100011
['D'] = 27502, -- 110101101101110
['E'] = 31143, -- 111100110100111
['F'] = 31140, -- 111100110100100
['G'] = 14635, -- 011100100101011
['H'] = 23533, -- 101101111101101
['I'] = 29847, -- 111010010010111
['J'] = 12874, -- 011001001001010
['K'] = 23469, -- 101101110101101
['L'] = 18727, -- 100100100100111
['M'] = 24557, -- 101111111101101
['N'] = 27501, -- 110101101101101
['O'] = 11114, -- 010101101101010
['P'] = 27556, -- 110101110100100
['Q'] = 11121, -- 010101101110001
['R'] = 31661, -- 111101110101101
['S'] = 14478, -- 011100010001110
['T'] = 29842, -- 111010010010010
['U'] = 23403, -- 101101101101011
['V'] = 23402, -- 101101101101010
['W'] = 23546, -- 101101111111010
['X'] = 23213, -- 101101010101101
['Y'] = 23186, -- 101101010010010
['Z'] = 29351 -- 111001010100111
}
Unless I missed the syntax, lua doesn’t do bitwise literals (not like Java, with a 0b
prefix, anyway). string.tonumber
might do the trick, but I didn’t try it.
There are some problems with this resolution, obviously. ‘M’, and ‘W’ are particularly bad. ‘N’ is actually lowercase, so as to not be confused with ‘H’.
Here’s a function which draws each glyph to an image using the Aseprite API’s image:drawPixel.
local function displayGlyph(image, glyph, hex, xLoc, yLoc, glyphWidth, glyphHeight)
local h = glyphHeight or 5
local w = glyphWidth or 3
local y = yLoc or 0
local x = xLoc or 0
local clr = hex or 0xffffffff
local g = glyph or 0
local len = w * h
local lenn1 = len - 1
for j = 0, lenn1, 1 do
local shift = lenn1 - j
local mark = (g >> shift) & 1
if mark ~= 0 then
image:drawPixel(x + (j % w), y + (j // w), clr)
end
end
end
j % w
and j // w
are how a one-dimensional index is converted to 2D coordinates. I’ve not tested this to make sure it works with matrices of other dimensions. You could also remove the checks to see if an input argument needs a default if you knew that this would only be called by the next function.
Here’s a function to display a string.
local function displayString(image, msg, hex, xLoc, yLoc, glyphWidth, glyphHeight)
local h = glyphHeight or 5
local w = glyphWidth or 3
local y = yLoc or 0
local x = xLoc or 0
local clr = hex or 0xffffffff
local msgUpper = string.upper(msg)
local msgLen = #msgUpper
-- print(msgUpper)
-- print(msgLen)
local chars = {}
for i = 1, msgLen, 1 do
chars[i] = msgUpper:sub(i, i)
end
local writeChar = xLoc
local writeLine = yLoc
for i = 1, msgLen, 1 do
local ch = chars[i]
-- print(ch)
if ch == '\n' then
-- Add 2, not 1, due to drop shadow.
writeLine = writeLine + h + 2
writeChar = xLoc
else
local glyph = glyphLut[ch]
-- print(glyph)
displayGlyph(image, glyph, clr, writeChar, writeLine, w, h)
writeChar = writeChar + w + 1
end
end
end
Seems like there are a few variations on how to convert a lua string to an array of characters out there. So this might not be the best one.
Lastly, here’s all the dialog stuff.
local dlg = Dialog {
title = "Print Message Test"
}
dlg:entry{
id = "msg",
label = "Message",
text = "Lorem ipsum dolor sit amet",
focus = "false"
}
dlg:color{
id = "fillClr",
label = "Fill:",
color = Color(255, 255, 255, 255)
}
dlg:color{
id = "shadowClr",
label = "Shadow:",
color = Color(0, 0, 0, 204)
}
dlg:number{
id = "xOrigin",
label = "Origin:",
text = string.format("%.1f", 0),
decimals = 5
}
dlg:number{
id = "yOrigin",
text = string.format("%.1f", 0),
decimals = 5
}
dlg:check {
id = "useShadow",
label = "Drop Shadow:",
selected = true
}
dlg:combobox {
id = "alignHoriz",
label = "Horizontal:",
option = "LEFT",
options = {"LEFT", "CENTER", "RIGHT"},
}
dlg:combobox {
id = "alignVert",
label = "Vertical:",
option = "TOP",
options = {"BOTTOM", "CENTER", "TOP"},
}
dlg:button{
id = "ok",
text = "OK",
focus = true,
onclick = function()
local args = dlg.data
if args.ok then
local sprite = app.activeSprite
if sprite then
local gw = 3
local gh = 5
local layer = sprite:newLayer()
local frame = app.activeFrame or 1
local cel = sprite:newCel(layer, frame)
local image = cel.image
local msg = args.msg
local msgLen = #msg
if msg == nil or msgLen < 1 then
msg = "Lorem ipsum dolor sit amet"
end
local hexFill = args.fillClr.rgbaPixel
local hexShd = args.shadowClr.rgbaPixel
local xLoc = args.xOrigin or 0
local yLoc = args.yOrigin or 0
local alignHoriz = args.alignHoriz
local alignVert = args.alignVert
-- Both alignments will give inexact results due to
-- integer arithmetic over small numbers, whether you
-- count spaces and drop shadows, what you consider the
-- rightmost, bottommost coord of the sprite.
if alignHoriz == "CENTER" then
local gwLen = msgLen * (gw + 1)
xLoc = xLoc - gwLen // 2
elseif alignHoriz == "RIGHT" then
local gwLen = msgLen * (gw + 1)
xLoc = xLoc - gwLen
end
-- Does not account for multiple line strings
-- that contain '\n'. That would require a search
-- for and sum of instances of any carriage return
-- in the string, plus account for gap of 2 per line
-- to account for drop shadow.
if alignVert == "CENTER" then
yLoc = yLoc - (gh + 1) // 2
elseif alignVert == "BOTTOM" then
yLoc = yLoc - (gh - 1)
end
layer.name = msg
if args.useShadow then
displayString(image, msg, hexShd, xLoc, yLoc + 1, gw, gh)
end
displayString(image, msg, hexFill, xLoc, yLoc, gw, gh)
app.refresh()
else
app.alert("There is no active sprite.")
end
else
app.alert("Dialog arguments are invalid.")
end
end
}
dlg:button{
id = "cancel",
text = "CANCEL",
onclick = function()
dlg:close()
end
}
dlg:show{
wait = false
}
In the comments, I mentioned some limitations of the script. For example, I couldn’t wedge a line break into the dialog entry widget, so I didn’t bother developing a line break counter. That means vertical alignment is simplistic. Also, what if you need the font to be bigger? Maybe there’s a neat way to upscale by 2x (to 6x10), 3x (to 9x15), etc. Dunno.
Here’s the motivation: In another thread, @Olga_Galvanova brought up the palette analyzer from grafx2. It’d be nice to be able to create custom analysis tools, so as to not have to rely on a shim to external software. As a first step, we need the ability to add labels.
Hope that was a fun read, maybe gave the scripters out there some ideas!
Best,
Jeremy