--[[ Lua Image save /*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2017 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ ]] -- save(img, path/name) -- bmp saving derived from rockbox - screendump.c -- bitdepth is limited by the device -- eg. device displays greyscale, rgb images are saved greyscale if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end do local rocklib_image = getmetatable(rb.lcd_framebuffer()) -- internal constants local _NIL = nil -- _NIL placeholder local _points = rocklib_image.points -- saves img to file: name return function(img, name) local file local bbuffer = {} -- concat buffer for s_bytes local fbuffer = {} -- concat buffer for file writes, reused local function s_bytesLE(bits, value) -- bits must be multiples of 8 (sizeof byte) local byte local nbytes = bit.rshift(bits, 3) for b = 1, nbytes do if value > 0 then byte = value % 256 value = (value - byte) / 256 else byte = 0 end bbuffer[b] = string.char(byte) end return table.concat(bbuffer, _NIL, 1, nbytes) end local function s_bytesBE(bits, value) -- bits must be multiples of 8 (sizeof byte) local byte local nbytes = bit.rshift(bits, 3) for b = nbytes, 1, -1 do if value > 0 then byte = value % 256 value = (value - byte) / 256 else byte = 0 end bbuffer[b] = string.char(byte) end return table.concat(bbuffer, _NIL, 1, nbytes) end local cmp = {["r"] = function(c) return bit.band(bit.rshift(c, 16), 0xFF) end, ["g"] = function(c) return bit.band(bit.rshift(c, 08), 0xFF) end, ["b"] = function(c) return bit.band(c, 0xFF) end} local function bmp_color(color) return s_bytesLE(8, cmp.b(color)).. s_bytesLE(8, cmp.g(color)).. s_bytesLE(8, cmp.r(color)).. s_bytesLE(8, 0) .. "" end -- c_cmp(color, c.r)) local function bmp_color_mix(c1, c2, num, den) -- mixes c1 and c2 as ratio of numerator / denominator -- used 2x each save results local bc1, gc1, rc1 = cmp.b(c1), cmp.g(c1), cmp.r(c1) return s_bytesLE(8, cmp.b(c2) - bc1 * num / den + bc1).. s_bytesLE(8, cmp.g(c2) - gc1 * num / den + gc1).. s_bytesLE(8, cmp.r(c2) - rc1 * num / den + rc1).. s_bytesLE(8, 0) .. "" end local w, h = img:width(), img:height() local depth = tonumber(img:__tostring(6)) -- RLI_INFO_DEPTH = 0x6 local format = tonumber(img:__tostring(7)) -- RLI_INFO_FORMAT = 0x7 local bpp, bypl -- bits per pixel, bytes per line -- bypl, pad rows to a multiple of 4 bytes if depth <= 4 then bpp = 8 -- 256 color image bypl = (w + 3) elseif depth <= 16 then bpp = 16 bypl = (w * 2 + 3) elseif depth <= 24 then bpp = 24 bypl = (w * 3 + 3) else bpp = 32 bypl = (w * 4 + 3) end local linebytes = bit.band(bypl, bit.bnot(3)) local bytesperpixel = bit.rshift(bpp, 3) local headersz = 54 local imgszpad = h * linebytes local compression, n_colors = 0, 0 local h_ppm, v_ppm = 0x00000EC4, 0x00000EC4 --Pixels Per Meter ~ 96 dpi if depth == 16 then compression = 3 -- BITFIELDS n_colors = 3 elseif depth <= 8 then n_colors = bit.lshift(1, depth) end headersz = headersz + (4 * n_colors) file = io.open('/' .. name, "w+") -- overwrite, rb ignores the 'b' flag if not file then rb.splash(rb.HZ, "Error opening /" .. name) return end -- create a bitmap header 'rope' with image details -- concatenated at end local bmpheader = fbuffer bmpheader[01] = "BM" bmpheader[02] = s_bytesLE(32, headersz + imgszpad) bmpheader[03] = "\0\0\0\0" -- WORD reserved 1 & 2 bmpheader[04] = s_bytesLE(32, headersz) -- BITMAPCOREHEADER size bmpheader[05] = s_bytesLE(32, 40) -- BITMAPINFOHEADER size bmpheader[06] = s_bytesLE(32, w) bmpheader[07] = s_bytesLE(32, h) bmpheader[08] = "\1\0" -- WORD color planes ALWAYS 1 bmpheader[09] = s_bytesLE(16, bpp) -- bits/pixel bmpheader[10] = s_bytesLE(32, compression) bmpheader[11] = s_bytesLE(32, imgszpad) bmpheader[12] = s_bytesLE(32, h_ppm) -- biXPelsPerMeter bmpheader[13] = s_bytesLE(32, v_ppm) -- biYPelsPerMeter bmpheader[14] = s_bytesLE(32, n_colors) bmpheader[15] = s_bytesLE(32, n_colors) -- Color Table (#n_colors entries) if depth == 1 then -- assuming positive display bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF) bmpheader[#bmpheader + 1] = bmp_color(0x0) elseif depth == 2 then bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF) bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 1, 3) bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 2, 3) bmpheader[#bmpheader + 1] = bmp_color(0x0) elseif depth == 16 then if format == 555 then -- red bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x00007C00) -- green bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000003E0) -- blue bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F) else --565 -- red bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000F800) -- green bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000007E0) -- blue bitfield mask bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F) end end file:write(table.concat(fbuffer))-- write the header to the file now for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table local imgdata = fbuffer -- pad rows to a multiple of 4 bytes local bytesleft = linebytes - (bytesperpixel * w) local t_data = {} local fs_bytes_E = s_bytesLE -- default save in Little Endian if format == 3553 then -- RGB565SWAPPED fs_bytes_E = s_bytesBE -- Saves in Big Endian end -- Bitmap lines start at bottom unless biHeight is negative for point in _points(img, 1, h, w + bytesleft, 1) do imgdata[#imgdata + 1] = fs_bytes_E(bpp, point or 0) if #fbuffer >= 31 then -- buffered write, increase # for performance file:write(table.concat(fbuffer)) for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table end end file:write(table.concat(fbuffer)) --write leftovers to file fbuffer = _NIL file:close() end -- save(img, name) end