af35d19916
Some devices(1-bit / 2-bit displays) have packed bit formats that need to be unpacked in order to work on them at a pixel level. This caused a few issues on 1 & 2-bit devices: Greatly Oversized data arrays for bitmaps Improper handling of native image data Framebuffer data was near unusable without jumping through hoops Conversion between native addressing and per pixel addressing incurs extra overhead but it is much faster to do it on the 'C' side rather than in lua. Not to mention the advantage of a unified interface for the end programer ------------------------------------------------------------------- Adds a sane way to access each pixel of image data Adds: -------------------------------------------------------------------- img:clear([color],[x1],[y1],[x2],[y2]) (set whole image or a portion to a particular value) -------------------------------------------------------------------- img:invert([x1],[y1],[x2],[y2]) (inverts whole image or a portion) -------------------------------------------------------------------- img:marshal([x1],[y1],[x2],[y2],[funct]) (calls funct for each point defined by rect of x1,y1 x2,y2 returns value and allows setting value of each point return nil to terminate early) -------------------------------------------------------------------- img:points([x1],[y1],[x2],[y2],[dx],[dy]) (returns iterator function that steps delta-x and delta-y pixels each call returns value of pixel each call but doesn't allow setting to a new value compare to lua pairs method) -------------------------------------------------------------------- img:copy(src,[x1],[y1],[x2],[y2],[w],[h],[clip][operation][clr/funct]) (copies all or part of an image -- straight copy or special ops optionally calls funct for each point defined by rect of x1, y1, w, h and x2, y2, w, h for dest and src images returns value of dst and src and allows setting value of each point return nil to terminate early) -------------------------------------------------------------------- img:line(x1, y1, x2, y2, color) -------------------------------------------------------------------- img:ellipse(x1, y1, x2, y2, color, [fillcolor] -------------------------------------------------------------------- Fixed handling of 2-bit vertical integrated screens Added direct element access for saving / restoring native image etc. Added more data to tostring() handler and a way to access individual items Added equals method to see if two variables reference the same image address (doesn't check if two separate images contain the same 'picture') Optimized get and set routines Fixed out of bound x coord access shifting to next line Added lua include files to expose new functionality Finished image saving routine Static allocation of set_viewport struct faster + saves ram over dynamic Cleaned up code Fixed pixel get/set for 1/2 bit devices Fixed handling for 24-bit devices (32?) ------------------------------------------------------------------------- Example lua script to follow on forums ------------------------------------------------------------------------- Change-Id: I8a9ff0ff72aacf4b1662767ccb2b312fc355239c
378 lines
11 KiB
Lua
378 lines
11 KiB
Lua
--[[ 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
|
|
|