1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
--[[ 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
|