Added bmp.lua and a logo that's more detailed yet smaller than the current one.

Also, the logo is now stored as a BMP.
Turnaround time on that thing is one click,
 we're nowhere near done reaping the benefits of indexed colour, etc.
This commit is contained in:
20kdc 2018-04-07 02:29:15 +01:00
parent c5b58e79ff
commit 899a3b2521
9 changed files with 540 additions and 12 deletions

View File

@ -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)

View File

@ -120,7 +120,7 @@ return {
},
files = {
"apps/app-klogo.lua",
"data/app-klogo/logo.data"
"data/app-klogo/logo.bmp"
},
},
["app-flash"] = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

250
code/libs/bmp.lua Normal file
View File

@ -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
}

View File

@ -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)

View File

@ -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"
},
}

248
repository/docs/ul-bmp__ Normal file
View File

@ -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.