mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-27 12:58:05 +11:00
251 lines
7.0 KiB
Lua
251 lines
7.0 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 = get32(0x2E)
|
|
|
|
-- negative height means sane coords
|
|
local upDown = true
|
|
if height >= 0x80000000 then
|
|
height = height - 0x100000000
|
|
height = -height
|
|
upDown = false
|
|
end
|
|
|
|
-- 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(width / 32) * 4
|
|
local planeWB = scanWB * height
|
|
|
|
if not packed then
|
|
basePtr = get32(0x0A) -- 'BM' header
|
|
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
|
|
}
|