diff --git a/code/apps/app-klogo.lua b/code/apps/app-klogo.lua index b6fed99..3153beb 100644 --- a/code/apps/app-klogo.lua +++ b/code/apps/app-klogo.lua @@ -4,8 +4,14 @@ local event = require("event")(neo) local neoux = require("neoux")(event, neo) local braille = require("braille") +local bmp = require("bmp") local icecap = neo.requireAccess("x.neo.pub.base", "loadimg") -local qt = icecap.open("/logo.data", false) +local qt = icecap.open("/logo.bmp", false) + +local header = qt.read(bmp.headerMinSzBMP) + +local lcBase = bmp.headerMinSzBMP +local lcWidth = 1 local lc = {} local lcdq = {} @@ -18,26 +24,46 @@ for i = 1, queueSize do end local function getLine(y) if not lc[y] then - local idx = (y - 1) * 120 - qt.seek("set", idx) + local idx = y * lcWidth + qt.seek("set", lcBase + idx - 1) if lcdq[1] then lc[table.remove(lcdq, 1)] = nil end table.insert(lcdq, y) - lc[y] = qt.read(120) or "" + lc[y] = qt.read(lcWidth) end return lc[y] end +local bitmap = bmp.connect(function (i) + if i >= lcBase then + local ld = getLine(math.floor((i - lcBase) / lcWidth)) + i = ((i - lcBase) % lcWidth) + 1 + return ld:byte(i) or 0 + end + return header:byte(i) or 0 +end) + +qt.seek("set", bitmap.paletteAddress - 1) +header = header .. qt.read(bitmap.paletteCol * 4) +lcBase = bitmap.dataAddress +lcWidth = bitmap.dsSpan + local running = true -neoux.create(20, 10, nil, neoux.tcwindow(20, 10, { - braille.new(1, 1, 20, 10, { +local function decodeRGB(rgb) + return math.floor(rgb / 65536) % 256, math.floor(rgb / 256) % 256, rgb % 256 +end + +local bW, bH = math.ceil(bitmap.width / 2), math.ceil(bitmap.height / 4) +neoux.create(bW, bH, nil, neoux.tcwindow(bW, bH, { + braille.new(1, 1, bW, bH, { selectable = true, get = function (window, x, y, bg, fg, selected, colour) - local data = getLine(y) - local idx = ((x - 1) * 3) + 1 - return data:byte(idx) or 255, data:byte(idx + 1) or 0, data:byte(idx + 2) or 255 + if bitmap.ignoresPalette then + return decodeRGB(bitmap.getPixel(x - 1, y - 1, 0)) + end + return decodeRGB(bitmap.getPalette(bitmap.getPixel(x - 1, y - 1, 0))) end }, 1) }, function (w) diff --git a/code/data/app-claw/local.lua b/code/data/app-claw/local.lua index e538ae1..7e633b7 100644 --- a/code/data/app-claw/local.lua +++ b/code/data/app-claw/local.lua @@ -120,7 +120,7 @@ return { }, files = { "apps/app-klogo.lua", - "data/app-klogo/logo.data" + "data/app-klogo/logo.bmp" }, }, ["app-flash"] = { diff --git a/code/data/app-klogo/logo.bmp b/code/data/app-klogo/logo.bmp new file mode 100644 index 0000000..b926433 Binary files /dev/null and b/code/data/app-klogo/logo.bmp differ diff --git a/code/data/app-klogo/logo.data b/code/data/app-klogo/logo.data deleted file mode 100644 index 5cbc81c..0000000 Binary files a/code/data/app-klogo/logo.data and /dev/null differ diff --git a/code/libs/bmp.lua b/code/libs/bmp.lua new file mode 100644 index 0000000..8b93cd8 --- /dev/null +++ b/code/libs/bmp.lua @@ -0,0 +1,250 @@ +-- 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 +} diff --git a/code/libs/braille.lua b/code/libs/braille.lua index 9020967..3cf8527 100644 --- a/code/libs/braille.lua +++ b/code/libs/braille.lua @@ -184,7 +184,10 @@ heldRef = { drop = cbs.drop and cTransform(cbs.drop), scroll = cbs.scroll and cTransform(cbs.scroll), line = function (window, x, y, iy, bg, fg, selected) - local colour = ((window.getDepth() <= 1) or nil) and colour + local colour = colour + if window.getDepth() <= 1 then + colour = nil + end calcLine(x, y, control.w, window.span, function (xb, yb) return cbs.get(window, xb + 1, yb + (iy * 4) - 3, bg, fg, selected, colour) end, colour) diff --git a/repository/data/app-claw/local.lua b/repository/data/app-claw/local.lua index 68caf78..5a36f2d 100644 --- a/repository/data/app-claw/local.lua +++ b/repository/data/app-claw/local.lua @@ -38,7 +38,8 @@ return { "docs/ul-event", "docs/ul-fmttx", "docs/ul-neoux", - "docs/ul-broil", + "docs/ul-brail", + "docs/ul-bmp__", "docs/gp-pedan" }, } diff --git a/repository/docs/ul-bmp__ b/repository/docs/ul-bmp__ new file mode 100644 index 0000000..fe8c079 --- /dev/null +++ b/repository/docs/ul-bmp__ @@ -0,0 +1,248 @@ +The "bmp" library is a library for + the express purpose of reading and + writing Windows BMP files and other + Windows DIB headers. + +It is written with portability, + memory-efficiency, and reusability + as it's primary goals. + +With this in mind, it can be used as + the backend for an ICO/CUR handling + library, works with packed-DIB data, + only requires the first 0x36 bytes + of a .BMP to be present, and can be + used on KittenOS NEO, OpenOS, and + just about any place with a desktop + Lua interpreter available for use. + +(That said, it's not 5.1-tested.) + +That said, it does not handle all the + possible bitmap formats, restricting + itself to support of BI_RGB where + possible, and BI_BITFIELDS to a + limited extent. + +Given this covers most bitmaps that + you are likely to generate, do not + underestimate the use of this. + +The library has 2 fields: + headerMinSzBMP, + headerMinSzDIB: The minimum data you + must have available when calling + connect for a given format. + +The library has 3 functions: + + connect(get, set, cMode, packed): + "Connects" to a bitmap. A very real + implication of this is that the + data get/set accesses is where the + actual bitmap is, and this merely + provides a way to access it in a + useful form (as pixels). + Technically, all but 'get' are + optional, but I feel it necessary + to document them as if they are + implicitly left nil by those users + who omit them because of their + functionality. + Due to this section being a rather + long length, the paragraph + separation is per-argument here. + + get is best described by: + function (a) return dt:byte(a) end + + set, which is optional unless you + call any of the set* functions in + the resulting bitmap, has a longer + description: + function (a, v) + dt = dt:sub(1, a - 1) .. + string.char(v) .. + dt:sub(a + 1) + end + Notably, while these are the + canonical implementations of these + functions, they are by no means + the only implementations, and it + is the purpose of these callbacks + to allow choice in how you write + them. + + cMode exists for cursor and icon + handling. These images are split + via a very horrifying mechanism + into an AND mask, and a XOR mask. + The XOR mask typically contains + what could be called the actual + image - the AND mask then acts + as a sort of alpha mask, blacking + out the areas where the XOR mask + is then projected onto. + It can be one of three values. + It can be nil, in which case this + is an ordinary DIB, with no evil + nonsense applied to it - + It can be "colour", in which case + the XOR mask (the actual image in + most cases) is shown - + And it can be "mask", in which case + the AND mask is accessed instead. + (Notably, bpp, paletteSize, and + ignoresPalette changes to the + 1bpp mask format in use here.) + + packed exists for the use of those + bitmaps that don't have a BMP file + header, the usual "BM". In this + case, you can remove support for + gap1 but allow yourself to avoid + the BM header as a result. + If not nil, it is how much to + offset the indexes from 0-based + offsets into a .BMP file to the + final positions. + The following table is useful for + understanding this: + 1: Standard 1-indexed BMP handling, + but with no gap1 support (!) + -13: Standard 1-indexed handling of + a packed DIB (no BMP header), + such as a BITMAP or ICON + resource (set cMode if ICON) + -9: Standard 1-indexed handling of + a CURSOR resource, which has + a 2-short hotspot header + + prepareDIB(w, h, p, bpp, paletteSize + , topDown, cMode) -> hd, sz, dp: + (See prepareBMP if you want a .BMP + file, but this describes how to + use the arguments and returns.) + This prepares a packed DIB, and + returns three values: + 1. The header. (The palette, in + the 4-byte-per-colour form, + will follow immediately.) + 2. The buffer size, including the + header in 1. The bytes that + are not specified can be + initialized in any fashion, + as they are part of the colour + and later image data itself. + 3. The pointer to the pixels for + BM creation. + + w, h, p, and bpp are the usual - + width, height, planes, pixel depth + - but the paletteSize needs note. + If the BPP is <= 8, then the size + of the palette must not be 0. + This is to avoid unintentionally + triggering legacy features in the + BMP format. + + topDown and cMode are essentially + booleans that can safely be nil, + where nil is interpreted as false. + + topDown indicates that the image is + to be top-down rather than the + standard upsidedown form of a BMP. + This may look odd if streaming a + BMP, so the option, while rather + an odd one, has been included. + + cMode indicates that this image is + intended for use in an ICON or + CURSOR resource of some form, and + should thus contain an AND mask. + + prepareBMP(...): + This prepares a .BMP file. It has + the same arguments and returns as + prepareDIB, and indeed wraps it. + +Bitmap objects, while not read-only, + do not particularly change if you + write to them. + +Going out of bounds with a bitmap + object will have hilarious results. + +...Don't do it. + +Anyway, they have these fields: + + width, height, planes: The usuals. + (NOTE: Due to lack of examples, + it is assumed that planes are a + dimension more major than height. + If this isn't the case, someone + do tell me, preferably with an + example and a way to open it that + does not involve this library, so + I can test and correct all of the + code involving them.) + bpp: Bits per pixel. This specifies + a limit on numbers passed to + and from the bitmap object, + of 1 << bpp (2 ^ bpp). + A number may not be equal to + or exceed this limit. + ignoresPalette: Used to indicate + the palette is worthless and does + not affect image contents in any + way at all. If one exists. + paletteCol: The amount of colours in + the palette. Multiply by 4 for a + byte count. + paletteAddress: Useful for caching, + the palette starts at this get/set + address. + dataAddress: Useful for caching, + the data starts at this get/set + address. + dataFull: The size of the data of + the image, according to the image. + dsSpan: This is a hint for the cache + and cannot be relied upon to be + correct, only >= 1: + Scanline length in bytes. + dsPlane: This is a hint for the + cache and cannot be relied upon to + be correct, only >= 1: + Plane length in bytes. + getPalette(i): Gets the XRGB value + of colour i as an integer. + Do not go out of range of I. + setPalette(i, v): Sets the XRGB + value of colour i as an integer. + Do not go out of range of I or V. + getPixel(x, y, p): Returns the pixel + value at X, Y, P. Do not go out of + the range of X, Y or P. + setPixel(x, y, p, v): Sets the pixel + value at X, Y, P to V. Do not go + out of the range of X, Y, P, or V. + +...in other words, about as much + usability as a BufferedImage with + getGraphics and createGraphics taken + out of it for some crazy reason. + +The primary use of this library is + because people like to use formats + they happen to have actually heard + of before, and everything but BMP is + too complicated for low-memory OSes + to stream from disk. + +-- This is released into + the public domain. +-- No warranty is provided, + implied or otherwise. diff --git a/repository/docs/ul-broil b/repository/docs/ul-brail similarity index 100% rename from repository/docs/ul-broil rename to repository/docs/ul-brail