OC-KittenOS/code/libs/bmp.lua

251 lines
7.1 KiB
Lua

-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- bmp: Portable OC BMP/DIB library
-- Flexible: Reading can be set to
-- ignore first 14 bytes,
-- allowing reuse of the library on
-- ICO/CUR data. Yes, really.
-- handle(i, valDiv, valMod)
local function bitsCore(i, fieldOfs, fieldWidth, handle)
local adv = math.floor(fieldOfs / 8)
i = i + adv
fieldOfs = fieldOfs - (adv * 8)
-- above 3 lines are a removable optimization
while fieldWidth > 0 do
local bitsHere = math.min(fieldWidth, math.max(0, 8 - fieldOfs))
if bitsHere > 0 then
local pow1 = math.floor(2 ^ bitsHere)
-- offset
-- pixels are "left to right" in the MostSigBitFirst stream
local aFieldOfs = 8 - (fieldOfs + bitsHere)
local pow2 = math.floor(2 ^ aFieldOfs)
handle(i, pow2, pow1)
fieldWidth = fieldWidth - bitsHere
fieldOfs = 0
else
-- in case the 'adv' opt. gets removed
fieldOfs = fieldOfs - 8
end
i = i + 1
end
end
local function encode16(t)
return string.char(t % 0x100) .. string.char(math.floor(t / 0x100))
end
local function encode32(t)
return encode16(t % 0x10000) .. encode16(math.floor(t / 0x10000))
end
-- This & the BMP equivalent return a header,
-- a buffer size, and a 1-based data pointer,
-- and are used to initially create the image.
-- Notably, for bpp <= 8, a paletteSize of 0 is illegal.
-- topDown adjusts the order of scanlines.
-- cMode adjusts some values.
-- IT IS RECOMMENDED YOU PROPERLY SET THE MASK UP.
local function prepareDIB(w, h, p, bpp, paletteSize, topDown, cMode)
if bpp <= 8 then
if paletteSize == 0 then
error("A palette size of 0 is invalid for <= 8-bit images. Use 16-bit or 32-bit for no palette, or specify the amount of palette entries.")
end
end
local scanWB = math.ceil((bpp * w) / 32) * 4
local palSize = paletteSize * 4
local bufSize = scanWB * h * p
local aH = h
if cMode then
-- O.o why change format? who knows!
aH = aH * 2
bufSize = bufSize + ((math.ceil(w / 32) * 4) * h * p)
end
if topDown then
aH = 0x100000000 - aH
end
return
"\x28\x00\x00\x00" .. -- 0x0E
encode32(w) .. -- 0x12
encode32(aH) .. -- 0x16
encode16(p) .. -- 0x1A
encode16(bpp) .. -- 0x1C
"\x00\x00\x00\x00" .. -- 0x1E
encode32(bufSize) .. -- 0x22
"\x00\x00\x00\x00" .. -- 0x26
"\x00\x00\x00\x00" .. -- 0x2A
encode32(paletteSize) .. -- 0x2E
encode32(paletteSize), -- 0x32 then EOH
-- -14 here to move back into headless units
0x36 + palSize + bufSize - 14,
0x36 + palSize + 1 - 14 -- 1-based data pointer
end
return {
headerMinSzBMP = 0x36,
headerMinSzDIB = 0x36 - 14,
-- get/set are (index) and (index, value) respectively
-- they are 1-based
-- If "packed" is used, two things happen:
-- 1. The +1 offeset for 1-based is replaced
-- with packed (so -13 is pure packed-DIB)
-- 2. We don't try to use the BMP header
connect = function (get, set, cMode, packed)
-- NOTE: Internally, BMP addresses are used,
-- so that the Wikipedia page can be used
-- as a valid reference for header fields.
-- verify cMode
if cMode ~= nil and cMode ~= "mask" and cMode ~= "colour" then
error("Unknown cMode " .. cMode)
end
-- NOTE: 0-base is used
local function get8(i)
return get(i + (packed or 1))
end
local function get16(i)
return get8(i) + (256 * get8(i + 1))
end
local function get32(i)
return get16(i) + (65536 * get16(i + 2))
end
local function set8(i, v)
set(i + (packed or 1), v)
end
local function set32(i, v)
local st = encode32(v)
set8(i, st:byte(1))
set8(i + 1, st:byte(2))
set8(i + 2, st:byte(3))
set8(i + 3, st:byte(4))
end
local function getBits(i, fieldOfs, fieldWidth)
local v = 0
local vp = 1
bitsCore(i, fieldOfs, fieldWidth, function (i, valDiv, valMod)
local data = math.floor(get8(i) / valDiv) % valMod
v = v + (data * vp)
vp = vp * valMod
end)
return v
end
local function setBits(i, fieldOfs, fieldWidth, v)
return bitsCore(i, fieldOfs, fieldWidth, function (i, valDiv, valMod)
local data = get8(i)
-- Firstly need to eliminate the old data
data = data - ((math.floor(data / valDiv) % valMod) * valDiv)
-- Now to insert the new data
data = data + ((v % valMod) * valDiv)
set8(i, data)
-- Advance
v = math.floor(v / valMod)
end)
end
-- direct header reads (all of them)
local hdrSize = get32(0x0E)
if hdrSize < 0x28 then
error("OS/2 Bitmaps Incompatible")
end
local width = get32(0x12)
local height = get32(0x16)
local planes = get16(0x1A)
local bpp = get16(0x1C)
local compression = get32(0x1E)
local paletteCol = 0
local other = get32(0x2E)
paletteCol = other
-- postprocess
-- The actual values used for addressing, for cMode to mess with
local basePtr = 14 + hdrSize + (paletteCol * 4)
local scanWB = math.ceil((bpp * width) / 32) * 4
local monoWB = (math.ceil((bpp * width) / 32) * 4)
local planeWB = scanWB * height
if not packed then
basePtr = get32(0x0A) -- 'BM' header
end
-- negative height means sane coords
local upDown = true
if height >= 0x80000000 then
height = height - 0x100000000
height = -height
upDown = false
end
-- Cursor/Icon
if cMode then
height = math.floor(height / 2)
assert(planes == 1, "planes ~= 1 for cursor")
planeWB = planeWB + (monoWB * height)
end
if cMode == "mask" then
if upDown then
basePtr = basePtr + (scanWB * height)
end
bpp = 1
scanWB = monoWB
paletteCol = 0
compression = 3
elseif cMode == "colour" then
if not upDown then
basePtr = basePtr + (monoWB * height)
end
end
-- Check compression
if (compression ~= 0) and (compression ~= 3) and (compression ~= 6) then
error("compression " .. compression .. " unavailable")
end
-- paletteSize correction for comp == 0
if (bpp <= 8) and (paletteCol == 0) and (compression == 0) then
paletteCol = math.floor(2 ^ bpp)
end
return {
width = width,
height = height,
planes = planes,
bpp = bpp,
ignoresPalette = (compression ~= 0) or (paletteCol == 0) or (cMode == "mask"),
paletteCol = paletteCol,
paletteAddress = 14 + hdrSize + (packed or 1),
dataAddress = basePtr + (packed or 1),
dsFull = get32(0x22),
dsSpan = scanWB,
dsPlane = planeWB,
getPalette = function (i)
return get32(14 + hdrSize + (i * 4))
end,
setPalette = function (i, xrgb)
set32(14 + hdrSize + (i * 4), xrgb)
end,
-- Coordinates are 0-based for sanity. Returns raw colour value.
getPixel = function (x, y, p)
if upDown then
y = height - (1 + y)
end
local i = basePtr + (y * scanWB) + (p * planeWB)
return getBits(i, x * bpp, bpp)
end,
-- Uses raw colour value.
setPixel = function (x, y, p, v)
if upDown then
y = height - (1 + y)
end
local i = basePtr + (y * scanWB) + (p * planeWB)
setBits(i, x * bpp, bpp, v)
end
}
end,
-- See prepareDIB above for format.
prepareBMP = function (...)
local head, tLen, dtPtr = prepareDIB(...)
tLen = tLen + 14 -- add BM header
dtPtr = dtPtr + 14
head = "BM" .. encode32(tLen) .. "mRWH" .. encode32(dtPtr - 1) .. head
return head, tLen, dtPtr
end,
prepareDIB = prepareDIB
}