--Load and save the bmp formats used by DEFCON. local t = {} local assert = assert local print = print local error = error local table = table local math = math local love = assert( love ) local test = {} function test.load( filename ) 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 function test.bitmapToWorld( x, y ) local w, a = 360.0, 600.0 / 800.0 local h = 360.0 * a x = w * ( x - 800 ) / 800 - w / 2 + 360 y = h * ( y - 600 ) / 600 + 180 return x, y end function test.compareData( a, b ) if a == b then return true end print( "lengths:", a:len(), b:len() ) local errors = 0 for i = 1, math.min( a:len(), b:len() ) do local a, b = a:sub( i, i ), b:sub( i, i ) if a ~= b then errors = errors + 1 print( "mismatch:", errors, i, string.byte( a ), string.byte( b ) ) end if errors > 1000 then break end end error( "test failed. output bitmap does not match!" ) end function test.worldToBitmap( x, y ) x = 800 * ( x + 180 ) / 360 y = 600 + 800 * ( y - 180 ) / 360 return x, y end local function getHeader( filename ) local offset = 2 + love.data.unpack( " 0.5 or g > 0.5 then local long = x * (360 / imgd:getWidth()) - 180 local lat = y * (200 / img:getHeight()) - 100 local attacking = (r > 0.5) local node = {x = long, y = lat, attacking = attacking, idx = idx} print( "ai marker", idx, x, y, long, lat ) nodes[ idx ] = node idx = idx + 1 end end end local encodedString = self:encode( nodes ) love.filesystem.write( "ai_markers.bmp", encodedString ) local eimg, eimgd = test.load( "ai_markers.bmp" ) --the one we just saved for x = 0, 511 do for y = 0, 284 do local r, g = imgd:getPixel( x, 284 - y ) local er, eg = eimgd:getPixel( x, 284 - y ) if math.max( r, g, er, eg ) > 0.5 then print( "node pixel: ", x, 284 - y, r, g, er, eg ) assert( ( r > 0.5 and er > 0.5 ) or ( g > 0.5 and eg > 0.5 ), "ai marker mismatch!" ) end end end print( "ai markers OK" ) end function formats.ai:encode( data ) --set up bitmap as an array of pixels local w, h = self.w, self.h local size = 2 + self.w * self.h local bitmap = { self.header:sub( 1, -3 ) } for j = 2, size do bitmap[j] = 0 end for i, point in ipairs( data ) do local wx, wy = point.x, point.y --get bitmap coordinates local x = math.floor(self.w * (wx + 180) / 360) --idk why exactly I need to round the value here instead of truncating it local y = math.floor(0.5 + self.h * (wy + 100) / 200) --get index into byte array local idx = 2 + y * self.w + x --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( 0, bitmap[j].place and 0xff or 0, bitmap[j].attack and 0xff or 0 ) --bgr24 end end return table.concat( bitmap ) end function formats.travel:test() print "testing travel nodes" local filename = "data/earth/travel_nodes.bmp" local img, imgd = test.load( filename ) local nodes = {} local n = 1 for x = 0, 799 do for y = 0, 399 do if imgd:getPixel( x, 399 - y ) > 0 then local long, lat = test.bitmapToWorld( x, y ) nodes[n] = {x = long, y = lat, idx = n} print( "read:", n, long, lat, x, y ) n = n + 1 end end end print( "loaded ", n, "nodes" ) local encodedString = self:encode( nodes ) test.compareData( love.filesystem.read( filename ), encodedString ) love.filesystem.write( "travel_nodes.bmp", encodedString ) print( "travel nodes OK" ) end --Sparse bitmap with a few white pixels. function formats.travel:encode( points ) --set up bitmap as an array of 8-bit pixels local w, h = self.w, self.h local size = 2 + w * h local bitmap = { self.header } for j = 2, size do bitmap[j] = 0 end --write white pixels for i, point in ipairs( points ) do local wx, wy = point.x, point.y -- get bitmap coordinates: local x = 800 * ( wx + 180 ) / 360 local y = 600 + 800 * ( wy - 180 ) / 360 local offset = math.floor( y * self.w + x - 2 ) --1 := white bitmap[offset] = 1 end --fold into 4-bit pixel array local nybbles = { bitmap[1] } for j = 2, size / 2 do local a, b = bitmap[ 2 * j - 2 ], bitmap[ 2 * j - 1 ] nybbles[j] = string.char( 16 * a + b ) end return table.concat( nybbles ) end function formats.sailable:test() print "testing sailable" local filename = "data/earth/sailable.bmp" local img, imgd = test.load( filename ) local encoded = self:encode( imgd ) love.filesystem.write( "sailable_out.bmp", encoded ) test.compareData( love.filesystem.read( filename ), encoded ) print "sailable OK" end do --sailable local reversePalette = {} for eight, four in pairs( formats.sailable.palette ) do reversePalette[ four ] = eight end --take the red channel in float [0, 1] format --expand it to a byte, then quantize it to 4 bits --according to the sailable palette local function sailableQuantize( r ) if r == 0 then return 0 end if r >= 1 then return 15 end r = math.floor( r * 255 ) for four = 1, #reversePalette do if reversePalette[ four ] > r then return four end end error( "Could not quantize sailable.bmp!" ) end function formats.sailable:encode( data ) local w, h = self.w, self.h local bytes = { self.header:sub( 1, -2 ) } local i = 2 --y coordinates are written top to bottom for y = h - 1, 0, -1 do for x = 0, w - 1 do bytes[i] = sailableQuantize( data:getPixel(x, y) ) i = i + 1 end end --fold into 4-bit pixel array local nybbles = { bytes[1] } for j = 2, #bytes / 2 do local a, b = bytes[ 2 * j ], bytes[ 2 * j + 1 ] nybbles[j] = string.char( 16 * a + b ) end return table.concat( nybbles ) end function formats.africa:test() print "testing africa" local filename = "data/earth/africa.bmp" local img, imgd = test.load( filename ) local encoded = self:encode( imgd ) love.filesystem.write( "africa_out.bmp", encoded ) test.compareData( love.filesystem.read( filename ), encoded ) print "africa OK" end end function formats.africa:encode( data ) local w, h = self.w, self.h local bytes = { self.header:sub( 1, -3 ) } local i = 2 --y coordinates are written top to bottom for y = h - 1, 0, -1 do for x = 0, w - 1 do bytes[i] = bgrChar( data:getPixel(x, y) ) i = i + 1 end end return table.concat( bytes ) end function t.load( filename ) 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 function t.save( data, format ) return assert(formats[format]):encode( data ) end --convenience formats.territory = formats.africa for fmt, tbl in pairs( formats ) do t[fmt] = function( data ) return tbl:encode( data ) end --TESTING --tbl:test() end return t