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 | |
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')
-rw-r--r-- | apps/plugins/lua/include_lua/blit.lua | 85 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/color.lua | 117 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/draw.lua | 469 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/image.lua | 392 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/lcd.lua | 154 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/math_ex.lua | 159 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/print.lua | 378 | ||||
-rw-r--r-- | apps/plugins/lua/include_lua/timer.lua | 115 |
8 files changed, 1869 insertions, 0 deletions
diff --git a/apps/plugins/lua/include_lua/blit.lua b/apps/plugins/lua/include_lua/blit.lua new file mode 100644 index 0000000000..6c5ea377e4 --- /dev/null +++ b/apps/plugins/lua/include_lua/blit.lua | |||
@@ -0,0 +1,85 @@ | |||
1 | --[[ Lua Blit Operations | ||
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 | --[[ | ||
25 | copy(dst, src, [dx, dy, sx, sy, offset_x, offset_y, clip, _blit.OP, clr/customfunct]) | ||
26 | blit allows you to copy a [portion of a] source image to a dest image applying | ||
27 | a transformation operation to the pixels as they are copied | ||
28 | offsets are auto calculated if left empty or out of range | ||
29 | blit will default to copy if operation is empty or out of range | ||
30 | |||
31 | it is slightly faster to use the number directly and you don't really | ||
32 | need to define all (any) of these if you don't use them but I put them | ||
33 | here for easier use of the blit function | ||
34 | ]] | ||
35 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
36 | |||
37 | local _blit ={} do | ||
38 | |||
39 | _blit.CUSTOM = 0xFF --user defined blit function func(dst_val, x, y, src_val, x, y) | ||
40 | _blit.BCOPY = 0x0 --copy (use :copy() instead it is slightly faster | ||
41 | _blit.BOR = 0x1 --OR source and dest pixels | ||
42 | _blit.BXOR = 0x2 --XOR source and dest pixels | ||
43 | _blit.BNOR = 0x3 --(NOT) (source OR dest pixels) | ||
44 | _blit.BSNOR = 0x4 --(NOT source) OR dest pixels | ||
45 | _blit.BAND = 0x5 --AND source and dest pixels | ||
46 | _blit.BNAND = 0x6 --(NOT) AND source and dest pixels | ||
47 | _blit.BNOT = 0x7 --NOT source and dest pixels | ||
48 | --blit functions for masks | ||
49 | _blit.BSAND = 0x8 --copy color to dest if source pixel <> 0 | ||
50 | _blit.BSNOT = 0x9 --copy color to dest if source pixel == 0 | ||
51 | --blit functions for masks with colors | ||
52 | _blit.BSORC = 0xA --copy source pixel or color | ||
53 | _blit.BSXORC = 0xB --copy source pixel xor color | ||
54 | _blit.BNSORC = 0xC --copy ~(src_val | clr) | ||
55 | _blit.BSORNC = 0xD --copy src_val | (~clr) | ||
56 | _blit.BSANDC = 0xE --copy src_val & clr; | ||
57 | _blit.BNSANDC = 0xF --copy (~src_val) & clr | ||
58 | _blit.BDORNSORC = 0x10 --copy dst | (~src_val) | clr | ||
59 | _blit.BXORSADXORC = 0x11 --copy dst ^ (src_val & (dst_val ^ clr)) | ||
60 | |||
61 | _blit.BSNEC = 0x12 --copy source pixel if source <> color | ||
62 | _blit.BSEQC = 0x13 --copy source pixel if source == color | ||
63 | _blit.BSGTC = 0x14 --copy source pixel if source > color | ||
64 | _blit.BSLTC = 0x15 --copy source pixel if source < color | ||
65 | _blit.BDNEC = 0x16 --copy source pixel if dest <> color | ||
66 | _blit.BDEQC = 0x17 --copy source pixel if dest == color | ||
67 | _blit.BDGTC = 0x18 --copy source pixel if dest > color | ||
68 | _blit.BDLTC = 0x19 --copy source pixel if dest < color | ||
69 | _blit.BDNES = 0x1A --copy color to dest if dest <> source pixel | ||
70 | _blit.BDEQS = 0x1B --copy color to dest if dest == source pixel | ||
71 | _blit.BDGTS = 0x1C --copy color to dest if dest > source pixel | ||
72 | _blit.BDLTS = 0x1D --copy color to dest if dest < source pixel | ||
73 | --Source unused for these blits | ||
74 | _blit.BCOPYC = 0x1E --copy color | ||
75 | _blit.BORC = 0x1F --OR dest and color | ||
76 | _blit.BXORC = 0x20 --XOR dest and color | ||
77 | _blit.BNDORC = 0x21 --~(dst_val | clr) | ||
78 | _blit.BDORNC = 0x22 --dst_val | (~clr) | ||
79 | _blit.BANDC = 0x23 --AND dest and color | ||
80 | _blit.BNDANDC = 0x24 --copy (~dst_val) & clr | ||
81 | _blit.BDLTS = 0x25 --dest NOT color | ||
82 | end -- _blit operations | ||
83 | |||
84 | return _blit | ||
85 | |||
diff --git a/apps/plugins/lua/include_lua/color.lua b/apps/plugins/lua/include_lua/color.lua new file mode 100644 index 0000000000..ed2e4f865e --- /dev/null +++ b/apps/plugins/lua/include_lua/color.lua | |||
@@ -0,0 +1,117 @@ | |||
1 | --[[ Lua Color 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 | _clr.inc | ||
27 | _clr.set | ||
28 | |||
29 | -- Exposed Constants | ||
30 | IS_COLOR_TARGET | ||
31 | |||
32 | ]] | ||
33 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
34 | |||
35 | IS_COLOR_TARGET = false | ||
36 | -- Only true when we're on a color target, i.e. when LCD_RGBPACK is available | ||
37 | if rb.lcd_rgbpack ~= _NIL then | ||
38 | IS_COLOR_TARGET = true | ||
39 | end | ||
40 | |||
41 | local _clr = {} do | ||
42 | |||
43 | -- Internal Constants | ||
44 | local _NIL = nil -- _NIL placeholder | ||
45 | |||
46 | local maxstate = (bit.lshift(1, rb.LCD_DEPTH) - 1) | ||
47 | |||
48 | if rb.LCD_DEPTH > 24 then -- no alpha channels | ||
49 | maxstate = (bit.lshift(1, 24) - 1) | ||
50 | end | ||
51 | |||
52 | local function init(v) | ||
53 | return v or 0 | ||
54 | end | ||
55 | |||
56 | -- clamps value to >= min and <= max rolls over to opposite | ||
57 | local function clamp_roll(val, min, max) | ||
58 | if min > max then | ||
59 | local swap = min | ||
60 | min, max = max, swap | ||
61 | end | ||
62 | |||
63 | if val < min then | ||
64 | val = max | ||
65 | elseif val > max then | ||
66 | val = min | ||
67 | end | ||
68 | |||
69 | return val | ||
70 | end | ||
71 | |||
72 | -- sets color -- monochrome / greyscale use 'set' -- color targets 'r,b,g' | ||
73 | -- on monochrome/ greyscale targets: | ||
74 | -- '-1' sets the highest 'color' state & 0 is the minimum 'color' state | ||
75 | local function clrset(set, r, g, b) | ||
76 | local color = set or 0 | ||
77 | |||
78 | if IS_COLOR_TARGET then | ||
79 | if (r ~= _NIL or g ~= _NIL or b ~= _NIL) then | ||
80 | r, g, b = init(r), init(g), init(b) | ||
81 | color = rb.lcd_rgbpack(r, g, b) | ||
82 | end | ||
83 | end | ||
84 | |||
85 | return clamp_roll(color, 0, maxstate) | ||
86 | end -- clrset | ||
87 | |||
88 | -- de/increments current color by 'inc' -- optionally color targets by 'r,g,b' | ||
89 | local function clrinc(current, inc, r, g, b) | ||
90 | local color = 0 | ||
91 | current = current or color | ||
92 | inc = inc or 1 | ||
93 | |||
94 | if IS_COLOR_TARGET then | ||
95 | local ru, gu, bu = rb.lcd_rgbunpack(current); | ||
96 | if (r ~= _NIL or g ~= _NIL or b ~= _NIL) then | ||
97 | r, g, b = init(r), init(g), init(b) | ||
98 | ru = ru + r; gu = gu + g; bu = bu + b | ||
99 | color = rb.lcd_rgbpack(ru, gu, bu) | ||
100 | else | ||
101 | ru = ru + inc; gu = gu + inc; bu = bu + inc | ||
102 | color = rb.lcd_rgbpack(ru, gu, bu) | ||
103 | end | ||
104 | else | ||
105 | color = current + inc | ||
106 | end | ||
107 | |||
108 | return clamp_roll(color, 0, maxstate) | ||
109 | end -- clrinc | ||
110 | |||
111 | -- expose functions to the outside through _clr table | ||
112 | _clr.set = clrset | ||
113 | _clr.inc = clrinc | ||
114 | end -- color functions | ||
115 | |||
116 | return _clr | ||
117 | |||
diff --git a/apps/plugins/lua/include_lua/draw.lua b/apps/plugins/lua/include_lua/draw.lua new file mode 100644 index 0000000000..4409ab7838 --- /dev/null +++ b/apps/plugins/lua/include_lua/draw.lua | |||
@@ -0,0 +1,469 @@ | |||
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 | |||
51 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
52 | |||
53 | local _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 | |||
466 | end -- _draw functions | ||
467 | |||
468 | return _draw | ||
469 | |||
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 | |||
diff --git a/apps/plugins/lua/include_lua/lcd.lua b/apps/plugins/lua/include_lua/lcd.lua new file mode 100644 index 0000000000..bbf0f240aa --- /dev/null +++ b/apps/plugins/lua/include_lua/lcd.lua | |||
@@ -0,0 +1,154 @@ | |||
1 | --[[ Lua LCD Wrapper 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 | _lcd.clear | ||
27 | _lcd.duplicate | ||
28 | _lcd.image | ||
29 | _lcd.set_viewport | ||
30 | _lcd.splashf | ||
31 | _lcd.text_extent | ||
32 | _lcd.update | ||
33 | _lcd.update_rect | ||
34 | |||
35 | -- Exposed Constants | ||
36 | _lcd.CX | ||
37 | _lcd.CY | ||
38 | _lcd.DEPTH | ||
39 | _lcd.W | ||
40 | _lcd.H | ||
41 | |||
42 | _lcd | ||
43 | _LCD | ||
44 | |||
45 | ]] | ||
46 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
47 | |||
48 | _LCD = rb.lcd_framebuffer() | ||
49 | |||
50 | local _lcd = {} do | ||
51 | |||
52 | --internal constants | ||
53 | local _NIL = nil -- _NIL placeholder | ||
54 | local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT | ||
55 | |||
56 | -- clamps value to >= min and <= max | ||
57 | local function clamp(val, min, max) | ||
58 | -- Warning doesn't check if min < max | ||
59 | if val < min then | ||
60 | return min | ||
61 | elseif val < max then | ||
62 | return val | ||
63 | end | ||
64 | return max | ||
65 | end | ||
66 | |||
67 | -- return a copy of lcd screen | ||
68 | local function duplicate(t, screen_img) | ||
69 | screen_img = screen_img or rb.new_image() | ||
70 | screen_img:copy(rb.lcd_framebuffer()) | ||
71 | return screen_img | ||
72 | end | ||
73 | |||
74 | -- updates screen in specified rectangle | ||
75 | local function update_rect(t, x, y, w, h) | ||
76 | rb.lcd_update_rect(x - 1, y - 1, | ||
77 | clamp(x + w, 1, LCD_W) - 1, | ||
78 | clamp(y + h, 1, LCD_H) - 1) | ||
79 | end | ||
80 | |||
81 | -- clears lcd, optional.. ([color, x1, y1, x2, y2, clip]) | ||
82 | local function clear(t, clr, ...) | ||
83 | if clr == _NIL and ... == _NIL then | ||
84 | rb.lcd_clear_display() | ||
85 | else | ||
86 | rb.lcd_scroll_stop() --rb really doesn't like bg change while scroll | ||
87 | _LCD:clear(clr, ...) | ||
88 | end | ||
89 | end | ||
90 | |||
91 | -- loads an image to the screen | ||
92 | local function image(t, src, x, y) | ||
93 | if not src then --make sure an image was passed, otherwise bail | ||
94 | rb.splash(rb.HZ, "No Image!") | ||
95 | return _NIL | ||
96 | end | ||
97 | _LCD:copy(src,x,y,1,1) | ||
98 | end | ||
99 | |||
100 | -- Formattable version of splash | ||
101 | local function splashf(t, timeout, ...) | ||
102 | rb.splash(timeout, string.format(...)) | ||
103 | end | ||
104 | |||
105 | -- Gets size of text | ||
106 | local function text_extent(t, msg, font) | ||
107 | font = font or rb.FONT_UI | ||
108 | |||
109 | return rb.font_getstringsize(msg, font) | ||
110 | end | ||
111 | |||
112 | -- Sets viewport size | ||
113 | local function set_viewport(t, vp) | ||
114 | if not vp then rb.set_viewport() return end | ||
115 | if rb.LCD_DEPTH == 2 then -- invert 2-bit screens | ||
116 | --vp.drawmode = bit.bxor(vp.drawmode, 4) | ||
117 | vp.fg_pattern = 3 - vp.fg_pattern | ||
118 | vp.bg_pattern = 3 - vp.bg_pattern | ||
119 | end | ||
120 | rb.set_viewport(vp) | ||
121 | end | ||
122 | |||
123 | -- allows the use of _lcd() as a identifier for the screen | ||
124 | local function index(k, v) | ||
125 | return function(x, ...) | ||
126 | _LCD[v](_LCD, ...) | ||
127 | end | ||
128 | end | ||
129 | |||
130 | -- allows the use of _lcd() as a identifier for the screen | ||
131 | local function call() | ||
132 | return rb.lcd_framebuffer() | ||
133 | end | ||
134 | |||
135 | --expose functions to the outside through _lcd table | ||
136 | _lcd.text_extent = text_extent | ||
137 | _lcd.set_viewport = set_viewport | ||
138 | _lcd.duplicate = duplicate | ||
139 | _lcd.update = rb.lcd_update | ||
140 | _lcd.update_rect = update_rect | ||
141 | _lcd.clear = clear | ||
142 | _lcd.splashf = splashf | ||
143 | _lcd.image = image | ||
144 | _lcd.DEPTH = rb.LCD_DEPTH | ||
145 | _lcd.W = rb.LCD_WIDTH | ||
146 | _lcd.H = rb.LCD_HEIGHT | ||
147 | _lcd.CX = (rb.LCD_WIDTH / 2) | ||
148 | _lcd.CY = (rb.LCD_HEIGHT / 2) | ||
149 | _lcd = setmetatable(_lcd,{__index = index, __call = call}) | ||
150 | |||
151 | end -- _lcd functions | ||
152 | |||
153 | return _lcd | ||
154 | |||
diff --git a/apps/plugins/lua/include_lua/math_ex.lua b/apps/plugins/lua/include_lua/math_ex.lua new file mode 100644 index 0000000000..bd4cc58765 --- /dev/null +++ b/apps/plugins/lua/include_lua/math_ex.lua | |||
@@ -0,0 +1,159 @@ | |||
1 | --[[ Lua Missing Math 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 | _math.clamp | ||
27 | _math.clamp_roll | ||
28 | _math.d_sin | ||
29 | _math.d_cos | ||
30 | _math.d_tan | ||
31 | _math.i_sqrt | ||
32 | |||
33 | ]] | ||
34 | |||
35 | local _math = {} do | ||
36 | |||
37 | -- internal constants | ||
38 | local _NIL = nil -- _NIL placeholder | ||
39 | |||
40 | -- clamps value to >= min and <= max | ||
41 | local function clamp(iVal, iMin, iMax) | ||
42 | if iMin > iMax then | ||
43 | local swap = iMin | ||
44 | iMin, iMax = iMax, swap | ||
45 | end | ||
46 | |||
47 | if iVal < iMin then | ||
48 | return iMin | ||
49 | elseif iVal < iMax then | ||
50 | return iVal | ||
51 | end | ||
52 | |||
53 | return iMax | ||
54 | end | ||
55 | |||
56 | -- clamps value to >= min and <= max rolls over to opposite | ||
57 | local function clamp_roll(iVal, iMin, iMax) | ||
58 | if iMin > iMax then | ||
59 | local swap = iMin | ||
60 | iMin, iMax = iMax, swap | ||
61 | end | ||
62 | |||
63 | if iVal < iMin then | ||
64 | iVal = iMax | ||
65 | elseif iVal > iMax then | ||
66 | iVal = iMin | ||
67 | end | ||
68 | |||
69 | return iVal | ||
70 | end | ||
71 | |||
72 | local function i_sqrt(n) | ||
73 | -- Newtons square root approximation | ||
74 | if n < 2 then return n end | ||
75 | local g = n / 2 | ||
76 | local l = 1 | ||
77 | |||
78 | for c = 1, 25 do -- if l,g haven't converged after 25 iterations quit | ||
79 | |||
80 | l = (n / g + g)/ 2 | ||
81 | g = (n / l + l)/ 2 | ||
82 | |||
83 | if g == l then return g end | ||
84 | end | ||
85 | |||
86 | -- check for period-two cycle between g and l | ||
87 | if g - l == 1 then | ||
88 | return l | ||
89 | elseif l - g == 1 then | ||
90 | return g | ||
91 | end | ||
92 | |||
93 | return _NIL | ||
94 | end | ||
95 | |||
96 | local function d_sin(iDeg, bExtraPrecision) | ||
97 | --[[ values are returned multiplied by 10000 | ||
98 | II | I 180-90 | 90-0 | ||
99 | ---(--)--- -------(--)------- | ||
100 | III | IV 180-270 | 270-360 | ||
101 | |||
102 | sine is only positive in quadrants I , II => 0 - 180 degrees | ||
103 | sine 180-360 degrees is a reflection of sine 0-180 | ||
104 | Bhaskara I's sine approximation formula isn't overly accurate | ||
105 | but it is close enough for rough image work. | ||
106 | ]] | ||
107 | local sign, x | ||
108 | -- no negative angles -10 degrees = 350 degrees | ||
109 | if iDeg < 0 then | ||
110 | x = 360 + (iDeg % 360) | ||
111 | else --keep rotation in 0-360 range | ||
112 | x = iDeg % 360 | ||
113 | end | ||
114 | |||
115 | -- reflect II & I onto III & IV | ||
116 | if x > 180 then | ||
117 | sign = -1 | ||
118 | x = x % 180 | ||
119 | else | ||
120 | sign = 1 | ||
121 | end | ||
122 | |||
123 | local x1 = x * (180 - x) | ||
124 | |||
125 | if bExtraPrecision then -- ~halves the largest errors | ||
126 | if x <= 22 or x >= 158 then | ||
127 | return sign * 39818 * x1 / (40497 - x1) | ||
128 | elseif (x >= 40 and x <= 56) or (x > 124 and x < 140) then | ||
129 | return sign * 40002 * x1 / (40450 - x1) | ||
130 | elseif (x > 31 and x < 71) or (x > 109 and x < 150) then | ||
131 | return sign * 40009 * x1 / (40470 - x1) | ||
132 | end | ||
133 | end | ||
134 | |||
135 | --multiplied by 10000 so no decimal in results (RB LUA is integer only) | ||
136 | return sign * 40000 * x1 / (40497 - x1) | ||
137 | end | ||
138 | |||
139 | local function d_cos(iDeg, bExtraPrecision) | ||
140 | --cos is just sine shifed by 90 degrees CCW | ||
141 | return d_sin(90 - iDeg, bExtraPrecision) | ||
142 | end | ||
143 | |||
144 | local function d_tan(iDeg, bExtraPrecision) | ||
145 | --tan = sin0 / cos0 | ||
146 | return (d_sin(iDeg, bExtraPrecision) * 10000 / d_sin(90 - iDeg, bExtraPrecision)) | ||
147 | end | ||
148 | |||
149 | --expose functions to the outside through _math table | ||
150 | _math.clamp = clamp | ||
151 | _math.clamp_roll = clamp_roll | ||
152 | _math.i_sqrt = i_sqrt | ||
153 | _math.d_sin = d_sin | ||
154 | _math.d_cos = d_cos | ||
155 | _math.d_tan = d_tan | ||
156 | end -- missing math functions | ||
157 | |||
158 | return _math | ||
159 | |||
diff --git a/apps/plugins/lua/include_lua/print.lua b/apps/plugins/lua/include_lua/print.lua new file mode 100644 index 0000000000..9b21dafb9d --- /dev/null +++ b/apps/plugins/lua/include_lua/print.lua | |||
@@ -0,0 +1,378 @@ | |||
1 | --[[ Lua Print 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 | _print.clear | ||
27 | _print.f | ||
28 | _print.opt | ||
29 | _print.opt.area | ||
30 | _print.opt.autoupdate | ||
31 | _print.opt.color | ||
32 | _print.opt.column | ||
33 | _print.opt.defaults | ||
34 | _print.opt.get | ||
35 | _print.opt.justify | ||
36 | _print.opt.line | ||
37 | _print.opt.overflow | ||
38 | _print.opt.sel_line | ||
39 | _print.opt.set | ||
40 | |||
41 | ]] | ||
42 | |||
43 | if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end | ||
44 | |||
45 | local _print = {} do | ||
46 | |||
47 | -- internal constants | ||
48 | local _clr = require("color") -- _clr functions required | ||
49 | |||
50 | local _NIL = nil -- _NIL placeholder | ||
51 | local _LCD = rb.lcd_framebuffer() | ||
52 | local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT | ||
53 | local WHITE = _clr.set(-1, 255, 255, 255) | ||
54 | local BLACK = _clr.set(0, 0, 0, 0) | ||
55 | local DRMODE_SOLID = 3 | ||
56 | local col_buf, s_lines = {}, {} | ||
57 | local _p_opts = _NIL | ||
58 | |||
59 | -- print internal helper functions | ||
60 | -------------------------------------------------------------------------------- | ||
61 | -- clamps value to >= min and <= max | ||
62 | local function clamp(val, min, max) | ||
63 | -- Warning doesn't check if min < max | ||
64 | if val < min then | ||
65 | return min | ||
66 | elseif val < max then | ||
67 | return val | ||
68 | end | ||
69 | return max | ||
70 | end | ||
71 | |||
72 | -- updates screen in specified rectangle | ||
73 | local function update_rect(x, y, w, h) | ||
74 | rb.lcd_update_rect(x - 1, y - 1, | ||
75 | clamp(x + w, 1, LCD_W) - 1, | ||
76 | clamp(y + h, 1, LCD_H) - 1) | ||
77 | end | ||
78 | |||
79 | -- Gets size of text | ||
80 | local function text_extent(msg, font) | ||
81 | font = font or rb.FONT_UI | ||
82 | -- res, w, h | ||
83 | return rb.font_getstringsize(msg, font) | ||
84 | end | ||
85 | |||
86 | -- Sets viewport size | ||
87 | local function set_viewport(vp) | ||
88 | if not vp then rb.set_viewport() return end | ||
89 | |||
90 | if rb.LCD_DEPTH == 2 then -- invert 2-bit screens | ||
91 | --vp.drawmode = bit.bxor(vp.drawmode, 4) | ||
92 | vp.fg_pattern = 3 - vp.fg_pattern | ||
93 | vp.bg_pattern = 3 - vp.bg_pattern | ||
94 | end | ||
95 | rb.set_viewport(vp) | ||
96 | end | ||
97 | |||
98 | -- shallow copy of table | ||
99 | function table_clone(t) | ||
100 | local copy = {} | ||
101 | for k, v in pairs(t) do | ||
102 | copy[k] = v | ||
103 | end | ||
104 | return copy | ||
105 | end | ||
106 | |||
107 | -- Updates a single line on the screen | ||
108 | local function update_line(enabled, opts, line, h) | ||
109 | if enabled ~= true then return end | ||
110 | local o = opts or _p_opts | ||
111 | update_rect(o.x, o.y + line * h + 1, o.width, h) | ||
112 | end | ||
113 | |||
114 | -- Clears a single line on the screen | ||
115 | local function clear_line(opts, line, h) | ||
116 | local o = opts or _p_opts | ||
117 | _LCD:clear(o.bg_pattern, o.x, o.y + line * h + 1, | ||
118 | o.x + o.width, line * h + h + o.y) | ||
119 | end | ||
120 | |||
121 | -- Sets the maximum number of lines on the screen | ||
122 | local function max_lines(opts) | ||
123 | local h = opts.height | ||
124 | local _, _, th = text_extent("W", opts.font) | ||
125 | return h / th | ||
126 | end | ||
127 | |||
128 | --saves the items displayed for side to side scroll | ||
129 | local function col_buf_insert(msg, line, _p_opts) | ||
130 | --if _p_opts.line <= 1 then col_buf = {} end | ||
131 | if not col_buf[line] then | ||
132 | table.insert(col_buf, line, msg) end | ||
133 | end | ||
134 | |||
135 | --replaces / strips escape characters | ||
136 | local function check_escapes(o, msg) | ||
137 | local tabsz = 2 | ||
138 | local tabstr = string.rep(" ", tabsz) | ||
139 | |||
140 | local function repl(esc) | ||
141 | local ret = "" | ||
142 | if esc:sub(1,1) == "\t" then ret = string.rep(tabstr, esc:len()) end | ||
143 | return ret | ||
144 | end | ||
145 | |||
146 | msg = msg:gsub("(%c+)", repl) | ||
147 | |||
148 | local res, w, h = text_extent(msg, o.font) | ||
149 | return w, h, msg | ||
150 | end | ||
151 | -------------------------------------------------------------------------------- | ||
152 | |||
153 | -- set defaults for print view | ||
154 | local function set_defaults() | ||
155 | _p_opts = { x = 1, | ||
156 | y = 1, | ||
157 | width = LCD_W - 1, | ||
158 | height = LCD_H - 1, | ||
159 | font = rb.FONT_UI, | ||
160 | drawmode = DRMODE_SOLID, | ||
161 | fg_pattern = WHITE, | ||
162 | bg_pattern = BLACK, | ||
163 | sel_pattern = WHITE, | ||
164 | line = 1, | ||
165 | max_line = _NIL, | ||
166 | col = 0, | ||
167 | ovfl = "auto", | ||
168 | justify = "left", | ||
169 | autoupdate = true, | ||
170 | } | ||
171 | _p_opts.max_line = max_lines(_p_opts) | ||
172 | |||
173 | s_lines, col_buf = {}, {} | ||
174 | return _p_opts | ||
175 | end | ||
176 | |||
177 | -- returns table with settings for print | ||
178 | -- if bByRef is _NIL or false then a copy is returned | ||
179 | local function get_settings(bByRef) | ||
180 | _p_opts = _p_opts or set_defaults() | ||
181 | if not bByRef then return table_clone(_p_opts) end | ||
182 | return _p_opts | ||
183 | end | ||
184 | |||
185 | -- sets the settings for print with your passed table | ||
186 | local function set_settings(t_opts) | ||
187 | _p_opts = t_opts or set_defaults() | ||
188 | if t_opts then | ||
189 | _p_opts.max_line = max_lines(_p_opts) | ||
190 | col_buf = {} | ||
191 | end | ||
192 | end | ||
193 | |||
194 | -- sets colors for print | ||
195 | local function set_color(fgclr, bgclr, selclr) | ||
196 | local o = get_settings(true) | ||
197 | |||
198 | if fgclr ~= _NIL then | ||
199 | o.fg_pattern, o.sel_pattern = fgclr, fgclr | ||
200 | end | ||
201 | o.sel_pattern = selclr or o.sel_pattern | ||
202 | o.bg_pattern = bgclr or o.bg_pattern | ||
203 | end | ||
204 | |||
205 | -- helper function sets up colors/marker for selected items | ||
206 | local function show_selected(iLine, msg) | ||
207 | local o = get_settings() -- using a copy of opts so changes revert | ||
208 | |||
209 | if s_lines[iLine] == true then | ||
210 | if not rb.lcd_set_background then | ||
211 | o.drawmode = bit.bxor(o.drawmode, 4) | ||
212 | else | ||
213 | o.fg_pattern = o.bg_pattern | ||
214 | o.bg_pattern = o.sel_pattern | ||
215 | end | ||
216 | -- alternative selection method | ||
217 | --msg = "> " .. msg | ||
218 | end | ||
219 | set_viewport(o) | ||
220 | o = _NIL | ||
221 | end | ||
222 | |||
223 | -- sets line explicitly or increments line if line is _NIL | ||
224 | local function set_line(iLine) | ||
225 | local o = get_settings(true) | ||
226 | |||
227 | o.line = iLine or o.line + 1 | ||
228 | |||
229 | if(o.line < 1 or o.line > o.max_line) then | ||
230 | o.line = 1 | ||
231 | end | ||
232 | end | ||
233 | |||
234 | -- clears the set print area | ||
235 | local function clear() | ||
236 | local o = get_settings(true) | ||
237 | _LCD:clear(o.bg_pattern, o.x, o.y, o.x + o.width, o.y + o.height) | ||
238 | if o.autoupdate == true then rb.lcd_update() end | ||
239 | set_line(1) | ||
240 | for i=1, #col_buf do col_buf[i] = _NIL end | ||
241 | s_lines = {} | ||
242 | collectgarbage("collect") | ||
243 | end | ||
244 | |||
245 | -- screen update after each call to print.f | ||
246 | local function set_update(bAutoUpdate) | ||
247 | local o = get_settings(true) | ||
248 | o.autoupdate = bAutoUpdate or false | ||
249 | end | ||
250 | |||
251 | -- sets print area | ||
252 | local function set_area(x, y, w, h) | ||
253 | local o = get_settings(true) | ||
254 | o.x, o.y = clamp(x, 1, LCD_W), clamp(y, 1, LCD_H) | ||
255 | o.width, o.height = clamp(w, 1, LCD_W - o.x), clamp(h, 1, LCD_H - o.y) | ||
256 | o.max_line = max_lines(_p_opts) | ||
257 | |||
258 | clear() | ||
259 | return o.line, o.max_line | ||
260 | end | ||
261 | |||
262 | -- when string is longer than print width scroll -- "auto", "manual", "none" | ||
263 | local function set_overflow(str_mode) | ||
264 | -- "auto", "manual", "none" | ||
265 | local str_mode = str_mode or "auto" | ||
266 | local o = get_settings(true) | ||
267 | o.ovfl = str_mode:lower() | ||
268 | col_buf = {} | ||
269 | end | ||
270 | |||
271 | -- aligns text to: "left", "center", "right" | ||
272 | local function set_justify(str_mode) | ||
273 | -- "left", "center", "right" | ||
274 | local str_mode = str_mode or "left" | ||
275 | local o = get_settings(true) | ||
276 | o.justify = str_mode:lower() | ||
277 | end | ||
278 | |||
279 | -- selects line | ||
280 | local function select_line(iLine) | ||
281 | s_lines[iLine] = true | ||
282 | end | ||
283 | |||
284 | -- Internal print function | ||
285 | local function print_internal(t_opts, x, w, h, msg) | ||
286 | |||
287 | local o = t_opts | ||
288 | if o.justify == "center" then | ||
289 | x = x + (o.width - w) / 2 | ||
290 | elseif o.justify == "right" then | ||
291 | x = x + (o.width - w) | ||
292 | end | ||
293 | |||
294 | local line = o.line - 1 -- rb is 0-based lua is 1-based | ||
295 | if(o.ovfl == "auto" and w >= o.width) then -- -o.x | ||
296 | rb.lcd_puts_scroll(0, line, msg) | ||
297 | else | ||
298 | rb.lcd_putsxy(x, line * h, msg) | ||
299 | if o.ovfl == "manual" then --save msg for later side scroll | ||
300 | col_buf_insert(msg, o.line, o) | ||
301 | end | ||
302 | end | ||
303 | |||
304 | --only update the line we changed | ||
305 | update_line(o.autoupdate, o, line, h) | ||
306 | |||
307 | set_line(_NIL) -- increments line counter | ||
308 | end | ||
309 | |||
310 | -- Helper function that acts mostly like a normal printf() would | ||
311 | local function printf(...) | ||
312 | local o = get_settings(true) | ||
313 | local w, h, msg | ||
314 | local line = o.line - 1 -- rb is 0-based lua is 1-based | ||
315 | if not (...) or (...) == "\n" then -- handles blank line / single '\n' | ||
316 | local res, w, h = text_extent(" ", o.font) | ||
317 | |||
318 | clear_line(o, line, h) | ||
319 | update_line(o.autoupdate, o, line, h) | ||
320 | |||
321 | if (...) then set_line(_NIL) end | ||
322 | |||
323 | return o.line, o.max_line, o.width, h | ||
324 | end | ||
325 | |||
326 | msg = string.format(...) | ||
327 | |||
328 | show_selected(o.line, msg) | ||
329 | |||
330 | w, h, msg = check_escapes(o, msg) | ||
331 | |||
332 | print_internal(o, o.col, w, h, msg) | ||
333 | |||
334 | return o.line, o.max_line, w, h | ||
335 | end | ||
336 | |||
337 | -- x > 0 scrolls right x < 0 scrolls left | ||
338 | local function set_column(x) | ||
339 | local o = get_settings() | ||
340 | if o.ovfl ~= "manual" then return end -- no buffer stored to scroll | ||
341 | local res, w, h, str, line | ||
342 | |||
343 | for key, value in pairs(col_buf) do | ||
344 | line = key - 1 -- rb is 0-based lua is 1-based | ||
345 | o.line = key | ||
346 | |||
347 | if value then | ||
348 | show_selected(key, value) | ||
349 | res, w, h = text_extent(value, o.font) | ||
350 | clear_line(o, line, h) | ||
351 | |||
352 | print_internal(o, x + o.col, w, h, value) | ||
353 | update_line(o.autoupdate, o, line, h) | ||
354 | end | ||
355 | end | ||
356 | o = _NIL | ||
357 | end | ||
358 | |||
359 | --expose functions to the outside through _print table | ||
360 | _print.opt = {} | ||
361 | _print.opt.column = set_column | ||
362 | _print.opt.color = set_color | ||
363 | _print.opt.area = set_area | ||
364 | _print.opt.set = set_settings | ||
365 | _print.opt.get = get_settings | ||
366 | _print.opt.defaults = set_defaults | ||
367 | _print.opt.overflow = set_overflow | ||
368 | _print.opt.justify = set_justify | ||
369 | _print.opt.sel_line = select_line | ||
370 | _print.opt.line = set_line | ||
371 | _print.opt.autoupdate = set_update | ||
372 | _print.clear = clear | ||
373 | _print.f = printf | ||
374 | |||
375 | end --_print functions | ||
376 | |||
377 | return _print | ||
378 | |||
diff --git a/apps/plugins/lua/include_lua/timer.lua b/apps/plugins/lua/include_lua/timer.lua new file mode 100644 index 0000000000..18b973cbbd --- /dev/null +++ b/apps/plugins/lua/include_lua/timer.lua | |||
@@ -0,0 +1,115 @@ | |||
1 | --[[ Lua Timer 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 | _timer.active | ||
27 | _timer.check | ||
28 | _timer.split | ||
29 | _timer.start | ||
30 | _timer.stop | ||
31 | |||
32 | ]] | ||
33 | |||
34 | local _timer = {} do | ||
35 | |||
36 | --internal constants | ||
37 | local _NIL = nil -- _NIL placeholder | ||
38 | |||
39 | -- newer versions of lua use table.unpack | ||
40 | local unpack = unpack or table.unpack | ||
41 | |||
42 | --stores time elapsed at call to split; only vaid for unique timers | ||
43 | local function split(index) | ||
44 | if type(index) ~= "table" then return end | ||
45 | index[#index + 1] = rb.current_tick() - _timer[index] | ||
46 | end | ||
47 | |||
48 | -- starts a new timer, if index is not specified a unique index is returned | ||
49 | -- numeric or string indices are valid to use directly for permanent timers | ||
50 | -- in this case its up to you to make sure you keep the index unique | ||
51 | local function start(index) | ||
52 | if index == _NIL then | ||
53 | ---if you have _timer.start create timer it returns a unique Id which | ||
54 | -- then has the same methods of _timer :start :stop :check :split | ||
55 | index = setmetatable({}, {__index = _timer}) | ||
56 | end | ||
57 | if _timer[index] == _NIL then | ||
58 | _timer[index] = rb.current_tick() | ||
59 | end | ||
60 | return index | ||
61 | end | ||
62 | |||
63 | -- returns time elapsed in centiseconds, assigning bCheckonly keeps timer active | ||
64 | local function stop(index, bCheckonly) | ||
65 | |||
66 | local time_end = rb.current_tick() | ||
67 | index = index or 0 | ||
68 | if not _timer[index] then | ||
69 | return 0 | ||
70 | else | ||
71 | local time_start = _timer[index] | ||
72 | if not bCheckonly then _timer[index] = _NIL end -- destroy timer | ||
73 | if type(index) ~= "table" then | ||
74 | return time_end - time_start | ||
75 | else | ||
76 | return time_end - time_start, unpack(index) | ||
77 | end | ||
78 | end | ||
79 | end | ||
80 | |||
81 | -- returns time elapsed in centiseconds, assigning to bUpdate.. updates timer | ||
82 | local function check(index, bUpdate) | ||
83 | local elapsed = stop(index, true) | ||
84 | if bUpdate ~= _NIL and index then | ||
85 | _timer[index] = rb.current_tick() | ||
86 | end | ||
87 | return elapsed | ||
88 | end | ||
89 | |||
90 | -- returns table of active timers | ||
91 | local function active() | ||
92 | local t_active = {} | ||
93 | local n = 0 | ||
94 | for k,v in pairs(_timer) do | ||
95 | if type(_timer[k]) ~= "function" then | ||
96 | n = n + 1 | ||
97 | t_active[n]=(k) | ||
98 | end | ||
99 | end | ||
100 | return n, t_active | ||
101 | end | ||
102 | |||
103 | -- expose functions to the outside through _timer table | ||
104 | _timer.active = active | ||
105 | _timer.check = check | ||
106 | _timer.split = split | ||
107 | _timer.start = start | ||
108 | _timer.stop = stop | ||
109 | |||
110 | -- allows a call to _timer.start() by just calling _timer() | ||
111 | _timer = setmetatable(_timer,{__call = function(t, i) return start(i) end}) | ||
112 | end -- timer functions | ||
113 | |||
114 | return _timer | ||
115 | |||