array: Graphic rendering of vectors and arrays

Graphic rendering of arrays and vectors like on the HP50

Fixes: #47

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2024-02-22 00:20:02 +01:00
parent f29346e830
commit 05d690712a
9 changed files with 259 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/vector-vertical.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -28,8 +28,10 @@
// ****************************************************************************
#include "array.h"
#include "arithmetic.h"
#include "functions.h"
#include "grob.h"
RECORDER(matrix, 16, "Determinant computation");
@ -70,6 +72,181 @@ HELP_BODY(array)
}
using pixsize = grob::pixsize;
static pixsize row_height(size_t r, size_t rows, size_t cols)
// ----------------------------------------------------------------------------
// Compute the height of a row (max of height of all grobs)
// ----------------------------------------------------------------------------
{
size_t nitems = rows * cols;
pixsize rh = 0;
for (size_t c = 0; c < cols; c++)
{
size_t i = r * cols + c;
grob_p colitem = grob_p(rt.stack(nitems + ~i));
ASSERT(colitem);
pixsize h = colitem->height();
if (rh < h)
rh = h;
}
return rh;
}
static pixsize col_width(size_t c, size_t rows, size_t cols)
// ----------------------------------------------------------------------------
// Compute the height of a row (max of height of all grobs)
// ----------------------------------------------------------------------------
{
size_t nitems = rows * cols;
pixsize cw = 0;
for (size_t r = 0; r < rows; r++)
{
size_t i = r * cols + c;
grob_p colitem = grob_p(rt.stack(nitems + ~i));
pixsize w = colitem->width();
if (cw < w)
cw = w;
}
return cw;
}
GRAPH_BODY(array)
// ----------------------------------------------------------------------------
// Render an array graphically
// ----------------------------------------------------------------------------
{
array_g a = o;
if (!a)
return nullptr;
size_t rows = 0;
size_t cols = 0;
bool mat = false;
bool vec = false;
if (a->is_matrix(&rows, &cols))
{
mat = true;
}
else if (a->is_vector(&cols))
{
vec = true;
rows = 1;
if (Settings.VerticalVectors())
{
rows = cols;
cols = 1;
}
}
// Fallback if we don't have a proper matrix or vector, e.g. non-rect
if (!mat && !vec)
return object::do_graph(a, g);
// Convert all elements to graphical equivalent
size_t nitems = rows * cols;
for (size_t i = 0; i < nitems; i++)
{
object_p item = rt.stack(i);
grob_g grob = item->graph(g);
if (!grob || grob->type() != ID_grob)
{
// Ran into a problem with one rendering, e.g. out of memory
// Fallback to rendering as text.
record(matrix_error, "Problem graphing %zu in %zu x %zu",
i, rows, cols);
rt.drop(nitems);
return object::do_graph(a, g);
}
rt.stack(i, grob);
}
// Compute the width, starting with 8 pixels for borders
pixsize bw = mat ? 4 : 2;
pixsize sw = 2;
pixsize gap = 12;
pixsize gw = 4 * bw + (cols - 1) * gap + 2 * sw;
pixsize gh = 0;
// Compute the width as the sum of the max width of all columns
for (size_t c = 0; c < cols; c++)
{
size_t cw = col_width(c, rows, cols);
gw += cw;
if (gw > g.maxw)
{
rt.drop(nitems);
return nullptr;
}
}
// Compute the height as the sum of the max height of all rows
for (size_t r = 0; r < rows; r++)
{
size_t rh = row_height(r, rows, cols);
gh += rh;
if (gh > g.maxh)
{
rt.drop(nitems);
return nullptr;
}
}
// Create the resulting graph
grob_g result = g.grob(gw, gh);
if (!result)
{
rt.drop(nitems);
return nullptr;
}
surface rs = result->pixels();
rs.fill(0, 0, gw, gh, g.background);
// Dimensions of porder and adjust
coord xl = 0;
coord xr = coord(gw)-2;
coord yt = 0;
coord yb = coord(gh)-4;
// Draw all items inside the matrix
coord yi = yt;
for (size_t r = 0; r < rows; r++)
{
pixsize rh = row_height(r, rows, cols);
coord xi = xl + 2 * bw + sw;
for (size_t c = 0; c < cols; c++)
{
pixsize cw = col_width(c, rows, cols);
size_t i = r * cols + c;
grob_p colitem = grob_p(rt.stack(nitems + ~i));
surface is = colitem->pixels();
pixsize iw = is.width();
pixsize ih = is.height();
rs.copy(is, xi + (cw - iw)/2, yi + (rh - ih)/2);
xi += cw + gap;
}
yi += rh;
}
// Add the borders
for (coord y = 1; y < yb; y++)
{
rs.fill(xl, y, xl+bw, y, g.foreground);
rs.fill(xr-bw, y, xr, y, g.foreground);
}
rs.fill(xl, yt, xl+2*bw, yt+1, g.foreground);
rs.fill(xr-2*bw, yt, xr, yt+1, g.foreground);
rs.fill(xl, yb, xl+2*bw, yb+1, g.foreground);
rs.fill(xr-2*bw, yb, xr, yb+1, g.foreground);
rt.drop(nitems);
return result;
}
// ============================================================================
//

