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
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
29 #include <libpayload.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
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
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
= {
68 struct color_transformation
{
73 struct color_mapping
{
74 struct color_transformation red
;
75 struct color_transformation green
;
76 struct color_transformation blue
;
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
,
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;
111 struct rgb_color rgb
;
114 static struct blend_value blend
;
116 int set_blend(const struct rgb_color
*rgb
, uint8_t alpha
)
119 return CBGFX_ERROR_INVALID_PARAMETER
;
124 return CBGFX_SUCCESS
;
127 void clear_blend(void)
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
)
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;
166 static void add_fractions(struct fraction
*out
,
167 const struct fraction
*f1
, const struct fraction
*f2
)
170 (int64_t)f1
->n
* f2
->d
+ (int64_t)f2
->n
* f1
->d
,
171 (int64_t)f1
->d
* f2
->d
);
175 static void subtract_fractions(struct fraction
*out
,
176 const struct fraction
*f1
,
177 const struct fraction
*f2
)
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,
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
)
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
)
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
)
234 return trans
->base
+ trans
->scale
* color
/ UINT8_MAX
;
238 * Helper function that applies color and opacity from blend struct
241 static inline uint8_t apply_blend(uint8_t color
, uint8_t blend_color
)
243 if (blend
.alpha
== 0 || color
== blend_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
,
255 color
|= (apply_blend(apply_map(rgb
->red
, &color_map
.red
),
257 >> (8 - fbinfo
->red_mask_size
))
258 << fbinfo
->red_mask_pos
;
259 color
|= (apply_blend(apply_map(rgb
->green
, &color_map
.green
),
261 >> (8 - fbinfo
->green_mask_size
))
262 << fbinfo
->green_mask_pos
;
263 color
|= (apply_blend(apply_map(rgb
->blue
, &color_map
.blue
),
265 >> (8 - fbinfo
->blue_mask_size
))
266 << fbinfo
->blue_mask_pos
;
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
;
283 switch (fbinfo
->orientation
) {
284 case CB_FB_ORIENTATION_NORMAL
:
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
;
293 case CB_FB_ORIENTATION_LEFT_UP
:
295 rcoord
.y
= screen
.size
.width
- 1 - coord
->x
;
297 case CB_FB_ORIENTATION_RIGHT_UP
:
298 rcoord
.x
= screen
.size
.height
- 1 - coord
->y
;
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)
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
;
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
;
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
;
341 canvas
.offset
.y
= (screen
.size
.height
- canvas
.size
.height
) / 2;
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;
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
);
357 int draw_box(const struct rect
*box
, const struct rgb_color
*rgb
)
359 struct vector top_left
;
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
;
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");
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
;
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
);
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
++) {
458 for (p
.x
= top_left
.x
; p
.x
< top_left
.x
+ d
.x
; p
.x
++)
459 set_pixel(&p
, color
);
461 for (p
.x
= t
.x
- d
.x
; p
.x
< t
.x
; p
.x
++)
462 set_pixel(&p
, color
);
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
;
471 x_begin
= top_left
.x
+ r
.x
;
474 for (p
.x
= x_begin
; p
.x
< x_end
; p
.x
++)
475 set_pixel(&p
, color
);
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.
495 /* Use 64 bits to avoid overflow */
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
;
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
)
511 /* The inequality must be valid now: y^2 + x_begin >= s^2 */
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
518 * [(4, 0), (4, 1), (4, 2), (3, 3), (2, 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).
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
);
528 p
.y
= top_left
.y
+ r
.y
- 1 - y
;
530 set_pixel(&p
, color
);
533 p
.x
= top_left
.x
+ r
.x
- 1 - x
;
534 set_pixel(&p
, color
);
538 set_pixel(&p
, color
);
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
)
552 struct vector top_left
;
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
);
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
)) {
576 subtract_fractions(&len
, &pos2
->y
, &pos1
->y
);
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);
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
= {
606 .width
= CANVAS_SCALE
,
607 .height
= CANVAS_SCALE
,
612 return CBGFX_ERROR_INIT
;
614 return draw_box(&box
, rgb
);
617 int clear_screen(const struct rgb_color
*rgb
)
620 return CBGFX_ERROR_INIT
;
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
);
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
);
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)
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
703 * L(x) = < a * sin(pi * x) * sin(pi * x / a) / (pi^2 * x^2) if -a < x <= a
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
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)))
725 /* x * 2 / a can save some instructions if 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
;
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
;
760 LOG("Non-palette bitmaps are not supported\n");
761 return CBGFX_ERROR_BITMAP_FORMAT
;
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.
779 if (header
->height
< 0) {
782 p
.y
+= dim
->height
- 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
) {
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
);
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
++) {
823 ypix
[sy
] = pixel_array
;
824 else if (sy
- S0
>= dim_org
->height
)
825 ypix
[sy
] = ypix
[sy
- 1];
827 ypix
[sy
] = &pixel_array
[y_stride
* (sy
- S0
)];
830 /* iy and ix track the input pixel corresponding to sample[S0][S0]. */
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
848 while (fpfloor(iyfp
) > 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
*
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.
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
];
877 * For pixels to the left of S0 there are no
878 * corresponding input pixels so just use
881 uint8_t i
= ypix
[sy
][MAX(0, sx
- S0
)];
882 if (pal_to_rgb(i
, pal
, header
->colors_used
,
885 if (i
== last_equal
) {
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
) {
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
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
920 uint8_t i
= ypix
[sy
][ix
+ LNCZ_A
];
921 if (i
== last_equal
) {
922 if (equals
++ >= (SSZ
* SSZ
))
928 if (pal_to_rgb(i
, pal
,
935 /* If all pixels in sample are equal, fast path. */
936 if (equals
>= (SSZ
* SSZ
)) {
937 set_pixel(&p
, calculate_color(&sample
[0][0],
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
],
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
));
975 return CBGFX_SUCCESS
;
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
,
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
;
1019 rv
= get_bitmap_file_header(bitmap
, size
, &file_header
);
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
+
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
,
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
)
1119 rv
= transform_vector(top_left
, &canvas
.size
, pos_rel
, &canvas
.offset
);
1123 switch (pivot
& PIVOT_H_MASK
) {
1126 case PIVOT_H_CENTER
:
1127 top_left
->x
-= dim
->width
/ 2;
1130 top_left
->x
-= dim
->width
;
1133 return CBGFX_ERROR_INVALID_PARAMETER
;
1136 switch (pivot
& PIVOT_V_MASK
) {
1139 case PIVOT_V_CENTER
:
1140 top_left
->y
-= dim
->height
/ 2;
1142 case PIVOT_V_BOTTOM
:
1143 top_left
->y
-= dim
->height
;
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
)
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
,
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
;
1174 const uint8_t pivot
= flags
& PIVOT_MASK
;
1175 const uint8_t invert
= (flags
& INVERT_COLORS
) >> INVERT_SHIFT
;
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
);
1186 /* Calculate height and width of the image */
1187 rv
= calculate_dimension(&dim_org
, dim_rel
, &dim
);
1191 /* Calculate coordinate */
1192 rv
= calculate_position(&dim
, pos_rel
, pivot
, &top_left
);
1196 rv
= check_boundary(&top_left
, &dim
, &canvas
);
1198 LOG("Bitmap image exceeds canvas boundary\n");
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
;
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
);
1224 rv
= check_boundary(top_left
, &dim
, &screen
);
1226 LOG("Bitmap image exceeds screen boundary\n");
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
;
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
);
1251 /* Calculate height and width of the image */
1252 rv
= calculate_dimension(&dim_org
, dim_rel
, &dim
);
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)
1268 return CBGFX_SUCCESS
;
1271 return CBGFX_ERROR_INIT
;
1273 size_t buffer_size
= fbinfo
->y_resolution
* fbinfo
->bytes_per_line
;
1274 gfx_buffer
= malloc(buffer_size
);
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)
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)