diff options
Diffstat (limited to 'apps/plugins/lua/include_lua/image_save.lua')
-rw-r--r-- | apps/plugins/lua/include_lua/image_save.lua | 215 |
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 | ||
27 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
28 | |||
29 | do | ||
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) | ||
215 | end | ||