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