rockbox/apps/plugins/lua/include_lua/print.lua
William Wilgus af35d19916 Rocklua -- Extend / Fix rliImage
Some devices(1-bit / 2-bit displays) have packed bit formats that
 need to be unpacked in order to work on them at a pixel level.

This caused a few issues on 1 & 2-bit devices:
 Greatly Oversized data arrays for bitmaps
 Improper handling of native image data
 Framebuffer data was near unusable without jumping through hoops

Conversion between native addressing and per pixel addressing
 incurs extra overhead but it is much faster to do it
 on the 'C' side rather than in lua.

Not to mention the advantage of a unified interface for the end programer

-------------------------------------------------------------------
Adds a sane way to access each pixel of image data
Adds:
--------------------------------------------------------------------
img:clear([color],[x1],[y1],[x2],[y2])
 (set whole image or a portion to a particular value)
--------------------------------------------------------------------
img:invert([x1],[y1],[x2],[y2])
 (inverts whole image or a portion)
--------------------------------------------------------------------
img:marshal([x1],[y1],[x2],[y2],[funct])
 (calls funct for each point defined by rect of x1,y1 x2,y2
  returns value and allows setting value of each point return
  nil to terminate early)
--------------------------------------------------------------------
img:points([x1],[y1],[x2],[y2],[dx],[dy])
 (returns iterator function that steps delta-x and delta-y pixels each call
  returns value of pixel each call but doesn't allow setting to a new value
  compare to lua pairs method)
--------------------------------------------------------------------
img:copy(src,[x1],[y1],[x2],[y2],[w],[h],[clip][operation][clr/funct])
 (copies all or part of an image -- straight copy or special ops
  optionally calls funct for each point defined by rect of
  x1, y1, w, h and  x2, y2, w, h for dest and src images
  returns value of dst and src and allows setting value of
  each point return nil to terminate early)
--------------------------------------------------------------------
img:line(x1, y1, x2, y2, color)
--------------------------------------------------------------------
img:ellipse(x1, y1, x2, y2, color, [fillcolor]
--------------------------------------------------------------------
Fixed handling of 2-bit vertical integrated screens

Added direct element access for saving / restoring native image etc.

Added more data to tostring() handler and a way to access individual items

Added equals method to see if two variables reference the same image address
(doesn't check if two separate images contain the same 'picture')

Optimized get and set routines

Fixed out of bound x coord access shifting to next line

Added lua include files to expose new functionality

Finished image saving routine

Static allocation of set_viewport struct faster + saves ram over dynamic

Cleaned up code

Fixed pixel get/set for 1/2 bit devices

Fixed handling for 24-bit devices (32?)

-------------------------------------------------------------------------
Example lua script to follow on forums
-------------------------------------------------------------------------

Change-Id: I8a9ff0ff72aacf4b1662767ccb2b312fc355239c
2018-07-23 05:13:32 +02:00

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