mb/google/brya/var/omnigul: Modify NVMe and UFS Storage support
[coreboot.git] / payloads / libpayload / drivers / video / graphics.c
blob632a283de9c166b92c64b6e4240a84b31965b3c3
1 /*
3 * Copyright (C) 2015 Google, Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
29 #include <libpayload.h>
30 #include <cbfs.h>
31 #include <fpmath.h>
32 #include <sysinfo.h>
33 #include "bitmap.h"
36 * 'canvas' is the drawing area located in the center of the screen. It's a
37 * square area, stretching vertically to the edges of the screen, leaving
38 * non-drawing areas on the left and right. The screen is assumed to be
39 * landscape.
41 static struct rect canvas;
42 static struct rect screen;
44 static uint8_t *gfx_buffer;
47 * Framebuffer is assumed to assign a higher coordinate (larger x, y) to
48 * a higher address
50 static const struct cb_framebuffer *fbinfo;
52 /* Shorthand for up-to-date virtual framebuffer address */
53 #define REAL_FB ((unsigned char *)phys_to_virt(fbinfo->physical_address))
54 #define FB (gfx_buffer ? gfx_buffer : REAL_FB)
56 #define LOG(x...) printf("CBGFX: " x)
57 #define PIVOT_H_MASK (PIVOT_H_LEFT|PIVOT_H_CENTER|PIVOT_H_RIGHT)
58 #define PIVOT_V_MASK (PIVOT_V_TOP|PIVOT_V_CENTER|PIVOT_V_BOTTOM)
59 #define ROUNDUP(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
61 static char initialized = 0;
63 static const struct vector vzero = {
64 .x = 0,
65 .y = 0,
68 struct color_transformation {
69 uint8_t base;
70 int16_t scale;
73 struct color_mapping {
74 struct color_transformation red;
75 struct color_transformation green;
76 struct color_transformation blue;
77 int enabled;
80 static struct color_mapping color_map;
82 static inline void set_color_trans(struct color_transformation *trans,
83 uint8_t bg_color, uint8_t fg_color)
85 trans->base = bg_color;
86 trans->scale = fg_color - bg_color;
89 int set_color_map(const struct rgb_color *background,
90 const struct rgb_color *foreground)
92 if (background == NULL || foreground == NULL)
93 return CBGFX_ERROR_INVALID_PARAMETER;
95 set_color_trans(&color_map.red, background->red, foreground->red);
96 set_color_trans(&color_map.green, background->green,
97 foreground->green);
98 set_color_trans(&color_map.blue, background->blue, foreground->blue);
99 color_map.enabled = 1;
101 return CBGFX_SUCCESS;
104 void clear_color_map(void)
106 color_map.enabled = 0;
109 struct blend_value {
110 uint8_t alpha;
111 struct rgb_color rgb;
114 static struct blend_value blend;
116 int set_blend(const struct rgb_color *rgb, uint8_t alpha)
118 if (rgb == NULL)
119 return CBGFX_ERROR_INVALID_PARAMETER;
121 blend.alpha = alpha;
122 blend.rgb = *rgb;
124 return CBGFX_SUCCESS;
127 void clear_blend(void)
129 blend.alpha = 0;
130 blend.rgb.red = 0;
131 blend.rgb.green = 0;
132 blend.rgb.blue = 0;
135 static void add_vectors(struct vector *out,
136 const struct vector *v1, const struct vector *v2)
138 out->x = v1->x + v2->x;
139 out->y = v1->y + v2->y;
142 static int fraction_equal(const struct fraction *f1, const struct fraction *f2)
144 return (int64_t)f1->n * f2->d == (int64_t)f2->n * f1->d;
147 static int is_valid_fraction(const struct fraction *f)
149 return f->d != 0;
152 static int is_valid_scale(const struct scale *s)
154 return is_valid_fraction(&s->x) && is_valid_fraction(&s->y);
157 static void reduce_fraction(struct fraction *out, int64_t n, int64_t d)
159 /* Simplest way to reduce the fraction until fitting in int32_t */
160 int shift = log2(MAX(ABS(n), ABS(d)) >> 31) + 1;
161 out->n = n >> shift;
162 out->d = d >> shift;
165 /* out = f1 + f2 */
166 static void add_fractions(struct fraction *out,
167 const struct fraction *f1, const struct fraction *f2)
169 reduce_fraction(out,
170 (int64_t)f1->n * f2->d + (int64_t)f2->n * f1->d,
171 (int64_t)f1->d * f2->d);
174 /* out = f1 - f2 */
175 static void subtract_fractions(struct fraction *out,
176 const struct fraction *f1,
177 const struct fraction *f2)
179 reduce_fraction(out,
180 (int64_t)f1->n * f2->d - (int64_t)f2->n * f1->d,
181 (int64_t)f1->d * f2->d);
184 static void add_scales(struct scale *out,
185 const struct scale *s1, const struct scale *s2)
187 add_fractions(&out->x, &s1->x, &s2->x);
188 add_fractions(&out->y, &s1->y, &s2->y);
192 * Transform a vector:
193 * x' = x * a_x + offset_x
194 * y' = y * a_y + offset_y
196 static int transform_vector(struct vector *out,
197 const struct vector *in,
198 const struct scale *a,
199 const struct vector *offset)
201 if (!is_valid_scale(a))
202 return CBGFX_ERROR_INVALID_PARAMETER;
203 out->x = (int64_t)a->x.n * in->x / a->x.d + offset->x;
204 out->y = (int64_t)a->y.n * in->y / a->y.d + offset->y;
205 return CBGFX_SUCCESS;
209 * Returns 1 if v is exclusively within box, 0 if v is inclusively within box,
210 * or -1 otherwise.
212 static int within_box(const struct vector *v, const struct rect *bound)
214 if (v->x > bound->offset.x &&
215 v->y > bound->offset.y &&
216 v->x < bound->offset.x + bound->size.width &&
217 v->y < bound->offset.y + bound->size.height)
218 return 1;
219 else if (v->x >= bound->offset.x &&
220 v->y >= bound->offset.y &&
221 v->x <= bound->offset.x + bound->size.width &&
222 v->y <= bound->offset.y + bound->size.height)
223 return 0;
224 else
225 return -1;
228 /* Helper function that applies color_map to the color. */
229 static inline uint8_t apply_map(uint8_t color,
230 const struct color_transformation *trans)
232 if (!color_map.enabled)
233 return color;
234 return trans->base + trans->scale * color / UINT8_MAX;
238 * Helper function that applies color and opacity from blend struct
239 * into the color.
241 static inline uint8_t apply_blend(uint8_t color, uint8_t blend_color)
243 if (blend.alpha == 0 || color == blend_color)
244 return color;
246 return (color * (256 - blend.alpha) +
247 blend_color * blend.alpha) / 256;
250 static inline uint32_t calculate_color(const struct rgb_color *rgb,
251 uint8_t invert)
253 uint32_t color = 0;
255 color |= (apply_blend(apply_map(rgb->red, &color_map.red),
256 blend.rgb.red)
257 >> (8 - fbinfo->red_mask_size))
258 << fbinfo->red_mask_pos;
259 color |= (apply_blend(apply_map(rgb->green, &color_map.green),
260 blend.rgb.green)
261 >> (8 - fbinfo->green_mask_size))
262 << fbinfo->green_mask_pos;
263 color |= (apply_blend(apply_map(rgb->blue, &color_map.blue),
264 blend.rgb.blue)
265 >> (8 - fbinfo->blue_mask_size))
266 << fbinfo->blue_mask_pos;
267 if (invert)
268 color ^= 0xffffffff;
269 return color;
273 * Plot a pixel in a framebuffer. This is called from tight loops. Keep it slim
274 * and do the validation at callers' site.
276 static inline void set_pixel(struct vector *coord, uint32_t color)
278 const int bpp = fbinfo->bits_per_pixel;
279 const int bpl = fbinfo->bytes_per_line;
280 struct vector rcoord;
281 int i;
283 switch (fbinfo->orientation) {
284 case CB_FB_ORIENTATION_NORMAL:
285 default:
286 rcoord.x = coord->x;
287 rcoord.y = coord->y;
288 break;
289 case CB_FB_ORIENTATION_BOTTOM_UP:
290 rcoord.x = screen.size.width - 1 - coord->x;
291 rcoord.y = screen.size.height - 1 - coord->y;
292 break;
293 case CB_FB_ORIENTATION_LEFT_UP:
294 rcoord.x = coord->y;
295 rcoord.y = screen.size.width - 1 - coord->x;
296 break;
297 case CB_FB_ORIENTATION_RIGHT_UP:
298 rcoord.x = screen.size.height - 1 - coord->y;
299 rcoord.y = coord->x;
300 break;
303 uint8_t * const pixel = FB + rcoord.y * bpl + rcoord.x * bpp / 8;
304 for (i = 0; i < bpp / 8; i++)
305 pixel[i] = (color >> (i * 8));
309 * Initializes the library. Automatically called by APIs. It sets up
310 * the canvas and the framebuffer.
312 static int cbgfx_init(void)
314 if (initialized)
315 return 0;
317 fbinfo = &lib_sysinfo.framebuffer;
319 if (!fbinfo->physical_address)
320 return CBGFX_ERROR_FRAMEBUFFER_ADDR;
322 switch (fbinfo->orientation) {
323 default: /* Normal or rotated 180 degrees. */
324 screen.size.width = fbinfo->x_resolution;
325 screen.size.height = fbinfo->y_resolution;
326 break;
327 case CB_FB_ORIENTATION_LEFT_UP: /* 90 degree rotation. */
328 case CB_FB_ORIENTATION_RIGHT_UP:
329 screen.size.width = fbinfo->y_resolution;
330 screen.size.height = fbinfo->x_resolution;
331 break;
333 screen.offset.x = 0;
334 screen.offset.y = 0;
336 /* Calculate canvas size & offset. Canvas is always square. */
337 if (screen.size.height > screen.size.width) {
338 canvas.size.height = screen.size.width;
339 canvas.size.width = canvas.size.height;
340 canvas.offset.x = 0;
341 canvas.offset.y = (screen.size.height - canvas.size.height) / 2;
342 } else {
343 canvas.size.height = screen.size.height;
344 canvas.size.width = canvas.size.height;
345 canvas.offset.x = (screen.size.width - canvas.size.width) / 2;
346 canvas.offset.y = 0;
349 initialized = 1;
350 LOG("cbgfx initialized: screen:width=%d, height=%d, offset=%d canvas:width=%d, height=%d, offset=%d\n",
351 screen.size.width, screen.size.height, screen.offset.x,
352 canvas.size.width, canvas.size.height, canvas.offset.x);
354 return 0;
357 int draw_box(const struct rect *box, const struct rgb_color *rgb)
359 struct vector top_left;
360 struct vector p, t;
362 if (cbgfx_init())
363 return CBGFX_ERROR_INIT;
365 const uint32_t color = calculate_color(rgb, 0);
366 const struct scale top_left_s = {
367 .x = { .n = box->offset.x, .d = CANVAS_SCALE, },
368 .y = { .n = box->offset.y, .d = CANVAS_SCALE, }
370 const struct scale bottom_right_s = {
371 .x = { .n = box->offset.x + box->size.x, .d = CANVAS_SCALE, },
372 .y = { .n = box->offset.y + box->size.y, .d = CANVAS_SCALE, }
375 transform_vector(&top_left, &canvas.size, &top_left_s, &canvas.offset);
376 transform_vector(&t, &canvas.size, &bottom_right_s, &canvas.offset);
377 if (within_box(&t, &canvas) < 0) {
378 LOG("Box exceeds canvas boundary\n");
379 return CBGFX_ERROR_BOUNDARY;
382 for (p.y = top_left.y; p.y < t.y; p.y++)
383 for (p.x = top_left.x; p.x < t.x; p.x++)
384 set_pixel(&p, color);
386 return CBGFX_SUCCESS;
389 int draw_rounded_box(const struct scale *pos_rel, const struct scale *dim_rel,
390 const struct rgb_color *rgb,
391 const struct fraction *thickness,
392 const struct fraction *radius)
394 struct scale pos_end_rel;
395 struct vector top_left;
396 struct vector p, t;
398 if (cbgfx_init())
399 return CBGFX_ERROR_INIT;
401 const uint32_t color = calculate_color(rgb, 0);
403 if (!is_valid_scale(pos_rel) || !is_valid_scale(dim_rel))
404 return CBGFX_ERROR_INVALID_PARAMETER;
406 add_scales(&pos_end_rel, pos_rel, dim_rel);
407 transform_vector(&top_left, &canvas.size, pos_rel, &canvas.offset);
408 transform_vector(&t, &canvas.size, &pos_end_rel, &canvas.offset);
409 if (within_box(&t, &canvas) < 0) {
410 LOG("Box exceeds canvas boundary\n");
411 return CBGFX_ERROR_BOUNDARY;
414 if (!is_valid_fraction(thickness) || !is_valid_fraction(radius))
415 return CBGFX_ERROR_INVALID_PARAMETER;
417 struct scale thickness_scale = {
418 .x = { .n = thickness->n, .d = thickness->d },
419 .y = { .n = thickness->n, .d = thickness->d },
421 struct scale radius_scale = {
422 .x = { .n = radius->n, .d = radius->d },
423 .y = { .n = radius->n, .d = radius->d },
425 struct vector d, r, s;
426 transform_vector(&d, &canvas.size, &thickness_scale, &vzero);
427 transform_vector(&r, &canvas.size, &radius_scale, &vzero);
428 const uint8_t has_thickness = d.x > 0 && d.y > 0;
429 if (thickness->n != 0 && !has_thickness)
430 LOG("Thickness truncated to 0\n");
431 const uint8_t has_radius = r.x > 0 && r.y > 0;
432 if (radius->n != 0 && !has_radius)
433 LOG("Radius truncated to 0\n");
434 if (has_radius) {
435 if (d.x > r.x || d.y > r.y) {
436 LOG("Thickness cannot be greater than radius\n");
437 return CBGFX_ERROR_INVALID_PARAMETER;
439 if (r.x * 2 > t.x - top_left.x || r.y * 2 > t.y - top_left.y) {
440 LOG("Radius cannot be greater than half of the box\n");
441 return CBGFX_ERROR_INVALID_PARAMETER;
445 /* Step 1: Draw edges */
446 int32_t x_begin, x_end;
447 if (has_thickness) {
448 /* top */
449 for (p.y = top_left.y; p.y < top_left.y + d.y; p.y++)
450 for (p.x = top_left.x + r.x; p.x < t.x - r.x; p.x++)
451 set_pixel(&p, color);
452 /* bottom */
453 for (p.y = t.y - d.y; p.y < t.y; p.y++)
454 for (p.x = top_left.x + r.x; p.x < t.x - r.x; p.x++)
455 set_pixel(&p, color);
456 for (p.y = top_left.y + r.y; p.y < t.y - r.y; p.y++) {
457 /* left */
458 for (p.x = top_left.x; p.x < top_left.x + d.x; p.x++)
459 set_pixel(&p, color);
460 /* right */
461 for (p.x = t.x - d.x; p.x < t.x; p.x++)
462 set_pixel(&p, color);
464 } else {
465 /* Fill the regions except circular sectors */
466 for (p.y = top_left.y; p.y < t.y; p.y++) {
467 if (p.y >= top_left.y + r.y && p.y < t.y - r.y) {
468 x_begin = top_left.x;
469 x_end = t.x;
470 } else {
471 x_begin = top_left.x + r.x;
472 x_end = t.x - r.x;
474 for (p.x = x_begin; p.x < x_end; p.x++)
475 set_pixel(&p, color);
479 if (!has_radius)
480 return CBGFX_SUCCESS;
483 * Step 2: Draw rounded corners
484 * When has_thickness, only the border is drawn. With fixed thickness,
485 * the time complexity is linear to the size of the box.
487 if (has_thickness) {
488 s.x = r.x - d.x;
489 s.y = r.y - d.y;
490 } else {
491 s.x = 0;
492 s.y = 0;
495 /* Use 64 bits to avoid overflow */
496 int32_t x, y;
497 uint64_t yy;
498 const uint64_t rrx = (uint64_t)r.x * r.x, rry = (uint64_t)r.y * r.y;
499 const uint64_t ssx = (uint64_t)s.x * s.x, ssy = (uint64_t)s.y * s.y;
500 x_begin = 0;
501 x_end = 0;
502 for (y = r.y - 1; y >= 0; y--) {
504 * The inequality is valid in the beginning of each iteration:
505 * y^2 + x_end^2 < r^2
507 yy = (uint64_t)y * y;
508 /* Check yy/ssy + xx/ssx < 1 */
509 while (yy * ssx + x_begin * x_begin * ssy < ssx * ssy)
510 x_begin++;
511 /* The inequality must be valid now: y^2 + x_begin >= s^2 */
512 x = x_begin;
513 /* Check yy/rry + xx/rrx < 1 */
514 while (x < x_end || yy * rrx + x * x * rry < rrx * rry) {
516 * Example sequence of (y, x) when s = (4, 4) and
517 * r = (5, 5):
518 * [(4, 0), (4, 1), (4, 2), (3, 3), (2, 4),
519 * (1, 4), (0, 4)].
520 * If s.x==s.y r.x==r.y, then the sequence will be
521 * symmetric, and x and y will range from 0 to (r-1).
523 /* top left */
524 p.y = top_left.y + r.y - 1 - y;
525 p.x = top_left.x + r.x - 1 - x;
526 set_pixel(&p, color);
527 /* top right */
528 p.y = top_left.y + r.y - 1 - y;
529 p.x = t.x - r.x + x;
530 set_pixel(&p, color);
531 /* bottom left */
532 p.y = t.y - r.y + y;
533 p.x = top_left.x + r.x - 1 - x;
534 set_pixel(&p, color);
535 /* bottom right */
536 p.y = t.y - r.y + y;
537 p.x = t.x - r.x + x;
538 set_pixel(&p, color);
539 x++;
541 x_end = x;
542 /* (x_begin <= x_end) must hold now */
545 return CBGFX_SUCCESS;
548 int draw_line(const struct scale *pos1, const struct scale *pos2,
549 const struct fraction *thickness, const struct rgb_color *rgb)
551 struct fraction len;
552 struct vector top_left;
553 struct vector size;
554 struct vector p, t;
556 if (cbgfx_init())
557 return CBGFX_ERROR_INIT;
559 const uint32_t color = calculate_color(rgb, 0);
561 if (!is_valid_fraction(thickness))
562 return CBGFX_ERROR_INVALID_PARAMETER;
564 transform_vector(&top_left, &canvas.size, pos1, &canvas.offset);
565 if (fraction_equal(&pos1->y, &pos2->y)) {
566 /* Horizontal line */
567 subtract_fractions(&len, &pos2->x, &pos1->x);
568 struct scale dim = {
569 .x = { .n = len.n, .d = len.d },
570 .y = { .n = thickness->n, .d = thickness->d },
572 transform_vector(&size, &canvas.size, &dim, &vzero);
573 size.y = MAX(size.y, 1);
574 } else if (fraction_equal(&pos1->x, &pos2->x)) {
575 /* Vertical line */
576 subtract_fractions(&len, &pos2->y, &pos1->y);
577 struct scale dim = {
578 .x = { .n = thickness->n, .d = thickness->d },
579 .y = { .n = len.n, .d = len.d },
581 transform_vector(&size, &canvas.size, &dim, &vzero);
582 size.x = MAX(size.x, 1);
583 } else {
584 LOG("Only support horizontal and vertical lines\n");
585 return CBGFX_ERROR_INVALID_PARAMETER;
588 add_vectors(&t, &top_left, &size);
589 if (within_box(&t, &canvas) < 0) {
590 LOG("Line exceeds canvas boundary\n");
591 return CBGFX_ERROR_BOUNDARY;
594 for (p.y = top_left.y; p.y < t.y; p.y++)
595 for (p.x = top_left.x; p.x < t.x; p.x++)
596 set_pixel(&p, color);
598 return CBGFX_SUCCESS;
601 int clear_canvas(const struct rgb_color *rgb)
603 const struct rect box = {
604 vzero,
605 .size = {
606 .width = CANVAS_SCALE,
607 .height = CANVAS_SCALE,
611 if (cbgfx_init())
612 return CBGFX_ERROR_INIT;
614 return draw_box(&box, rgb);
617 int clear_screen(const struct rgb_color *rgb)
619 if (cbgfx_init())
620 return CBGFX_ERROR_INIT;
622 int x, y, i;
623 uint32_t color = calculate_color(rgb, 0);
624 const int bpp = fbinfo->bits_per_pixel;
625 const int bpl = fbinfo->bytes_per_line;
626 uint8_t *line = malloc(bpl);
628 if (!line) {
629 LOG("Failed to allocate line buffer (%u bytes)\n", bpl);
630 return CBGFX_ERROR_UNKNOWN;
633 /* Set line buffer pixels, then memcpy to framebuffer */
634 for (x = 0; x < fbinfo->x_resolution; x++)
635 for (i = 0; i < bpp / 8; i++)
636 line[x * bpp / 8 + i] = (color >> (i * 8));
637 for (y = 0; y < fbinfo->y_resolution; y++)
638 memcpy(FB + y * bpl, line, bpl);
640 free(line);
641 return CBGFX_SUCCESS;
644 static int pal_to_rgb(uint8_t index, const struct bitmap_palette_element_v3 *pal,
645 size_t palcount, struct rgb_color *out)
647 if (index >= palcount) {
648 LOG("Color index %d exceeds palette boundary\n", index);
649 return CBGFX_ERROR_BITMAP_DATA;
652 out->red = pal[index].red;
653 out->green = pal[index].green;
654 out->blue = pal[index].blue;
655 return CBGFX_SUCCESS;
659 * We're using the Lanczos resampling algorithm to rescale images to a new size.
660 * Since output size is often not cleanly divisible by input size, an output
661 * pixel (ox,oy) corresponds to a point that lies in the middle between several
662 * input pixels (ix,iy), meaning that if you transformed the coordinates of the
663 * output pixel into the input image space, they would be fractional. To sample
664 * the color of this "virtual" pixel with fractional coordinates, we gather the
665 * 6x6 grid of nearest real input pixels in a sample array. Then we multiply the
666 * color values for each of those pixels (separately for red, green and blue)
667 * with a "weight" value that was calculated from the distance between that
668 * input pixel and the fractional output pixel coordinates. This is done for
669 * both X and Y dimensions separately. The combined weights for all 36 sample
670 * pixels add up to 1.0, so by adding up the multiplied color values we get the
671 * interpolated color for the output pixel.
673 * The CONFIG_LP_CBGFX_FAST_RESAMPLE option let's the user change the 'a'
674 * parameter from the Lanczos weight formula from 3 to 2, which effectively
675 * reduces the size of the sample array from 6x6 to 4x4. This is a bit faster
676 * but doesn't look as good. Most use cases should be fine without it.
678 #if CONFIG(LP_CBGFX_FAST_RESAMPLE)
679 #define LNCZ_A 2
680 #else
681 #define LNCZ_A 3
682 #endif
685 * When walking the sample array we often need to start at a pixel close to our
686 * fractional output pixel (for convenience we choose the pixel on the top-left
687 * which corresponds to the integer parts of the output pixel coordinates) and
688 * then work our way outwards in both directions from there. Arrays in C must
689 * start at 0 but we'd really prefer indexes to go from -2 to 3 (for 6x6)
690 * instead, so that this "start pixel" could be 0. Since we cannot do that,
691 * define a constant for the index of that "0th" pixel instead.
693 #define S0 (LNCZ_A - 1)
695 /* The size of the sample array, which we need a lot. */
696 #define SSZ (LNCZ_A * 2)
699 * This is implementing the Lanczos kernel according to:
700 * https://en.wikipedia.org/wiki/Lanczos_resampling
702 * / 1 if x = 0
703 * L(x) = < a * sin(pi * x) * sin(pi * x / a) / (pi^2 * x^2) if -a < x <= a
704 * \ 0 otherwise
706 static fpmath_t lanczos_weight(fpmath_t in, int off)
709 * |in| is the output pixel coordinate scaled into the input pixel
710 * space. |off| is the offset in the sample array for the pixel whose
711 * weight we're calculating. (off - S0) is the distance from that
712 * sample pixel to the S0 pixel, and the fractional part of |in|
713 * (in - floor(in)) is by definition the distance between S0 and the
714 * output pixel.
716 * So (off - S0) - (in - floor(in)) is the distance from the sample
717 * pixel to S0 minus the distance from S0 to the output pixel, aka
718 * the distance from the sample pixel to the output pixel.
720 fpmath_t x = fpisub(off - S0, fpsubi(in, fpfloor(in)));
722 if (fpequals(x, fp(0)))
723 return fp(1);
725 /* x * 2 / a can save some instructions if a == 2 */
726 fpmath_t x2a = x;
727 if (LNCZ_A != 2)
728 x2a = fpmul(x, fpfrac(2, LNCZ_A));
730 fpmath_t x_times_pi = fpmul(x, fppi());
733 * Rather than using sinr(pi*x), we leverage the "one-based" sine
734 * function (see <fpmath.h>) with sin1(2*x) so that the pi is eliminated
735 * since multiplication by an integer is a slightly faster operation.
737 fpmath_t tmp = fpmuli(fpdiv(fpsin1(fpmuli(x, 2)), x_times_pi), LNCZ_A);
738 return fpdiv(fpmul(tmp, fpsin1(x2a)), x_times_pi);
741 static int draw_bitmap_v3(const struct vector *top_left,
742 const struct vector *dim,
743 const struct vector *dim_org,
744 const struct bitmap_header_v3 *header,
745 const struct bitmap_palette_element_v3 *pal,
746 const uint8_t *pixel_array, uint8_t invert)
748 const int bpp = header->bits_per_pixel;
749 int32_t dir;
750 struct vector p;
751 int32_t ox, oy; /* output (resampled) pixel coordinates */
752 int32_t ix, iy; /* input (source image) pixel coordinates */
753 int sx, sy; /* index into |sample| (not ringbuffer adjusted) */
755 if (header->compression) {
756 LOG("Compressed bitmaps are not supported\n");
757 return CBGFX_ERROR_BITMAP_FORMAT;
759 if (bpp >= 16) {
760 LOG("Non-palette bitmaps are not supported\n");
761 return CBGFX_ERROR_BITMAP_FORMAT;
763 if (bpp != 8) {
764 LOG("Unsupported bits per pixel: %d\n", bpp);
765 return CBGFX_ERROR_BITMAP_FORMAT;
768 const int32_t y_stride = ROUNDUP(dim_org->width * bpp / 8, 4);
770 * header->height can be positive or negative.
772 * If it's negative, pixel data is stored from top to bottom. We render
773 * image from the lowest row to the highest row.
775 * If it's positive, pixel data is stored from bottom to top. We render
776 * image from the highest row to the lowest row.
778 p.y = top_left->y;
779 if (header->height < 0) {
780 dir = 1;
781 } else {
782 p.y += dim->height - 1;
783 dir = -1;
786 /* Don't waste time resampling when the scale is 1:1. */
787 if (dim_org->width == dim->width && dim_org->height == dim->height) {
788 for (oy = 0; oy < dim->height; oy++, p.y += dir) {
789 p.x = top_left->x;
790 for (ox = 0; ox < dim->width; ox++, p.x++) {
791 struct rgb_color rgb;
792 if (pal_to_rgb(pixel_array[oy * y_stride + ox],
793 pal, header->colors_used, &rgb))
794 return CBGFX_ERROR_BITMAP_DATA;
795 set_pixel(&p, calculate_color(&rgb, invert));
798 return CBGFX_SUCCESS;
801 /* Precalculate the X-weights for every possible ox so that we only have
802 to multiply weights together in the end. */
803 fpmath_t (*weight_x)[SSZ] = malloc(sizeof(fpmath_t) * SSZ * dim->width);
804 if (!weight_x)
805 return CBGFX_ERROR_UNKNOWN;
806 for (ox = 0; ox < dim->width; ox++) {
807 for (sx = 0; sx < SSZ; sx++) {
808 fpmath_t ixfp = fpfrac(ox * dim_org->width, dim->width);
809 weight_x[ox][sx] = lanczos_weight(ixfp, sx);
814 * For every sy in the sample array, we directly cache a pointer into
815 * the .BMP pixel array for the start of the corresponding line. On the
816 * edges of the image (where we don't have any real pixels to fill all
817 * lines in the sample array), we just reuse the last valid lines inside
818 * the image for all lines that would lie outside.
820 const uint8_t *ypix[SSZ];
821 for (sy = 0; sy < SSZ; sy++) {
822 if (sy <= S0)
823 ypix[sy] = pixel_array;
824 else if (sy - S0 >= dim_org->height)
825 ypix[sy] = ypix[sy - 1];
826 else
827 ypix[sy] = &pixel_array[y_stride * (sy - S0)];
830 /* iy and ix track the input pixel corresponding to sample[S0][S0]. */
831 iy = 0;
832 for (oy = 0; oy < dim->height; oy++, p.y += dir) {
833 struct rgb_color sample[SSZ][SSZ];
835 /* Like with X weights, we also cache all Y weights. */
836 fpmath_t iyfp = fpfrac(oy * dim_org->height, dim->height);
837 fpmath_t weight_y[SSZ];
838 for (sy = 0; sy < SSZ; sy++)
839 weight_y[sy] = lanczos_weight(iyfp, sy);
842 * If we have a new input pixel line between the last oy and
843 * this one, we have to adjust iy forward. When upscaling, this
844 * is not always the case for each new output line. When
845 * downscaling, we may even cross more than one line per output
846 * pixel.
848 while (fpfloor(iyfp) > iy) {
849 iy++;
851 /* Shift ypix array up to center around next iy line. */
852 for (sy = 0; sy < SSZ - 1; sy++)
853 ypix[sy] = ypix[sy + 1];
855 /* Calculate the last ypix that is being shifted in,
856 but beware of reaching the end of the input image. */
857 if (iy + LNCZ_A < dim_org->height)
858 ypix[SSZ - 1] = &pixel_array[y_stride *
859 (iy + LNCZ_A)];
863 * Initialize the sample array for this line, and also
864 * the equals counter, which counts how many of the latest
865 * pixels were exactly equal.
867 int equals = 0;
868 uint8_t last_equal = ypix[0][0];
869 for (sx = 0; sx < SSZ; sx++) {
870 for (sy = 0; sy < SSZ; sy++) {
871 if (sx - S0 >= dim_org->width) {
872 sample[sx][sy] = sample[sx - 1][sy];
873 equals++;
874 continue;
877 * For pixels to the left of S0 there are no
878 * corresponding input pixels so just use
879 * ypix[sy][0].
881 uint8_t i = ypix[sy][MAX(0, sx - S0)];
882 if (pal_to_rgb(i, pal, header->colors_used,
883 &sample[sx][sy]))
884 goto bitmap_error;
885 if (i == last_equal) {
886 equals++;
887 } else {
888 last_equal = i;
889 equals = 1;
894 ix = 0;
895 p.x = top_left->x;
896 for (ox = 0; ox < dim->width; ox++, p.x++) {
897 /* Adjust ix forward, same as iy above. */
898 fpmath_t ixfp = fpfrac(ox * dim_org->width, dim->width);
899 while (fpfloor(ixfp) > ix) {
900 ix++;
903 * We want to reuse the sample columns we
904 * already have, but we don't want to copy them
905 * all around for every new column either.
906 * Instead, treat the X dimension of the sample
907 * array like a ring buffer indexed by ix. rx is
908 * the ringbuffer-adjusted offset of the new
909 * column in sample (the rightmost one) we're
910 * trying to fill.
912 int rx = (SSZ - 1 + ix) % SSZ;
913 for (sy = 0; sy < SSZ; sy++) {
914 if (ix + LNCZ_A >= dim_org->width) {
915 sample[rx][sy] = sample[(SSZ - 2
916 + ix) % SSZ][sy];
917 equals++;
918 continue;
920 uint8_t i = ypix[sy][ix + LNCZ_A];
921 if (i == last_equal) {
922 if (equals++ >= (SSZ * SSZ))
923 continue;
924 } else {
925 last_equal = i;
926 equals = 1;
928 if (pal_to_rgb(i, pal,
929 header->colors_used,
930 &sample[rx][sy]))
931 goto bitmap_error;
935 /* If all pixels in sample are equal, fast path. */
936 if (equals >= (SSZ * SSZ)) {
937 set_pixel(&p, calculate_color(&sample[0][0],
938 invert));
939 continue;
942 fpmath_t red = fp(0);
943 fpmath_t green = fp(0);
944 fpmath_t blue = fp(0);
945 for (sy = 0; sy < SSZ; sy++) {
946 for (sx = 0; sx < SSZ; sx++) {
947 int rx = (sx + ix) % SSZ;
948 fpmath_t weight = fpmul(weight_x[ox][sx],
949 weight_y[sy]);
950 red = fpadd(red, fpmuli(weight,
951 sample[rx][sy].red));
952 green = fpadd(green, fpmuli(weight,
953 sample[rx][sy].green));
954 blue = fpadd(blue, fpmuli(weight,
955 sample[rx][sy].blue));
960 * Weights *should* sum up to 1.0 (making this not
961 * necessary) but just to hedge against rounding errors
962 * we should clamp color values to their legal limits.
964 struct rgb_color rgb = {
965 .red = MAX(0, MIN(UINT8_MAX, fpround(red))),
966 .green = MAX(0, MIN(UINT8_MAX, fpround(green))),
967 .blue = MAX(0, MIN(UINT8_MAX, fpround(blue))),
970 set_pixel(&p, calculate_color(&rgb, invert));
974 free(weight_x);
975 return CBGFX_SUCCESS;
977 bitmap_error:
978 free(weight_x);
979 return CBGFX_ERROR_BITMAP_DATA;
982 static int get_bitmap_file_header(const void *bitmap, size_t size,
983 struct bitmap_file_header *file_header)
985 const struct bitmap_file_header *fh;
987 if (sizeof(*file_header) > size) {
988 LOG("Invalid bitmap data\n");
989 return CBGFX_ERROR_BITMAP_DATA;
991 fh = (struct bitmap_file_header *)bitmap;
992 if (fh->signature[0] != 'B' || fh->signature[1] != 'M') {
993 LOG("Bitmap signature mismatch\n");
994 return CBGFX_ERROR_BITMAP_SIGNATURE;
996 file_header->file_size = le32toh(fh->file_size);
997 if (file_header->file_size != size) {
998 LOG("Bitmap file size does not match cbfs file size\n");
999 return CBGFX_ERROR_BITMAP_DATA;
1001 file_header->bitmap_offset = le32toh(fh->bitmap_offset);
1003 return CBGFX_SUCCESS;
1006 static int parse_bitmap_header_v3(
1007 const uint8_t *bitmap,
1008 size_t size,
1009 /* ^--- IN / OUT ---v */
1010 struct bitmap_header_v3 *header,
1011 const struct bitmap_palette_element_v3 **palette,
1012 const uint8_t **pixel_array,
1013 struct vector *dim_org)
1015 struct bitmap_file_header file_header;
1016 struct bitmap_header_v3 *h;
1017 int rv;
1019 rv = get_bitmap_file_header(bitmap, size, &file_header);
1020 if (rv)
1021 return rv;
1023 size_t header_offset = sizeof(struct bitmap_file_header);
1024 size_t header_size = sizeof(struct bitmap_header_v3);
1025 size_t palette_offset = header_offset + header_size;
1026 size_t file_size = file_header.file_size;
1028 h = (struct bitmap_header_v3 *)(bitmap + header_offset);
1029 header->header_size = le32toh(h->header_size);
1030 if (header->header_size != header_size) {
1031 LOG("Unsupported bitmap format\n");
1032 return CBGFX_ERROR_BITMAP_FORMAT;
1035 header->width = le32toh(h->width);
1036 header->height = le32toh(h->height);
1037 if (header->width == 0 || header->height == 0) {
1038 LOG("Invalid image width or height\n");
1039 return CBGFX_ERROR_BITMAP_DATA;
1041 dim_org->width = header->width;
1042 dim_org->height = ABS(header->height);
1044 header->bits_per_pixel = le16toh(h->bits_per_pixel);
1045 header->compression = le32toh(h->compression);
1046 header->size = le32toh(h->size);
1047 header->colors_used = le32toh(h->colors_used);
1048 size_t palette_size = header->colors_used
1049 * sizeof(struct bitmap_palette_element_v3);
1050 size_t pixel_offset = file_header.bitmap_offset;
1051 if (pixel_offset > file_size) {
1052 LOG("Bitmap pixel data exceeds buffer boundary\n");
1053 return CBGFX_ERROR_BITMAP_DATA;
1055 if (palette_offset + palette_size > pixel_offset) {
1056 LOG("Bitmap palette data exceeds palette boundary\n");
1057 return CBGFX_ERROR_BITMAP_DATA;
1059 *palette = (struct bitmap_palette_element_v3 *)(bitmap +
1060 palette_offset);
1062 size_t pixel_size = header->size;
1063 if (pixel_size != dim_org->height *
1064 ROUNDUP(dim_org->width * header->bits_per_pixel / 8, 4)) {
1065 LOG("Bitmap pixel array size does not match expected size\n");
1066 return CBGFX_ERROR_BITMAP_DATA;
1068 if (pixel_offset + pixel_size > file_size) {
1069 LOG("Bitmap pixel array exceeds buffer boundary\n");
1070 return CBGFX_ERROR_BITMAP_DATA;
1072 *pixel_array = bitmap + pixel_offset;
1074 return CBGFX_SUCCESS;
1078 * This calculates the dimension of the image projected on the canvas from the
1079 * dimension relative to the canvas size. If either width or height is zero, it
1080 * is derived from the other (non-zero) value to keep the aspect ratio.
1082 static int calculate_dimension(const struct vector *dim_org,
1083 const struct scale *dim_rel,
1084 struct vector *dim)
1086 if (dim_rel->x.n == 0 && dim_rel->y.n == 0)
1087 return CBGFX_ERROR_INVALID_PARAMETER;
1089 if (dim_rel->x.n > dim_rel->x.d || dim_rel->y.n > dim_rel->y.d)
1090 return CBGFX_ERROR_INVALID_PARAMETER;
1092 if (dim_rel->x.n > 0) {
1093 if (!is_valid_fraction(&dim_rel->x))
1094 return CBGFX_ERROR_INVALID_PARAMETER;
1095 dim->width = canvas.size.width * dim_rel->x.n / dim_rel->x.d;
1097 if (dim_rel->y.n > 0) {
1098 if (!is_valid_fraction(&dim_rel->y))
1099 return CBGFX_ERROR_INVALID_PARAMETER;
1100 dim->height = canvas.size.height * dim_rel->y.n / dim_rel->y.d;
1103 /* Derive height from width using aspect ratio */
1104 if (dim_rel->y.n == 0)
1105 dim->height = dim->width * dim_org->height / dim_org->width;
1106 /* Derive width from height using aspect ratio */
1107 if (dim_rel->x.n == 0)
1108 dim->width = dim->height * dim_org->width / dim_org->height;
1110 return CBGFX_SUCCESS;
1113 static int calculate_position(const struct vector *dim,
1114 const struct scale *pos_rel, uint8_t pivot,
1115 struct vector *top_left)
1117 int rv;
1119 rv = transform_vector(top_left, &canvas.size, pos_rel, &canvas.offset);
1120 if (rv)
1121 return rv;
1123 switch (pivot & PIVOT_H_MASK) {
1124 case PIVOT_H_LEFT:
1125 break;
1126 case PIVOT_H_CENTER:
1127 top_left->x -= dim->width / 2;
1128 break;
1129 case PIVOT_H_RIGHT:
1130 top_left->x -= dim->width;
1131 break;
1132 default:
1133 return CBGFX_ERROR_INVALID_PARAMETER;
1136 switch (pivot & PIVOT_V_MASK) {
1137 case PIVOT_V_TOP:
1138 break;
1139 case PIVOT_V_CENTER:
1140 top_left->y -= dim->height / 2;
1141 break;
1142 case PIVOT_V_BOTTOM:
1143 top_left->y -= dim->height;
1144 break;
1145 default:
1146 return CBGFX_ERROR_INVALID_PARAMETER;
1149 return CBGFX_SUCCESS;
1152 static int check_boundary(const struct vector *top_left,
1153 const struct vector *dim,
1154 const struct rect *bound)
1156 struct vector v;
1157 add_vectors(&v, dim, top_left);
1158 if (top_left->x < bound->offset.x
1159 || top_left->y < bound->offset.y
1160 || within_box(&v, bound) < 0)
1161 return CBGFX_ERROR_BOUNDARY;
1162 return CBGFX_SUCCESS;
1165 int draw_bitmap(const void *bitmap, size_t size,
1166 const struct scale *pos_rel, const struct scale *dim_rel,
1167 uint32_t flags)
1169 struct bitmap_header_v3 header;
1170 const struct bitmap_palette_element_v3 *palette;
1171 const uint8_t *pixel_array;
1172 struct vector top_left, dim, dim_org;
1173 int rv;
1174 const uint8_t pivot = flags & PIVOT_MASK;
1175 const uint8_t invert = (flags & INVERT_COLORS) >> INVERT_SHIFT;
1177 if (cbgfx_init())
1178 return CBGFX_ERROR_INIT;
1180 /* only v3 is supported now */
1181 rv = parse_bitmap_header_v3(bitmap, size,
1182 &header, &palette, &pixel_array, &dim_org);
1183 if (rv)
1184 return rv;
1186 /* Calculate height and width of the image */
1187 rv = calculate_dimension(&dim_org, dim_rel, &dim);
1188 if (rv)
1189 return rv;
1191 /* Calculate coordinate */
1192 rv = calculate_position(&dim, pos_rel, pivot, &top_left);
1193 if (rv)
1194 return rv;
1196 rv = check_boundary(&top_left, &dim, &canvas);
1197 if (rv) {
1198 LOG("Bitmap image exceeds canvas boundary\n");
1199 return rv;
1202 return draw_bitmap_v3(&top_left, &dim, &dim_org,
1203 &header, palette, pixel_array, invert);
1206 int draw_bitmap_direct(const void *bitmap, size_t size,
1207 const struct vector *top_left)
1209 struct bitmap_header_v3 header;
1210 const struct bitmap_palette_element_v3 *palette;
1211 const uint8_t *pixel_array;
1212 struct vector dim;
1213 int rv;
1215 if (cbgfx_init())
1216 return CBGFX_ERROR_INIT;
1218 /* only v3 is supported now */
1219 rv = parse_bitmap_header_v3(bitmap, size,
1220 &header, &palette, &pixel_array, &dim);
1221 if (rv)
1222 return rv;
1224 rv = check_boundary(top_left, &dim, &screen);
1225 if (rv) {
1226 LOG("Bitmap image exceeds screen boundary\n");
1227 return rv;
1230 return draw_bitmap_v3(top_left, &dim, &dim,
1231 &header, palette, pixel_array, 0);
1234 int get_bitmap_dimension(const void *bitmap, size_t sz, struct scale *dim_rel)
1236 struct bitmap_header_v3 header;
1237 const struct bitmap_palette_element_v3 *palette;
1238 const uint8_t *pixel_array;
1239 struct vector dim, dim_org;
1240 int rv;
1242 if (cbgfx_init())
1243 return CBGFX_ERROR_INIT;
1245 /* Only v3 is supported now */
1246 rv = parse_bitmap_header_v3(bitmap, sz,
1247 &header, &palette, &pixel_array, &dim_org);
1248 if (rv)
1249 return rv;
1251 /* Calculate height and width of the image */
1252 rv = calculate_dimension(&dim_org, dim_rel, &dim);
1253 if (rv)
1254 return rv;
1256 /* Calculate size relative to the canvas */
1257 dim_rel->x.n = dim.width;
1258 dim_rel->x.d = canvas.size.width;
1259 dim_rel->y.n = dim.height;
1260 dim_rel->y.d = canvas.size.height;
1262 return CBGFX_SUCCESS;
1265 int enable_graphics_buffer(void)
1267 if (gfx_buffer)
1268 return CBGFX_SUCCESS;
1270 if (cbgfx_init())
1271 return CBGFX_ERROR_INIT;
1273 size_t buffer_size = fbinfo->y_resolution * fbinfo->bytes_per_line;
1274 gfx_buffer = malloc(buffer_size);
1275 if (!gfx_buffer) {
1276 LOG("%s: Failed to create graphics buffer (%zu bytes).\n",
1277 __func__, buffer_size);
1278 return CBGFX_ERROR_GRAPHICS_BUFFER;
1281 return CBGFX_SUCCESS;
1284 int flush_graphics_buffer(void)
1286 if (!gfx_buffer)
1287 return CBGFX_ERROR_GRAPHICS_BUFFER;
1289 memcpy(REAL_FB, gfx_buffer, fbinfo->y_resolution * fbinfo->bytes_per_line);
1290 return CBGFX_SUCCESS;
1293 void disable_graphics_buffer(void)
1295 free(gfx_buffer);
1296 gfx_buffer = NULL;