summaryrefslogtreecommitdiff
path: root/apps/plugins/lua/include_lua/image.lua
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/lua/include_lua/image.lua')
-rw-r--r--apps/plugins/lua/include_lua/image.lua392
1 files changed, 392 insertions, 0 deletions
diff --git a/apps/plugins/lua/include_lua/image.lua b/apps/plugins/lua/include_lua/image.lua
new file mode 100644
index 0000000000..c30e8ab556
--- /dev/null
+++ b/apps/plugins/lua/include_lua/image.lua
@@ -0,0 +1,392 @@
1--[[ Lua Image functions
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
24--[[ Exposed Functions
25
26 _img.save
27 _img.search
28 _img.rotate
29 _img.resize
30 _img.tile
31 _img.new
32 _img.load
33
34-- Exposed Constants
35 _img.RLI_INFO_ALL
36 _img.RLI_INFO_TYPE
37 _img.RLI_INFO_WIDTH
38 _img.RLI_INFO_HEIGHT
39 _img.RLI_INFO_ELEMS
40 _img.RLI_INFO_BYTES
41 _img.RLI_INFO_DEPTH
42 _img.RLI_INFO_FORMAT
43 _img.RLI_INFO_ADDRESS
44
45]]
46
47--[[Other rbimage Functions:
48--------------------------------------------------------------------------------
49img:_len() or #img -- returns number of pixels in image
50
51img:__tostring([item]) or tostring(img) -- returns data about the image item = 0
52 is the same as tostring(img) otherwise
53 item = 1 is the first item in list
54 item = 7 is the 7th item
55 item = 8 is the data address in hex
56 -- See Constants _img.RLI_INFO_....
57
58img:_data(element) -- returns/sets raw pixel data
59 NOTE!! this data is defined by the target and targets with
60 different color depth, bit packing, etc will not be
61 compatible with the same image's data on another target
62]]
63--------------------------------------------------------------------------------
64if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
65
66local _img = {} do
67
68 -- internal constants
69 local _NIL = nil -- _NIL placeholder
70 local _math = require("math_ex") -- math functions needed
71 local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
72
73 -- returns new image -of- img sized to fit w/h tiling to fit if needed
74 local function tile(img, w, h)
75 local hs , ws = img:height(), img:width()
76 local t_img = rb.new_image(w, h)
77
78 for x = 1, w, ws do t_img:copy(img, x, 1, 1, 1) end
79 for y = hs, h, hs do t_img:copy(t_img, 1, y, 1, 1, w, hs) end
80 return t_img
81 end
82
83 -- resizes src to size of dst
84 local function resize(dst, src)
85 -- simple nearest neighbor resize derived from rockbox - pluginlib_bmp.c
86 -- pretty rough results highly recommend building one more suited..
87 local dw, dh = dst:width(), dst:height()
88
89 local xstep = (bit.lshift(src:width(),8) / (dw)) + 1
90 local ystep = (bit.lshift(src:height(),8) / (dh))
91
92 local xpos, ypos = 0, 0
93 local src_x, src_y
94
95 -- walk the dest get src pixel
96 function rsz_trans(val, x, y)
97 if x == 1 then
98 src_y = bit.rshift(ypos,8) + 1
99 xpos = xstep - bit.rshift(xstep,4) + 1
100 ypos = ypos + ystep;
101 end
102 src_x = bit.rshift(xpos,8) + 1
103 xpos = xpos + xstep
104 return (src:get(src_x, src_y, true) or 0)
105 end
106 --/* (dst*, [x1, y1, x2, y2, dx, dy, clip, function]) */
107 dst:marshal(1, 1, dw, dh, _NIL, _NIL, false, rsz_trans)
108 end
109
110 -- returns new image -of- img rotated in whole degrees 0 - 360
111 local function rotate(img, degrees)
112 -- we do this backwards as if dest was the unrotated object
113 degrees = 360 - degrees
114 local c, s = _math.d_cos(degrees), _math.d_sin(degrees)
115
116 -- get the center of the source image
117 local s_xctr, s_yctr = img:width() / 2, img:height() / 2
118
119 -- get the the new center of the dest image at rotation angle
120 local d_xctr = ((math.abs(s_xctr * c) + math.abs(s_yctr * s))/ 10000) + 1
121 local d_yctr = ((math.abs(s_xctr * s) + math.abs(s_yctr * c))/ 10000) + 1
122
123 -- calculate size of rect new image will occupy
124 local dw, dh = d_xctr * 2 - 1, d_yctr * 2 - 1
125
126 local r_img = rb.new_image(dw, dh)
127 -- r_img:clear() -- doesn't need cleared as we walk every pixel
128
129 --[[rotation works on origin of 0,0 we need to offset to the center of the
130 image and then place the upper left back at the origin (0, 0)]]
131 --[[0,0|-----| ^< |-------| v> 0,0|-------|
132 | | | 0,0 | | |
133 |_____| |_______| |_______| ]]
134
135 -- walk the dest get translated src pixel, oversamples src to fill gaps
136 function rot_trans(val, x, y)
137 -- move center x/y to the origin
138 local xtran = x - d_xctr;
139 local ytran = y - d_yctr;
140
141 -- rotate about the center of the image by x degrees
142 local yrot = ((xtran * s) + (ytran * c)) / 10000 + s_yctr
143 local xrot = ((xtran * c) - (ytran * s)) / 10000 + s_xctr
144 -- upper left of src image back to origin, copy src pixel
145 return img:get(xrot, yrot, true) or 0
146 end
147 r_img:marshal(1, 1, dw, dh, _NIL, _NIL, false, rot_trans)
148 return r_img
149 end
150
151 -- saves img to file: name
152 local function save(img, name)
153 -- bmp saving derived from rockbox - screendump.c
154 -- bitdepth is limited by the device
155 -- eg. device displays greyscale, rgb images are saved greyscale
156 local file
157
158 local fbuffer = {} -- concat buffer for file writes, reused
159
160 local function dump_fbuffer(thresh)
161 if #fbuffer >= thresh then
162 file:write(table.concat(fbuffer))
163 for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table
164 end
165 end
166
167 local function s_bytesLE(bits, value)
168 -- bits must be multiples of 8 (sizeof byte)
169 local byte
170 local result = ""
171 for b = 1, bit.rshift(bits, 3) do
172 if value > 0 then
173 byte = value % 256
174 value = (value - byte) / 256
175 result = result .. string.char(byte)
176 else
177 result = result .. string.char(0)
178 end
179 end
180 return result
181 end
182
183 local function s_bytesBE(bits, value)
184 -- bits must be multiples of 8 (sizeof byte)
185 local byte
186 local result = ""
187 for b = 1, bit.rshift(bits, 3) do
188 if value > 0 then
189 byte = value % 256
190 value = (value - byte) / 256
191 result = string.char(byte) .. result
192 else
193 result = string.char(0) .. result
194 end
195 end
196 return result
197 end
198
199 local function c_cmp(color, shift)
200 -- [RR][GG][BB]
201 return bit.band(bit.rshift(color, shift), 0xFF)
202 end
203
204 local cmp = {["r"] = function(c) return c_cmp(c, 16) end,
205 ["g"] = function(c) return c_cmp(c, 08) end,
206 ["b"] = function(c) return c_cmp(c, 00) end}
207
208 local function bmp_color(color)
209 return s_bytesLE(8, cmp.b(color))..
210 s_bytesLE(8, cmp.g(color))..
211 s_bytesLE(8, cmp.r(color))..
212 s_bytesLE(8, 0) .. ""
213 end -- c_cmp(color, c.r))
214
215 local function bmp_color_mix(c1, c2, num, den)
216 -- mixes c1 and c2 as ratio of numerator / denominator
217 -- used 2x each save results
218 local bc1, gc1, rc1 = cmp.b(c1), cmp.g(c1), cmp.r(c1)
219
220 return s_bytesLE(8, cmp.b(c2) - bc1 * num / den + bc1)..
221 s_bytesLE(8, cmp.g(c2) - gc1 * num / den + gc1)..
222 s_bytesLE(8, cmp.r(c2) - rc1 * num / den + rc1)..
223 s_bytesLE(8, 0) .. ""
224 end
225
226 local w, h = img:width(), img:height()
227 local depth = tonumber(img:__tostring(6)) -- RLI_INFO_DEPTH = 0x6
228 local format = tonumber(img:__tostring(7)) -- RLI_INFO_FORMAT = 0x7
229
230 local bpp, bypl -- bits per pixel, bytes per line
231 -- bypl, pad rows to a multiple of 4 bytes
232 if depth <= 4 then
233 bpp = 8 -- 256 color image
234 bypl = (w + 3)
235 elseif depth <= 16 then
236 bpp = 16
237 bypl = (w * 2 + 3)
238 else
239 bpp = 24
240 bypl = (w * 3 + 3)
241 end
242
243 local linebytes = bit.band(bypl, bit.bnot(3))
244
245 local bytesperpixel = bit.rshift(bpp, 3)
246 local headersz = 54
247 local imgszpad = h * linebytes
248
249 local compression, n_colors = 0, 0
250 local h_ppm, v_ppm = 0x00000EC4, 0x00000EC4 --Pixels Per Meter ~ 96 dpi
251
252 if depth == 16 then
253 compression = 3 -- BITFIELDS
254 n_colors = 3
255 elseif depth <= 8 then
256 n_colors = bit.lshift(1, depth)
257 end
258
259 headersz = headersz + (4 * n_colors)
260
261 file = io.open('/' .. name, "w+") -- overwrite, rb ignores the 'b' flag
262
263 if not file then
264 rb.splash(rb.HZ, "Error opening /" .. name)
265 return
266 end
267 -- create a bitmap header 'rope' with image details -- concatenated at end
268 local bmpheader = fbuffer
269
270 bmpheader[01] = "BM"
271 bmpheader[02] = s_bytesLE(32, headersz + imgszpad)
272 bmpheader[03] = "\0\0\0\0" -- WORD reserved 1 & 2
273 bmpheader[04] = s_bytesLE(32, headersz) -- BITMAPCOREHEADER size
274 bmpheader[05] = s_bytesLE(32, 40) -- BITMAPINFOHEADER size
275
276 bmpheader[06] = s_bytesLE(32, w)
277 bmpheader[07] = s_bytesLE(32, h)
278 bmpheader[08] = "\1\0" -- WORD color planes ALWAYS 1
279 bmpheader[09] = s_bytesLE(16, bpp) -- bits/pixel
280 bmpheader[10] = s_bytesLE(32, compression)
281 bmpheader[11] = s_bytesLE(32, imgszpad)
282 bmpheader[12] = s_bytesLE(32, h_ppm) -- biXPelsPerMeter
283 bmpheader[13] = s_bytesLE(32, v_ppm) -- biYPelsPerMeter
284 bmpheader[14] = s_bytesLE(32, n_colors)
285 bmpheader[15] = s_bytesLE(32, n_colors)
286
287 -- Color Table (#n_colors entries)
288 if depth == 1 then -- assuming positive display
289 bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
290 bmpheader[#bmpheader + 1] = bmp_color(0x0)
291 elseif depth == 2 then
292 bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
293 bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 1, 3)
294 bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 2, 3)
295 bmpheader[#bmpheader + 1] = bmp_color(0x0)
296 elseif depth == 16 then
297 -- red bitfield mask
298 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000F800)
299 -- green bitfield mask
300 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000007E0)
301 -- blue bitfield mask
302 bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
303 end
304
305 dump_fbuffer(0) -- write the header to the file now
306
307 local imgdata = fbuffer
308 -- pad rows to a multiple of 4 bytes
309 local bytesleft = linebytes - (bytesperpixel * w)
310 local t_data = {}
311 local fs_bytes_E = s_bytesLE -- default save in Little Endian
312
313 if format == 3553 then -- RGB565SWAPPED
314 fs_bytes_E = s_bytesBE -- Saves in Big Endian
315 end
316
317 -- Bitmap lines start at bottom unless biHeight is negative
318 for point in img:points(1, h, w + bytesleft, 1) do
319 imgdata[#imgdata + 1] = fs_bytes_E(bpp, point or 0)
320 dump_fbuffer(31) -- buffered write, increase # for performance
321 end
322
323 dump_fbuffer(0) --write leftovers to file
324
325 file:close()
326 end -- save(img, name)
327
328 --searches an image for target color
329 local function search(img, x1, y1, x2, y2, targetclr, variation, stepx, stepy)
330
331 if variation > 128 then variation = 128 end
332 if variation < -128 then variation = -128 end
333
334 local targeth = targetclr + variation
335 local targetl = targetclr - variation
336
337 if targeth < targetl then
338 local swap = targeth
339 targeth = targetl
340 targetl = swap
341 end
342
343 for point, x, y in img:points(x1, y1, x2, y2, stepx, stepy) do
344 if point >= targetl and point <= targeth then
345 return point, x, y
346 end
347 end
348 return nil, nil, nil
349 end
350
351 --[[ we won't be extending these into RLI_IMAGE]]
352 -- creates a new rbimage size w x h
353 local function new(w, h)
354 return rb.new_image(w, h)
355 end
356
357 -- returns new image -of- file: name (_NIL if error)
358 local function load(name)
359 return rb.read_bmp_file("/" .. name)
360 end
361
362 -- expose tostring constants to outside through _img table
363 _img.RLI_INFO_ALL = 0x0
364 _img.RLI_INFO_TYPE = 0x1
365 _img.RLI_INFO_WIDTH = 0x2
366 _img.RLI_INFO_HEIGHT = 0x3
367 _img.RLI_INFO_ELEMS = 0x4
368 _img.RLI_INFO_BYTES = 0x5
369 _img.RLI_INFO_DEPTH = 0x6
370 _img.RLI_INFO_FORMAT = 0x7
371 _img.RLI_INFO_ADDRESS = 0x8
372
373 -- expose functions to the outside through _img table
374 _img.save = save
375 _img.search = search
376 _img.rotate = rotate
377 _img.resize = resize
378 _img.tile = tile
379
380 -- adds the above _img functions into the metatable for RLI_IMAGE
381 local ex = getmetatable(rb.lcd_framebuffer())
382 for k, v in pairs(_img) do
383 if ex[k] == _NIL then ex[k] = v end
384 end
385 -- not exposed through RLI_IMAGE
386 _img.new = new
387 _img.load = load
388
389end -- _img functions
390
391return _img
392