summaryrefslogtreecommitdiff
path: root/apps/plugins/lua/include_lua/draw.lua
diff options
context:
space:
mode:
authorWilliam Wilgus <me.theuser@yahoo.com>2018-05-28 17:56:06 +0200
committerWilliam Wilgus <me.theuser@yahoo.com>2018-07-22 18:05:02 +0200
commit2daec3d3c3d84e7176a22bc073ca5530e8e44c6d (patch)
tree0f7e64ed5abd305fe80a7fbfabf79d2a9374ed4f /apps/plugins/lua/include_lua/draw.lua
parent19b2964d78b2ad6624a0e7cddd0ac6a49082cca4 (diff)
downloadrockbox-2daec3d3c3d84e7176a22bc073ca5530e8e44c6d.tar.gz
rockbox-2daec3d3c3d84e7176a22bc073ca5530e8e44c6d.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 ------------------------------------------------------------------------- Example lua script to follow on forums ------------------------------------------------------------------------- Change-Id: I7b9c1fd699442fb683760f781021091786c18509
Diffstat (limited to 'apps/plugins/lua/include_lua/draw.lua')
-rw-r--r--apps/plugins/lua/include_lua/draw.lua468
1 files changed, 468 insertions, 0 deletions
diff --git a/apps/plugins/lua/include_lua/draw.lua b/apps/plugins/lua/include_lua/draw.lua
new file mode 100644
index 0000000000..f59e439137
--- /dev/null
+++ b/apps/plugins/lua/include_lua/draw.lua
@@ -0,0 +1,468 @@
1--[[ Lua Drawing 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 _draw.circle
27 _draw.circle_filled
28 _draw.ellipse
29 _draw.ellipse_filled
30 _draw.ellipse_rect_filled
31 _draw.ellipse_rect
32 _draw.flood_fill
33 _draw.hline
34 _draw.image
35 _draw.line
36 _draw.polygon
37 _draw.polyline
38 _draw.rect
39 _draw.rect_filled
40 _draw.rounded_rect
41 _draw.rounded_rect_filled
42 _draw.text
43 _draw.vline
44
45]]
46
47--[[ bClip allows drawing out of bounds without raising an error it is slower
48 than having a correctly bounded figure, but can be helpful in some cases..
49]]
50
51if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
52
53local _draw = {} do
54
55 -- Internal Constants
56 local _LCD = rb.lcd_framebuffer()
57 local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
58 local BSAND = 8 -- blits color to dst if src <> 0
59 local _NIL = nil -- nil placeholder
60
61 local function set_viewport(vp)
62 if not vp then rb.set_viewport() return end
63 if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
64 --vp.drawmode = bit.bxor(vp.drawmode, 4)
65 vp.fg_pattern = 3 - vp.fg_pattern
66 vp.bg_pattern = 3 - vp.bg_pattern
67 end
68 rb.set_viewport(vp)
69 end
70
71 -- line
72 local function line(img, x1, y1, x2, y2, color, bClip)
73 img:line(x1, y1, x2, y2, color, bClip)
74 end
75
76 -- horizontal line; x, y define start point; length in horizontal direction
77 local function hline(img, x, y , length, color, bClip)
78 img:line(x, y, x + length, _NIL, color, bClip)
79 end
80
81 -- vertical line; x, y define start point; length in vertical direction
82 local function vline(img, x, y , length, color, bClip)
83 img:line(x, y, _NIL, y + length, color, bClip)
84 end
85
86 -- draws a non-filled figure based on points in t-points
87 local function polyline(img, x, y, t_points, color, bClosed, bClip)
88 if #t_points < 2 then error("not enough points", 3) end
89
90 local pt_first_last
91
92 if bClosed then
93 pt_first_last = t_points[1]
94 else
95 pt_first_last = t_points[#t_points]
96 end
97
98 for i = 1, #t_points, 1 do
99 local pt1 = t_points[i]
100
101 local pt2 = t_points[i + 1] or pt_first_last-- first and last point
102
103 img:line(pt1[1] + x, pt1[2] + y, pt2[1]+x, pt2[2]+y, color, bClip)
104 end
105
106 end
107
108 -- rectangle
109 local function rect(img, x, y, width, height, color, bClip)
110 if width == 0 or height == 0 then return end
111
112 local ppt = {{0, 0}, {width, 0}, {width, height}, {0, height}}
113 polyline(img, x, y, ppt, color, true, bClip)
114 --[[
115 vline(img, x, y, height, color, bClip);
116 vline(img, x + width, y, height, color, bClip);
117 hline(img, x, y, width, color, bClip);
118 hline(img, x, y + height, width, color, bClip);]]
119 end
120
121 -- filled rect, fillcolor is color if left empty
122 local function rect_filled(img, x, y, width, height, color, fillcolor, bClip)
123 if width == 0 or height == 0 then return end
124
125 if not fillcolor then
126 img:clear(color, x, y, x + width, y + height, bClip)
127 else
128 img:clear(fillcolor, x, y, x + width, y + height, bClip)
129 rect(img, x, y, width, height, color, bClip)
130 end
131 end
132
133 -- circle cx,cy define center point
134 local function circle(img, cx, cy, radius, color, bClip)
135 local r = radius
136 img:ellipse(cx - r, cy - r, cx + r, cy + r, color, _NIL, bClip)
137 end
138
139 -- filled circle cx,cy define center, fillcolor is color if left empty
140 local function circle_filled(img, cx, cy, radius, color, fillcolor, bClip)
141 fillcolor = fillcolor or color
142 local r = radius
143 img:ellipse(cx - r, cy - r, cx + r, cy + r, color, fillcolor, bClip)
144 end
145
146 -- ellipse that fits into defined rect
147 local function ellipse_rect(img, x1, y1, x2, y2, color, bClip)
148 img:ellipse(x1, y1, x2, y2, color, _NIL, bClip)
149 end
150
151 --ellipse that fits into defined rect, fillcolor is color if left empty
152 local function ellipse_rect_filled(img, x1, y1, x2, y2, color, fillcolor, bClip)
153 if not fillcolor then fillcolor = color end
154
155 img:ellipse(x1, y1, x2, y2, color, fillcolor, bClip)
156 end
157
158 -- ellipse cx, cy define center point; a, b the major/minor axis
159 local function ellipse(img, cx, cy, a, b, color, bClip)
160 img:ellipse(cx - a, cy - b, cx + a, cy + b, color, _NIL, bClip)
161 end
162
163 -- filled ellipse cx, cy define center point; a, b the major/minor axis
164 -- fillcolor is color if left empty
165 local function ellipse_filled(img, cx, cy, a, b, color, fillcolor, bClip)
166 if not fillcolor then fillcolor = color end
167
168 img:ellipse(cx - a, cy - b, cx + a, cy + b, color, fillcolor, bClip)
169 end
170
171 -- rounded rectangle
172 local function rounded_rect(img, x, y, w, h, radius, color, bClip)
173 local c_img
174
175 local function blit(dx, dy, sx, sy, ox, oy)
176 img:copy(c_img, dx, dy, sx, sy, ox, oy, bClip, BSAND, color)
177 end
178
179 if w == 0 or h == 0 then return end
180
181 -- limit the radius of the circle otherwise it will overtake the rect
182 radius = math.min(w / 2, radius)
183 radius = math.min(h / 2, radius)
184
185 local r = radius
186
187 c_img = rb.new_image(r * 2 + 1, r * 2 + 1)
188 c_img:clear(0)
189 circle(c_img, r + 1, r + 1, r, 0xFFFFFF)
190
191 -- copy 4 pieces of circle to their respective corners
192 blit(x, y, _NIL, _NIL, r + 1, r + 1) --TL
193 blit(x + w - r - 2, y, r, _NIL, r + 1, r + 1) --TR
194 blit(x , y + h - r - 2, _NIL, r, r + 1, _NIL) --BL
195 blit(x + w - r - 2, y + h - r - 2, r, r, r + 1, r + 1)--BR
196 c_img = _NIL
197
198 vline(img, x, y + r, h - r * 2, color, bClip);
199 vline(img, x + w - 1, y + r, h - r * 2, color, bClip);
200 hline(img, x + r, y, w - r * 2, color, bClip);
201 hline(img, x + r, y + h - 1, w - r * 2, color, bClip);
202 end
203
204 -- rounded rectangle fillcolor is color if left empty
205 local function rounded_rect_filled(img, x, y, w, h, radius, color, fillcolor, bClip)
206 local c_img
207
208 local function blit(dx, dy, sx, sy, ox, oy)
209 img:copy(c_img, dx, dy, sx, sy, ox, oy, bClip, BSAND, fillcolor)
210 end
211
212 if w == 0 or h == 0 then return end
213
214 if not fillcolor then fillcolor = color end
215
216 -- limit the radius of the circle otherwise it will overtake the rect
217 radius = math.min(w / 2, radius)
218 radius = math.min(h / 2, radius)
219
220 local r = radius
221
222 c_img = rb.new_image(r * 2 + 1, r * 2 + 1)
223 c_img:clear(0)
224 circle_filled(c_img, r + 1, r + 1, r, fillcolor)
225
226 -- copy 4 pieces of circle to their respective corners
227 blit(x, y, _NIL, _NIL, r + 1, r + 1) --TL
228 blit(x + w - r - 2, y, r, _NIL, r + 1, r + 1) --TR
229 blit(x, y + h - r - 2, _NIL, r, r + 1, _NIL) --BL
230 blit(x + w - r - 2, y + h - r - 2, r, r, r + 1, r + 1) --BR
231 c_img = _NIL
232
233 -- finish filling areas circles didn't cover
234 img:clear(fillcolor, x + r, y, x + w - r, y + h - 1, bClip)
235 img:clear(fillcolor, x, y + r, x + r, y + h - r, bClip)
236 img:clear(fillcolor, x + w - r, y + r, x + w - 1, y + h - r - 1, bClip)
237
238 if fillcolor ~= color then
239 rounded_rect(img, x, y, w, h, r, color, bClip)
240 end
241 end
242
243 -- draws an image at xy coord in dest image
244 local function image(dst, src, x, y, bClip)
245 if not src then --make sure an image was passed, otherwise bail
246 rb.splash(rb.HZ, "No Image!")
247 return _NIL
248 end
249
250 dst:copy(src, x, y, 1, 1, _NIL, _NIL, bClip)
251 end
252
253 -- floods an area of targetclr with fillclr x, y specifies the start seed
254 function flood_fill(img, x, y, targetclr, fillclr)
255 -- scanline 4-way flood algorithm
256 -- ^
257 -- <--------x--->
258 -- v
259 -- check that target color doesn't = fill and the first point is target color
260 if targetclr == fillclr or targetclr ~= img:get(x,y, true) then return end
261 local max_w = img:width()
262 local max_h = img:height()
263
264 local qpt = {} -- FIFO queue
265 -- rather than moving elements around in our FIFO queue
266 -- for each read; increment 'qhead' by 2
267 -- set both elements to nil and let the
268 -- garbage collector worry about it
269 -- for each write; increment 'qtail' by 2
270 -- x coordinates are in odd indices while
271 -- y coordinates are in even indices
272
273 local qtail = 0
274 local iter_n; -- North iteration
275 local iter_s; -- South iteration
276
277 local function check_ns(val, x, y)
278 if targetclr == val then
279 if targetclr == iter_n() then
280 qtail = qtail + 2
281 qpt[qtail - 1] = x
282 qpt[qtail] = (y - 1)
283 end
284
285 if targetclr == iter_s() then
286 qtail = qtail + 2
287 qpt[qtail - 1] = x
288 qpt[qtail] = (y + 1)
289 end
290 return fillclr
291 end
292 return _NIL -- signal marshal to stop
293 end
294
295 local function seed_pt(x, y)
296 -- will never hit max_w * max_h^2 but make sure not to end early
297 for qhead = 2, max_w * max_h * max_w * max_h, 2 do
298
299 if targetclr == img:get(x, y, true) then
300 iter_n = img:points(x, y - 1, 1, y - 1)
301 iter_s = img:points(x, y + 1, 1, y + 1)
302 img:marshal(x, y, 1, y, _NIL, _NIL, true, check_ns)
303
304 iter_n = img:points(x + 1, y - 1, max_w, y - 1)
305 iter_s = img:points(x + 1, y + 1, max_w, y + 1)
306 img:marshal(x + 1, y, max_w, y, _NIL, _NIL, true, check_ns)
307 end
308
309 x = qpt[qhead - 1]
310 qpt[qhead - 1] = _NIL
311
312 if not x then break end
313
314 y = qpt[qhead]
315 qpt[qhead] = _NIL
316 end
317 end
318
319 seed_pt(x, y) -- Begin
320 end -- flood_fill
321
322 -- draws a closed figure based on points in t_points
323 local function polygon(img, x, y, t_points, color, fillcolor, bClip)
324 if #t_points < 2 then error("not enough points", 3) end
325
326 if fillcolor then
327 local x_min, x_max = 0, 0
328 local y_min, y_max = 0, 0
329 local w, h = 0, 0
330 -- find boundries of polygon
331 for i = 1, #t_points, 1 do
332 local pt = t_points[i]
333 if pt[1] < x_min then x_min = pt[1] end
334 if pt[1] > x_max then x_max = pt[1] end
335 if pt[2] < y_min then y_min = pt[2] end
336 if pt[2] > y_max then y_max = pt[2] end
337 end
338 w = math.abs(x_max) + math.abs(x_min)
339 h = math.abs(y_max) + math.abs(y_min)
340 x_min = x_min - 2 -- leave a border to use flood_fill
341 y_min = y_min - 2
342
343 local fill_img = rb.new_image(w + 3, h + 3)
344 fill_img:clear(0xFFFFFF)
345
346 for i = 1, #t_points, 1 do
347 local pt1 = t_points[i]
348 local pt2 = t_points[i + 1] or t_points[1]-- first and last point
349 fill_img:line(pt1[1] - x_min, pt1[2] - y_min,
350 pt2[1]- x_min, pt2[2] - y_min, 0)
351
352 end
353 flood_fill(fill_img, fill_img:width(), fill_img:height() , 1, 0)
354 img:copy(fill_img, x - 1, y - 1, _NIL, _NIL, _NIL, _NIL, bClip, BSAND, fillcolor)
355 end
356
357 polyline(img, x, y, t_points, color, true, bClip)
358 end
359
360 -- draw text onto image if width/height are supplied text is centered
361 local function text(img, x, y, width, height, font, color, text)
362 font = font or rb.FONT_UI
363
364 local opts = {x = 0, y = 0, width = LCD_W - 1, height = LCD_H - 1,
365 font = font, drawmode = 3, fg_pattern = 0xFFFFFF, bg_pattern = 0}
366 set_viewport(opts)
367
368 local res, w, h = rb.font_getstringsize(text, font)
369
370 if not width then
371 width = 0
372 else
373 width = (width - w) / 2
374 end
375
376 if not height then
377 height = 0
378 else
379 height = (height - h) / 2
380 end
381
382 -- make a copy of the current screen for later
383 local screen_img = rb.new_image(LCD_W, LCD_H)
384 screen_img:copy(_LCD)
385
386 -- check if the screen buffer is supplied image if so set img to the copy
387 if img == _LCD then
388 img = screen_img
389 end
390
391 -- we will be printing the text to the screen then blitting into img
392 rb.lcd_clear_display()
393
394 local function blit(dx, dy)
395 img:copy(_LCD, dx, dy, _NIL, _NIL, _NIL, _NIL, false, BSAND, color)
396 end
397
398 if w > LCD_W then -- text is too long for the screen do it in chunks
399 local l = 1
400 local resp, wp, hp
401 local lenr = text:len()
402
403 while lenr > 1 do
404 l = lenr
405 resp, wp, hp = rb.font_getstringsize(text:sub(1, l), font)
406
407 while wp >= LCD_W and l > 1 do
408 l = l - 1
409 resp, wp, hp = rb.font_getstringsize(text:sub( 1, l), font)
410 end
411
412 rb.lcd_putsxy(0, 0, text:sub(1, l))
413 text = text:sub(l)
414
415 if x + width > img:width() or y + height > img:height() then
416 break
417 end
418
419 -- using the mask we made blit color into img
420 blit(x + width, y + height)
421 x = x + wp
422 rb.lcd_clear_display()
423 lenr = text:len()
424 end
425 else --w <= LCD_W
426 rb.lcd_putsxy(0, 0, text)
427
428 -- using the mask we made blit color into img
429 blit(x + width, y + height)
430 end
431
432 _LCD:copy(screen_img) -- restore screen
433 set_viewport() -- set viewport default
434 return res, w, h
435 end
436
437 -- expose functions to the outside through _draw table
438 _draw.image = image
439 _draw.text = text
440 _draw.line = line
441 _draw.hline = hline
442 _draw.vline = vline
443 _draw.polygon = polygon
444 _draw.polyline = polyline
445 _draw.rect = rect
446 _draw.circle = circle
447 _draw.ellipse = ellipse
448 _draw.flood_fill = flood_fill
449 _draw.ellipse_rect = ellipse_rect
450 _draw.rounded_rect = rounded_rect
451 -- filled functions use color as fillcolor if fillcolor is left empty...
452 _draw.rect_filled = rect_filled
453 _draw.circle_filled = circle_filled
454 _draw.ellipse_filled = ellipse_filled
455 _draw.ellipse_rect_filled = ellipse_rect_filled
456 _draw.rounded_rect_filled = rounded_rect_filled
457
458 -- adds the above _draw functions into the metatable for RLI_IMAGE
459 local ex = getmetatable(rb.lcd_framebuffer())
460 for k, v in pairs(_draw) do
461 if ex[k] == _NIL then
462 ex[k] = v
463 end
464 end
465
466end -- _draw functions
467
468return _draw