From a26986e1a7b9ae26224454ec453bba7738a54d55 Mon Sep 17 00:00:00 2001 From: Yu-Ping Wu Date: Fri, 13 Dec 2019 17:13:42 +0800 Subject: libpayload: cbgfx: Support drawing a box with rounded corners A function draw_rounded_box() is added to draw a box with rounded corners. In addition, this function is different from draw_box() in 2 ways: - The position and size arguments are relative to the canvas. - This function supports drawing only the border of a box (linear time complexity when the thickness is fixed). BRANCH=none BUG=b:146105976 TEST=emerge-nami libpayload Change-Id: Ie480410d2fd8316462d5ff874999ae2317de04f9 Signed-off-by: Yu-Ping Wu Reviewed-on: https://review.coreboot.org/c/coreboot/+/37757 Tested-by: build bot (Jenkins) Reviewed-by: Hung-Te Lin --- payloads/libpayload/drivers/video/graphics.c | 156 +++++++++++++++++++++++++++ payloads/libpayload/include/cbgfx.h | 19 ++++ 2 files changed, 175 insertions(+) diff --git a/payloads/libpayload/drivers/video/graphics.c b/payloads/libpayload/drivers/video/graphics.c index 6b5664bbcf..d346e4b733 100644 --- a/payloads/libpayload/drivers/video/graphics.c +++ b/payloads/libpayload/drivers/video/graphics.c @@ -243,6 +243,162 @@ int draw_box(const struct rect *box, const struct rgb_color *rgb) return CBGFX_SUCCESS; } +int draw_rounded_box(const struct scale *pos_rel, const struct scale *dim_rel, + const struct rgb_color *rgb, + const struct fraction *thickness, + const struct fraction *radius) +{ + struct vector top_left; + struct vector size; + struct vector p, t; + + if (cbgfx_init()) + return CBGFX_ERROR_INIT; + + const uint32_t color = calculate_color(rgb, 0); + + transform_vector(&top_left, &canvas.size, pos_rel, &canvas.offset); + transform_vector(&size, &canvas.size, dim_rel, &vzero); + add_vectors(&t, &top_left, &size); + if (within_box(&t, &canvas) < 0) { + LOG("Box exceeds canvas boundary\n"); + return CBGFX_ERROR_BOUNDARY; + } + + if (!is_valid_fraction(thickness) || !is_valid_fraction(radius)) + return CBGFX_ERROR_INVALID_PARAMETER; + + struct scale thickness_scale = { + .x = { .n = thickness->n, .d = thickness->d }, + .y = { .n = thickness->n, .d = thickness->d }, + }; + struct scale radius_scale = { + .x = { .n = radius->n, .d = radius->d }, + .y = { .n = radius->n, .d = radius->d }, + }; + struct vector d, r, s; + transform_vector(&d, &canvas.size, &thickness_scale, &vzero); + transform_vector(&r, &canvas.size, &radius_scale, &vzero); + const uint8_t has_thickness = d.x > 0 && d.y > 0; + if (thickness->n != 0 && !has_thickness) + LOG("Thickness truncated to 0\n"); + const uint8_t has_radius = r.x > 0 && r.y > 0; + if (radius->n != 0 && !has_radius) + LOG("Radius truncated to 0\n"); + if (has_radius) { + if (d.x > r.x || d.y > r.y) { + LOG("Thickness cannot be greater than radius\n"); + return CBGFX_ERROR_INVALID_PARAMETER; + } + if (r.x * 2 > t.x - top_left.x || r.y * 2 > t.y - top_left.y) { + LOG("Radius cannot be greater than half of the box\n"); + return CBGFX_ERROR_INVALID_PARAMETER; + } + } + + /* Step 1: Draw edges */ + int32_t x_begin, x_end; + if (has_thickness) { + /* top */ + for (p.y = top_left.y; p.y < top_left.y + d.y; p.y++) + for (p.x = top_left.x + r.x; p.x < t.x - r.x; p.x++) + set_pixel(&p, color); + /* bottom */ + for (p.y = t.y - d.y; p.y < t.y; p.y++) + for (p.x = top_left.x + r.x; p.x < t.x - r.x; p.x++) + set_pixel(&p, color); + for (p.y = top_left.y + r.y; p.y < t.y - r.y; p.y++) { + /* left */ + for (p.x = top_left.x; p.x < top_left.x + d.x; p.x++) + set_pixel(&p, color); + /* right */ + for (p.x = t.x - d.x; p.x < t.x; p.x++) + set_pixel(&p, color); + } + } else { + /* Fill the regions except circular sectors */ + for (p.y = top_left.y; p.y < t.y; p.y++) { + if (p.y >= top_left.y + r.y && p.y < t.y - r.y) { + x_begin = top_left.x; + x_end = t.x; + } else { + x_begin = top_left.x + r.x; + x_end = t.x - r.x; + } + for (p.x = x_begin; p.x < x_end; p.x++) + set_pixel(&p, color); + } + } + + if (!has_radius) + return CBGFX_SUCCESS; + + /* + * Step 2: Draw rounded corners + * When has_thickness, only the border is drawn. With fixed thickness, + * the time complexity is linear to the size of the box. + */ + if (has_thickness) { + s.x = r.x - d.x; + s.y = r.y - d.y; + } else { + s.x = 0; + s.y = 0; + } + + /* Use 64 bits to avoid overflow */ + int32_t x, y; + uint64_t yy; + const uint64_t rrx = r.x * r.x, rry = r.y * r.y; + const uint64_t ssx = s.x * s.x, ssy = s.y * s.y; + x_begin = 0; + x_end = 0; + for (y = r.y - 1; y >= 0; y--) { + /* + * The inequality is valid in the beginning of each iteration: + * y^2 + x_end^2 < r^2 + */ + yy = y * y; + /* Check yy/ssy + xx/ssx < 1 */ + while (yy * ssx + x_begin * x_begin * ssy < ssx * ssy) + x_begin++; + /* The inequality must be valid now: y^2 + x_begin >= s^2 */ + x = x_begin; + /* Check yy/rry + xx/rrx < 1 */ + while (x < x_end || yy * rrx + x * x * rry < rrx * rry) { + /* + * Example sequence of (y, x) when s = (4, 4) and + * r = (5, 5): + * [(4, 0), (4, 1), (4, 2), (3, 3), (2, 4), + * (1, 4), (0, 4)]. + * If s.x==s.y r.x==r.y, then the sequence will be + * symmetric, and x and y will range from 0 to (r-1). + */ + /* top left */ + p.y = top_left.y + r.y - 1 - y; + p.x = top_left.x + r.x - 1 - x; + set_pixel(&p, color); + /* top right */ + p.y = top_left.y + r.y - 1 - y; + p.x = t.x - r.x + x; + set_pixel(&p, color); + /* bottom left */ + p.y = t.y - r.y + y; + p.x = top_left.x + r.x - 1 - x; + set_pixel(&p, color); + /* bottom right */ + p.y = t.y - r.y + y; + p.x = t.x - r.x + x; + set_pixel(&p, color); + x++; + } + x_end = x; + /* (x_begin <= x_end) must hold now */ + } + + return CBGFX_SUCCESS; +} + int clear_canvas(const struct rgb_color *rgb) { const struct rect box = { diff --git a/payloads/libpayload/include/cbgfx.h b/payloads/libpayload/include/cbgfx.h index cffc7fd7a6..3276867cb2 100644 --- a/payloads/libpayload/include/cbgfx.h +++ b/payloads/libpayload/include/cbgfx.h @@ -114,6 +114,25 @@ struct rgb_color { */ int draw_box(const struct rect *box, const struct rgb_color *rgb); +/** + * Draw a box with rounded corners on screen. + * + * @param[in] pos_rel Coordinate of the top left corner of the box relative to + * the canvas. + * @param[in] dim_rel Width and height of the image relative to the canvas. + * @param[in] rgb Color of the border of the box. + * @param[in] thickness Thickness of the border relative to the canvas. If zero + * is given, the box will be filled with the rgb color. + * @param[in] radius Radius of the rounded corners relative to the canvas. A + * zero value indicates sharp corners will be drawn. + * + * @return CBGFX_* error codes + */ +int draw_rounded_box(const struct scale *pos_rel, const struct scale *dim_rel, + const struct rgb_color *rgb, + const struct fraction *thickness, + const struct fraction *radius); + /** * Clear the canvas */ -- cgit v1.2.3