cbgfx: allow draw_bitmap to render outside canvas

This change allows draw_bitmap to draw an image outside the canvas
with the original size if the scale parameter is zero. This is used
for example when drawing a splash screen which has to be positioned
at a pixel perfect location.

BUG=none
BRANCH=master
TEST=Draw pictures and boxes on Samus and Ryu

Change-Id: Ia2d8799184d1aa192e2c50850e248bee8f234006
Signed-off-by: Patrick Georgi <pgeorgi@google.com>
Original-Commit-Id: 45d4717fe5c3e3554bd79b63ade490d88cf00bbe
Original-Change-Id: I48aa21122cfc2ee43bcb1b8f87b00c66abdc230e
Original-Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/295961
Original-Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/11923
Tested-by: build bot (Jenkins)
This commit is contained in:
Daisuke Nojiri 2015-08-26 14:47:06 -07:00 committed by Patrick Georgi
parent 09ad206cda
commit dd49eccb50
2 changed files with 159 additions and 87 deletions

View File

@ -14,8 +14,8 @@
* non-drawing areas on the left and right. The screen is assumed to be
* landscape.
*/
static struct vector canvas;
static uint32_t canvas_offset; /* horizontal position of canvas */
static struct rect canvas;
static struct rect screen;
/*
* Framebuffer is assumed to assign a higher coordinate (larger x, y) to
@ -34,9 +34,14 @@ static char initialized = 0;
*/
#define BITMAP_SCALE_BASE 256
#define ROUNDUP(x, y) ((x) + ((y) - ((x) % (y))))
#define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
#define ABS(x) ((x) < 0 ? -(x) : (x))
static const struct vector vzero = {
.x = 0,
.y = 0,
};
static void add_vectors(struct vector *out,
const struct vector *v1, const struct vector *v2)
{
@ -44,27 +49,31 @@ static void add_vectors(struct vector *out,
out->y = v1->y + v2->y;
}
static void scale_vector(struct vector *out, const struct vector *in,
size_t scale, size_t base)
/*
* Transform a vector:
* x' = x * a_x + offset_x
* y' = y * a_y + offset_y
*/
static void transform_vector(struct vector *out,
const struct vector *in,
const struct scale *a,
const struct vector *offset)
{
out->x = in->x * scale / base;
out->y = in->y * scale / base;
}
static void to_canvas(const struct vector *relative, struct vector *absolute)
{
absolute->x = canvas.width * relative->x / CANVAS_SCALE;
absolute->y = canvas.height * relative->y / CANVAS_SCALE;
out->x = a->x.nume * in->x / a->x.deno + offset->x;
out->y = a->y.nume * in->y / a->y.deno + offset->y;
}
/*
* Returns 1 if exclusively within canvas, or 0 if inclusively within canvas.
* Returns 1 if v is exclusively within box, 0 if v is inclusively within box,
* or -1 otherwise. Note that only the left and bottom edges are considered.
*/
static int within_canvas(const struct vector *v)
static int within_box(const struct vector *v, const struct rect *bound)
{
if (v->x < canvas.width && v->y < canvas.height)
if (v->x < bound->offset.x + bound->size.width &&
v->y < bound->offset.y + bound->size.height)
return 1;
else if (v->x <= canvas.width && v->y <= canvas.height)
else if (v->x <= bound->offset.x + bound->size.width &&
v->y <= bound->offset.y + bound->size.height)
return 0;
else
return -1;
@ -90,7 +99,7 @@ static inline void set_pixel(struct vector *coord, uint32_t color)
{
const int bpp = fbinfo->bits_per_pixel;
int i;
uint8_t * const pixel = fbaddr + (coord->x + canvas_offset +
uint8_t * const pixel = fbaddr + (coord->x +
coord->y * fbinfo->x_resolution) * bpp / 8;
for (i = 0; i < bpp / 8; i++)
pixel[i] = (color >> (i * 8));
@ -113,40 +122,53 @@ static int cbgfx_init(void)
if (!fbaddr)
return -1;
/* calculate canvas size, assuming the screen is landscape */
canvas.height = fbinfo->y_resolution;
canvas.width = canvas.height;
canvas_offset = (fbinfo->x_resolution - canvas.width) / 2;
if (canvas_offset < 0) {
LOG("Portrait screens are not supported\n");
screen.size.width = fbinfo->x_resolution;
screen.size.height = fbinfo->y_resolution;
screen.offset.x = 0;
screen.offset.y = 0;
/* Calculate canvas size & offset, assuming the screen is landscape */
if (screen.size.height > screen.size.width) {
LOG("Portrait screen not supported\n");
return -1;
}
canvas.size.height = screen.size.height;
canvas.size.width = canvas.size.height;
canvas.offset.x = (screen.size.width - canvas.size.width) / 2;
canvas.offset.y = 0;
initialized = 1;
LOG("cbgfx initialized: canvas width=%d, height=%d, offset=%d\n",
canvas.width, canvas.height, canvas_offset);
LOG("cbgfx initialized: screen:width=%d, height=%d, offset=%d canvas:width=%d, height=%d, offset=%d\n",
screen.size.width, screen.size.height, screen.offset.x,
canvas.size.width, canvas.size.height, canvas.offset.x);
return 0;
}
int draw_box(const struct vector *top_left_rel,
const struct vector *size_rel,
const struct rgb_color *rgb)
int draw_box(const struct rect *box, const struct rgb_color *rgb)
{
struct vector top_left;
struct vector size;
struct vector p, t;
const uint32_t color = calculate_color(rgb);
const struct scale top_left_s = {
.x = { .nume = box->offset.x, .deno = CANVAS_SCALE, },
.y = { .nume = box->offset.y, .deno = CANVAS_SCALE, }
};
const struct scale size_s = {
.x = { .nume = box->size.x, .deno = CANVAS_SCALE, },
.y = { .nume = box->size.y, .deno = CANVAS_SCALE, }
};
if (cbgfx_init())
return CBGFX_ERROR_INIT;
to_canvas(top_left_rel, &top_left);
to_canvas(size_rel, &size);
transform_vector(&top_left, &canvas.size, &top_left_s, &canvas.offset);
transform_vector(&size, &canvas.size, &size_s, &vzero);
add_vectors(&t, &top_left, &size);
if (within_canvas(&t) < 0) {
if (within_box(&t, &canvas) < 0) {
LOG("Box exceeds canvas boundary\n");
return CBGFX_ERROR_BOUNDARY;
return CBGFX_ERROR_CANVAS_BOUNDARY;
}
for (p.y = top_left.y; p.y < t.y; p.y++)
@ -158,23 +180,40 @@ int draw_box(const struct vector *top_left_rel,
int clear_canvas(struct rgb_color *rgb)
{
const struct vector coord = {
.x = 0,
.y = 0,
};
const struct vector size = {
.width = CANVAS_SCALE,
.height = CANVAS_SCALE,
const struct rect box = {
vzero,
.size = {
.width = CANVAS_SCALE,
.height = CANVAS_SCALE,
},
};
if (cbgfx_init())
return CBGFX_ERROR_INIT;
return draw_box(&coord, &size, rgb);
return draw_box(&box, rgb);
}
/*
* This check guarantees we will not try to read outside pixel data.
*/
static int check_bound(const struct vector *image,
const struct bitmap_header_v3 *header,
const struct scale *scale)
{
struct vector p = {
.x = (image->width - 1) * scale->x.deno / scale->x.nume,
.y = (image->height -1) * scale->y.deno / scale->y.nume,
};
struct rect bound = {
.offset = vzero,
.size = { .width = header->width, .height = header->height, },
};
return within_box(&p, &bound) < 0;
}
static int draw_bitmap_v3(const struct vector *top_left,
size_t scale,
const struct scale *scale,
const struct vector *image,
const struct bitmap_header_v3 *header,
const struct bitmap_palette_element_v3 *palette,
@ -196,11 +235,14 @@ static int draw_bitmap_v3(const struct vector *top_left,
LOG("Unsupported bits per pixel: %d\n", bpp);
return CBGFX_ERROR_BITMAP_FORMAT;
}
if (scale == 0) {
if (scale->x.nume == 0 || scale->y.nume == 0) {
LOG("Scaling out of range\n");
return CBGFX_ERROR_SCALE_OUT_OF_RANGE;
}
if (check_bound(image, header, scale))
return CBGFX_ERROR_SCALE_OUT_OF_RANGE;
const int32_t y_stride = ROUNDUP(header->width * bpp / 8, 4);
/*
* header->height can be positive or negative.
@ -225,18 +267,11 @@ static int draw_bitmap_v3(const struct vector *top_left,
*/
struct vector s, d;
for (d.y = 0; d.y < image->height; d.y++, p.y += dir) {
s.y = d.y * BITMAP_SCALE_BASE / scale;
s.y = d.y * scale->y.deno / scale->y.nume;
const uint8_t *data = pixel_array + s.y * y_stride;
p.x = top_left->x;
for (d.x = 0; d.x < image->width; d.x++, p.x++) {
s.x = d.x * BITMAP_SCALE_BASE / scale;
if (s.y * y_stride + s.x > header->size)
/*
* Because we're handling integers rounded by
* divisions, we might get here legitimately
* when rendering the last row of a sane image.
*/
return CBGFX_SUCCESS;
s.x = d.x * scale->x.deno / scale->x.nume;
uint8_t index = data[s.x];
if (index >= header->colors_used) {
LOG("Color index exceeds palette boundary\n");
@ -279,11 +314,11 @@ static int get_bitmap_file_header(const void *bitmap, size_t size,
}
static int parse_bitmap_header_v3(const uint8_t *bitmap,
const struct bitmap_file_header *file_header,
/* ^--- IN / OUT ---v */
struct bitmap_header_v3 *header,
const struct bitmap_palette_element_v3 **palette,
const uint8_t **pixel_array)
const struct bitmap_file_header *file_header,
/* ^--- IN / OUT ---v */
struct bitmap_header_v3 *header,
const struct bitmap_palette_element_v3 **palette,
const uint8_t **pixel_array)
{
struct bitmap_header_v3 *h;
size_t header_offset = sizeof(struct bitmap_file_header);
@ -339,8 +374,9 @@ int draw_bitmap(const struct vector *top_left_rel,
struct bitmap_header_v3 header;
const struct bitmap_palette_element_v3 *palette;
const uint8_t *pixel_array;
struct vector top_left, t, image;
size_t scale;
struct vector top_left, image;
struct scale scale;
struct vector t;
int rv;
if (cbgfx_init())
@ -356,25 +392,43 @@ int draw_bitmap(const struct vector *top_left_rel,
if (rv)
return rv;
/* convert relative coordinate to canvas coordinate */
to_canvas(top_left_rel, &top_left);
/*
* Calculate absolute coordinate and self-scale (scale relative to image
* size). If relative scale is zero, the image is displayed at the
* original scale and tol_left_rel is treated as absolute coordinate.
*/
if (scale_rel) {
const struct scale s = {
.x = { .nume = top_left_rel->x, .deno = CANVAS_SCALE, },
.y = { .nume = top_left_rel->y, .deno = CANVAS_SCALE, },
};
transform_vector(&top_left, &canvas.size, &s, &canvas.offset);
scale.x.nume = scale_rel * canvas.size.width;
scale.x.deno = header.width * CANVAS_SCALE;
} else {
add_vectors(&top_left, top_left_rel, &vzero);
scale.x.nume = 1;
scale.x.deno = 1;
}
scale.y.nume = scale.x.nume;
scale.y.deno = scale.x.deno;
/* convert canvas scale to self scale (relative to image width) */
scale = scale_rel * canvas.width * BITMAP_SCALE_BASE /
(CANVAS_SCALE * header.width);
/* calculate height and width of the image on canvas */
/* Calculate height and width of the image on screen */
image.width = header.width;
image.height = ABS(header.height);
scale_vector(&image, &image, scale, BITMAP_SCALE_BASE);
transform_vector(&image, &image, &scale, &vzero);
/* check whether right bottom corner exceeds canvas boundaries or not */
/* Check whether the right bottom corner is within screen and canvas */
add_vectors(&t, &image, &top_left);
if (within_canvas(&t) < 0) {
if (scale_rel && within_box(&t, &canvas) < 0) {
LOG("Bitmap image exceeds canvas boundary\n");
return CBGFX_ERROR_BOUNDARY;
return CBGFX_ERROR_CANVAS_BOUNDARY;
}
if (within_box(&t, &screen) < 0) {
LOG("Bitmap image exceeds screen boundary\n");
return CBGFX_ERROR_SCREEN_BOUNDARY;
}
return draw_bitmap_v3(&top_left, scale, &image,
return draw_bitmap_v3(&top_left, &scale, &image,
&header, palette, pixel_array);
}

View File

@ -11,13 +11,15 @@
/*
* API error codes
*/
#define CBGFX_SUCCESS 0
#define CBGFX_SUCCESS 0
/* unknown error */
#define CBGFX_ERROR_UNKNOWN 1
#define CBGFX_ERROR_UNKNOWN 1
/* failed to initialize cbgfx library */
#define CBGFX_ERROR_INIT 2
#define CBGFX_ERROR_INIT 2
/* drawing beyond screen boundary */
#define CBGFX_ERROR_SCREEN_BOUNDARY 3
/* drawing beyond canvas boundary */
#define CBGFX_ERROR_BOUNDARY 3
#define CBGFX_ERROR_CANVAS_BOUNDARY 4
/* bitmap error: signature mismatch */
#define CBGFX_ERROR_BITMAP_SIGNATURE 0x10
/* bitmap error: unsupported format */
@ -27,17 +29,32 @@
/* bitmap error: scaling out of range */
#define CBGFX_ERROR_SCALE_OUT_OF_RANGE 0x13
struct fraction {
int32_t nume;
int32_t deno;
};
struct scale {
struct fraction x;
struct fraction y;
};
struct vector {
union {
uint32_t x;
uint32_t width;
int32_t x;
int32_t width;
};
union {
uint32_t y;
uint32_t height;
int32_t y;
int32_t height;
};
};
struct rect {
struct vector offset;
struct vector size;
};
struct rgb_color {
uint8_t red;
uint8_t green;
@ -59,16 +76,14 @@ struct rgb_color {
/*
* draw a box filled with a color on screen
*
* top_left_rel: coordinate of top left corner of the box, relative to canvas.
* (0 - CANVAS_SCALE).
* size_rel: width and height of the box, relative to canvas. (0 - CANVAS_SCALE)
* box: .offset points the coordinate of the top left corner and .size specifies
* width and height of the box. Both are relative to the canvas size thus scale
* from 0 to CANVAS_SCALE (0 to 100%).
* rgb: RGB color of the box.
*
* return: CBGFX_* error codes
*/
int draw_box(const struct vector *top_left_rel,
const struct vector *size_rel,
const struct rgb_color *rgb);
int draw_box(const struct rect *box, const struct rgb_color *rgb);
/*
* Clear the canvas
@ -79,8 +94,11 @@ int clear_canvas(struct rgb_color *rgb);
* Draw a bitmap image on screen.
*
* top_left_rel: coordinate of the top left corner of the image relative to the
* canvas (0 - CANVAS_SCALE).
* scale_rel: scale factor relative to the canvas width (0 - CANVAS_SCALE).
* canvas (0 - CANVAS_SCALE). If scale_rel is zero, this is treated as absolute
* coordinate.
* scale_rel: scale factor relative to the canvas width (0 - CANVAS_SCALE). If
* this is zero, scaling is turned off and the image is rendered with the
* original size.
* bitmap: pointer to the bitmap data, starting from the file header.
* size: size of the bitmap data
*