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