dcearth/bmp.lua

142 lines
4.5 KiB
Lua
Raw Normal View History

2023-04-28 17:35:52 +00:00
--Load and save the bmp formats used by DEFCON.
local t = {}
2023-07-28 23:23:08 +00:00
local love = assert( love )
local lfs = love.filesystem
2023-04-28 17:35:52 +00:00
--FFI bit-twiddling stuff.
local ffi = require 'ffi'
local bit = require 'bit'
2023-04-28 17:35:52 +00:00
2024-04-29 01:21:32 +00:00
local function getHeader( filename )
local offset = love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 )
local header, size = assert( love.filesystem.read( filename, offset ) )
print( "BMP HEADER", filename, size, "\n", header )
return header
end
local formats = {
["512rgb24"] = {
header = getHeader( "data/earth/africa.bmp" ),
encode = function( r ) return math.floor( r * 255 ) end,
bytesPerPixel = 3,
w = 512,
h = 285,
},
["512r4"] = {
header = getHeader( "data/earth/sailable.bmp" ),
encode = function( r ) return math.floor( r * 255 / 16 ) end,
bytesPerPixel = 0.5,
w = 512,
h = 285,
},
["800r4"] = {
header = getHeader( "data/earth/travel_nodes.bmp" ),
encode = function( r ) return math.floor( r * 255 / 16 ) end,
bytesPerPixel = 0.5,
w = 512,
h = 285,
}
}
function t.header( format )
return formats[format] and formats[format].header
end
function t.load( filename )
2023-07-28 23:23:08 +00:00
local imgd = love.image.newImageData( filename )
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
local img = love.graphics.newImage( imgd )
img:setFilter( "nearest", "nearest" )
return img, imgd
end
2024-04-29 01:21:32 +00:00
--maps an array of 1-byte pixel values (numbers 0 to 15 inclusive)
--to an array of half-byte pixel values (which can be concatenated into a string)
--BUT don't touch the first element (which is assumed to be a header or something)
local function foldByteArray( bytes )
local nybbles = { bytes[1] }
for j = 2, #bytes / 2 do
local a, b = bytes[ 2 * j - 2 ], bytes[ 2 * j - 1 ]
nybbles[j] = string.char( 16 * a + b )
end
return nybbles
end
local function packTwentyFour( bytes )
local twentyFour = { bytes[1] }
for j = 2, #bytes do
bytes[j] = string.char( bytes[j], bytes[j], bytes[j] )
end
end
function t.save( data, format )
2024-04-21 14:10:53 +00:00
local w, h = data:getDimensions()
2024-04-29 01:21:32 +00:00
format = assert( formats[format] )
local bytes = { format.header }
format.byte = 0
local i = 2
2024-04-28 01:38:23 +00:00
for x = 0, w - 1 do
for y = 0, h - 1 do
2024-04-29 01:21:32 +00:00
bytes[i] = format.encode( data:getPixel(x, y) )
i = i + 1
2024-04-28 01:38:23 +00:00
end
end
2024-04-29 01:21:32 +00:00
if format.bytesPerPixel < 1 then bytes = foldByteArray( bytes )
else packTwentyFour( bytes ) end
return table.concat( bytes )
end
2024-04-29 01:21:32 +00:00
--takes an array of world-space points in [-180, 180] x [-100, 100]
--e.g { { x = 0.5, y = 0.5 }, { x = 0.4, y = 0.6 }, }, etc.
--and a function which maps points to colours
function t.savePoints( points, format )
format = assert( formats[format] )
--set up bitmap as an array of pixels
local w, h = format.w, format.h
local size = 2 + format.w * format.h
local bitmap = { format.header }
for j = 2, size do bitmap[j] = 0 end
--this is black-and-white only. easy case
if format.bytesPerPixel < 1 then
for i, point in ipairs( points ) do
local wx, wy = point.x, point.y
--get bitmap coordinates
local x, y = math.floor(format.w * (wx + 180) / 360), math.floor(format.h * (1.0 - (wy + 100) / 200))
--get index into byte array
local idx = 2 + x * format.h + y
bitmap[idx] = 0x0f --0b00001111
end
--now map pixels to 4-bit strings, slap on the header, and pass it back up
return table.concat( foldByteArray( bitmap ) )
end
--cf. ai.lua: these points can be red or green
for i, point in ipairs( points ) do
local wx, wy = point.x, point.y
--get bitmap coordinates
local x, y = math.floor(format.w * (wx + 180) / 360), math.floor(format.h * (1.0 - (wy + 100) / 200))
--get index into byte array
local idx = 2 + x * format.h + y
--in-editor we could have two points in the same place, possibly of the same or different types
--in case we miss something upstream, don't corrupt these duplicate points,
--just saturate the attack/defense value
if bitmap[idx] == 0 then bitmap[idx] = { attack = point.attacking, place = not(point.attacking)}
else
bitmap[idx].attack = bitmap[idx].attack or point.attacking
bitmap[idx].place = bitmap[idx].place or not(point.attacking)
end
end
--now map pixels to 3-byte strings
for j = 2, size do
if bitmap[j] == 0
then bitmap[j] = "\0\0\0"
else bitmap[j] = string.char( bitmap[j].attack and 0xff or 0, bitmap[j].place and 0xff or 0, 0 )
end
end
return table.concat( bitmap )
end
return t