I’m making Sprites as a hobby and my favorite designs are the Game Boy Advance ones, mostly because of their bright and colorful palettes. The problem is that I can’t find a palette that contains the colors used in this console and I would like to use them to make that contrast that that console showed (the Wikipedia entry is of no use, only to show you the combination operation and that’s it)
The Wikipedia entry mentioned that Game Boy Advance has 15 bit RGB, which is 32768 colors.
That’s so many colors I can’t even copy and paste the palette into my reply. So here’s a link to a Github gist.
The soft cap on palette length in Aseprite (and GIMP) for indexed color mode is 256 colors. So palettes for a specific game on the GBA might be more practical if you can find one of them.
Another option is to work in the default 24 bit RGB (plus another 8 for alpha), then quantize to 15 bit.
This is a Lua script that generated the palette:
local sprite <const> = app.sprite
if not sprite then return end
local floor <const> = math.floor
local n <const> = 2 << (5 - 1)
local palette <const> = Palette(1 + n * n * n)
palette:setColor(0, Color { r = 0, g = 0, b = 0, a = 0 })
local m = 0
local i = 0
while i < n do
local blue <const> = floor(0.5 + 255.0 * i / (n - 1.0))
local j = 0
while j < n do
local green <const> = floor(0.5 + 255.0 * j / (n - 1.0))
local k = 0
while k < n do
local red <const> = floor(0.5 + 255.0 * k / (n - 1.0))
local aseColor <const> = Color { r = red, g = green, b = blue, a = 255 }
m = m + 1
palette:setColor(m, aseColor)
k = k + 1
end
j = j + 1
end
i = i + 1
end
sprite:setPalette(palette)
in fact, the “32768 colors” refer to the number of colors that the hardware can create, but the GBA does not show this number, but a reduced number of 512, which is what I need, but thank you very much for your help : )
In that case, the next place I would look are emulator tools. For example, mGBA has a palette tool, a forum where you can ask questions and its source code is on Github.
Unfortunately, the export pal button writes binary pal files. As far as I know Aseprite imports plain text ASCII files. [Edit: If you don’t mind exporting act files, an Aseprite act importer can be found here.]
Is it possible to translate the Binary export into ASCII text?
Aseprite is extensible with a Lua scripting API, so an importer can be made. Scripts with the lua extension are placed in the directory opened from File > Scripts > Open Scripts Folder
. After the folder is rescanned, the script can then be run from File > Scripts
.
Below is a rough script that is not robust, but does ok parsing the binary riff pals I’ve exported from mGba. If you search, I’m sure you can find alternatives that convert palettes from riff pal to gpl or whatever, as well as explainers on how they work, like this one.
I found out a few minutes after I posted that mGba also supports act file format export. I already wrote an importer for that format, see above.
local dlg = Dialog { title = "RIFF PAL Import" }
dlg:file {
id = "importFilepath",
label = "Open:",
filetypes = { "pal" },
open = true,
focus = true
}
dlg:check {
id = "preserveIndices",
label = "Keep",
text = "&Order",
selected = false
}
dlg:button {
id = "importButton",
text = "&IMPORT",
focus = false,
onclick = function()
local sprite <const> = app.sprite
if not sprite then
app.alert {
title = "Error",
text = "There is no active sprite."
}
return
end
local args <const> = dlg.data
local importFilepath <const> = args.importFilepath --[[@as string]]
if (not importFilepath) or (#importFilepath < 1)
or (not app.fs.isFile(importFilepath)) then
app.alert {
title = "Error",
text = "Invalid file path."
}
return
end
local fileExtLc <const> = string.lower(app.fs.fileExtension(importFilepath))
if fileExtLc ~= "pal" then
app.alert {
title = "Error",
text = "File format must be pal."
}
return
end
local binFile <const>, err <const> = io.open(importFilepath, "rb")
if err ~= nil then
if binFile then binFile:close() end
app.alert { title = "Error", text = err }
return
end
if binFile == nil then return end
local fileData <const> = binFile:read("a")
binFile:close()
local strbyte <const> = string.byte
local strsub <const> = string.sub
local strunpack <const> = string.unpack
local lenFileData <const> = #fileData
-- print(string.format("lenFileData: %d", lenFileData))
local magicCheckRiff <const> = strunpack("I4", "RIFF")
local magicWordRiff <const> = strunpack("I4", strsub(fileData, 1, 4))
-- print(string.format("magicWordRiff: %d, magicCheckRiff: %d %s",
-- magicWordRiff, magicCheckRiff,
-- magicWordRiff == magicCheckRiff and "MATCH" or "MISMATCH"))
if magicWordRiff ~= magicCheckRiff then
app.alert { title = "Error", text = "File is not a RIFF." }
return
end
-- local lenRiff <const> = strunpack("I4", strsub(fileData, 5, 8))
-- print(string.format("lenRiff: %d", lenRiff))
local magicCheckPal <const> = strunpack("I4", "PAL ")
local magicWordPal <const> = strunpack("I4", strsub(fileData, 9, 12))
-- print(string.format("magicWordPal: %d, magicCheckPal: %d %s",
-- magicWordPal, magicCheckPal,
-- magicWordPal == magicCheckPal and "MATCH" or "MISMATCH"))
if magicWordPal ~= magicCheckPal then
app.alert { title = "Error", text = "File is not a palette." }
return
end
local magicCheckData <const> = strunpack("I4", "data")
local magicWordData = 0
local i = 12
while i < lenFileData and magicWordData ~= magicCheckData do
i = i + 1
magicWordData = strunpack("I4", strsub(fileData, i, i + 3))
end
-- print(string.format("i: %d, magicWordData: %d, magicCheckData: %d %s",
-- i, magicWordData, magicCheckData,
-- magicWordData == magicCheckData and "MATCH" or "MISMATCH"))
local palIdx = 0
---@type table<integer, integer[]>
local hexDict <const> = {}
local lenUniques = 0
local j = i + 12
while j < lenFileData do
local r8 <const> = strbyte(fileData, j, j)
local g8 <const> = strbyte(fileData, j + 1, j + 1)
local b8 <const> = strbyte(fileData, j + 2, j + 2)
local abgr32 <const> = 0xff000000
| (b8 << 0x10)
| (g8 << 0x08)
| r8
if hexDict[abgr32] then
local arr <const> = hexDict[abgr32]
arr[#arr + 1] = palIdx
else
hexDict[abgr32] = { palIdx }
lenUniques = lenUniques + 1
end
palIdx = palIdx + 1
j = j + 4
end
local preserveIndices = args.preserveIndices --[[@as boolean]]
if preserveIndices then
local pal <const> = Palette(256)
for abgr32, indices in pairs(hexDict) do
local r8 <const> = abgr32 & 0xff
local g8 <const> = (abgr32 >> 0x08) & 0xff
local b8 <const> = (abgr32 >> 0x10) & 0xff
local aseColor <const> = Color { r = r8, g = g8, b = b8, a = 255 }
local lenIdcs <const> = #indices
local k = 0
while k < lenIdcs do
k = k + 1
local idx <const> = indices[k]
pal:setColor(idx, aseColor)
end
end
sprite:setPalette(pal)
else
---@type integer[]
local sortedUniquesArr <const> = {}
for abgr32, _ in pairs(hexDict) do
sortedUniquesArr[#sortedUniquesArr + 1] = abgr32
end
table.sort(sortedUniquesArr, function(a, b)
return hexDict[a][1] < hexDict[b][1]
end)
local pal <const> = Palette(lenUniques)
local k = 0
while k < lenUniques do
local abgr32 <const> = sortedUniquesArr[1 + k]
local r8 <const> = abgr32 & 0xff
local g8 <const> = (abgr32 >> 0x08) & 0xff
local b8 <const> = (abgr32 >> 0x10) & 0xff
pal:setColor(k, Color { r = r8, g = g8, b = b8, a = 255 })
k = k + 1
end
sprite:setPalette(pal)
end
end
}
dlg:button {
id = "cancel",
text = "&CANCEL",
focus = false,
onclick = function()
dlg:close()
end
}
dlg:show { wait = false }
Once imported into Aseprite, you can re-export the palette (as pal, gpl, an image, etc.) by looking in the hamburger menu above the color swatches, to the right of the palette padlock and palette presets buttons.
uhhh… I think I can understand that
thank you
also, do you know a game that uses the complete palette in one screen?