mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-13 17:00:58 +02:00
144 lines
4.6 KiB
Lua
144 lines
4.6 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
|
|
|
|
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
|