summaryrefslogtreecommitdiff
path: root/apps/plugins/lua/include_lua/image_save.lua
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/lua/include_lua/image_save.lua')
-rw-r--r--apps/plugins/lua/include_lua/image_save.lua215
1 files changed, 215 insertions, 0 deletions
diff --git a/apps/plugins/lua/include_lua/image_save.lua b/apps/plugins/lua/include_lua/image_save.lua
new file mode 100644
index 0000000000..4735af46d7
--- /dev/null
+++ b/apps/plugins/lua/include_lua/image_save.lua
@@ -0,0 +1,215 @@
1--[[ Lua Image save
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23-- save(img, path/name)
24-- bmp saving derived from rockbox - screendump.c
25-- bitdepth is limited by the device
26-- eg. device displays greyscale, rgb images are saved greyscale
27if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
28
29do
30 local rocklib_image = getmetatable(rb.lcd_framebuffer())
31
32 -- internal constants
33 local _NIL = nil -- _NIL placeholder
34 local _points = rocklib_image.points
35
36 -- saves img to file: name
37 return function(img, name)
38 local file
39 local bbuffer = {} -- concat buffer for s_bytes
40 local fbuffer = {} -- concat buffer for file writes, reused
41
42 local function s_bytesLE(bits, value)
43 -- bits must be multiples of 8 (sizeof byte)
44 local byte
45 local nbytes = bit.rshift(bits, 3)
46 for b = 1, nbytes do
47 if value > 0 then
48 byte = value % 256
49 value = (value - byte) / 256
50 else
51 byte = 0
52 end
53 bbuffer[b] = string.char(byte)
54 end
55 return table.concat(bbuffer, _NIL, 1, nbytes)
56 end
57
58 local function s_bytesBE(bits, value)
59 -- bits must be multiples of 8 (sizeof byte)
60 local byte
61 local nbytes = bit.rshift(bits, 3)
62 for b = nbytes, 1, -1 do
63 if value > 0 then
64 byte = value % 256
65 value = (value - byte) / 256
66 else
67 byte = 0
68 end
69 bbuffer[b] = string.char(byte)
70 end
71 return table.concat(bbuffer, _NIL, 1, nbytes)
72 end
73
74 local cmp = {["r"] = function(c) return bit.band(bit.rshift(c, 16), 0xFF) end,
75 ["g"] = function(c) return bit.band(bit.rshift(c, 08), 0xFF) end,
76 ["b"] = function(c) return bit.band(c, 0xFF) end}
77
78 local function bmp_color(color)
79 return s_bytesLE(8, cmp.b(color))..
80 s_bytesLE(8, cmp.g(color))..
81 s_bytesLE(8, cmp.r(color))..
82 s_bytesLE(8, 0) .. ""
83 end -- c_cmp(color, c.r))
84
85 local function bmp_color_mix(c1, c2, num, den)
86 -- mixes c1 and c2 as ratio of numerator / denominator
87 -- used 2x each save results
88 local bc1, gc1, rc1 = cmp.b(c1), cmp.g(c1), cmp.r(c1)
89
90 return s_bytesLE(8, cmp.b(c2) - bc1 * num / den + bc1)..
91 s_bytesLE(8, cmp.g(c2) - gc1 * num / den + gc1)..
92 s_bytesLE(8, cmp.r(c2) - rc1 * num / den + rc1)..
93 s_bytesLE(8, 0) .. ""
94 end
95
96 local w, h = img:width(), img:height()
97 local depth = tonumber(img:__tostring(6)) -- RLI_INFO_DEPTH = 0x6
98 local format = tonumber(img:__tostring(7)) -- RLI_INFO_FORMAT = 0x7
99
100 local bpp, bypl -- bits per pixel, bytes per line
101 -- bypl, pad rows to a multiple of 4 bytes
102 if depth <= 4 then
103 bpp = 8 -- 256 color image
104 bypl = (w + 3)
105 elseif depth <= 16 then
106 bpp = 16
107 bypl = (w * 2 + 3)
108 elseif depth <= 24 then
109 bpp = 24
110 bypl = (w * 3 + 3)
111 else
112 bpp = 32
113 bypl = (w * 4 + 3)
114 end
115
116 local linebytes = bit.band(bypl, bit.bnot(3))
117
118 local bytesperpixel = bit.rshift(bpp, 3)
119 local headersz = 54
120 local imgszpad = h * linebytes
121
122 local compression, n_colors = 0, 0
123 local h_ppm, v_ppm = 0x00000EC4, 0x00000EC4 --Pixels Per Meter ~ 96 dpi
124
125 if depth == 16 then
126 compression = 3 -- BITFIELDS
127 n_colors = 3
128 elseif depth <= 8 then
129 n_colors = bit.lshift(1, depth)
130 end
131
132 headersz = headersz + (4 * n_colors)
133
134 file = io.open('/' .. name, "w+") -- overwrite, rb ignores the 'b' flag
135
136 if not file then
137 rb.splash(rb.HZ, "Error opening /" .. name)
138 return
139 end
140 -- create a bitmap header 'rope' with image details -- concatenated at end
141 local bmpheader = fbuffer
142
143 bmpheader[01] = "BM"
144 bmpheader[02] = s_bytesLE(32, headersz + imgszpad)
145 bmpheader[03] = "\0\0\0\0" -- WORD reserved 1 & 2
146 bmpheader[04] = s_bytesLE(32, headersz) -- BITMAPCOREHEADER size
147 bmpheader[05] = s_bytesLE(32, 40) -- BITMAPINFOHEADER size
148
149 bmpheader[06] = s_bytesLE(32, w)
150 bmpheader[07] = s_bytesLE(32, h)
151 bmpheader[08] = "\1\0" -- WORD color planes ALWAYS 1
152 bmpheader[09] = s_bytesLE(16, bpp) -- bits/pixel
153 bmpheader[10] = s_bytesLE(32, compression)
154 bmpheader[11] = s_bytesLE(32, imgszpad)
155 bmpheader[12] = s_bytesLE(32, h_ppm) -- biXPelsPerMeter
156 bmpheader[13] = s_bytesLE(32, v_ppm) -- biYPelsPerMeter
157 bmpheader[14] = s_bytesLE(32, n_colors)
158 bmpheader[15] = s_bytesLE(32, n_colors)
159
160 -- Color Table (#n_colors entries)
161 if depth == 1 then -- assuming positive display
162 bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
163 bmpheader[#bmpheader + 1] = bmp_color(0x0)
164 elseif depth == 2 then
165 bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
166 bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 1, 3)
167 bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 2, 3)
168 bmpheader[#bmpheader + 1] = bmp_color(0x0)
169 elseif depth == 16 then
170 if format == 555 then
171 -- red bitfield mask
172 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x00007C00)
173 -- green bitfield mask
174 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000003E0)
175 -- blue bitfield mask
176 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
177 else --565
178 -- red bitfield mask
179 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000F800)
180 -- green bitfield mask
181 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000007E0)
182 -- blue bitfield mask
183 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
184 end
185 end
186
187 file:write(table.concat(fbuffer))-- write the header to the file now
188 for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table
189
190 local imgdata = fbuffer
191 -- pad rows to a multiple of 4 bytes
192 local bytesleft = linebytes - (bytesperpixel * w)
193 local t_data = {}
194 local fs_bytes_E = s_bytesLE -- default save in Little Endian
195
196 if format == 3553 then -- RGB565SWAPPED
197 fs_bytes_E = s_bytesBE -- Saves in Big Endian
198 end
199
200 -- Bitmap lines start at bottom unless biHeight is negative
201 for point in _points(img, 1, h, w + bytesleft, 1) do
202 imgdata[#imgdata + 1] = fs_bytes_E(bpp, point or 0)
203
204 if #fbuffer >= 31 then -- buffered write, increase # for performance
205 file:write(table.concat(fbuffer))
206 for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table
207 end
208
209 end
210 file:write(table.concat(fbuffer)) --write leftovers to file
211 fbuffer = _NIL
212
213 file:close()
214 end -- save(img, name)
215end