rockbox/apps/plugins/lua/include_lua/image_save.lua

216 lines
8.2 KiB
Lua
Raw Normal View History

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