1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <efi.h>
4 #include <efilib.h>
5 
6 #include "graphics.h"
7 #include "splash.h"
8 #include "util.h"
9 
10 struct bmp_file {
11         CHAR8 signature[2];
12         UINT32 size;
13         UINT16 reserved[2];
14         UINT32 offset;
15 } _packed_;
16 
17 /* we require at least BITMAPINFOHEADER, later versions are
18    accepted, but their features ignored */
19 struct bmp_dib {
20         UINT32 size;
21         UINT32 x;
22         UINT32 y;
23         UINT16 planes;
24         UINT16 depth;
25         UINT32 compression;
26         UINT32 image_size;
27         INT32 x_pixel_meter;
28         INT32 y_pixel_meter;
29         UINT32 colors_used;
30         UINT32 colors_important;
31 } _packed_;
32 
33 struct bmp_map {
34         UINT8 blue;
35         UINT8 green;
36         UINT8 red;
37         UINT8 reserved;
38 } _packed_;
39 
bmp_parse_header(const UINT8 * bmp,UINTN size,struct bmp_dib ** ret_dib,struct bmp_map ** ret_map,const UINT8 ** pixmap)40 static EFI_STATUS bmp_parse_header(
41                 const UINT8 *bmp,
42                 UINTN size,
43                 struct bmp_dib **ret_dib,
44                 struct bmp_map **ret_map,
45                 const UINT8 **pixmap) {
46 
47         struct bmp_file *file;
48         struct bmp_dib *dib;
49         struct bmp_map *map;
50         UINTN row_size;
51 
52         assert(bmp);
53         assert(ret_dib);
54         assert(ret_map);
55         assert(pixmap);
56 
57         if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib))
58                 return EFI_INVALID_PARAMETER;
59 
60         /* check file header */
61         file = (struct bmp_file *)bmp;
62         if (file->signature[0] != 'B' || file->signature[1] != 'M')
63                 return EFI_INVALID_PARAMETER;
64         if (file->size != size)
65                 return EFI_INVALID_PARAMETER;
66         if (file->size < file->offset)
67                 return EFI_INVALID_PARAMETER;
68 
69         /*  check device-independent bitmap */
70         dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file));
71         if (dib->size < sizeof(struct bmp_dib))
72                 return EFI_UNSUPPORTED;
73 
74         switch (dib->depth) {
75         case 1:
76         case 4:
77         case 8:
78         case 24:
79                 if (dib->compression != 0)
80                         return EFI_UNSUPPORTED;
81 
82                 break;
83 
84         case 16:
85         case 32:
86                 if (dib->compression != 0 && dib->compression != 3)
87                         return EFI_UNSUPPORTED;
88 
89                 break;
90 
91         default:
92                 return EFI_UNSUPPORTED;
93         }
94 
95         row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4;
96         if (file->size - file->offset <  dib->y * row_size)
97                 return EFI_INVALID_PARAMETER;
98         if (row_size * dib->y > 64 * 1024 * 1024)
99                 return EFI_INVALID_PARAMETER;
100 
101         /* check color table */
102         map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size);
103         if (file->offset < sizeof(struct bmp_file) + dib->size)
104                 return EFI_INVALID_PARAMETER;
105 
106         if (file->offset > sizeof(struct bmp_file) + dib->size) {
107                 UINT32 map_count;
108                 UINTN map_size;
109 
110                 if (dib->colors_used)
111                         map_count = dib->colors_used;
112                 else {
113                         switch (dib->depth) {
114                         case 1:
115                         case 4:
116                         case 8:
117                                 map_count = 1 << dib->depth;
118                                 break;
119 
120                         default:
121                                 map_count = 0;
122                                 break;
123                         }
124                 }
125 
126                 map_size = file->offset - (sizeof(struct bmp_file) + dib->size);
127                 if (map_size != sizeof(struct bmp_map) * map_count)
128                         return EFI_INVALID_PARAMETER;
129         }
130 
131         *ret_map = map;
132         *ret_dib = dib;
133         *pixmap = bmp + file->offset;
134 
135         return EFI_SUCCESS;
136 }
137 
pixel_blend(UINT32 * dst,const UINT32 source)138 static void pixel_blend(UINT32 *dst, const UINT32 source) {
139         UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g;
140 
141         assert(dst);
142 
143         alpha = (source & 0xff);
144 
145         /* convert src from RGBA to XRGB */
146         src = source >> 8;
147 
148         /* decompose into RB and G components */
149         src_rb = (src & 0xff00ff);
150         src_g  = (src & 0x00ff00);
151 
152         dst_rb = (*dst & 0xff00ff);
153         dst_g  = (*dst & 0x00ff00);
154 
155         /* blend */
156         rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff;
157         g  = ((((src_g  -  dst_g) * alpha + 0x008000) >> 8) +  dst_g) & 0x00ff00;
158 
159         *dst = (rb | g);
160 }
161 
bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL * buf,struct bmp_dib * dib,struct bmp_map * map,const UINT8 * pixmap)162 static EFI_STATUS bmp_to_blt(
163                 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf,
164                 struct bmp_dib *dib,
165                 struct bmp_map *map,
166                 const UINT8 *pixmap) {
167 
168         const UINT8 *in;
169 
170         assert(buf);
171         assert(dib);
172         assert(map);
173         assert(pixmap);
174 
175         /* transform and copy pixels */
176         in = pixmap;
177         for (UINTN y = 0; y < dib->y; y++) {
178                 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out;
179                 UINTN row_size;
180 
181                 out = &buf[(dib->y - y - 1) * dib->x];
182                 for (UINTN x = 0; x < dib->x; x++, in++, out++) {
183                         switch (dib->depth) {
184                         case 1: {
185                                 for (UINTN i = 0; i < 8 && x < dib->x; i++) {
186                                         out->Red = map[((*in) >> (7 - i)) & 1].red;
187                                         out->Green = map[((*in) >> (7 - i)) & 1].green;
188                                         out->Blue = map[((*in) >> (7 - i)) & 1].blue;
189                                         out++;
190                                         x++;
191                                 }
192                                 out--;
193                                 x--;
194                                 break;
195                         }
196 
197                         case 4: {
198                                 UINTN i;
199 
200                                 i = (*in) >> 4;
201                                 out->Red = map[i].red;
202                                 out->Green = map[i].green;
203                                 out->Blue = map[i].blue;
204                                 if (x < (dib->x - 1)) {
205                                         out++;
206                                         x++;
207                                         i = (*in) & 0x0f;
208                                         out->Red = map[i].red;
209                                         out->Green = map[i].green;
210                                         out->Blue = map[i].blue;
211                                 }
212                                 break;
213                         }
214 
215                         case 8:
216                                 out->Red = map[*in].red;
217                                 out->Green = map[*in].green;
218                                 out->Blue = map[*in].blue;
219                                 break;
220 
221                         case 16: {
222                                 UINT16 i = *(UINT16 *) in;
223 
224                                 out->Red = (i & 0x7c00) >> 7;
225                                 out->Green = (i & 0x3e0) >> 2;
226                                 out->Blue = (i & 0x1f) << 3;
227                                 in += 1;
228                                 break;
229                         }
230 
231                         case 24:
232                                 out->Red = in[2];
233                                 out->Green = in[1];
234                                 out->Blue = in[0];
235                                 in += 2;
236                                 break;
237 
238                         case 32: {
239                                 UINT32 i = *(UINT32 *) in;
240 
241                                 pixel_blend((UINT32 *)out, i);
242 
243                                 in += 3;
244                                 break;
245                         }
246                         }
247                 }
248 
249                 /* add row padding; new lines always start at 32 bit boundary */
250                 row_size = in - pixmap;
251                 in += ((row_size + 3) & ~3) - row_size;
252         }
253 
254         return EFI_SUCCESS;
255 }
256 
graphics_splash(const UINT8 * content,UINTN len,const EFI_GRAPHICS_OUTPUT_BLT_PIXEL * background)257 EFI_STATUS graphics_splash(const UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) {
258         EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {};
259         EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
260         struct bmp_dib *dib;
261         struct bmp_map *map;
262         const UINT8 *pixmap;
263         _cleanup_freepool_ void *blt = NULL;
264         UINTN x_pos = 0;
265         UINTN y_pos = 0;
266         EFI_STATUS err;
267 
268         if (len == 0)
269                 return EFI_SUCCESS;
270 
271         assert(content);
272 
273         if (!background) {
274                 if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) {
275                         pixel.Red = 0xc0;
276                         pixel.Green = 0xc0;
277                         pixel.Blue = 0xc0;
278                 }
279                 background = &pixel;
280         }
281 
282         err = LibLocateProtocol(&GraphicsOutputProtocol, (void **)&GraphicsOutput);
283         if (EFI_ERROR(err))
284                 return err;
285 
286         err = bmp_parse_header(content, len, &dib, &map, &pixmap);
287         if (EFI_ERROR(err))
288                 return err;
289 
290         if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
291                 x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
292         if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
293                 y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
294 
295         err = GraphicsOutput->Blt(
296                         GraphicsOutput, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background,
297                         EfiBltVideoFill, 0, 0, 0, 0,
298                         GraphicsOutput->Mode->Info->HorizontalResolution,
299                         GraphicsOutput->Mode->Info->VerticalResolution, 0);
300         if (EFI_ERROR(err))
301                 return err;
302 
303         /* EFI buffer */
304         blt = xnew(EFI_GRAPHICS_OUTPUT_BLT_PIXEL, dib->x * dib->y);
305 
306         err = GraphicsOutput->Blt(
307                         GraphicsOutput, blt,
308                         EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
309                         dib->x, dib->y, 0);
310         if (EFI_ERROR(err))
311                 return err;
312 
313         err = bmp_to_blt(blt, dib, map, pixmap);
314         if (EFI_ERROR(err))
315                 return err;
316 
317         err = graphics_mode(TRUE);
318         if (EFI_ERROR(err))
319                 return err;
320 
321         return GraphicsOutput->Blt(
322                         GraphicsOutput, blt,
323                         EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
324                         dib->x, dib->y, 0);
325 }
326