summaryrefslogtreecommitdiffstats
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
parent19b2964d78b2ad6624a0e7cddd0ac6a49082cca4 (diff)
downloadrockbox-2daec3d.tar.gz
rockbox-2daec3d.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
-rw-r--r--apps/plugins/lua/include_lua/blit.lua84
-rw-r--r--apps/plugins/lua/include_lua/color.lua112
-rw-r--r--apps/plugins/lua/include_lua/draw.lua468
-rw-r--r--apps/plugins/lua/include_lua/image.lua391
-rw-r--r--apps/plugins/lua/include_lua/lcd.lua153
-rw-r--r--apps/plugins/lua/include_lua/math_ex.lua158
-rw-r--r--apps/plugins/lua/include_lua/print.lua377
-rw-r--r--apps/plugins/lua/include_lua/timer.lua114
-rw-r--r--apps/plugins/lua/lua.make8
-rw-r--r--apps/plugins/lua/rocklib.c1253
10 files changed, 3029 insertions, 89 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..934804b5b3
--- /dev/null
+++ b/apps/plugins/lua/include_lua/blit.lua
@@ -0,0 +1,84 @@
+--[[ Lua Blit Operations
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[
+copy(dst, src, [dx, dy, sx, sy, offset_x, offset_y, clip, _blit.OP, clr/customfunct])
+ blit allows you to copy a [portion of a] source image to a dest image applying
+ a transformation operation to the pixels as they are copied
+ offsets are auto calculated if left empty or out of range
+ blit will default to copy if operation is empty or out of range
+
+it is slightly faster to use the number directly and you don't really
+ need to define all (any) of these if you don't use them but I put them
+ here for easier use of the blit function
+]]
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+local _blit ={} do
+
+ _blit.CUSTOM = 0xFF --user defined blit function func(dst_val, x, y, src_val, x, y)
+ _blit.BCOPY = 0x0 --copy (use :copy() instead it is slightly faster
+ _blit.BOR = 0x1 --OR source and dest pixels
+ _blit.BXOR = 0x2 --XOR source and dest pixels
+ _blit.BNOR = 0x3 --(NOT) (source OR dest pixels)
+ _blit.BSNOR = 0x4 --(NOT source) OR dest pixels
+ _blit.BAND = 0x5 --AND source and dest pixels
+ _blit.BNAND = 0x6 --(NOT) AND source and dest pixels
+ _blit.BNOT = 0x7 --NOT source and dest pixels
+ --blit functions for masks
+ _blit.BSAND = 0x8 --copy color to dest if source pixel <> 0
+ _blit.BSNOT = 0x9 --copy color to dest if source pixel == 0
+ --blit functions for masks with colors
+ _blit.BSORC = 0xA --copy source pixel or color
+ _blit.BSXORC = 0xB --copy source pixel xor color
+ _blit.BNSORC = 0xC --copy ~(src_val | clr)
+ _blit.BSORNC = 0xD --copy src_val | (~clr)
+ _blit.BSANDC = 0xE --copy src_val & clr;
+ _blit.BNSANDC = 0xF --copy (~src_val) & clr
+ _blit.BDORNSORC = 0x10 --copy dst | (~src_val) | clr
+ _blit.BXORSADXORC = 0x11 --copy dst ^ (src_val & (dst_val ^ clr))
+
+ _blit.BSNEC = 0x12 --copy source pixel if source <> color
+ _blit.BSEQC = 0x13 --copy source pixel if source == color
+ _blit.BSGTC = 0x14 --copy source pixel if source > color
+ _blit.BSLTC = 0x15 --copy source pixel if source < color
+ _blit.BDNEC = 0x16 --copy source pixel if dest <> color
+ _blit.BDEQC = 0x17 --copy source pixel if dest == color
+ _blit.BDGTC = 0x18 --copy source pixel if dest > color
+ _blit.BDLTC = 0x19 --copy source pixel if dest < color
+ _blit.BDNES = 0x1A --copy color to dest if dest <> source pixel
+ _blit.BDEQS = 0x1B --copy color to dest if dest == source pixel
+ _blit.BDGTS = 0x1C --copy color to dest if dest > source pixel
+ _blit.BDLTS = 0x1D --copy color to dest if dest < source pixel
+ --Source unused for these blits
+ _blit.BCOPYC = 0x1E --copy color
+ _blit.BORC = 0x1F --OR dest and color
+ _blit.BXORC = 0x20 --XOR dest and color
+ _blit.BNDORC = 0x21 --~(dst_val | clr)
+ _blit.BDORNC = 0x22 --dst_val | (~clr)
+ _blit.BANDC = 0x23 --AND dest and color
+ _blit.BNDANDC = 0x24 --copy (~dst_val) & clr
+ _blit.BDLTS = 0x25 --dest NOT color
+end -- _blit operations
+
+return _blit
diff --git a/apps/plugins/lua/include_lua/color.lua b/apps/plugins/lua/include_lua/color.lua
new file mode 100644
index 0000000000..267728e072
--- /dev/null
+++ b/apps/plugins/lua/include_lua/color.lua
@@ -0,0 +1,112 @@
+--[[ Lua Color functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _clr.inc
+ _clr.set
+
+-- Exposed Constants
+ IS_COLOR_TARGET
+
+]]
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+IS_COLOR_TARGET = false
+-- Only true when we're on a color target, i.e. when LCD_RGBPACK is available
+if rb.lcd_rgbpack ~= _NIL then
+ IS_COLOR_TARGET = true
+end
+
+local _clr = {} do
+
+ -- Internal Constants
+ local _NIL = nil -- _NIL placeholder
+
+ local maxstate = (bit.lshift(1, rb.LCD_DEPTH) - 1)
+
+ local function init(v)
+ return v or 0
+ end
+
+ -- clamps value to >= min and <= max rolls over to opposite
+ local function clamp_roll(val, min, max)
+ if min > max then
+ local swap = min
+ min, max = max, swap
+ end
+
+ if val < min then
+ val = max
+ elseif val > max then
+ val = min
+ end
+
+ return val
+ end
+
+ -- sets color -- monochrome / greyscale use 'set' -- color targets 'r,b,g'
+ -- on monochrome/ greyscale targets:
+ -- '-1' sets the highest 'color' state & 0 is the minimum 'color' state
+ local function clrset(set, r, g, b)
+ local color = set or 0
+
+ if IS_COLOR_TARGET then
+ if (r ~= _NIL or g ~= _NIL or b ~= _NIL) then
+ r, g, b = init(r), init(g), init(b)
+ color = rb.lcd_rgbpack(r, g, b)
+ end
+ end
+
+ return clamp_roll(color, 0, maxstate)
+ end -- clrset
+
+ -- de/increments current color by 'inc' -- optionally color targets by 'r,g,b'
+ local function clrinc(current, inc, r, g, b)
+ local color = 0
+ current = current or color
+ inc = inc or 1
+
+ if IS_COLOR_TARGET then
+ local ru, gu, bu = rb.lcd_rgbunpack(current);
+ if (r ~= _NIL or g ~= _NIL or b ~= _NIL) then
+ r, g, b = init(r), init(g), init(b)
+ ru = ru + r; gu = gu + g; bu = bu + b
+ color = rb.lcd_rgbpack(ru, gu, bu)
+ else
+ ru = ru + inc; gu = gu + inc; bu = bu + inc
+ color = rb.lcd_rgbpack(ru, gu, bu)
+ end
+ else
+ color = current + inc
+ end
+
+ return clamp_roll(color, 0, maxstate)
+ end -- clrinc
+
+ -- expose functions to the outside through _clr table
+ _clr.set = clrset
+ _clr.inc = clrinc
+end -- color functions
+
+return _clr
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 @@
+--[[ Lua Drawing functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _draw.circle
+ _draw.circle_filled
+ _draw.ellipse
+ _draw.ellipse_filled
+ _draw.ellipse_rect_filled
+ _draw.ellipse_rect
+ _draw.flood_fill
+ _draw.hline
+ _draw.image
+ _draw.line
+ _draw.polygon
+ _draw.polyline
+ _draw.rect
+ _draw.rect_filled
+ _draw.rounded_rect
+ _draw.rounded_rect_filled
+ _draw.text
+ _draw.vline
+
+]]
+
+--[[ bClip allows drawing out of bounds without raising an error it is slower
+ than having a correctly bounded figure, but can be helpful in some cases..
+]]
+
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+local _draw = {} do
+
+ -- Internal Constants
+ local _LCD = rb.lcd_framebuffer()
+ local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
+ local BSAND = 8 -- blits color to dst if src <> 0
+ local _NIL = nil -- nil placeholder
+
+ local function set_viewport(vp)
+ if not vp then rb.set_viewport() return end
+ if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
+ --vp.drawmode = bit.bxor(vp.drawmode, 4)
+ vp.fg_pattern = 3 - vp.fg_pattern
+ vp.bg_pattern = 3 - vp.bg_pattern
+ end
+ rb.set_viewport(vp)
+ end
+
+ -- line
+ local function line(img, x1, y1, x2, y2, color, bClip)
+ img:line(x1, y1, x2, y2, color, bClip)
+ end
+
+ -- horizontal line; x, y define start point; length in horizontal direction
+ local function hline(img, x, y , length, color, bClip)
+ img:line(x, y, x + length, _NIL, color, bClip)
+ end
+
+ -- vertical line; x, y define start point; length in vertical direction
+ local function vline(img, x, y , length, color, bClip)
+ img:line(x, y, _NIL, y + length, color, bClip)
+ end
+
+ -- draws a non-filled figure based on points in t-points
+ local function polyline(img, x, y, t_points, color, bClosed, bClip)
+ if #t_points < 2 then error("not enough points", 3) end
+
+ local pt_first_last
+
+ if bClosed then
+ pt_first_last = t_points[1]
+ else
+ pt_first_last = t_points[#t_points]
+ end
+
+ for i = 1, #t_points, 1 do
+ local pt1 = t_points[i]
+
+ local pt2 = t_points[i + 1] or pt_first_last-- first and last point
+
+ img:line(pt1[1] + x, pt1[2] + y, pt2[1]+x, pt2[2]+y, color, bClip)
+ end
+
+ end
+
+ -- rectangle
+ local function rect(img, x, y, width, height, color, bClip)
+ if width == 0 or height == 0 then return end
+
+ local ppt = {{0, 0}, {width, 0}, {width, height}, {0, height}}
+ polyline(img, x, y, ppt, color, true, bClip)
+ --[[
+ vline(img, x, y, height, color, bClip);
+ vline(img, x + width, y, height, color, bClip);
+ hline(img, x, y, width, color, bClip);
+ hline(img, x, y + height, width, color, bClip);]]
+ end
+
+ -- filled rect, fillcolor is color if left empty
+ local function rect_filled(img, x, y, width, height, color, fillcolor, bClip)
+ if width == 0 or height == 0 then return end
+
+ if not fillcolor then
+ img:clear(color, x, y, x + width, y + height, bClip)
+ else
+ img:clear(fillcolor, x, y, x + width, y + height, bClip)
+ rect(img, x, y, width, height, color, bClip)
+ end
+ end
+
+ -- circle cx,cy define center point
+ local function circle(img, cx, cy, radius, color, bClip)
+ local r = radius
+ img:ellipse(cx - r, cy - r, cx + r, cy + r, color, _NIL, bClip)
+ end
+
+ -- filled circle cx,cy define center, fillcolor is color if left empty
+ local function circle_filled(img, cx, cy, radius, color, fillcolor, bClip)
+ fillcolor = fillcolor or color
+ local r = radius
+ img:ellipse(cx - r, cy - r, cx + r, cy + r, color, fillcolor, bClip)
+ end
+
+ -- ellipse that fits into defined rect
+ local function ellipse_rect(img, x1, y1, x2, y2, color, bClip)
+ img:ellipse(x1, y1, x2, y2, color, _NIL, bClip)
+ end
+
+ --ellipse that fits into defined rect, fillcolor is color if left empty
+ local function ellipse_rect_filled(img, x1, y1, x2, y2, color, fillcolor, bClip)
+ if not fillcolor then fillcolor = color end
+
+ img:ellipse(x1, y1, x2, y2, color, fillcolor, bClip)
+ end
+
+ -- ellipse cx, cy define center point; a, b the major/minor axis
+ local function ellipse(img, cx, cy, a, b, color, bClip)
+ img:ellipse(cx - a, cy - b, cx + a, cy + b, color, _NIL, bClip)
+ end
+
+ -- filled ellipse cx, cy define center point; a, b the major/minor axis
+ -- fillcolor is color if left empty
+ local function ellipse_filled(img, cx, cy, a, b, color, fillcolor, bClip)
+ if not fillcolor then fillcolor = color end
+
+ img:ellipse(cx - a, cy - b, cx + a, cy + b, color, fillcolor, bClip)
+ end
+
+ -- rounded rectangle
+ local function rounded_rect(img, x, y, w, h, radius, color, bClip)
+ local c_img
+
+ local function blit(dx, dy, sx, sy, ox, oy)
+ img:copy(c_img, dx, dy, sx, sy, ox, oy, bClip, BSAND, color)
+ end
+
+ if w == 0 or h == 0 then return end
+
+ -- limit the radius of the circle otherwise it will overtake the rect
+ radius = math.min(w / 2, radius)
+ radius = math.min(h / 2, radius)
+
+ local r = radius
+
+ c_img = rb.new_image(r * 2 + 1, r * 2 + 1)
+ c_img:clear(0)
+ circle(c_img, r + 1, r + 1, r, 0xFFFFFF)
+
+ -- copy 4 pieces of circle to their respective corners
+ blit(x, y, _NIL, _NIL, r + 1, r + 1) --TL
+ blit(x + w - r - 2, y, r, _NIL, r + 1, r + 1) --TR
+ blit(x , y + h - r - 2, _NIL, r, r + 1, _NIL) --BL
+ blit(x + w - r - 2, y + h - r - 2, r, r, r + 1, r + 1)--BR
+ c_img = _NIL
+
+ vline(img, x, y + r, h - r * 2, color, bClip);
+ vline(img, x + w - 1, y + r, h - r * 2, color, bClip);
+ hline(img, x + r, y, w - r * 2, color, bClip);
+ hline(img, x + r, y + h - 1, w - r * 2, color, bClip);
+ end
+
+ -- rounded rectangle fillcolor is color if left empty
+ local function rounded_rect_filled(img, x, y, w, h, radius, color, fillcolor, bClip)
+ local c_img
+
+ local function blit(dx, dy, sx, sy, ox, oy)
+ img:copy(c_img, dx, dy, sx, sy, ox, oy, bClip, BSAND, fillcolor)
+ end
+
+ if w == 0 or h == 0 then return end
+
+ if not fillcolor then fillcolor = color end
+
+ -- limit the radius of the circle otherwise it will overtake the rect
+ radius = math.min(w / 2, radius)
+ radius = math.min(h / 2, radius)
+
+ local r = radius
+
+ c_img = rb.new_image(r * 2 + 1, r * 2 + 1)
+ c_img:clear(0)
+ circle_filled(c_img, r + 1, r + 1, r, fillcolor)
+
+ -- copy 4 pieces of circle to their respective corners
+ blit(x, y, _NIL, _NIL, r + 1, r + 1) --TL
+ blit(x + w - r - 2, y, r, _NIL, r + 1, r + 1) --TR
+ blit(x, y + h - r - 2, _NIL, r, r + 1, _NIL) --BL
+ blit(x + w - r - 2, y + h - r - 2, r, r, r + 1, r + 1) --BR
+ c_img = _NIL
+
+ -- finish filling areas circles didn't cover
+ img:clear(fillcolor, x + r, y, x + w - r, y + h - 1, bClip)
+ img:clear(fillcolor, x, y + r, x + r, y + h - r, bClip)
+ img:clear(fillcolor, x + w - r, y + r, x + w - 1, y + h - r - 1, bClip)
+
+ if fillcolor ~= color then
+ rounded_rect(img, x, y, w, h, r, color, bClip)
+ end
+ end
+
+ -- draws an image at xy coord in dest image
+ local function image(dst, src, x, y, bClip)
+ if not src then --make sure an image was passed, otherwise bail
+ rb.splash(rb.HZ, "No Image!")
+ return _NIL
+ end
+
+ dst:copy(src, x, y, 1, 1, _NIL, _NIL, bClip)
+ end
+
+ -- floods an area of targetclr with fillclr x, y specifies the start seed
+ function flood_fill(img, x, y, targetclr, fillclr)
+ -- scanline 4-way flood algorithm
+ -- ^
+ -- <--------x--->
+ -- v
+ -- check that target color doesn't = fill and the first point is target color
+ if targetclr == fillclr or targetclr ~= img:get(x,y, true) then return end
+ local max_w = img:width()
+ local max_h = img:height()
+
+ local qpt = {} -- FIFO queue
+ -- rather than moving elements around in our FIFO queue
+ -- for each read; increment 'qhead' by 2
+ -- set both elements to nil and let the
+ -- garbage collector worry about it
+ -- for each write; increment 'qtail' by 2
+ -- x coordinates are in odd indices while
+ -- y coordinates are in even indices
+
+ local qtail = 0
+ local iter_n; -- North iteration
+ local iter_s; -- South iteration
+
+ local function check_ns(val, x, y)
+ if targetclr == val then
+ if targetclr == iter_n() then
+ qtail = qtail + 2
+ qpt[qtail - 1] = x
+ qpt[qtail] = (y - 1)
+ end
+
+ if targetclr == iter_s() then
+ qtail = qtail + 2
+ qpt[qtail - 1] = x
+ qpt[qtail] = (y + 1)
+ end
+ return fillclr
+ end
+ return _NIL -- signal marshal to stop
+ end
+
+ local function seed_pt(x, y)
+ -- will never hit max_w * max_h^2 but make sure not to end early
+ for qhead = 2, max_w * max_h * max_w * max_h, 2 do
+
+ if targetclr == img:get(x, y, true) then
+ iter_n = img:points(x, y - 1, 1, y - 1)
+ iter_s = img:points(x, y + 1, 1, y + 1)
+ img:marshal(x, y, 1, y, _NIL, _NIL, true, check_ns)
+
+ iter_n = img:points(x + 1, y - 1, max_w, y - 1)
+ iter_s = img:points(x + 1, y + 1, max_w, y + 1)
+ img:marshal(x + 1, y, max_w, y, _NIL, _NIL, true, check_ns)
+ end
+
+ x = qpt[qhead - 1]
+ qpt[qhead - 1] = _NIL
+
+ if not x then break end
+
+ y = qpt[qhead]
+ qpt[qhead] = _NIL
+ end
+ end
+
+ seed_pt(x, y) -- Begin
+ end -- flood_fill
+
+ -- draws a closed figure based on points in t_points
+ local function polygon(img, x, y, t_points, color, fillcolor, bClip)
+ if #t_points < 2 then error("not enough points", 3) end
+
+ if fillcolor then
+ local x_min, x_max = 0, 0
+ local y_min, y_max = 0, 0
+ local w, h = 0, 0
+ -- find boundries of polygon
+ for i = 1, #t_points, 1 do
+ local pt = t_points[i]
+ if pt[1] < x_min then x_min = pt[1] end
+ if pt[1] > x_max then x_max = pt[1] end
+ if pt[2] < y_min then y_min = pt[2] end
+ if pt[2] > y_max then y_max = pt[2] end
+ end
+ w = math.abs(x_max) + math.abs(x_min)
+ h = math.abs(y_max) + math.abs(y_min)
+ x_min = x_min - 2 -- leave a border to use flood_fill
+ y_min = y_min - 2
+
+ local fill_img = rb.new_image(w + 3, h + 3)
+ fill_img:clear(0xFFFFFF)
+
+ for i = 1, #t_points, 1 do
+ local pt1 = t_points[i]
+ local pt2 = t_points[i + 1] or t_points[1]-- first and last point
+ fill_img:line(pt1[1] - x_min, pt1[2] - y_min,
+ pt2[1]- x_min, pt2[2] - y_min, 0)
+
+ end
+ flood_fill(fill_img, fill_img:width(), fill_img:height() , 1, 0)
+ img:copy(fill_img, x - 1, y - 1, _NIL, _NIL, _NIL, _NIL, bClip, BSAND, fillcolor)
+ end
+
+ polyline(img, x, y, t_points, color, true, bClip)
+ end
+
+ -- draw text onto image if width/height are supplied text is centered
+ local function text(img, x, y, width, height, font, color, text)
+ font = font or rb.FONT_UI
+
+ local opts = {x = 0, y = 0, width = LCD_W - 1, height = LCD_H - 1,
+ font = font, drawmode = 3, fg_pattern = 0xFFFFFF, bg_pattern = 0}
+ set_viewport(opts)
+
+ local res, w, h = rb.font_getstringsize(text, font)
+
+ if not width then
+ width = 0
+ else
+ width = (width - w) / 2
+ end
+
+ if not height then
+ height = 0
+ else
+ height = (height - h) / 2
+ end
+
+ -- make a copy of the current screen for later
+ local screen_img = rb.new_image(LCD_W, LCD_H)
+ screen_img:copy(_LCD)
+
+ -- check if the screen buffer is supplied image if so set img to the copy
+ if img == _LCD then
+ img = screen_img
+ end
+
+ -- we will be printing the text to the screen then blitting into img
+ rb.lcd_clear_display()
+
+ local function blit(dx, dy)
+ img:copy(_LCD, dx, dy, _NIL, _NIL, _NIL, _NIL, false, BSAND, color)
+ end
+
+ if w > LCD_W then -- text is too long for the screen do it in chunks
+ local l = 1
+ local resp, wp, hp
+ local lenr = text:len()
+
+ while lenr > 1 do
+ l = lenr
+ resp, wp, hp = rb.font_getstringsize(text:sub(1, l), font)
+
+ while wp >= LCD_W and l > 1 do
+ l = l - 1
+ resp, wp, hp = rb.font_getstringsize(text:sub( 1, l), font)
+ end
+
+ rb.lcd_putsxy(0, 0, text:sub(1, l))
+ text = text:sub(l)
+
+ if x + width > img:width() or y + height > img:height() then
+ break
+ end
+
+ -- using the mask we made blit color into img
+ blit(x + width, y + height)
+ x = x + wp
+ rb.lcd_clear_display()
+ lenr = text:len()
+ end
+ else --w <= LCD_W
+ rb.lcd_putsxy(0, 0, text)
+
+ -- using the mask we made blit color into img
+ blit(x + width, y + height)
+ end
+
+ _LCD:copy(screen_img) -- restore screen
+ set_viewport() -- set viewport default
+ return res, w, h
+ end
+
+ -- expose functions to the outside through _draw table
+ _draw.image = image
+ _draw.text = text
+ _draw.line = line
+ _draw.hline = hline
+ _draw.vline = vline
+ _draw.polygon = polygon
+ _draw.polyline = polyline
+ _draw.rect = rect
+ _draw.circle = circle
+ _draw.ellipse = ellipse
+ _draw.flood_fill = flood_fill
+ _draw.ellipse_rect = ellipse_rect
+ _draw.rounded_rect = rounded_rect
+ -- filled functions use color as fillcolor if fillcolor is left empty...
+ _draw.rect_filled = rect_filled
+ _draw.circle_filled = circle_filled
+ _draw.ellipse_filled = ellipse_filled
+ _draw.ellipse_rect_filled = ellipse_rect_filled
+ _draw.rounded_rect_filled = rounded_rect_filled
+
+ -- adds the above _draw functions into the metatable for RLI_IMAGE
+ local ex = getmetatable(rb.lcd_framebuffer())
+ for k, v in pairs(_draw) do
+ if ex[k] == _NIL then
+ ex[k] = v
+ end
+ end
+
+end -- _draw functions
+
+return _draw
diff --git a/apps/plugins/lua/include_lua/image.lua b/apps/plugins/lua/include_lua/image.lua
new file mode 100644
index 0000000000..0cf3f370e5
--- /dev/null
+++ b/apps/plugins/lua/include_lua/image.lua
@@ -0,0 +1,391 @@
+--[[ Lua Image functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _img.save
+ _img.search
+ _img.rotate
+ _img.resize
+ _img.tile
+ _img.new
+ _img.load
+
+-- Exposed Constants
+ _img.RLI_INFO_ALL
+ _img.RLI_INFO_TYPE
+ _img.RLI_INFO_WIDTH
+ _img.RLI_INFO_HEIGHT
+ _img.RLI_INFO_ELEMS
+ _img.RLI_INFO_BYTES
+ _img.RLI_INFO_DEPTH
+ _img.RLI_INFO_FORMAT
+ _img.RLI_INFO_ADDRESS
+
+]]
+
+--[[Other rbimage Functions:
+--------------------------------------------------------------------------------
+img:_len() or #img -- returns number of pixels in image
+
+img:__tostring([item]) or tostring(img) -- returns data about the image item = 0
+ is the same as tostring(img) otherwise
+ item = 1 is the first item in list
+ item = 7 is the 7th item
+ item = 8 is the data address in hex
+ -- See Constants _img.RLI_INFO_....
+
+img:_data(element) -- returns/sets raw pixel data
+ NOTE!! this data is defined by the target and targets with
+ different color depth, bit packing, etc will not be
+ compatible with the same image's data on another target
+]]
+--------------------------------------------------------------------------------
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+local _img = {} do
+
+ -- internal constants
+ local _NIL = nil -- _NIL placeholder
+ local _math = require("math_ex") -- math functions needed
+ local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
+
+ -- returns new image -of- img sized to fit w/h tiling to fit if needed
+ local function tile(img, w, h)
+ local hs , ws = img:height(), img:width()
+ local t_img = rb.new_image(w, h)
+
+ for x = 1, w, ws do t_img:copy(img, x, 1, 1, 1) end
+ for y = hs, h, hs do t_img:copy(t_img, 1, y, 1, 1, w, hs) end
+ return t_img
+ end
+
+ -- resizes src to size of dst
+ local function resize(dst, src)
+ -- simple nearest neighbor resize derived from rockbox - pluginlib_bmp.c
+ -- pretty rough results highly recommend building one more suited..
+ local dw, dh = dst:width(), dst:height()
+
+ local xstep = (bit.lshift(src:width(),8) / (dw)) + 1
+ local ystep = (bit.lshift(src:height(),8) / (dh))
+
+ local xpos, ypos = 0, 0
+ local src_x, src_y
+
+ -- walk the dest get src pixel
+ function rsz_trans(val, x, y)
+ if x == 1 then
+ src_y = bit.rshift(ypos,8) + 1
+ xpos = xstep - bit.rshift(xstep,4) + 1
+ ypos = ypos + ystep;
+ end
+ src_x = bit.rshift(xpos,8) + 1
+ xpos = xpos + xstep
+ return (src:get(src_x, src_y, true) or 0)
+ end
+ --/* (dst*, [x1, y1, x2, y2, dx, dy, clip, function]) */
+ dst:marshal(1, 1, dw, dh, _NIL, _NIL, false, rsz_trans)
+ end
+
+ -- returns new image -of- img rotated in whole degrees 0 - 360
+ local function rotate(img, degrees)
+ -- we do this backwards as if dest was the unrotated object
+ degrees = 360 - degrees
+ local c, s = _math.d_cos(degrees), _math.d_sin(degrees)
+
+ -- get the center of the source image
+ local s_xctr, s_yctr = img:width() / 2, img:height() / 2
+
+ -- get the the new center of the dest image at rotation angle
+ local d_xctr = ((math.abs(s_xctr * c) + math.abs(s_yctr * s))/ 10000) + 1
+ local d_yctr = ((math.abs(s_xctr * s) + math.abs(s_yctr * c))/ 10000) + 1
+
+ -- calculate size of rect new image will occupy
+ local dw, dh = d_xctr * 2 - 1, d_yctr * 2 - 1
+
+ local r_img = rb.new_image(dw, dh)
+ -- r_img:clear() -- doesn't need cleared as we walk every pixel
+
+ --[[rotation works on origin of 0,0 we need to offset to the center of the
+ image and then place the upper left back at the origin (0, 0)]]
+ --[[0,0|-----| ^< |-------| v> 0,0|-------|
+ | | | 0,0 | | |
+ |_____| |_______| |_______| ]]
+
+ -- walk the dest get translated src pixel, oversamples src to fill gaps
+ function rot_trans(val, x, y)
+ -- move center x/y to the origin
+ local xtran = x - d_xctr;
+ local ytran = y - d_yctr;
+
+ -- rotate about the center of the image by x degrees
+ local yrot = ((xtran * s) + (ytran * c)) / 10000 + s_yctr
+ local xrot = ((xtran * c) - (ytran * s)) / 10000 + s_xctr
+ -- upper left of src image back to origin, copy src pixel
+ return img:get(xrot, yrot, true) or 0
+ end
+ r_img:marshal(1, 1, dw, dh, _NIL, _NIL, false, rot_trans)
+ return r_img
+ end
+
+ -- saves img to file: name
+ local function save(img, name)
+ -- bmp saving derived from rockbox - screendump.c
+ -- bitdepth is limited by the device
+ -- eg. device displays greyscale, rgb images are saved greyscale
+ local file
+
+ local fbuffer = {} -- concat buffer for file writes, reused
+
+ local function dump_fbuffer(thresh)
+ if #fbuffer >= thresh then
+ file:write(table.concat(fbuffer))
+ for i=1, #fbuffer do fbuffer[i] = _NIL end -- reuse table
+ end
+ end
+
+ local function s_bytesLE(bits, value)
+ -- bits must be multiples of 8 (sizeof byte)
+ local byte
+ local result = ""
+ for b = 1, bit.rshift(bits, 3) do
+ if value > 0 then
+ byte = value % 256
+ value = (value - byte) / 256
+ result = result .. string.char(byte)
+ else
+ result = result .. string.char(0)
+ end
+ end
+ return result
+ end
+
+ local function s_bytesBE(bits, value)
+ -- bits must be multiples of 8 (sizeof byte)
+ local byte
+ local result = ""
+ for b = 1, bit.rshift(bits, 3) do
+ if value > 0 then
+ byte = value % 256
+ value = (value - byte) / 256
+ result = string.char(byte) .. result
+ else
+ result = string.char(0) .. result
+ end
+ end
+ return result
+ end
+
+ local function c_cmp(color, shift)
+ -- [RR][GG][BB]
+ return bit.band(bit.rshift(color, shift), 0xFF)
+ end
+
+ local cmp = {["r"] = function(c) return c_cmp(c, 16) end,
+ ["g"] = function(c) return c_cmp(c, 08) end,
+ ["b"] = function(c) return c_cmp(c, 00) end}
+
+ local function bmp_color(color)
+ return s_bytesLE(8, cmp.b(color))..
+ s_bytesLE(8, cmp.g(color))..
+ s_bytesLE(8, cmp.r(color))..
+ s_bytesLE(8, 0) .. ""
+ end -- c_cmp(color, c.r))
+
+ local function bmp_color_mix(c1, c2, num, den)
+ -- mixes c1 and c2 as ratio of numerator / denominator
+ -- used 2x each save results
+ local bc1, gc1, rc1 = cmp.b(c1), cmp.g(c1), cmp.r(c1)
+
+ return s_bytesLE(8, cmp.b(c2) - bc1 * num / den + bc1)..
+ s_bytesLE(8, cmp.g(c2) - gc1 * num / den + gc1)..
+ s_bytesLE(8, cmp.r(c2) - rc1 * num / den + rc1)..
+ s_bytesLE(8, 0) .. ""
+ end
+
+ local w, h = img:width(), img:height()
+ local depth = tonumber(img:__tostring(6)) -- RLI_INFO_DEPTH = 0x6
+ local format = tonumber(img:__tostring(7)) -- RLI_INFO_FORMAT = 0x7
+
+ local bpp, bypl -- bits per pixel, bytes per line
+ -- bypl, pad rows to a multiple of 4 bytes
+ if depth <= 4 then
+ bpp = 8 -- 256 color image
+ bypl = (w + 3)
+ elseif depth <= 16 then
+ bpp = 16
+ bypl = (w * 2 + 3)
+ else
+ bpp = 24
+ bypl = (w * 3 + 3)
+ end
+
+ local linebytes = bit.band(bypl, bit.bnot(3))
+
+ local bytesperpixel = bit.rshift(bpp, 3)
+ local headersz = 54
+ local imgszpad = h * linebytes
+
+ local compression, n_colors = 0, 0
+ local h_ppm, v_ppm = 0x00000EC4, 0x00000EC4 --Pixels Per Meter ~ 96 dpi
+
+ if depth == 16 then
+ compression = 3 -- BITFIELDS
+ n_colors = 3
+ elseif depth <= 8 then
+ n_colors = bit.lshift(1, depth)
+ end
+
+ headersz = headersz + (4 * n_colors)
+
+ file = io.open('/' .. name, "w+") -- overwrite, rb ignores the 'b' flag
+
+ if not file then
+ rb.splash(rb.HZ, "Error opening /" .. name)
+ return
+ end
+ -- create a bitmap header 'rope' with image details -- concatenated at end
+ local bmpheader = fbuffer
+
+ bmpheader[01] = "BM"
+ bmpheader[02] = s_bytesLE(32, headersz + imgszpad)
+ bmpheader[03] = "\0\0\0\0" -- WORD reserved 1 & 2
+ bmpheader[04] = s_bytesLE(32, headersz) -- BITMAPCOREHEADER size
+ bmpheader[05] = s_bytesLE(32, 40) -- BITMAPINFOHEADER size
+
+ bmpheader[06] = s_bytesLE(32, w)
+ bmpheader[07] = s_bytesLE(32, h)
+ bmpheader[08] = "\1\0" -- WORD color planes ALWAYS 1
+ bmpheader[09] = s_bytesLE(16, bpp) -- bits/pixel
+ bmpheader[10] = s_bytesLE(32, compression)
+ bmpheader[11] = s_bytesLE(32, imgszpad)
+ bmpheader[12] = s_bytesLE(32, h_ppm) -- biXPelsPerMeter
+ bmpheader[13] = s_bytesLE(32, v_ppm) -- biYPelsPerMeter
+ bmpheader[14] = s_bytesLE(32, n_colors)
+ bmpheader[15] = s_bytesLE(32, n_colors)
+
+ -- Color Table (#n_colors entries)
+ if depth == 1 then -- assuming positive display
+ bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
+ bmpheader[#bmpheader + 1] = bmp_color(0x0)
+ elseif depth == 2 then
+ bmpheader[#bmpheader + 1] = bmp_color(0xFFFFFF)
+ bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 1, 3)
+ bmpheader[#bmpheader + 1] = bmp_color_mix(0xFFFFFF, 0, 2, 3)
+ bmpheader[#bmpheader + 1] = bmp_color(0x0)
+ elseif depth == 16 then
+ -- red bitfield mask
+ bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000F800)
+ -- green bitfield mask
+ bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x000007E0)
+ -- blue bitfield mask
+ bmpheader[#bmpheader + 1] = s_bytesLE(32, 0x0000001F)
+ end
+
+ dump_fbuffer(0) -- write the header to the file now
+
+ local imgdata = fbuffer
+ -- pad rows to a multiple of 4 bytes
+ local bytesleft = linebytes - (bytesperpixel * w)
+ local t_data = {}
+ local fs_bytes_E = s_bytesLE -- default save in Little Endian
+
+ if format == 3553 then -- RGB565SWAPPED
+ fs_bytes_E = s_bytesBE -- Saves in Big Endian
+ end
+
+ -- Bitmap lines start at bottom unless biHeight is negative
+ for point in img:points(1, h, w + bytesleft, 1) do
+ imgdata[#imgdata + 1] = fs_bytes_E(bpp, point or 0)
+ dump_fbuffer(31) -- buffered write, increase # for performance
+ end
+
+ dump_fbuffer(0) --write leftovers to file
+
+ file:close()
+ end -- save(img, name)
+
+ --searches an image for target color
+ local function search(img, x1, y1, x2, y2, targetclr, variation, stepx, stepy)
+
+ if variation > 128 then variation = 128 end
+ if variation < -128 then variation = -128 end
+
+ local targeth = targetclr + variation
+ local targetl = targetclr - variation
+
+ if targeth < targetl then
+ local swap = targeth
+ targeth = targetl
+ targetl = swap
+ end
+
+ for point, x, y in img:points(x1, y1, x2, y2, stepx, stepy) do
+ if point >= targetl and point <= targeth then
+ return point, x, y
+ end
+ end
+ return nil, nil, nil
+ end
+
+ --[[ we won't be extending these into RLI_IMAGE]]
+ -- creates a new rbimage size w x h
+ local function new(w, h)
+ return rb.new_image(w, h)
+ end
+
+ -- returns new image -of- file: name (_NIL if error)
+ local function load(name)
+ return rb.read_bmp_file("/" .. name)
+ end
+
+ -- expose tostring constants to outside through _img table
+ _img.RLI_INFO_ALL = 0x0
+ _img.RLI_INFO_TYPE = 0x1
+ _img.RLI_INFO_WIDTH = 0x2
+ _img.RLI_INFO_HEIGHT = 0x3
+ _img.RLI_INFO_ELEMS = 0x4
+ _img.RLI_INFO_BYTES = 0x5
+ _img.RLI_INFO_DEPTH = 0x6
+ _img.RLI_INFO_FORMAT = 0x7
+ _img.RLI_INFO_ADDRESS = 0x8
+
+ -- expose functions to the outside through _img table
+ _img.save = save
+ _img.search = search
+ _img.rotate = rotate
+ _img.resize = resize
+ _img.tile = tile
+
+ -- adds the above _img functions into the metatable for RLI_IMAGE
+ local ex = getmetatable(rb.lcd_framebuffer())
+ for k, v in pairs(_img) do
+ if ex[k] == _NIL then ex[k] = v end
+ end
+ -- not exposed through RLI_IMAGE
+ _img.new = new
+ _img.load = load
+
+end -- _img functions
+
+return _img
diff --git a/apps/plugins/lua/include_lua/lcd.lua b/apps/plugins/lua/include_lua/lcd.lua
new file mode 100644
index 0000000000..62fa988ec1
--- /dev/null
+++ b/apps/plugins/lua/include_lua/lcd.lua
@@ -0,0 +1,153 @@
+--[[ Lua LCD Wrapper functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _lcd.clear
+ _lcd.duplicate
+ _lcd.image
+ _lcd.set_viewport
+ _lcd.splashf
+ _lcd.text_extent
+ _lcd.update
+ _lcd.update_rect
+
+-- Exposed Constants
+ _lcd.CX
+ _lcd.CY
+ _lcd.DEPTH
+ _lcd.W
+ _lcd.H
+
+ _lcd
+ _LCD
+
+]]
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+_LCD = rb.lcd_framebuffer()
+
+local _lcd = {} do
+
+ --internal constants
+ local _NIL = nil -- _NIL placeholder
+ local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
+
+ -- clamps value to >= min and <= max
+ local function clamp(val, min, max)
+ -- Warning doesn't check if min < max
+ if val < min then
+ return min
+ elseif val < max then
+ return val
+ end
+ return max
+ end
+
+ -- return a copy of lcd screen
+ local function duplicate(t, screen_img)
+ screen_img = screen_img or rb.new_image()
+ screen_img:copy(rb.lcd_framebuffer())
+ return screen_img
+ end
+
+ -- updates screen in specified rectangle
+ local function update_rect(t, x, y, w, h)
+ rb.lcd_update_rect(x - 1, y - 1,
+ clamp(x + w, 1, LCD_W) - 1,
+ clamp(y + h, 1, LCD_H) - 1)
+ end
+
+ -- clears lcd, optional.. ([color, x1, y1, x2, y2, clip])
+ local function clear(t, clr, ...)
+ if clr == _NIL and ... == _NIL then
+ rb.lcd_clear_display()
+ else
+ rb.lcd_scroll_stop() --rb really doesn't like bg change while scroll
+ _LCD:clear(clr, ...)
+ end
+ end
+
+ -- loads an image to the screen
+ local function image(t, src, x, y)
+ if not src then --make sure an image was passed, otherwise bail
+ rb.splash(rb.HZ, "No Image!")
+ return _NIL
+ end
+ _LCD:copy(src,x,y,1,1)
+ end
+
+ -- Formattable version of splash
+ local function splashf(t, timeout, ...)
+ rb.splash(timeout, string.format(...))
+ end
+
+ -- Gets size of text
+ local function text_extent(t, msg, font)
+ font = font or rb.FONT_UI
+
+ return rb.font_getstringsize(msg, font)
+ end
+
+ -- Sets viewport size
+ local function set_viewport(t, vp)
+ if not vp then rb.set_viewport() return end
+ if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
+ --vp.drawmode = bit.bxor(vp.drawmode, 4)
+ vp.fg_pattern = 3 - vp.fg_pattern
+ vp.bg_pattern = 3 - vp.bg_pattern
+ end
+ rb.set_viewport(vp)
+ end
+
+ -- allows the use of _lcd() as a identifier for the screen
+ local function index(k, v)
+ return function(x, ...)
+ _LCD[v](_LCD, ...)
+ end
+ end
+
+ -- allows the use of _lcd() as a identifier for the screen
+ local function call()
+ return rb.lcd_framebuffer()
+ end
+
+ --expose functions to the outside through _lcd table
+ _lcd.text_extent = text_extent
+ _lcd.set_viewport = set_viewport
+ _lcd.duplicate = duplicate
+ _lcd.update = rb.lcd_update
+ _lcd.update_rect = update_rect
+ _lcd.clear = clear
+ _lcd.splashf = splashf
+ _lcd.image = image
+ _lcd.DEPTH = rb.LCD_DEPTH
+ _lcd.W = rb.LCD_WIDTH
+ _lcd.H = rb.LCD_HEIGHT
+ _lcd.CX = (rb.LCD_WIDTH / 2)
+ _lcd.CY = (rb.LCD_HEIGHT / 2)
+ _lcd = setmetatable(_lcd,{__index = index, __call = call})
+
+end -- _lcd functions
+
+return _lcd
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..c0bf55c1ad
--- /dev/null
+++ b/apps/plugins/lua/include_lua/math_ex.lua
@@ -0,0 +1,158 @@
+--[[ Lua Missing Math functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _math.clamp
+ _math.clamp_roll
+ _math.d_sin
+ _math.d_cos
+ _math.d_tan
+ _math.i_sqrt
+
+]]
+
+local _math = {} do
+
+ -- internal constants
+ local _NIL = nil -- _NIL placeholder
+
+ -- clamps value to >= min and <= max
+ local function clamp(iVal, iMin, iMax)
+ if iMin > iMax then
+ local swap = iMin
+ iMin, iMax = iMax, swap
+ end
+
+ if iVal < iMin then
+ return iMin
+ elseif iVal < iMax then
+ return iVal
+ end
+
+ return iMax
+ end
+
+ -- clamps value to >= min and <= max rolls over to opposite
+ local function clamp_roll(iVal, iMin, iMax)
+ if iMin > iMax then
+ local swap = iMin
+ iMin, iMax = iMax, swap
+ end
+
+ if iVal < iMin then
+ iVal = iMax
+ elseif iVal > iMax then
+ iVal = iMin
+ end
+
+ return iVal
+ end
+
+ local function i_sqrt(n)
+ -- Newtons square root approximation
+ if n < 2 then return n end
+ local g = n / 2
+ local l = 1
+
+ for c = 1, 25 do -- if l,g haven't converged after 25 iterations quit
+
+ l = (n / g + g)/ 2
+ g = (n / l + l)/ 2
+
+ if g == l then return g end
+ end
+
+ -- check for period-two cycle between g and l
+ if g - l == 1 then
+ return l
+ elseif l - g == 1 then
+ return g
+ end
+
+ return _NIL
+ end
+
+ local function d_sin(iDeg, bExtraPrecision)
+ --[[ values are returned multiplied by 10000
+ II | I 180-90 | 90-0
+ ---(--)--- -------(--)-------
+ III | IV 180-270 | 270-360
+
+ sine is only positive in quadrants I , II => 0 - 180 degrees
+ sine 180-360 degrees is a reflection of sine 0-180
+ Bhaskara I's sine approximation formula isn't overly accurate
+ but it is close enough for rough image work.
+ ]]
+ local sign, x
+ -- no negative angles -10 degrees = 350 degrees
+ if iDeg < 0 then
+ x = 360 + (iDeg % 360)
+ else --keep rotation in 0-360 range
+ x = iDeg % 360
+ end
+
+ -- reflect II & I onto III & IV
+ if x > 180 then
+ sign = -1
+ x = x % 180
+ else
+ sign = 1
+ end
+
+ local x1 = x * (180 - x)
+
+ if bExtraPrecision then -- ~halves the largest errors
+ if x <= 22 or x >= 158 then
+ return sign * 39818 * x1 / (40497 - x1)
+ elseif (x >= 40 and x <= 56) or (x > 124 and x < 140) then
+ return sign * 40002 * x1 / (40450 - x1)
+ elseif (x > 31 and x < 71) or (x > 109 and x < 150) then
+ return sign * 40009 * x1 / (40470 - x1)
+ end
+ end
+
+ --multiplied by 10000 so no decimal in results (RB LUA is integer only)
+ return sign * 40000 * x1 / (40497 - x1)
+ end
+
+ local function d_cos(iDeg, bExtraPrecision)
+ --cos is just sine shifed by 90 degrees CCW
+ return d_sin(90 - iDeg, bExtraPrecision)
+ end
+
+ local function d_tan(iDeg, bExtraPrecision)
+ --tan = sin0 / cos0
+ return (d_sin(iDeg, bExtraPrecision) * 10000 / d_sin(90 - iDeg, bExtraPrecision))
+ end
+
+ --expose functions to the outside through _math table
+ _math.clamp = clamp
+ _math.clamp_roll = clamp_roll
+ _math.i_sqrt = i_sqrt
+ _math.d_sin = d_sin
+ _math.d_cos = d_cos
+ _math.d_tan = d_tan
+end -- missing math functions
+
+return _math
diff --git a/apps/plugins/lua/include_lua/print.lua b/apps/plugins/lua/include_lua/print.lua
new file mode 100644
index 0000000000..0e9be373d8
--- /dev/null
+++ b/apps/plugins/lua/include_lua/print.lua
@@ -0,0 +1,377 @@
+--[[ Lua Print functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _print.clear
+ _print.f
+ _print.opt
+ _print.opt.area
+ _print.opt.autoupdate
+ _print.opt.color
+ _print.opt.column
+ _print.opt.defaults
+ _print.opt.get
+ _print.opt.justify
+ _print.opt.line
+ _print.opt.overflow
+ _print.opt.sel_line
+ _print.opt.set
+
+]]
+
+if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
+
+local _print = {} do
+
+ -- internal constants
+ local _clr = require("color") -- _clr functions required
+
+ local _NIL = nil -- _NIL placeholder
+ local _LCD = rb.lcd_framebuffer()
+ local LCD_W, LCD_H = rb.LCD_WIDTH, rb.LCD_HEIGHT
+ local WHITE = _clr.set(-1, 255, 255, 255)
+ local BLACK = _clr.set(0, 0, 0, 0)
+ local DRMODE_SOLID = 3
+ local col_buf, s_lines = {}, {}
+ local _p_opts = _NIL
+
+-- print internal helper functions
+--------------------------------------------------------------------------------
+ -- clamps value to >= min and <= max
+ local function clamp(val, min, max)
+ -- Warning doesn't check if min < max
+ if val < min then
+ return min
+ elseif val < max then
+ return val
+ end
+ return max
+ end
+
+ -- updates screen in specified rectangle
+ local function update_rect(x, y, w, h)
+ rb.lcd_update_rect(x - 1, y - 1,
+ clamp(x + w, 1, LCD_W) - 1,
+ clamp(y + h, 1, LCD_H) - 1)
+ end
+
+ -- Gets size of text
+ local function text_extent(msg, font)
+ font = font or rb.FONT_UI
+ -- res, w, h
+ return rb.font_getstringsize(msg, font)
+ end
+
+ -- Sets viewport size
+ local function set_viewport(vp)
+ if not vp then rb.set_viewport() return end
+
+ if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
+ --vp.drawmode = bit.bxor(vp.drawmode, 4)
+ vp.fg_pattern = 3 - vp.fg_pattern
+ vp.bg_pattern = 3 - vp.bg_pattern
+ end
+ rb.set_viewport(vp)
+ end
+
+ -- shallow copy of table
+ function table_clone(t)
+ local copy = {}
+ for k, v in pairs(t) do
+ copy[k] = v
+ end
+ return copy
+ end
+
+ -- Updates a single line on the screen
+ local function update_line(enabled, opts, line, h)
+ if enabled ~= true then return end
+ local o = opts or _p_opts
+ update_rect(o.x, o.y + line * h + 1, o.width, h)
+ end
+
+ -- Clears a single line on the screen
+ local function clear_line(opts, line, h)
+ local o = opts or _p_opts
+ _LCD:clear(o.bg_pattern, o.x, o.y + line * h + 1,
+ o.x + o.width, line * h + h + o.y)
+ end
+
+ -- Sets the maximum number of lines on the screen
+ local function max_lines(opts)
+ local h = opts.height
+ local _, _, th = text_extent("W", opts.font)
+ return h / th
+ end
+
+ --saves the items displayed for side to side scroll
+ local function col_buf_insert(msg, line, _p_opts)
+ --if _p_opts.line <= 1 then col_buf = {} end
+ if not col_buf[line] then
+ table.insert(col_buf, line, msg) end
+ end
+
+ --replaces / strips escape characters
+ local function check_escapes(o, msg)
+ local tabsz = 2
+ local tabstr = string.rep(" ", tabsz)
+
+ local function repl(esc)
+ local ret = ""
+ if esc:sub(1,1) == "\t" then ret = string.rep(tabstr, esc:len()) end
+ return ret
+ end
+
+ msg = msg:gsub("(%c+)", repl)
+
+ local res, w, h = text_extent(msg, o.font)
+ return w, h, msg
+ end
+--------------------------------------------------------------------------------
+
+ -- set defaults for print view
+ local function set_defaults()
+ _p_opts = { x = 1,
+ y = 1,
+ width = LCD_W - 1,
+ height = LCD_H - 1,
+ font = rb.FONT_UI,
+ drawmode = DRMODE_SOLID,
+ fg_pattern = WHITE,
+ bg_pattern = BLACK,
+ sel_pattern = WHITE,
+ line = 1,
+ max_line = _NIL,
+ col = 0,
+ ovfl = "auto",
+ justify = "left",
+ autoupdate = true,
+ }
+ _p_opts.max_line = max_lines(_p_opts)
+
+ s_lines, col_buf = {}, {}
+ return _p_opts
+ end
+
+ -- returns table with settings for print
+ -- if bByRef is _NIL or false then a copy is returned
+ local function get_settings(bByRef)
+ _p_opts = _p_opts or set_defaults()
+ if not bByRef then return table_clone(_p_opts) end
+ return _p_opts
+ end
+
+ -- sets the settings for print with your passed table
+ local function set_settings(t_opts)
+ _p_opts = t_opts or set_defaults()
+ if t_opts then
+ _p_opts.max_line = max_lines(_p_opts)
+ col_buf = {}
+ end
+ end
+
+ -- sets colors for print
+ local function set_color(fgclr, bgclr, selclr)
+ local o = get_settings(true)
+
+ if fgclr ~= _NIL then
+ o.fg_pattern, o.sel_pattern = fgclr, fgclr
+ end
+ o.sel_pattern = selclr or o.sel_pattern
+ o.bg_pattern = bgclr or o.bg_pattern
+ end
+
+ -- helper function sets up colors/marker for selected items
+ local function show_selected(iLine, msg)
+ local o = get_settings() -- using a copy of opts so changes revert
+
+ if s_lines[iLine] == true then
+ if not rb.lcd_set_background then
+ o.drawmode = bit.bxor(o.drawmode, 4)
+ else
+ o.fg_pattern = o.bg_pattern
+ o.bg_pattern = o.sel_pattern
+ end
+ -- alternative selection method
+ --msg = "> " .. msg
+ end
+ set_viewport(o)
+ o = _NIL
+ end
+
+ -- sets line explicitly or increments line if line is _NIL
+ local function set_line(iLine)
+ local o = get_settings(true)
+
+ o.line = iLine or o.line + 1
+
+ if(o.line < 1 or o.line > o.max_line) then
+ o.line = 1
+ end
+ end
+
+ -- clears the set print area
+ local function clear()
+ local o = get_settings(true)
+ _LCD:clear(o.bg_pattern, o.x, o.y, o.x + o.width, o.y + o.height)
+ if o.autoupdate == true then rb.lcd_update() end
+ set_line(1)
+ for i=1, #col_buf do col_buf[i] = _NIL end
+ s_lines = {}
+ collectgarbage("collect")
+ end
+
+ -- screen update after each call to print.f
+ local function set_update(bAutoUpdate)
+ local o = get_settings(true)
+ o.autoupdate = bAutoUpdate or false
+ end
+
+ -- sets print area
+ local function set_area(x, y, w, h)
+ local o = get_settings(true)
+ o.x, o.y = clamp(x, 1, LCD_W), clamp(y, 1, LCD_H)
+ o.width, o.height = clamp(w, 1, LCD_W - o.x), clamp(h, 1, LCD_H - o.y)
+ o.max_line = max_lines(_p_opts)
+
+ clear()
+ return o.line, o.max_line
+ end
+
+ -- when string is longer than print width scroll -- "auto", "manual", "none"
+ local function set_overflow(str_mode)
+ -- "auto", "manual", "none"
+ local str_mode = str_mode or "auto"
+ local o = get_settings(true)
+ o.ovfl = str_mode:lower()
+ col_buf = {}
+ end
+
+ -- aligns text to: "left", "center", "right"
+ local function set_justify(str_mode)
+ -- "left", "center", "right"
+ local str_mode = str_mode or "left"
+ local o = get_settings(true)
+ o.justify = str_mode:lower()
+ end
+
+ -- selects line
+ local function select_line(iLine)
+ s_lines[iLine] = true
+ end
+
+ -- Internal print function
+ local function print_internal(t_opts, x, w, h, msg)
+
+ local o = t_opts
+ if o.justify == "center" then
+ x = x + (o.width - w) / 2
+ elseif o.justify == "right" then
+ x = x + (o.width - w)
+ end
+
+ local line = o.line - 1 -- rb is 0-based lua is 1-based
+ if(o.ovfl == "auto" and w >= o.width) then -- -o.x
+ rb.lcd_puts_scroll(0, line, msg)
+ else
+ rb.lcd_putsxy(x, line * h, msg)
+ if o.ovfl == "manual" then --save msg for later side scroll
+ col_buf_insert(msg, o.line, o)
+ end
+ end
+
+ --only update the line we changed
+ update_line(o.autoupdate, o, line, h)
+
+ set_line(_NIL) -- increments line counter
+ end
+
+ -- Helper function that acts mostly like a normal printf() would
+ local function printf(...)
+ local o = get_settings(true)
+ local w, h, msg
+ local line = o.line - 1 -- rb is 0-based lua is 1-based
+ if not (...) or (...) == "\n" then -- handles blank line / single '\n'
+ local res, w, h = text_extent(" ", o.font)
+
+ clear_line(o, line, h)
+ update_line(o.autoupdate, o, line, h)
+
+ if (...) then set_line(_NIL) end
+
+ return o.line, o.max_line, o.width, h
+ end
+
+ msg = string.format(...)
+
+ show_selected(o.line, msg)
+
+ w, h, msg = check_escapes(o, msg)
+
+ print_internal(o, o.col, w, h, msg)
+
+ return o.line, o.max_line, w, h
+ end
+
+ -- x > 0 scrolls right x < 0 scrolls left
+ local function set_column(x)
+ local o = get_settings()
+ if o.ovfl ~= "manual" then return end -- no buffer stored to scroll
+ local res, w, h, str, line
+
+ for key, value in pairs(col_buf) do
+ line = key - 1 -- rb is 0-based lua is 1-based
+ o.line = key
+
+ if value then
+ show_selected(key, value)
+ res, w, h = text_extent(value, o.font)
+ clear_line(o, line, h)
+
+ print_internal(o, x + o.col, w, h, value)
+ update_line(o.autoupdate, o, line, h)
+ end
+ end
+ o = _NIL
+ end
+
+ --expose functions to the outside through _print table
+ _print.opt = {}
+ _print.opt.column = set_column
+ _print.opt.color = set_color
+ _print.opt.area = set_area
+ _print.opt.set = set_settings
+ _print.opt.get = get_settings
+ _print.opt.defaults = set_defaults
+ _print.opt.overflow = set_overflow
+ _print.opt.justify = set_justify
+ _print.opt.sel_line = select_line
+ _print.opt.line = set_line
+ _print.opt.autoupdate = set_update
+ _print.clear = clear
+ _print.f = printf
+
+end --_print functions
+
+return _print
diff --git a/apps/plugins/lua/include_lua/timer.lua b/apps/plugins/lua/include_lua/timer.lua
new file mode 100644
index 0000000000..6797874329
--- /dev/null
+++ b/apps/plugins/lua/include_lua/timer.lua
@@ -0,0 +1,114 @@
+--[[ Lua Timer functions
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 William Wilgus
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+]]
+
+--[[ Exposed Functions
+
+ _timer.active
+ _timer.check
+ _timer.split
+ _timer.start
+ _timer.stop
+
+]]
+
+local _timer = {} do
+
+ --internal constants
+ local _NIL = nil -- _NIL placeholder
+
+ -- newer versions of lua use table.unpack
+ local unpack = unpack or table.unpack
+
+ --stores time elapsed at call to split; only vaid for unique timers
+ local function split(index)
+ if type(index) ~= "table" then return end
+ index[#index + 1] = rb.current_tick() - _timer[index]
+ end
+
+ -- starts a new timer, if index is not specified a unique index is returned
+ -- numeric or string indices are valid to use directly for permanent timers
+ -- in this case its up to you to make sure you keep the index unique
+ local function start(index)
+ if index == _NIL then
+ ---if you have _timer.start create timer it returns a unique Id which
+ -- then has the same methods of _timer :start :stop :check :split
+ index = setmetatable({}, {__index = _timer})
+ end
+ if _timer[index] == _NIL then
+ _timer[index] = rb.current_tick()
+ end
+ return index
+ end
+
+ -- returns time elapsed in centiseconds, assigning bCheckonly keeps timer active
+ local function stop(index, bCheckonly)
+
+ local time_end = rb.current_tick()
+ index = index or 0
+ if not _timer[index] then
+ return 0
+ else
+ local time_start = _timer[index]
+ if not bCheckonly then _timer[index] = _NIL end -- destroy timer
+ if type(index) ~= "table" then
+ return time_end - time_start
+ else
+ return time_end - time_start, unpack(index)
+ end
+ end
+ end
+
+ -- returns time elapsed in centiseconds, assigning to bUpdate.. updates timer
+ local function check(index, bUpdate)
+ local elapsed = stop(index, true)
+ if bUpdate ~= _NIL and index then
+ _timer[index] = rb.current_tick()
+ end
+ return elapsed
+ end
+
+ -- returns table of active timers
+ local function active()
+ local t_active = {}
+ local n = 0
+ for k,v in pairs(_timer) do
+ if type(_timer[k]) ~= "function" then
+ n = n + 1
+ t_active[n]=(k)
+ end
+ end
+ return n, t_active
+ end
+
+ -- expose functions to the outside through _timer table
+ _timer.active = active
+ _timer.check = check
+ _timer.split = split
+ _timer.start = start
+ _timer.stop = stop
+
+ -- allows a call to _timer.start() by just calling _timer()
+ _timer = setmetatable(_timer,{__call = function(t, i) return start(i) end})
+end -- timer functions
+
+return _timer
diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make
index 2365150c1e..a8932c0482 100644
--- a/apps/plugins/lua/lua.make
+++ b/apps/plugins/lua/lua.make
@@ -15,6 +15,9 @@ LUA_OBJ := $(call c2obj, $(LUA_SRC))
OTHER_SRC += $(LUA_SRC)
+LUA_INCLUDEDIR := $(LUA_SRCDIR)/include_lua
+LUA_INCLUDELIST = blit color draw image lcd math_ex print timer
+
ifndef APP_TYPE
ifneq (,$(strip $(foreach tgt,RECORDER ONDIO,$(findstring $(tgt),$(TARGET)))))
### lowmem targets
@@ -30,7 +33,7 @@ else
ROCKS += $(LUA_BUILDDIR)/lua.rock
endif
-$(LUA_BUILDDIR)/lua.rock: $(LUA_OBJ) $(TLSFLIB) $(LUA_BUILDDIR)/actions.lua $(LUA_BUILDDIR)/buttons.lua $(LUA_BUILDDIR)/rocklib_aux.o
+$(LUA_BUILDDIR)/lua.rock: $(LUA_OBJ) $(TLSFLIB) $(LUA_BUILDDIR)/actions.lua $(LUA_BUILDDIR)/buttons.lua $(LUA_BUILDDIR)/rocklib_aux.o $(LUA_INCLUDELIST)
$(LUA_BUILDDIR)/actions.lua: $(LUA_OBJ) $(LUA_SRCDIR)/action_helper.pl
$(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -E $(APPSDIR)/plugins/lib/pluginlib_actions.h | $(LUA_SRCDIR)/action_helper.pl > $(LUA_BUILDDIR)/actions.lua
@@ -46,6 +49,9 @@ $(LUA_BUILDDIR)/rocklib_aux.c: $(APPSDIR)/plugin.h $(LUA_OBJ) $(LUA_SRCDIR)/rock
$(LUA_BUILDDIR)/rocklib_aux.o: $(LUA_BUILDDIR)/rocklib_aux.c
$(call PRINTS,CC $(<F))$(CC) $(INCLUDES) $(PLUGINFLAGS) -I $(LUA_SRCDIR) -c $< -o $@
+$(LUA_INCLUDELIST): %: $(LUA_INCLUDEDIR)/%.lua
+ $(call PRINTS,CP $(subst $(LUA_INCLUDEDIR)/,,$<))cp $< $(LUA_BUILDDIR)/$@.lua
+
$(LUA_BUILDDIR)/lua.refmap: $(LUA_OBJ) $(TLSFLIB)
$(LUA_OUTLDS): $(PLUGIN_LDS) $(LUA_BUILDDIR)/lua.refmap
diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c
index 2268063d3f..ced83a51df 100644
--- a/apps/plugins/lua/rocklib.c
+++ b/apps/plugins/lua/rocklib.c
@@ -9,6 +9,7 @@
*
* Copyright (C) 2008 Dan Everton (safetydan)
* Copyright (C) 2009 Maurus Cuelenaere
+ * Copyright (C) 2017 William Wilgus
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -47,127 +48,1215 @@
/*
- * -----------------------------
+ * -----------------------------------------------------------------------------
*
* Rockbox Lua image wrapper
*
- * -----------------------------
+ * 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.
+ *
+ * The internal formats of these devices do not follow the same paradigm
+ * for image sizes either; We still display the actual width and height to
+ * the user but store stride based on the native values
+ *
+ * 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.
+ *
+ * -----------------------------------------------------------------------------
*/
+#ifdef HAVE_LCD_BITMAP
+#define RLI_EXTENDED
+#endif
+
#define ROCKLUA_IMAGE "rb.image"
+#define ERR_IDX_RANGE "index out of range"
+#define ERR_DATA_OVF "data overflow"
+
+/* mark for RLI to LUA Interface functions (luaState *L) is the only argument */
+#define RLI_LUA static int
+
+#ifndef ABS
+#define ABS(a)(((a) < 0) ? - (a) :(a))
+#endif
+
+#ifndef LCD_BLACK
+#define LCD_BLACK 0x0
+#endif
+
struct rocklua_image
{
- int width;
- int height;
+ int width;
+ int height;
+ int stride;
+ size_t elems;
fb_data *data;
- fb_data dummy[1][1];
+ fb_data dummy[1];
};
-static void rli_wrap(lua_State *L, fb_data *src, int width, int height)
+/* holds iterator data for rlimages */
+struct rli_iter_d
{
- struct rocklua_image *a = (struct rocklua_image *)lua_newuserdata(L, sizeof(struct rocklua_image));
+ struct rocklua_image *img;
+ fb_data *elem;
+ int x , y;
+ int x1, y1;
+ int x2, y2;
+ int dx, dy;
+};
- luaL_getmetatable(L, ROCKLUA_IMAGE);
- lua_setmetatable(L, -2);
+/* __tostring information enums */
+enum rli_info {RLI_INFO_ALL = 0, RLI_INFO_TYPE, RLI_INFO_WIDTH,
+ RLI_INFO_HEIGHT, RLI_INFO_ELEMS, RLI_INFO_BYTES,
+ RLI_INFO_DEPTH, RLI_INFO_FORMAT, RLI_INFO_ADDRESS};
- a->width = width;
- a->height = height;
- a->data = src;
+#ifdef HAVE_LCD_COLOR
+
+static inline unsigned invert_color(unsigned rgb)
+{
+ uint8_t r = 0xFFU - RGB_UNPACK_RED((unsigned) rgb);
+ uint8_t g = 0xFFU - RGB_UNPACK_GREEN((unsigned) rgb);
+ uint8_t b = 0xFFU - RGB_UNPACK_BLUE((unsigned) rgb);
+
+ return LCD_RGBPACK(r, g, b);
}
+#else /* !HAVE_LCD_COLOR */
+
+#define invert_color(c) (~c)
+
+#endif /* HAVE_LCD_COLOR */
+
+
+#if (LCD_DEPTH > 2) /* no native to pixel mapping needed */
+
+#define pixel_to_fb(x, y, o, n) {(void) x; (void) y; do { } while (0);}
+#define pixel_to_native(x, y, xn, yn) {*xn = x; *yn = y;}
+#define init_pixelmask(x, y, m, p) do { } while (0)
+
+
+#else /* some devices need x | y coords shifted to match native format */
+
+static fb_data x_shift = 0;
+static fb_data y_shift = 0;
+static fb_data xy_mask = 0;
+static const fb_data *pixelmask = NULL;
+
+/* conversion between packed native formats and individual pixel addressing */
+static inline void init_pixelmask(fb_data *x_shift, fb_data *y_shift,
+ fb_data *xy_mask, const fb_data **pixelmask)
+{
+
+#if(LCD_PIXELFORMAT == VERTICAL_PACKING) && LCD_DEPTH == 1
+ static const fb_data pixelmask_v1[8] =
+ {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
+ *pixelmask = pixelmask_v1;
+
+ (void) x_shift;
+ *y_shift = 3U;
+ *xy_mask = ((1 << (*y_shift)) - 1);
+#elif(LCD_PIXELFORMAT == VERTICAL_PACKING) && LCD_DEPTH == 2
+ static const fb_data pixelmask_v2[4] = {0x03, 0x0C, 0x30, 0xC0};
+ *pixelmask = pixelmask_v2;
+
+ (void) x_shift;
+ *y_shift = 2U;
+ *xy_mask = ((1 << (*y_shift)) - 1);
+#elif(LCD_PIXELFORMAT == VERTICAL_INTERLEAVED) && LCD_DEPTH == 2
+ static const fb_data pixelmask_vi2[8] =
+ {0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080};
+ *pixelmask = pixelmask_vi2;
+
+ (void) x_shift;
+ *y_shift = 3U;
+ *xy_mask = ((1 << (*y_shift)) - 1);
+#elif(LCD_PIXELFORMAT == HORIZONTAL_PACKING) && LCD_DEPTH == 2
+ /* MSB on left */
+ static const fb_data pixelmask_h2[4] = {0x03, 0x0C, 0x30, 0xC0};
+ *pixelmask = pixelmask_h2;
+
+ (void) y_shift;
+ *x_shift = 2U;
+ *xy_mask = ((1 << (*x_shift)) - 1);
+#else
+ #warning Unknown Pixel Format
+#endif /* LCD_PIXELFORMAT */
+
+} /* init_pixelmask */
+
+static inline void pixel_to_native(int x, int y, int *x_native, int *y_native)
+{
+ *x_native = ((x - 1) >> x_shift) + 1;
+ *y_native = ((y - 1) >> y_shift) + 1;
+} /* pixel_to_native */
+
+static inline fb_data set_masked_pixel(fb_data old,
+ fb_data new,
+ fb_data mask,
+ int bit_n)
+{
+ /*equivalent of: (old & (~mask)) | ((new << bit_n) & mask);*/
+ return old ^ ((old ^ (new << bit_n)) & mask);
+
+} /* set_masked_pixel */
+
+static inline fb_data get_masked_pixel(fb_data val, fb_data mask, int bit_n)
+{
+ val = val & mask;
+ return val >> bit_n;
+} /* get_masked_pixel */
+
+/* conversion between packed native formats and individual pixel addressing */
+static void pixel_to_fb(int x, int y, fb_data *oldv, fb_data *newv)
+{
+ fb_data mask;
+ int bit_n;
+
+#if(LCD_PIXELFORMAT == VERTICAL_INTERLEAVED) && LCD_DEPTH == 2
+ (void) x;
+ const uint16_t greymap_vi2[4] = {0x0000, 0x0001, 0x0100, 0x0101};
+
+ bit_n = (y - 1) & xy_mask;
+ mask = pixelmask[bit_n];
+
+ *newv = greymap_vi2[*newv &(0x3)]; /* [0-3] => greymap */
+ *newv = set_masked_pixel(*oldv, *newv, mask, bit_n);
+
+ *oldv = get_masked_pixel(*oldv, mask, bit_n);
+
+ if((*oldv) > 1) /* greymap => [0-3] */
+ *oldv = ((*oldv) & 0x1U) + 2U; /* 2, 3 */
+ else
+ *oldv &= 1U; /* 0, 1 */
+
+#elif(LCD_DEPTH <= 2)
+ if(y_shift)
+ bit_n = (y - 1) & xy_mask;
+ else if(x_shift)
+ bit_n = xy_mask - ((x - 1) & xy_mask); /*MSB on left*/
+
+ if(y_shift || x_shift)
+ {
+ mask = pixelmask[bit_n];
+ bit_n *= LCD_DEPTH;
+
+ *newv = set_masked_pixel(*oldv, *newv, mask, bit_n);
-static fb_data* rli_alloc(lua_State *L, int width, int height)
+ *oldv = get_masked_pixel(*oldv, mask, bit_n);
+ }
+#else
+ #error Unknown Pixel Format
+#endif /* LCD_PIXELFORMAT == VERTICAL_INTERLEAVED && LCD_DEPTH == 2 */
+} /* pixel_to_fb */
+
+#endif /* (LCD_DEPTH > 2) no native to pixel mapping needed */
+
+/* Internal worker functions for image data array *****************************/
+
+static inline void swap_int(bool swap, int *v1, int *v2)
{
- size_t nbytes = sizeof(struct rocklua_image) + ((width*height) - 1) * sizeof(fb_data);
- struct rocklua_image *a = (struct rocklua_image *)lua_newuserdata(L, nbytes);
+ if(swap)
+ {
+ int val = *v1;
+ *v1 = *v2;
+ *v2 = val;
+ }
+} /* swap_int */
+
+/* Throws error if x or y are out of bounds notifies user which narg indice
+ the out of bound variable originated */
+static void bounds_check_xy(lua_State *L, struct rocklua_image *img,
+ int nargx, int x, int nargy, int y)
+{
+ luaL_argcheck(L, x <= img->width && x > 0, nargx, ERR_IDX_RANGE);
+ luaL_argcheck(L, y <= img->height && y > 0, nargy, ERR_IDX_RANGE);
+} /* bounds_check_xy */
+
+static struct rocklua_image* rli_checktype(lua_State *L, int arg)
+{
+ void *ud = luaL_checkudata(L, arg, ROCKLUA_IMAGE);
+
+ luaL_argcheck(L, ud != NULL, arg, "'" ROCKLUA_IMAGE "' expected");
+
+ return (struct rocklua_image*) ud;
+} /* rli_checktype */
+
+static struct rocklua_image * alloc_rlimage(lua_State *L, bool alloc_data,
+ int width, int height)
+{
+ /* rliimage is pushed on the stack it is up to you to pop it */
+ struct rocklua_image *img;
+
+ const size_t sz_header = sizeof(struct rocklua_image);
+ size_t sz_data = 0;
+ size_t n_elems;
+
+ int w_native;
+ int h_native;
+
+ pixel_to_native(width, height, &w_native, &h_native);
+
+ n_elems = (size_t)(w_native * h_native);
+
+ if(alloc_data) /* if this a new image we need space for image data */
+ sz_data = n_elems * sizeof(fb_data);
+
+ /* newuserdata pushes the userdata onto the stack */
+ img = (struct rocklua_image *) lua_newuserdata(L, sz_header + sz_data);
luaL_getmetatable(L, ROCKLUA_IMAGE);
lua_setmetatable(L, -2);
- a->width = width;
- a->height = height;
- a->data = &a->dummy[0][0];
+ /* apparent w/h is stored but behind the scenes native w/h is used */
+ img->width = width;
+ img->height = height;
+ img->stride = w_native;
+ img->elems = n_elems;
+
+ return img;
+} /* alloc_rlimage */
+
+static inline void rli_wrap(lua_State *L, fb_data *src, int width, int height)
+{
+ /* rliimage is pushed on the stack it is up to you to pop it */
+ struct rocklua_image *a = alloc_rlimage(L, false, width, height);
+
+ a->data = src;
+} /* rli_wrap */
+
+static inline fb_data* rli_alloc(lua_State *L, int width, int height)
+{
+ /* rliimage is pushed on the stack it is up to you to pop it */
+ struct rocklua_image *a = alloc_rlimage(L, true, width, height);
+
+ a->data = &a->dummy[0]; /* ref to beginning of alloc'd img data */
return a->data;
-}
+} /* rli_alloc */
+
+static inline fb_data data_setget(fb_data *elem, int x, int y, fb_data *val)
+{
+ fb_data old_val = 0;
+ fb_data new_val = 0;
+
+ if(elem)
+ {
+ old_val = *elem;
+
+ if(val)
+ {
+ new_val = *val;
+ pixel_to_fb(x, y, &old_val, &new_val);
+ *elem = new_val;
+ }
+ else
+ pixel_to_fb(x, y, &old_val, &new_val);
+ }
+
+ return old_val;
+} /* data_setget */
+
+static inline fb_data data_set(fb_data *elem, int x, int y, fb_data *new_val)
+{
+ /* get and set share the same underlying function */
+ return data_setget(elem, x, y, new_val);
+} /* data_set */
+
+static inline fb_data data_get(fb_data *elem, int x, int y)
+{
+ /* get and set share the same underlying function */
+ return data_setget(elem, x, y, NULL);
+} /* data_get */
+
+static fb_data* rli_get_element(struct rocklua_image* img, int x, int y)
+{
+ int stride = img->stride;
+ size_t elements = img->elems;
+ fb_data *data = img->data;
+
+ pixel_to_native(x, y, &x, &y);
+
+ /* row major address */
+ size_t data_address = (stride * (y - 1)) + (x - 1);
+
+ /* x needs bound between 0 and stride otherwise overflow to prev/next y */
+ if(x <= 0 || x > stride || data_address >= elements)
+ return NULL; /* data overflow */
+
+ return &data[data_address]; /* return element address */
+} /* rli_get_element */
+
+/* Lua to C Interface for pixel set and get functions */
+static int rli_setget(lua_State *L, bool is_get)
+{
+ /*(set) (dst*, [x1, y1, clr, clip]) */
+ /*(get) (dst*, [x1, y1, clip]) */
+ struct rocklua_image *a = rli_checktype(L, 1);
+ int x = luaL_checkint(L, 2);
+ int y = luaL_checkint(L, 3);
+
+ fb_data clr = 0; /* Arg 4 is color if set element */
+ fb_data *p_clr = &clr;
+
+ int clip_narg;
+
+ if(is_get) /* get element */
+ {
+ p_clr = NULL;
+ clip_narg = 4;
+ }
+ else /* set element */
+ {
+ clr = FB_SCALARPACK((unsigned) luaL_checknumber(L, 4));
+ clip_narg = 5;
+ }
+
+ fb_data *element = rli_get_element(a, x, y);
+
+ if(!element)
+ {
+ if(!luaL_optboolean(L, clip_narg, false)) /* Error if !clip */
+ bounds_check_xy(L, a, 2, x, 3, y);
+
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(data_setget(element, x, y, p_clr)));
+
+ /* returns old value */
+ return 1;
+} /* rli_setget */
+
+#ifdef RLI_EXTENDED
+static bool init_rli_iter(struct rli_iter_d *d,
+ struct rocklua_image *img,
+ int x1, int y1,
+ int x2, int y2,
+ int dx, int dy,
+ bool swx, bool swy)
+{
+
+ swap_int((swx), &x1, &x2);
+ swap_int((swy), &y1, &y2);
+
+ /* stepping in the correct x direction ? */
+ if((dx > 0 && x1 > x2) || (dx < 0 && x1 < x2))
+ dx = -dx;
+
+ /* stepping in the correct y direction ? */
+ if((dy > 0 && y1 > y2) || (dy < 0 && y1 < y2))
+ dy = -dy;
+
+ d->img = img;
+ d->x = x1;
+ d->y = y1;
+ d->x1 = x1;
+ d->y1 = y1;
+ d->x2 = x2;
+ d->y2 = y2;
+ d->dx = dx;
+ d->dy = dy;
+ d->elem = rli_get_element(img, d->x, d->y);
+
+ return(dx != 0 || dy != 0);
+} /* init_rli_iter */
+
+/* steps to the next point(x, y) by delta x/y, stores pointer to element
+ returns true if x & y haven't reached x2 & y2
+ if limit reached - element set to NULL, deltas set to 0 & false returned
+*/
+static bool next_rli_iter(struct rli_iter_d *d)
+{
+ if((d->dx > 0 && d->x < d->x2) || (d->dx < 0 && d->x > d->x2))
+ d->x += d->dx;
+ else if((d->dy > 0 && d->y < d->y2) || (d->dy < 0 && d->y > d->y2))
+ {
+ d->x = d->x1; /* Reset x*/
+ d->y += d->dy;
+ }
+ else
+ {
+ d->elem = NULL;
+ d->dx = 0;
+ d->dy = 0;
+ return false;
+ }
+
+ d->elem = rli_get_element(d->img, d->x, d->y);
+
+ return true;
+} /* next_rli_iter */
+
+static int d_line(struct rocklua_image *img,
+ int x1, int y1,
+ int x2, int y2,
+ fb_data *clr,
+ bool clip)
+{
+ /* NOTE! clr passed as pointer */
+ /* Bresenham midpoint line algorithm */
+ fb_data *element;
+
+ int r_a = x2 - x1; /* range of x direction called 'a' for now */
+ int r_b = y2 - y1; /* range of y direction called 'b' for now */
+
+ int s_a = 1; /* step of a direction */
+ int s_b = 1; /* step of b direction */
+
+ int d_err;
+ int numpixels;
+
+ int *a1 = &x1; /* pointer to the x var 'a' */
+ int *b1 = &y1; /* pointer to the y var 'b' */
+
+ if(r_a < 0) /* instead of negative range we will switch step instead */
+ {
+ r_a = -r_a;
+ s_a = -s_a;
+ }
+
+ if(r_b < 0) /* instead of negative range we will switch step instead */
+ {
+ r_b = -r_b;
+ s_b = -s_b;
+ }
+
+ x2 += s_a; /* add 1 extra point to make the whole line */
+ y2 += s_b; /* add 1 extra point */
+
+ if(r_b > r_a) /*if rangeY('b') > rangeX('a') swap their identities */
+ {
+ a1 = &y1;
+ b1 = &x1;
+ swap_int((true), &r_a, &r_b);
+ swap_int((true), &s_a, &s_b);
+ }
+
+ d_err = ((r_b << 1) - r_a) >> 1; /* preload err of 1 step (px centered) */
+
+ numpixels = r_a + 1;
+
+ r_a -= r_b; /* pre-subtract 'a' - 'b' */
+
+ for(;numpixels > 0; numpixels--)
+ {
+ element = rli_get_element(img, x1, y1);
+ if(element || clip)
+ data_set(element, x1, y1, clr);
+ else
+ return numpixels + 1; /* Error */
+
+ if(d_err >= 0) /* 0 is our target midpoint(exact point on the line) */
+ {
+ *b1 += s_b; /* whichever axis is in 'b' stepped(-1 or +1) */
+ d_err -= r_a;
+ }
+ else
+ d_err += r_b; /* only add 'b' when d_err < 0 */
+
+ *a1 += s_a; /* whichever axis is in 'a' stepped(-1 or +1) */
+ }
+
+ return 0;
+} /* d_line */
+
+/* ellipse worker function */
+static int d_ellipse_elements(struct rocklua_image * img,
+ int x1, int y1,
+ int x2, int y2,
+ int sx, int sy,
+ fb_data *clr,
+ fb_data *fillclr,
+ bool clip)
+{
+ int ret = 0;
+ fb_data *element1, *element2, *element3, *element4;
+
+ if(fillclr && x1 - sx != x2 + sx)
+ {
+ ret |= d_line(img, x1, y1, x2, y1, fillclr, clip); /* I. II.*/
+ ret |= d_line(img, x1, y2, x2, y2, fillclr, clip); /* III.IV.*/
+ }
+
+ x1 -= sx; /* shift x & y */
+ y1 -= sy;
+ x2 += sx;
+ y2 += sy;
+
+ element1 = rli_get_element(img, x2, y1);
+ element2 = rli_get_element(img, x1, y1);
+ element3 = rli_get_element(img, x1, y2);
+ element4 = rli_get_element(img, x2, y2);
+
+ if(clip || (element1 && element2 && element3 && element4))
+ {
+ data_set(element1, x2, y1, clr); /* I. Quadrant +x +y */
+ data_set(element2, x1, y1, clr); /* II. Quadrant -x +y */
+ data_set(element3, x1, y2, clr); /* III. Quadrant -x -y */
+ data_set(element4, x2, y2, clr); /* IV. Quadrant +x -y */
+ }
+ else
+ ret = 2; /* ERROR */
+
+ return ret;
+} /* d_ellipse_elements */
+
+static int d_ellipse(struct rocklua_image *img,
+ int x1, int y1,
+ int x2, int y2,
+ fb_data *clr,
+ fb_data *fillclr,
+ bool clip)
+{
+ /* NOTE! clr and fillclr passed as pointers */
+ /* Rasterizing algorithm derivative of work by Alois Zing */
+#if LCD_WIDTH > 1024 || LCD_HEIGHT > 1024
+ /* Prevents overflow on large screens */
+ double dx, dy, err, e2;
+#else
+ long dx, dy, err, e2;
+#endif
+
+ int ret = 0;
+
+ int a = ABS(x2 - x1); /* diameter */
+ int b = ABS(y2 - y1); /* diameter */
+
+ if(a == 0 || b == 0 || !clr)
+ return ret; /* not an error but nothing to display */
+
+ int b1 = (b & 1);
+ b = b - (1 - b1);
+
+ int a2 = (a * a);
+ int b2 = (b * b);
+
+ dx = ((1 - a) * b2) >> 1; /* error increment */
+ dy = (b1 * a2) >> 1; /* error increment */
+
+ err = dx + dy + b1 * a2; /* error of 1.step */
+
+ /* if called with swapped points .. exchange them */
+ swap_int((x1 > x2), &x1, &x2);
+ swap_int((y1 > y2), &y1, &y2);
+
+ y1 += (b + 1) >> 1;
+ y2 = y1 - b1;
+
+ do
+ {
+ ret = d_ellipse_elements(img, x1, y1, x2, y2, 0, 0, clr, fillclr, clip);
+
+ e2 = err;
+
+ /* using division because you can't use bit shift on doubles.. */
+ if(e2 <= (dy / 2)) /* target midpoint - y step */
+ {
+ y1++;
+ y2--;
+ dy += a2;
+ err += dy;
+ }
+
+ if(e2 >= (dx / 2) || err > (dy / 2)) /* target midpoint - x step */
+ {
+ x1++;
+ x2--;
+ dx += b2;
+ err += dx;
+ }
+
+ } while(ret == 0 && x1 <= x2);
+
+ while (ret == 0 && y1 - y2 < b) /* early stop of flat ellipse a=1 finish tip */
+ {
+ ret = d_ellipse_elements(img, x1, y1, x2, y2, 1, 0, clr, fillclr, clip);
+
+ y1++;
+ y2--;
+ }
+
+ return ret;
+} /* d_ellipse */
+
+/* Lua to C Interface for line and ellipse */
+static int rli_line_ellipse(lua_State *L, bool is_ellipse)
+{
+ struct rocklua_image *a = rli_checktype(L, 1);
+
+ int x1 = luaL_checkint(L, 2);
+ int y1 = luaL_checkint(L, 3);
+ int x2 = luaL_optint(L, 4, x1);
+ int y2 = luaL_optint(L, 5, y1);
+
+ fb_data clr = FB_SCALARPACK((unsigned) luaL_checknumber(L, 6));
+
+ fb_data fillclr; /* fill color is index 7 if is_ellipse */
+ fb_data *p_fillclr = NULL;
+
+ bool clip;
+ int clip_narg;
+
+ if(is_ellipse)
+ clip_narg = 8;
+ else
+ clip_narg = 7;
+
+ clip = luaL_optboolean(L, clip_narg, false);
+
+ if(!clip)
+ {
+ bounds_check_xy(L, a, 2, x1, 3, y1);
+ bounds_check_xy(L, a, 4, x2, 5, y2);
+ }
+
+ if(is_ellipse)
+ {
+ if(!lua_isnoneornil(L, 7))
+ {
+ fillclr = FB_SCALARPACK((unsigned) luaL_checkint(L, 7));
+ p_fillclr = &fillclr;
+ }
+
+ luaL_argcheck(L, d_ellipse(a, x1, y1, x2, y2, &clr, p_fillclr, clip) == 0,
+ 1, ERR_DATA_OVF);
+ }
+ else
+ luaL_argcheck(L, d_line(a, x1, y1, x2, y2, &clr, clip) == 0,
+ 1, ERR_DATA_OVF);
+
+ return 0;
+} /* rli_line_ellipse */
+
+/* Pushes lua function from Stack at position narg to top of stack
+ and puts a reference in the global registry for later use */
+static inline int register_luafunc(lua_State *L, int narg_funct)
+{
+ lua_pushvalue(L, narg_funct); /* lua function */
+ int lf_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_settop(L, 0); /* clear C stack for use by lua function */
+ return lf_ref;
+} /* register_luafunc */
+
+/* User defined pixel manipulations through rli_copy, rli_marshal */
+static int custom_transform(lua_State *L,
+ struct rli_iter_d *ds,
+ struct rli_iter_d *ss,
+ int op,
+ fb_data *color)
+{
+ (void) color;
+
+ fb_data dst;
+ fb_data src;
+
+ unsigned int params = 3;
+ int ret = 0;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, op);
+
+ dst = data_get(ds->elem, ds->x, ds->y);
+ lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(dst));
+ lua_pushnumber(L, ds->x);
+ lua_pushnumber(L, ds->y);
+
+ if(ss) /* Allows src to be omitted */
+ {
+ params += 3;
+ src = data_get(ss->elem, ss->x, ss->y);
+ lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(src));
+ lua_pushnumber(L, ss->x);
+ lua_pushnumber(L, ss->y);
+ }
+
+ lua_call(L, params, 2); /* call custom function w/ n-params & 2 ret */
+
+ if(!lua_isnoneornil(L, -2))
+ {
+ ret = 1;
+ dst = FB_SCALARPACK((unsigned) luaL_checknumber(L, -2));
+ data_set(ds->elem, ds->x, ds->y, &dst);
+ }
+
+ if(!lua_isnoneornil(L, -1) && ss)
+ {
+ ret |= 2;
+ src = FB_SCALARPACK((unsigned) luaL_checknumber(L, -1));
+ data_set(ss->elem, ss->x, ss->y, &src);
+ }
+
+ lua_pop(L, 2);
+ return ret; /* 0 signals iterator to stop */
+} /* custom_transform */
+
+/* Pre defined pixel manipulations through rli_copy */
+static int blit_transform(lua_State *L,
+ struct rli_iter_d *ds,
+ struct rli_iter_d *ss,
+ int op,
+ fb_data *color)
+{
+ (void) L;
+ fb_data clr = *color;
+ fb_data dst = data_get(ds->elem, ds->x, ds->y);
+ fb_data src;
+
+ /* Reuse 0 - 7 for src / clr blits*/
+ if(op >= 30 && op <= 37)
+ {
+ op -= 30;
+ src = clr;
+ }
+ else
+ src = data_get(ss->elem, ss->x, ss->y);
+
+ switch(op)
+ {
+ default:
+ /* case 30: */
+ case 0: { dst = src; break; }/* copyS/C */
+ /* case 31: */
+ case 1: { dst = src | dst; break; }/* DorS/C */
+ /* case 32: */
+ case 2: { dst = src ^ dst; break; }/* DxorS/C */
+ /* case 33: */
+ case 3: { dst = ~(src | dst); break; }/* nDorS/C */
+ /* case 34: */
+ case 4: { dst = (~src) | dst; break; }/* DornS/C */
+ /* case 35: */
+ case 5: { dst = src & dst; break; }/* DandS/C */
+ /* case 36: */
+ case 6: { dst = src & (~dst); break; }/* nDandS/C */
+ /* case 37: */
+ case 7: { dst = ~src; break; }/* notS/C */
+
+ /* mask blits */
+ case 8: { if(src != 0) { dst = clr; } break; }/* Sand */
+ case 9: { if(src == 0) { dst = clr; } break; }/* Snot */
+
+ case 10: { dst = src | clr; break; }/* SorC */
+ case 11: { dst = src ^ clr; break; }/* SxorC */
+ case 12: { dst = ~(src | clr); break; }/* nSorC */
+ case 13: { dst = src | (~clr); break; }/* SornC */
+ case 14: { dst = src & clr; break; }/* SandC */
+ case 15: { dst = (~src) & clr; break; }/* nSandC */
+ case 16: { dst |= (~src) | clr; break; }/* DornSorC */
+ case 17: { dst ^= (src & (dst ^ clr)); break; }/* DxorSandDxorC */
+
+ case 18: { if(src != clr) { dst = src; } break; }
+ case 19: { if(src == clr) { dst = src; } break; }
+ case 20: { if(src > clr) { dst = src; } break; }
+ case 21: { if(src < clr) { dst = src; } break; }
+
+ case 22: { if(dst != clr) { dst = src; } break; }
+ case 23: { if(dst == clr) { dst = src; } break; }
+ case 24: { if(dst > clr) { dst = src; } break; }
+ case 25: { if(dst < clr) { dst = src; } break; }
+
+ case 26: { if(dst != src) { dst = clr; } break; }
+ case 27: { if(dst == src) { dst = clr; } break; }
+ case 28: { if(dst > src) { dst = clr; } break; }
+ case 29: { if(dst < src) { dst = clr; } break; }
+
+ }/*switch op*/
+
+ data_set(ds->elem, ds->x, ds->y, &dst);
+ return 1;
+} /* blit_transform */
-static int rli_new(lua_State *L)
+static int invert_transform(lua_State *L,
+ struct rli_iter_d *ds,
+ struct rli_iter_d *ss,
+ int op,
+ fb_data *color)
{
- int width = luaL_checkint(L, 1);
- int height = luaL_checkint(L, 2);
+ (void) L;
+ (void) color;
+ (void) op;
+ (void) ss;
+
+ fb_data val = invert_color(data_get(ds->elem, ds->x, ds->y));
+ data_set(ds->elem, ds->x, ds->y, &val);
+
+ return 1;
+} /* invert_transform */
+#endif /* RLI_EXTENDED */
+
+/* RLI to LUA Interface functions *********************************************/
+RLI_LUA rli_new(lua_State *L)
+{ /* [width, height] */
+ int width = luaL_optint(L, 1, LCD_WIDTH);
+ int height = luaL_optint(L, 2, LCD_HEIGHT);
+
+ luaL_argcheck(L, width > 0, 1, ERR_IDX_RANGE);
+ luaL_argcheck(L, height > 0, 2, ERR_IDX_RANGE);
rli_alloc(L, width, height);
return 1;
}
-static struct rocklua_image* rli_checktype(lua_State *L, int arg)
+RLI_LUA rli_set(lua_State *L)
{
- void *ud = luaL_checkudata(L, arg, ROCKLUA_IMAGE);
- luaL_argcheck(L, ud != NULL, arg, "'" ROCKLUA_IMAGE "' expected");
- return (struct rocklua_image*) ud;
+ /*(set) (dst*, [x1, y1, clr, clip]) */
+ /* get and set share the same underlying function */
+ return rli_setget(L, false);
+}
+
+RLI_LUA rli_get(lua_State *L)
+{
+ /*(get) (dst*, [x1, y1, clip]) */
+ /* get and set share the same underlying function */
+ return rli_setget(L, true);
}
-static int rli_width(lua_State *L)
+RLI_LUA rli_height(lua_State *L)
+{
+ struct rocklua_image *a = rli_checktype(L, 1);
+ lua_pushnumber(L, a->height);
+ return 1;
+}
+
+RLI_LUA rli_width(lua_State *L)
{
struct rocklua_image *a = rli_checktype(L, 1);
lua_pushnumber(L, a->width);
return 1;
}
-static int rli_height(lua_State *L)
+RLI_LUA rli_equal(lua_State *L)
{
struct rocklua_image *a = rli_checktype(L, 1);
- lua_pushnumber(L, a->height);
+ struct rocklua_image *b = rli_checktype(L, 2);
+ lua_pushboolean(L, a->data == b->data);
return 1;
}
-static fb_data* rli_element(lua_State *L)
+RLI_LUA rli_size(lua_State *L)
{
struct rocklua_image *a = rli_checktype(L, 1);
- int x = luaL_checkint(L, 2);
- int y = luaL_checkint(L, 3);
+ lua_pushnumber(L, a->elems);
+ return 1;
+}
+
+RLI_LUA rli_raw(lua_State *L)
+{
+ /*val = (img*, index, [new_val]) */
+ struct rocklua_image *a = rli_checktype(L, 1);
+
+ size_t i = (unsigned) luaL_checkint(L, 2);
+
+ fb_data val;
- luaL_argcheck(L, 1 <= x && x <= a->width, 2,
- "index out of range");
- luaL_argcheck(L, 1 <= y && y <= a->height, 3,
- "index out of range");
+ luaL_argcheck(L, i > 0 && i <= (a->elems), 2, ERR_IDX_RANGE);
- /* return element address */
- return &a->data[a->width * (y - 1) + (x - 1)];
+ lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(a->data[i-1]));
+
+ if(!lua_isnoneornil(L, 3))
+ {
+ val = FB_SCALARPACK((unsigned) luaL_checknumber(L, 3));
+ a->data[i-1] = val;
+ }
+
+ return 1;
}
-static int rli_set(lua_State *L)
+RLI_LUA rli_tostring(lua_State *L)
{
- fb_data newvalue = FB_SCALARPACK((unsigned)luaL_checknumber(L, 4));
- *rli_element(L) = newvalue;
- return 0;
+ /* (img, [infoitem]) */
+ struct rocklua_image *a = rli_checktype(L, 1);
+
+ int item = (unsigned) luaL_optint(L, 2, 0);
+ size_t bytes = a->elems * sizeof(fb_data);
+
+ switch(item)
+ {
+ default:
+ case RLI_INFO_ALL:
+ {
+ lua_pushfstring(L,
+ ROCKLUA_IMAGE ": %dx%d, %d elements, %d bytes, %d-bit depth, %d pack",
+ a->width, a->height, a->elems, bytes, LCD_DEPTH, LCD_PIXELFORMAT);
+ break;
+ }
+ case RLI_INFO_TYPE: { lua_pushfstring(L, ROCKLUA_IMAGE ); break; }
+ case RLI_INFO_WIDTH: { lua_pushfstring(L, "%d", a->width ); break; }
+ case RLI_INFO_HEIGHT: { lua_pushfstring(L, "%d", a->height ); break; }
+ case RLI_INFO_ELEMS: { lua_pushfstring(L, "%d", a->elems ); break; }
+ case RLI_INFO_BYTES: { lua_pushfstring(L, "%d", bytes ); break; }
+ case RLI_INFO_DEPTH: { lua_pushfstring(L, "%d", LCD_DEPTH ); break; }
+ case RLI_INFO_FORMAT: { lua_pushfstring(L, "%d", LCD_PIXELFORMAT); break; }
+ case RLI_INFO_ADDRESS: { lua_pushfstring(L, "%p", a->data); break; }
+ }
+
+ return 1;
+}
+
+#ifdef RLI_EXTENDED
+RLI_LUA rli_ellipse(lua_State *L)
+{
+ /* (dst*, x1, y1, x2, y2, [clr, fillclr, clip]) */
+ /* line and ellipse share the same init function */
+ return rli_line_ellipse(L, true);
}
-static int rli_get(lua_State *L)
+RLI_LUA rli_line(lua_State *L)
{
- lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(*rli_element(L)));
+ /* (dst*, x1, y1, [x2, y2, clr, clip]) */
+ /* line and ellipse share the same init function */
+ return rli_line_ellipse(L, false);
+}
+
+RLI_LUA rli_iterator(lua_State *L) {
+ /* see rli_iterator_factory */
+ struct rli_iter_d *ds;
+ ds = (struct rli_iter_d *) lua_touserdata(L, lua_upvalueindex(1));
+
+ if(ds->dx != 0 || ds->dy != 0)
+ {
+ lua_pushnumber(L, FB_UNPACK_SCALAR_LCD(data_get(ds->elem, ds->x, ds->y)));
+
+ lua_pushinteger(L, ds->x);
+ lua_pushinteger(L, ds->y);
+
+ next_rli_iter(ds); /* load next element */
+
+ return 3;
+ }
+ return 0; /* nothing left to do */
+}
+
+RLI_LUA rli_iterator_factory(lua_State *L) {
+ /* (src*, [x1, y1, x2, y2, dx, dy]) */
+ struct rocklua_image *a = rli_checktype(L, 1); /*image we wish to iterate*/
+
+ struct rli_iter_d *ds;
+
+ int x1 = luaL_optint(L, 2, 1);
+ int y1 = luaL_optint(L, 3, 1);
+ int x2 = luaL_optint(L, 4, a->width - x1 + 1);
+ int y2 = luaL_optint(L, 5, a->height - y1 + 1);
+ int dx = luaL_optint(L, 6, 1);
+ int dy = luaL_optint(L, 7, 1);
+
+ /* create new iter + pushed onto stack */
+ ds = (struct rli_iter_d *) lua_newuserdata(L, sizeof(struct rli_iter_d));
+
+ init_rli_iter(ds, a, x1, y1, x2, y2, dx, dy, false, false);
+
+ /* returns the iter function with embedded iter data(up values) */
+ lua_pushcclosure(L, &rli_iterator, 1);
+
return 1;
}
-static int rli_tostring(lua_State *L)
+RLI_LUA rli_marshal(lua_State *L) /* also invert */
{
+ /* (img*, [x1, y1, x2, y2, dx, dy, clip, function]) */
struct rocklua_image *a = rli_checktype(L, 1);
- lua_pushfstring(L, ROCKLUA_IMAGE ": %dx%d", a->width, a->height);
- return 1;
+
+ struct rli_iter_d ds;
+
+ int x1 = luaL_optint(L, 2, 1);
+ int y1 = luaL_optint(L, 3, 1);
+ int x2 = luaL_optint(L, 4, a->width);
+ int y2 = luaL_optint(L, 5, a->height);
+ int dx = luaL_optint(L, 6, 1);
+ int dy = luaL_optint(L, 7, 1);
+ bool clip = luaL_optboolean(L, 8, false);
+
+ int lf_ref = LUA_NOREF; /* de-ref'd without consequence */
+
+ int (*rli_trans)(lua_State *, struct rli_iter_d *, struct rli_iter_d *, int, fb_data *);
+ rli_trans = invert_transform; /* default transformation */
+
+ if(!clip)
+ {
+ bounds_check_xy(L, a, 2, x1, 3, y1);
+ bounds_check_xy(L, a, 4, x2, 5, y2);
+ }
+
+ init_rli_iter(&ds, a, x1, y1, x2, y2, dx, dy, false, false);
+
+ if(lua_isfunction(L, 9)) /* custom function */
+ {
+ rli_trans = custom_transform;
+ lf_ref = register_luafunc(L, 9);
+ }
+
+ do
+ {
+ luaL_argcheck(L, clip || (ds.elem != NULL), 1, ERR_DATA_OVF);
+
+ if(!(*rli_trans)(L, &ds, NULL, lf_ref, NULL))
+ break; /* Custom op can quit early */
+
+ } while(next_rli_iter(&ds));
+
+ luaL_unref(L, LUA_REGISTRYINDEX, lf_ref); /* de-reference custom function */
+ return 0;
+}
+
+RLI_LUA rli_copy(lua_State *L)
+{
+ /* (dst*, src*, [d_x, d_y, s_x, s_y, x_off, y_off, clip, [op, funct/clr]]) */
+ struct rocklua_image *d = rli_checktype(L, 1); /*dst*/
+ struct rocklua_image *s = rli_checktype(L, 2); /*src*/
+
+ struct rli_iter_d ds; /*dst*/
+ struct rli_iter_d ss; /*src*/
+
+ /* copy whole image if possible */
+ if(s->elems == d->elems && s->width == d->width && lua_gettop(L) < 3)
+ {
+ memcpy(d->data, s->data, d->elems * sizeof(fb_data));
+ return 0;
+ }
+
+ int d_x = luaL_optint(L, 3, 1);
+ int d_y = luaL_optint(L, 4, 1);
+ int s_x = luaL_optint(L, 5, 1);
+ int s_y = luaL_optint(L, 6, 1);
+
+ int w = MIN(d->width - d_x, s->width - s_x);
+ int h = MIN(d->height - d_y, s->height - s_y);
+
+ int x_off = luaL_optint(L, 7, w);
+ int y_off = luaL_optint(L, 8, h);
+
+ bool clip = luaL_optboolean(L, 9, false);
+ int op = luaL_optint(L, 10, 0);
+ fb_data clr = 0; /* 11 is custom function | color */
+
+ bool d_swx = (x_off < 0); /* dest swap */
+ bool d_swy = (y_off < 0);
+ bool s_swx = false; /* src swap */
+ bool s_swy = false;
+
+ int (*rli_trans)(lua_State *, struct rli_iter_d *, struct rli_iter_d *, int, fb_data *);
+ rli_trans = blit_transform; /* default transformation */
+
+ int lf_ref = LUA_NOREF; /* de-ref'd without consequence */
+
+ if(!clip) /* Out of bounds is not allowed */
+ {
+ bounds_check_xy(L, d, 3, d_x, 4, d_y);
+ bounds_check_xy(L, s, 5, s_x, 6, s_y);
+ }
+ else if (w < 0 || h < 0) /* not an error but nothing to display */
+ return 0;
+
+ w = MIN(w, ABS(x_off));
+ h = MIN(h, ABS(y_off));
+
+ /* if(s->data == d->data) need to care about fill direction */
+ if(d_x > s_x)
+ {
+ d_swx = !d_swx;
+ s_swx = !s_swx;
+ }
+
+ if(d_y > s_y)
+ {
+ d_swy = !d_swy;
+ s_swy = !s_swy;
+ }
+
+ init_rli_iter(&ds, d, d_x, d_y, d_x + w, d_y + h, 1, 1, d_swx, d_swy);
+
+ init_rli_iter(&ss, s, s_x, s_y, s_x + w, s_y + h, 1, 1, s_swx, s_swy);
+
+ if (op == 0xFF && lua_isfunction(L, 11)) /* custom function specified.. */
+ {
+ rli_trans = custom_transform;
+ lf_ref = register_luafunc(L, 11);
+ op = lf_ref;
+ }
+ else
+ clr = FB_SCALARPACK((unsigned) luaL_optnumber(L, 11, LCD_BLACK));
+
+ do
+ {
+ if(!clip)
+ {
+ luaL_argcheck(L, ss.elem != NULL, 2, ERR_DATA_OVF);
+ luaL_argcheck(L, ds.elem != NULL, 1, ERR_DATA_OVF);
+ }
+
+ if(!(*rli_trans)(L, &ds, &ss, op, &clr))
+ break; /* Custom op can quit early */
+
+ } while(next_rli_iter(&ds) && next_rli_iter(&ss));
+
+ luaL_unref(L, LUA_REGISTRYINDEX, lf_ref); /* de-reference custom function */
+ return 0;
+}
+
+RLI_LUA rli_clear(lua_State *L)
+{
+ /* (dst*, [color, x1, y1, x2, y2, clip]) */
+ struct rocklua_image *a = rli_checktype(L, 1);
+
+ struct rli_iter_d ds;
+
+ fb_data clr = FB_SCALARPACK((unsigned) luaL_optnumber(L, 2, LCD_BLACK));
+ int x1 = luaL_optint(L, 3, 1);
+ int y1 = luaL_optint(L, 4, 1);
+ int x2 = luaL_optint(L, 5, a->width);
+ int y2 = luaL_optint(L, 6, a->height);
+ bool clip = luaL_optboolean(L, 7, false);
+
+ if(!clip)
+ {
+ bounds_check_xy(L, a, 3, x1, 4, y1);
+ bounds_check_xy(L, a, 5, x2, 6, y2);
+ }
+
+ init_rli_iter(&ds, a, x1, y1, x2, y2, 1, 1, false, false);
+
+ do
+ {
+ luaL_argcheck(L, clip || (ds.elem != NULL), 1, ERR_DATA_OVF);
+
+ data_set(ds.elem, ds.x, ds.y, &clr);
+
+ } while(next_rli_iter(&ds));
+
+ return 0;
}
+#endif /* RLI_EXTENDED */
+/* Rli Image methods exported to lua */
static const struct luaL_reg rli_lib [] =
{
{"__tostring", rli_tostring},
- {"set", rli_set},
- {"get", rli_get},
- {"width", rli_width},
- {"height", rli_height},
+ {"_data", rli_raw},
+ {"__len", rli_size},
+ {"__eq", rli_equal},
+ {"width", rli_width},
+ {"height", rli_height},
+ {"set", rli_set},
+ {"get", rli_get},
+
+#ifdef RLI_EXTENDED
+ {"copy", rli_copy},
+ {"clear", rli_clear},
+ {"invert", rli_marshal},
+ {"marshal", rli_marshal},
+ {"points", rli_iterator_factory},
+ {"line", rli_line},
+ {"ellipse", rli_ellipse},
+#endif /* RLI_EXTENDED */
{NULL, NULL}
};
static inline void rli_init(lua_State *L)
{
+ /* some devices need x | y coords shifted to match native format */
+ /* conversion between packed native formats and individual pixel addressing */
+ init_pixelmask(&x_shift, &y_shift, &xy_mask, &pixelmask);
+
luaL_newmetatable(L, ROCKLUA_IMAGE);
lua_pushstring(L, "__index");
@@ -189,57 +1278,45 @@ static inline void rli_init(lua_State *L)
#define SIMPLE_VOID_WRAPPER(func) RB_WRAP(func) { (void)L; func(); return 0; }
/* Helper function for opt_viewport */
-static void check_tablevalue(lua_State *L, const char* key, int tablepos, void* res, bool unsigned_val)
+static void check_tablevalue(lua_State *L,
+ const char* key,
+ int tablepos,
+ void* res,
+ bool is_unsigned)
{
lua_getfield(L, tablepos, key); /* Find table[key] */
- if(!lua_isnoneornil(L, -1))
- {
- if(unsigned_val)
- *(unsigned*)res = luaL_checkint(L, -1);
- else
- *(int*)res = luaL_checkint(L, -1);
- }
+ int val = luaL_optint(L, -1, 0);
+
+ if(is_unsigned)
+ *(unsigned*)res = (unsigned) val;
+ else
+ *(int*)res = val;
lua_pop(L, 1); /* Pop the value off the stack */
}
-static struct viewport* opt_viewport(lua_State *L, int narg, struct viewport* alt)
+static inline struct viewport* opt_viewport(lua_State *L,
+ int narg,
+ struct viewport* vp,
+ struct viewport* alt)
{
if(lua_isnoneornil(L, narg))
return alt;
- int tablepos = lua_gettop(L);
- struct viewport *vp;
-
- lua_getfield(L, tablepos, "vp"); /* get table['vp'] */
- if(lua_isnoneornil(L, -1))
- {
- lua_pop(L, 1); /* Pop nil off stack */
-
- vp = (struct viewport*) lua_newuserdata(L, sizeof(struct viewport)); /* Allocate memory and push it as udata on the stack */
- memset(vp, 0, sizeof(struct viewport)); /* Init viewport values to 0 */
- lua_setfield(L, tablepos, "vp"); /* table['vp'] = vp (pops value off the stack) */
- }
- else
- {
- vp = (struct viewport*) lua_touserdata(L, -1); /* Reuse viewport struct */
- lua_pop(L, 1); /* We don't need the value on stack */
- }
-
luaL_checktype(L, narg, LUA_TTABLE);
- check_tablevalue(L, "x", tablepos, &vp->x, false);
- check_tablevalue(L, "y", tablepos, &vp->y, false);
- check_tablevalue(L, "width", tablepos, &vp->width, false);
- check_tablevalue(L, "height", tablepos, &vp->height, false);
+ check_tablevalue(L, "x", narg, &vp->x, false);
+ check_tablevalue(L, "y", narg, &vp->y, false);
+ check_tablevalue(L, "width", narg, &vp->width, false);
+ check_tablevalue(L, "height", narg, &vp->height, false);
#ifdef HAVE_LCD_BITMAP
- check_tablevalue(L, "font", tablepos, &vp->font, false);
- check_tablevalue(L, "drawmode", tablepos, &vp->drawmode, false);
+ check_tablevalue(L, "font", narg, &vp->font, false);
+ check_tablevalue(L, "drawmode", narg, &vp->drawmode, false);
#endif
#if LCD_DEPTH > 1
- check_tablevalue(L, "fg_pattern", tablepos, &vp->fg_pattern, true);
- check_tablevalue(L, "bg_pattern", tablepos, &vp->bg_pattern, true);
+ check_tablevalue(L, "fg_pattern", narg, &vp->fg_pattern, true);
+ check_tablevalue(L, "bg_pattern", narg, &vp->bg_pattern, true);
#endif
return vp;
@@ -247,9 +1324,9 @@ static struct viewport* opt_viewport(lua_State *L, int narg, struct viewport* al
RB_WRAP(set_viewport)
{
- struct viewport *vp = opt_viewport(L, 1, NULL);
+ static struct viewport vp;
int screen = luaL_optint(L, 2, SCREEN_MAIN);
- rb->screens[screen]->set_viewport(vp);
+ rb->screens[screen]->set_viewport(opt_viewport(L, 1, &vp, NULL));
return 0;
}