mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-15 06:58:05 +11:00
dcd7154ec2
This license change should only increase the amount of stuff people can do in countries where the public domain is not a valid concept. If this license somehow restricts you as opposed to the previous one, please file an issue.
255 lines
7.6 KiB
Lua
255 lines
7.6 KiB
Lua
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
|
|
--
|
|
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
|
--
|
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
-- THIS SOFTWARE.
|
|
|
|
-- 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
|
|
}
|