mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 13:09:54 +02:00
780246c4f6
Implemented a new deinterleave function in ImageWriter to restore row-major order for PNGs. Updated RomExtractorGen2 to utilize this function for extracting Slot Machine graphics, and added new functions for padding and writing graphics sheets. Updated the ROM manifests to include necessary symbols for Slots and Card Flip assets.
164 lines
5.3 KiB
Lua
164 lines
5.3 KiB
Lua
local ImageWriter = {}
|
|
|
|
local SHADES = {
|
|
{ 1, 1, 1, 1 },
|
|
{ 2 / 3, 2 / 3, 2 / 3, 1 },
|
|
{ 1 / 3, 1 / 3, 1 / 3, 1 },
|
|
{ 0, 0, 0, 1 },
|
|
}
|
|
|
|
local function assertDimensions(raw, width, height, bits)
|
|
assert(width % 8 == 0 and height % 8 == 0,
|
|
("%dbpp dimensions must be tile-aligned: %dx%d")
|
|
:format(bits, width, height))
|
|
local expected = width * height * bits / 8
|
|
assert(#raw == expected,
|
|
("%dbpp payload is %d bytes, expected %d")
|
|
:format(bits, #raw, expected))
|
|
end
|
|
|
|
function ImageWriter.decode2bpp(raw, width, height, transparent)
|
|
assertDimensions(raw, width, height, 2)
|
|
local image = love.image.newImageData(width, height)
|
|
local tilesPerRow = width / 8
|
|
for tile = 0, #raw / 16 - 1 do
|
|
local tileX = tile % tilesPerRow * 8
|
|
local tileY = math.floor(tile / tilesPerRow) * 8
|
|
for y = 0, 7 do
|
|
local low = raw[tile * 16 + y * 2 + 1]
|
|
local high = raw[tile * 16 + y * 2 + 2]
|
|
for x = 0, 7 do
|
|
local divisor = 2 ^ (7 - x)
|
|
local shade = math.floor(high / divisor) % 2 * 2
|
|
+ math.floor(low / divisor) % 2
|
|
local color = SHADES[shade + 1]
|
|
local alpha = color[4]
|
|
if transparent and shade == 0 then alpha = 0 end
|
|
image:setPixel(tileX + x, tileY + y,
|
|
color[1], color[2], color[3], alpha)
|
|
end
|
|
end
|
|
end
|
|
return image
|
|
end
|
|
|
|
function ImageWriter.decode1bpp(raw, width, height, transparent)
|
|
assertDimensions(raw, width, height, 1)
|
|
local image = love.image.newImageData(width, height)
|
|
local tilesPerRow = width / 8
|
|
for tile = 0, #raw / 8 - 1 do
|
|
local tileX = tile % tilesPerRow * 8
|
|
local tileY = math.floor(tile / tilesPerRow) * 8
|
|
for y = 0, 7 do
|
|
local row = raw[tile * 8 + y + 1]
|
|
for x = 0, 7 do
|
|
local filled = math.floor(row / 2 ^ (7 - x)) % 2 ~= 0
|
|
local value = filled and 0 or 1
|
|
local alpha = 1
|
|
if transparent and not filled then alpha = 0 end
|
|
image:setPixel(tileX + x, tileY + y,
|
|
value, value, value, alpha)
|
|
end
|
|
end
|
|
end
|
|
return image
|
|
end
|
|
|
|
function ImageWriter.blank(width, height, r, g, b, a)
|
|
local image = love.image.newImageData(width, height)
|
|
image:mapPixel(function() return r or 0, g or 0, b or 0, a or 0 end)
|
|
return image
|
|
end
|
|
|
|
function ImageWriter.blit(target, source, targetX, targetY,
|
|
sourceX, sourceY, width, height, flipX)
|
|
sourceX, sourceY = sourceX or 0, sourceY or 0
|
|
width, height = width or source:getWidth(), height or source:getHeight()
|
|
for y = 0, height - 1 do
|
|
for x = 0, width - 1 do
|
|
local sampleX = flipX and sourceX + width - 1 - x or sourceX + x
|
|
target:setPixel(targetX + x, targetY + y,
|
|
source:getPixel(sampleX, sourceY + y))
|
|
end
|
|
end
|
|
end
|
|
|
|
function ImageWriter.matteColor0(image)
|
|
local width, height = image:getDimensions()
|
|
local queueX, queueY, head = {}, {}, 1
|
|
local seen = {}
|
|
local function add(x, y)
|
|
local key = y * width + x
|
|
if seen[key] then return end
|
|
local r, g, b, a = image:getPixel(x, y)
|
|
if r == 1 and g == 1 and b == 1 and a == 1 then
|
|
seen[key] = true
|
|
queueX[#queueX + 1], queueY[#queueY + 1] = x, y
|
|
end
|
|
end
|
|
for x = 0, width - 1 do add(x, 0); add(x, height - 1) end
|
|
for y = 0, height - 1 do add(0, y); add(width - 1, y) end
|
|
while head <= #queueX do
|
|
local x, y = queueX[head], queueY[head]
|
|
head = head + 1
|
|
image:setPixel(x, y, 1, 1, 1, 0)
|
|
if x > 0 then add(x - 1, y) end
|
|
if x + 1 < width then add(x + 1, y) end
|
|
if y > 0 then add(x, y - 1) end
|
|
if y + 1 < height then add(x, y + 1) end
|
|
end
|
|
return image
|
|
end
|
|
|
|
function ImageWriter.columnsToRows(raw, tilesWide, tilesHigh, bytesPerTile)
|
|
bytesPerTile = bytesPerTile or 16
|
|
local out = {}
|
|
for y = 0, tilesHigh - 1 do
|
|
for x = 0, tilesWide - 1 do
|
|
local source = (x * tilesHigh + y) * bytesPerTile
|
|
local target = (y * tilesWide + x) * bytesPerTile
|
|
for offset = 1, bytesPerTile do
|
|
out[target + offset] = raw[source + offset]
|
|
end
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- Inverse of pret tools/gfx --interleave (pokecrystal tools/gfx.c).
|
|
-- Build-time interleave stores each vertical 8x16 pair as consecutive 8x8
|
|
-- tiles for OBJ mode; this restores row-major sheet order for PNGs.
|
|
function ImageWriter.deinterleave(raw, width, bytesPerTile)
|
|
bytesPerTile = bytesPerTile or 16
|
|
local widthTiles = width / 8
|
|
local numTiles = #raw / bytesPerTile
|
|
local out = {}
|
|
for i = 0, numTiles - 1 do
|
|
local row = math.floor(i / widthTiles)
|
|
local src = i * 2 - (row % 2 == 1
|
|
and widthTiles * (row + 1) - 1
|
|
or widthTiles * row)
|
|
for offset = 1, bytesPerTile do
|
|
out[i * bytesPerTile + offset] = raw[src * bytesPerTile + offset]
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
function ImageWriter.save(image, path)
|
|
local ok, fileData = pcall(image.encode, image, "png")
|
|
if not ok then error("could not encode " .. path .. ": " .. tostring(fileData)) end
|
|
-- CacheFs routes this to the OS save directory (normal builds) or straight
|
|
-- into the game folder (portable installs), creating parent directories as
|
|
-- needed. io.* needs the bytes as a string; love.filesystem would also
|
|
-- take the FileData, but getString() keeps one code path.
|
|
local CacheFs = require("src.import.CacheFs")
|
|
local written, writeError = CacheFs.write(path, fileData:getString())
|
|
if not written then
|
|
error("could not write " .. path .. ": " .. tostring(writeError))
|
|
end
|
|
return fileData
|
|
end
|
|
|
|
return ImageWriter
|