View file

@ -93,6 +93,7 @@ struct array : list
OBJECT_DECL(array);
PARSE_DECL(array);
RENDER_DECL(array);
GRAPH_DECL(array);
HELP_DECL(array);
};

View file

@ -373,8 +373,8 @@ struct blitter
{
surface(pixword *p, size w, size h, size scanline)
: pixels(p),
width(w),
height(h),
w(w),
h(h),
scanline(scanline),
drawable(w, h)
{
@ -408,7 +408,7 @@ struct blitter
// --------------------------------------------------------------------
{
drawable = r;
drawable &= rect(width, height);
drawable &= rect(w, h);
}
void clip(coord x1, coord y1, coord x2, coord y2)
@ -432,7 +432,23 @@ struct blitter
// Return total drawing area
// --------------------------------------------------------------------
{
return rect(width, height);
return rect(w, h);
}
size width() const
// --------------------------------------------------------------------
// Total drawing width
// --------------------------------------------------------------------
{
return w;
}
size height() const
// --------------------------------------------------------------------
// Total drawing height
// --------------------------------------------------------------------
{
return h;
}
template <clipping Clip = FILL_SAFE>
@ -526,8 +542,8 @@ struct blitter
// Copy a rectangular area from the source
// --------------------------------------------------------------------
{
size w = src.width;
size h = src.height;
size w = src.w;
size h = src.h;
rect dest(x, y, x + w - 1, y + h - 1);
blit<Clip>(*this, src, dest, point(), blitop_source, clr);
}
@ -685,8 +701,8 @@ struct blitter
protected:
friend struct blitter;
pixword *pixels; // Word-aligned address of surface buffer
size width; // Pixel width of buffer
size height; // Pixel height of buffer
size w; // Pixel width of buffer
size h; // Pixel height of buffer
size scanline; // Scanline for the buffer (can be > width)
rect drawable; // Draw area (clipping outside)
};
@ -1501,7 +1517,7 @@ void blitter::blit(Dst &dst,
}
ASSERT(dp >= dst.pixels);
ASSERT(dp <= dst.pixels + (dst.scanline * dst.height + (BPW-1)) / BPW);
ASSERT(dp <= dst.pixels + (dst.scanline * dst.h + (BPW-1)) / BPW);
pixword ddata = dp[0];
pixword tdata = op(ddata, sdata, cdata);
*dp = (tdata & dmask) | (ddata & ~dmask);
@ -1545,7 +1561,7 @@ inline void blitter::surface<blitter::MONOCHROME_REVERSE>::horizontal_adjust(
// On the DM42, we need horizontal adjustment for coordinates
// ----------------------------------------------------------------------------
{
size w = width - 1;
size w = width() - 1;
coord ox1 = w - x2;
x2 = w - x1;
x1 = ox1;

View file

@ -710,6 +710,8 @@ FLAG(NumberedVariables, NoNumberedVariables)
FLAG(UseCrossForMultiplication, UseDotForMultiplication)
FLAG(HardwareFloatingPoint, SoftwareFloatingPoint)
FLAG(NoAngleUnits, SetAngleUnits)
FLAG(VerticalLists, HorizontalLists)
FLAG(VerticalVectors, HorizontalVectors)
ALIAS(HardwareFloatingPoint, "HFP")
ALIAS(HardwareFloatingPoint, "HardFP")

View file

@ -5297,8 +5297,53 @@ void tests::graphic_expressions_rendering()
.test(CLEAR, EXIT, EXIT)
.test("1 'X' +", ENTER, B, C, E, "3 X 3", LSHIFT, B, MUL, ADD)
.test(ALPHA, X, NOSHIFT, J, K, L, ADD, C, B, C, B)
.wait(100)
.image_noheader("reduced");
step("Constants")
.test(CLEAR, LSHIFT, I, F1, F1, F2, F3)
.image_noheader("constants");
step("Vector")
.test(CLEAR, LSHIFT, KEY9, "1 2 3", ENTER, EXIT)
.wait(100)
.image_noheader("vector-horizontal");
step("Vector vertical rendering")
.test("VerticalVectors", ENTER)
.wait(100)
.image_noheader("vector-vertical");
step("Vector horizontal rendering")
.test("HorizontalVectors", ENTER)
.wait(100)
.image_noheader("vector-horizontal");
step("Matrix")
.test(CLEAR, LSHIFT, KEY9,
LSHIFT, KEY9, "1 2 3 4", DOWN,
LSHIFT, KEY9, "4 5 6 7", DOWN,
LSHIFT, KEY9, "8 9 10 11", DOWN,
LSHIFT, KEY9, "12 13 14 18", ENTER, EXIT)
.wait(100)
.image_noheader("matrix");
step("Matrix with smaller size")
.test(13, DIV, ENTER, MUL)
.wait(100)
.image_noheader("matrix-smaller");
step("Lists")
.test(CLEAR, RSHIFT, SPACE, "1 2 \"ABC\"", ENTER, EXIT)
.wait(100)
.image_noheader("list-horizontal");
step("List vertical")
.test("VerticalLists", ENTER)
.test(CLEAR, RSHIFT, SPACE, "1 2 \"ABC\"", ENTER, EXIT)
.wait(100)
.image_noheader("list-vertical");
step("List horizontal")
.test("HorizontalLists", ENTER)
.test(CLEAR, RSHIFT, SPACE, "1 2 \"ABC\"", ENTER, EXIT)
.wait(100)
.image_noheader("list-horizontal");
}
@ -6650,13 +6695,14 @@ tests &tests::image(cstring file, int x, int y, int w, int h)
}
tests &tests::image_noheader(cstring name)
tests &tests::image_noheader(cstring name, uint ignoremenus)
// ----------------------------------------------------------------------------
// Image, skipping the header area
// ----------------------------------------------------------------------------
{
const int header_h = 20;
return image(name, 0, header_h, LCD_W, LCD_H - header_h);
const int menu_h = 22 * ignoremenus;
return image(name, 0, header_h, LCD_W, LCD_H - header_h - menu_h);
}

View file

@ -294,7 +294,7 @@ public:
tests &expect(unsigned long long output);
tests &match(cstring regexp);
tests &image(cstring name, int x=0, int y=0, int w=LCD_W, int h=LCD_H);
tests &image_noheader(cstring name);
tests &image_noheader(cstring name, uint ignoremenus=0);
tests &type(object::id ty);
tests &shift(bool s);
tests &xshift(bool x);

View file

@ -1250,6 +1250,8 @@ static flag_conversion flag_conversions[] =
{ -56, object::ID_BeepOff },
{ -64, object::ID_IndexWrapped },
{ -65, object::ID_MultiLineStack },
{ -97, object::ID_VerticalLists },
{ -98, object::ID_VerticalVectors },
{ -103, object::ID_ComplexResults },
};