1
0
Fork 0
forked from len0rd/rockbox

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
This commit is contained in:
William Wilgus 2018-05-28 17:56:06 +02:00
parent 19b2964d78
commit 2daec3d3c3
10 changed files with 3034 additions and 94 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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