diff options
author | William Wilgus <me.theuser@yahoo.com> | 2018-07-23 02:50:22 +0200 |
---|---|---|
committer | William Wilgus <me.theuser@yahoo.com> | 2018-07-23 05:13:32 +0200 |
commit | af35d1991633f33965801dcb153a9860c4432816 (patch) | |
tree | b6accfcf2914ff7f565d60b9338dfa7998e27c68 /apps/plugins/lua/include_lua/image.lua | |
parent | ef210b5fe4f72a102dcaa1f752d3022d62da8bc4 (diff) | |
download | rockbox-af35d1991633f33965801dcb153a9860c4432816.tar.gz rockbox-af35d1991633f33965801dcb153a9860c4432816.zip |
Rocklua -- Extend / Fix rliImage
Some devices(1-bit / 2-bit displays) have packed bit formats that
need to be unpacked in order to work on them at a pixel level.
This caused a few issues on 1 & 2-bit devices:
Greatly Oversized data arrays for bitmaps
Improper handling of native image data
Framebuffer data was near unusable without jumping through hoops
Conversion between native addressing and per pixel addressing
incurs extra overhead but it is much faster to do it
on the 'C' side rather than in lua.
Not to mention the advantage of a unified interface for the end programer
-------------------------------------------------------------------
Adds a sane way to access each pixel of image data
Adds:
--------------------------------------------------------------------
img:clear([color],[x1],[y1],[x2],[y2])
(set whole image or a portion to a particular value)
--------------------------------------------------------------------
img:invert([x1],[y1],[x2],[y2])
(inverts whole image or a portion)
--------------------------------------------------------------------
img:marshal([x1],[y1],[x2],[y2],[funct])
(calls funct for each point defined by rect of x1,y1 x2,y2
returns value and allows setting value of each point return
nil to terminate early)
--------------------------------------------------------------------
img:points([x1],[y1],[x2],[y2],[dx],[dy])
(returns iterator function that steps delta-x and delta-y pixels each call
returns value of pixel each call but doesn't allow setting to a new value
compare to lua pairs method)
--------------------------------------------------------------------
img:copy(src,[x1],[y1],[x2],[y2],[w],[h],[clip][operation][clr/funct])
(copies all or part of an image -- straight copy or special ops
optionally calls funct for each point defined by rect of
x1, y1, w, h and x2, y2, w, h for dest and src images
returns value of dst and src and allows setting value of
each point return nil to terminate early)
--------------------------------------------------------------------
img:line(x1, y1, x2, y2, color)
--------------------------------------------------------------------
img:ellipse(x1, y1, x2, y2, color, [fillcolor]
--------------------------------------------------------------------
Fixed handling of 2-bit vertical integrated screens
Added direct element access for saving / restoring native image etc.
Added more data to tostring() handler and a way to access individual items
Added equals method to see if two variables reference the same image address
(doesn't check if two separate images contain the same 'picture')
Optimized get and set routines
Fixed out of bound x coord access shifting to next line
Added lua include files to expose new functionality
Finished image saving routine
Static allocation of set_viewport struct faster + saves ram over dynamic
Cleaned up code
Fixed pixel get/set for 1/2 bit devices
Fixed handling for 24-bit devices (32?)
-------------------------------------------------------------------------
Example lua script to follow on forums
-------------------------------------------------------------------------
Change-Id: I8a9ff0ff72aacf4b1662767ccb2b312fc355239c
Diffstat (limited to 'apps/plugins/lua/include_lua/image.lua')
-rw-r--r-- | apps/plugins/lua/include_lua/image.lua | 392 |
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 | -------------------------------------------------------------------------------- | ||
49 | img:_len() or #img -- returns number of pixels in image | ||
50 | |||
51 | img:__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 | |||
58 | img:_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 | -------------------------------------------------------------------------------- | ||
64 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
65 | |||
66 | local _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 | |||
389 | end -- _img functions | ||
390 | |||
391 | return _img | ||
392 | |||