(11-11-2022, 02:35 AM)vonhoff Wrote: Hey, this looks really cool! I would love to implement something like this in my own projects. Can you share the code for loading palettes like this?
I don't mind that it's written in Nim, I think it should be easy to port to other languages.
Hi! Thanks for your interest!
First, here is the file format (the screenshot of the binary)
![[Image: image.png]](https://media.discordapp.net/attachments/803944031285280778/1040566205569843230/image.png)
- The 5 firsts bytes in blue define the header, it's just the string "PAL64"
- The most significant bit in the pink byte tells if the custom NES palette is embedded in the PAL64. 1 means True, 0 means False. The others bits tell which version of the format it is.
- The green byte tells how long are the palettes (up to 255, but limited to 16 in the tool)
- The yellow byte tells how many palettes there is (up to 255, but limited to 16 in the tool)
- The purple byte tells which palette type it is (this way, you also can guess how many bytes a color use)
- The red bytes are the data of the palettes. The 3-3-2 RGB, SMS and NES colors are encoded in one byte (so one byte = one color). 5 levels, WEB, MegaDrive, Amiga and SNES palettes are encoded in 2 bytes (so 2 bytes = one color). The DS and RGB24 palettes are encoded in 3 bytes (so 3 bytes = one color)
- The orange bytes represents the embedded NES palette. The custom NES palette can have an arbitrary number of colors, up to 256. Those bytes are there if you tell the tool to embed the NES Palette in the PAL64 file. The embedded NES palette is loaded into the internal NES lookup table.
Here is some code!
Code:
import Tilengine, Palette
import std/streams
import strutils
const PAL64VER = 1.uint8
type
paletteType = enum
RGB332,
SMS,
FIVELEVELS,
WEB,
MEGADRIVE,
AMIGA,
SNES,
DS,
RGB24,
NES
var nesLUT = @[5592405.uint32, 6003, 1926, 3016056, 5833293, 7471121, 7208960, 4982784, 1514240, 10752, 12544, 11784, 9797, 0, 0, 0, 10855845, 22470, 2244581, 7219417, 11410086, 13768537, 13705479, 10958592, 6508800, 1599232, 29184, 29489, 27268, 0, 0, 0, 16711679, 3123455, 6128127, 10252543, 16216831, 16742333, 16744053, 16747051, 13475840, 8501250, 4048944, 1232251, 902608, 3947580, 0, 0, 16711679, 10804991, 11651327, 13418239, 16040703, 16762346, 16762825, 16764330, 15718038, 13688981, 11790245, 10480323, 10152166, 11513775, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
proc loadNESPal*(path: string): void =
let
file = open(path)
fSize = file.getFileSize()
fs = newFileStream(file)
defer: fs.close()
var data = newSeq[uint8](fSize)
discard fs.readData(data[0].addr, sizeof(uint8) * data.len)
for i in countup(0, nesLUT.len-1):
if(i >= data.len div 3):
var j = i * 3
var msb = data[j]
var mid = data[j + 1]
var lsb = data[j + 2]
var colVal = (msb.uint32 shl 16) or (mid.uint32 shl 8) or lsb.uint32
nesLUT[i] = colVal
else:
nesLUT[i] = 0
proc loadHEXtoNES*(path: string): void =
let
file = open(path)
defer: file.close()
for i in countup(0, nesLUT.len-1):
if not file.endOfFile():
nesLUT[i] = parseHexInt(file.readLine()).uint32
else:
nesLUT[i] = 0
# Converts to RGB24
proc bin2rgb(bin: uint32, palType: paletteType): uint32 =
var
r: uint32 = 0
g: uint32 = 0
b: uint32 = 0
case palType
of RGB332: # RGB 3-3-2
# echo "test"
r = ((bin shr 5 and 0b00000111).float32 * 36.42).uint32
g = ((bin shr 2 and 0b00000111).float32 * 36.42).uint32
b = (bin and 0b00000011) * 85
of SMS: # SMS
r = (bin shr 4 and 0b00000011) * 85
g = (bin shr 2 and 0b00000011) * 85
b = (bin and 0b00000011) * 85
of FIVELEVELS: # 5 Levels
r = (bin shr 6 and 0b0000000000000111) * 63
g = (bin shr 3 and 0b0000000000000111) * 63
b = (bin and 0b0000000000000111) * 63
of WEB: # WEB
r = (bin shr 6 and 0b0000000000000111) * 51
g = (bin shr 3 and 0b0000000000000111) * 51
b = (bin and 0b0000000000000111) * 51
of MEGADRIVE: # MegaDrive
r = (bin shr 6 and 0b0000000000000111) * 36
g = (bin shr 3 and 0b0000000000000111) * 36
b = (bin and 0b0000000000000111) * 36
of AMIGA: # Amiga
r = (bin shr 8 and 0b0000000000001111)
# echo bin.toHex()
r = r or (r shl 4)
g = (bin shr 4 and 0b0000000000001111)
g = g or (g shl 4)
b = (bin and 0b0000000000001111)
b = b or (b shl 4)
of SNES: # SNES
r = (bin shr 10 and 0b0000000000011111) * 8
g = (bin shr 5 and 0b0000000000011111) * 8
b = (bin and 0b0000000000011111) * 8
of DS:
r = (bin shr 12 and 0b111111) * 4
g = (bin shr 6 and 0b111111) * 4
b = (bin and 0b111111) * 4
of RGB24:
r = bin shr 16 and 0xFF
g = bin shr 8 and 0xFF
b = bin and 0xFF
of NES:
return nesLUT[bin].uint32
else:
echo "Not supported!"
return 0
return ((r shl 16) or (g shl 8) or b)
# Loads the PAL64 and sets into Tilengine's global palettes
proc loadPalsAndSet*(path: string, importEmbeddedNes: bool = false): void =
let
file = open(path)
fSize = file.getFileSize()
fs = newFileStream(file)
defer: fs.close()
# Data of the palettes
var data = newSeq[uint8](fSize)
# Reading the data of file and put it into the data buffer
var i = fs.readData(data[0].addr, sizeof(uint8) * data.len)
# Header and version checks
var str = data[0].char & data[1].char & data[2].char & data[3].char & data[4].char
var index = 5
if(str != "PAL64"):
echo "Invalid PAL64 format!!"
return
var myVer = data[index] and 0b01111111
if(myVer > PAL64VER):
echo "PAL64 version too new!!!"
return
# Is the NES palette embedded in the file?
var isNesEmbedded = data[index] shr 7
inc index
# Length of the palettes
let len = data[index]
inc index
# Number of palettes
var numPal = data[index]
# Tilengine supports up to 8 palettes. So we clip this number.
if(numPal > 8):
numPal = 8
# Global palettes initialization and setup
var pal0 = createPalette(len.int)
var pal1 = createPalette(len.int)
var pal2 = createPalette(len.int)
var pal3 = createPalette(len.int)
var pal4 = createPalette(len.int)
var pal5 = createPalette(len.int)
var pal6 = createPalette(len.int)
var pal7 = createPalette(len.int)
discard setGlobalPal(0, pal0)
discard setGlobalPal(1, pal1)
discard setGlobalPal(2, pal2)
discard setGlobalPal(3, pal3)
discard setGlobalPal(4, pal4)
discard setGlobalPal(5, pal5)
discard setGlobalPal(6, pal6)
discard setGlobalPal(7, pal7)
index.inc
# Getting the type of the palette (RGB 3-3-2, SMS, WEB Safe, MegaDrive, Amiga or SNES)
let palType = data[index].paletteType
index.inc
# To know how much bytes a color uses.
var bytes = 1
var bgColor = 0.uint32
# Applying the palettes
for i in countup(0, (len.int * numPal.int)-1):
var indexCol = i.uint32 mod (len)
var palId = i div len.int
var color: uint32 = 0
if(palType == RGB332 or palType == SMS or palType == NES):
color = bin2rgb(data[index + i].uint32, palType)
bgColor = bin2rgb(data[index].uint32, palType)
bytes = 1
elif(palType != DS and palType != RGB24):
var j = i * 2
var msb = data[index + j]
var lsb = data[index + j + 1]
color = bin2rgb((msb.uint32 shl 8) or lsb.uint32, palType)
bgColor = bin2rgb((data[index].uint32 shl 8) or data[index + 1].uint32, palType)
bytes = 2
else:
var j = i * 3
var msb = data[index + j]
var mid = data[index + j + 1]
var lsb = data[index + j + 2]
var colVal = (msb.uint32 shl 16) or (mid.uint32 shl 8) or lsb.uint32
color = bin2rgb(colVal, palType)
bgColor = bin2rgb((data[index].uint32 shl 16) or (data[index + 1].uint32 shl 8) or data[index + 2], palType)
bytes = 3
discard getGlobalPal(palId).setPaletteColor(indexCol.int, color)
index = index + (len.int * bytes * 8.int)
# We load the embedded NES palette into the Lookup Table if the user wants and if it is a NES palette type
if(isNesEmbedded.bool and palType == NES and importEmbeddedNes):
for i in countup(0, nesLUT.len-1):
var k = i * 3
var msb = data[index + k] shl 16;
var mid = (data[index + k + 1]) shl 8;
var lsb = data[index + k + 2];
nesLUT[i] = msb or mid or lsb
setBgColor(bgColor)
Edit : if you see something like "myVariable.someType", I'm just casting a type to another type, it's the equivalent of "(someType)myVariable" in other languages.