/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "graphics.h" #include "logarithm.h" #include "proto/graphics-output.h" #include "splash.h" #include "unaligned-fundamental.h" #include "util.h" struct bmp_file { char signature[2]; uint32_t size; uint16_t reserved[2]; uint32_t offset; } _packed_; /* we require at least BITMAPINFOHEADER, later versions are accepted, but their features ignored */ struct bmp_dib { uint32_t size; uint32_t x; uint32_t y; uint16_t planes; uint16_t depth; uint32_t compression; uint32_t image_size; int32_t x_pixel_meter; int32_t y_pixel_meter; uint32_t colors_used; uint32_t colors_important; uint32_t channel_mask_r; uint32_t channel_mask_g; uint32_t channel_mask_b; uint32_t channel_mask_a; } _packed_; #define SIZEOF_BMP_DIB offsetof(struct bmp_dib, channel_mask_r) #define SIZEOF_BMP_DIB_RGB offsetof(struct bmp_dib, channel_mask_a) #define SIZEOF_BMP_DIB_RGBA sizeof(struct bmp_dib) struct bmp_map { uint8_t blue; uint8_t green; uint8_t red; uint8_t reserved; } _packed_; static EFI_STATUS bmp_parse_header( const uint8_t *bmp, size_t size, struct bmp_dib **ret_dib, struct bmp_map **ret_map, const uint8_t **pixmap) { assert(bmp); assert(ret_dib); assert(ret_map); assert(pixmap); if (size < sizeof(struct bmp_file) + SIZEOF_BMP_DIB) return EFI_INVALID_PARAMETER; /* check file header */ struct bmp_file *file = (struct bmp_file *) bmp; if (file->signature[0] != 'B' || file->signature[1] != 'M') return EFI_INVALID_PARAMETER; if (file->size != size) return EFI_INVALID_PARAMETER; if (file->size < file->offset) return EFI_INVALID_PARAMETER; /* check device-independent bitmap */ struct bmp_dib *dib = (struct bmp_dib *) (bmp + sizeof(struct bmp_file)); if (dib->size < SIZEOF_BMP_DIB) return EFI_UNSUPPORTED; switch (dib->depth) { case 1: case 4: case 8: case 24: if (dib->compression != 0) return EFI_UNSUPPORTED; break; case 16: case 32: if (dib->compression != 0 && dib->compression != 3) return EFI_UNSUPPORTED; break; default: return EFI_UNSUPPORTED; } size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4; if (file->size - file->offset < dib->y * row_size) return EFI_INVALID_PARAMETER; if (row_size * dib->y > 64 * 1024 * 1024) return EFI_INVALID_PARAMETER; /* check color table */ struct bmp_map *map = (struct bmp_map *) (bmp + sizeof(struct bmp_file) + dib->size); if (file->offset < sizeof(struct bmp_file) + dib->size) return EFI_INVALID_PARAMETER; if (file->offset > sizeof(struct bmp_file) + dib->size) { uint32_t map_count = 0; if (dib->colors_used) map_count = dib->colors_used; else if (IN_SET(dib->depth, 1, 4, 8)) map_count = 1 << dib->depth; size_t map_size = file->offset - (sizeof(struct bmp_file) + dib->size); if (map_size != sizeof(struct bmp_map) * map_count) return EFI_INVALID_PARAMETER; } *ret_map = map; *ret_dib = dib; *pixmap = bmp + file->offset; return EFI_SUCCESS; } enum Channels { R, G, B, A, _CHANNELS_MAX }; static void read_channel_maks( const struct bmp_dib *dib, uint32_t channel_mask[static _CHANNELS_MAX], uint8_t channel_shift[static _CHANNELS_MAX], uint8_t channel_scale[static _CHANNELS_MAX]) { assert(dib); if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) { channel_mask[R] = dib->channel_mask_r; channel_mask[G] = dib->channel_mask_g; channel_mask[B] = dib->channel_mask_b; channel_shift[R] = __builtin_ctz(dib->channel_mask_r); channel_shift[G] = __builtin_ctz(dib->channel_mask_g); channel_shift[B] = __builtin_ctz(dib->channel_mask_b); channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1); channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1); channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1); if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) { channel_mask[A] = dib->channel_mask_a; channel_shift[A] = __builtin_ctz(dib->channel_mask_a); channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1); } else { channel_mask[A] = 0; channel_shift[A] = 0; channel_scale[A] = 0; } } else { bool bpp16 = dib->depth == 16; channel_mask[R] = bpp16 ? 0x7C00 : 0xFF0000; channel_mask[G] = bpp16 ? 0x03E0 : 0x00FF00; channel_mask[B] = bpp16 ? 0x001F : 0x0000FF; channel_mask[A] = bpp16 ? 0x0000 : 0x000000; channel_shift[R] = bpp16 ? 0xA : 0x10; channel_shift[G] = bpp16 ? 0x5 : 0x08; channel_shift[B] = bpp16 ? 0x0 : 0x00; channel_shift[A] = bpp16 ? 0x0 : 0x00; channel_scale[R] = bpp16 ? 0x08 : 0x1; channel_scale[G] = bpp16 ? 0x08 : 0x1; channel_scale[B] = bpp16 ? 0x08 : 0x1; channel_scale[A] = bpp16 ? 0x00 : 0x0; } } static EFI_STATUS bmp_to_blt( EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, struct bmp_dib *dib, struct bmp_map *map, const uint8_t *pixmap) { const uint8_t *in; assert(buf); assert(dib); assert(map); assert(pixmap); uint32_t channel_mask[_CHANNELS_MAX]; uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX]; read_channel_maks(dib, channel_mask, channel_shift, channel_scale); /* transform and copy pixels */ in = pixmap; for (uint32_t y = 0; y < dib->y; y++) { EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out = &buf[(dib->y - y - 1) * dib->x]; for (uint32_t x = 0; x < dib->x; x++, in++, out++) { switch (dib->depth) { case 1: { for (unsigned i = 0; i < 8 && x < dib->x; i++) { out->Red = map[((*in) >> (7 - i)) & 1].red; out->Green = map[((*in) >> (7 - i)) & 1].green; out->Blue = map[((*in) >> (7 - i)) & 1].blue; out++; x++; } out--; x--; break; } case 4: { unsigned i = (*in) >> 4; out->Red = map[i].red; out->Green = map[i].green; out->Blue = map[i].blue; if (x < (dib->x - 1)) { out++; x++; i = (*in) & 0x0f; out->Red = map[i].red; out->Green = map[i].green; out->Blue = map[i].blue; } break; } case 8: out->Red = map[*in].red; out->Green = map[*in].green; out->Blue = map[*in].blue; break; case 24: out->Red = in[2]; out->Green = in[1]; out->Blue = in[0]; in += 2; break; case 16: case 32: { uint32_t i = dib->depth == 16 ? unaligned_read_ne16(in) : unaligned_read_ne32(in); uint8_t r = ((i & channel_mask[R]) >> channel_shift[R]) * channel_scale[R], g = ((i & channel_mask[G]) >> channel_shift[G]) * channel_scale[G], b = ((i & channel_mask[B]) >> channel_shift[B]) * channel_scale[B], a = 0xFFu; if (channel_mask[A] != 0) a = ((i & channel_mask[A]) >> channel_shift[A]) * channel_scale[A]; out->Red = (out->Red * (0xFFu - a) + r * a) >> 8; out->Green = (out->Green * (0xFFu - a) + g * a) >> 8; out->Blue = (out->Blue * (0xFFu - a) + b * a) >> 8; in += dib->depth == 16 ? 1 : 3; break; } } } /* add row padding; new lines always start at 32 bit boundary */ size_t row_size = in - pixmap; in += ((row_size + 3) & ~3) - row_size; } return EFI_SUCCESS; } EFI_STATUS graphics_splash(const uint8_t *content, size_t len) { EFI_GRAPHICS_OUTPUT_BLT_PIXEL background = {}; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; struct bmp_dib *dib; struct bmp_map *map; const uint8_t *pixmap; size_t x_pos = 0, y_pos = 0; EFI_STATUS err; if (len == 0) return EFI_SUCCESS; assert(content); if (strcaseeq16(ST->FirmwareVendor, u"Apple")) { background.Red = 0xc0; background.Green = 0xc0; background.Blue = 0xc0; } err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &GraphicsOutput); if (err != EFI_SUCCESS) return err; err = bmp_parse_header(content, len, &dib, &map, &pixmap); if (err != EFI_SUCCESS) return err; if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution) x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2; if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution) y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2; err = GraphicsOutput->Blt( GraphicsOutput, &background, EfiBltVideoFill, 0, 0, 0, 0, GraphicsOutput->Mode->Info->HorizontalResolution, GraphicsOutput->Mode->Info->VerticalResolution, 0); if (err != EFI_SUCCESS) return err; /* Read in current screen content to perform proper alpha blending. */ _cleanup_free_ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *blt = xnew( EFI_GRAPHICS_OUTPUT_BLT_PIXEL, dib->x * dib->y); err = GraphicsOutput->Blt( GraphicsOutput, blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, dib->x, dib->y, 0); if (err != EFI_SUCCESS) return err; err = bmp_to_blt(blt, dib, map, pixmap); if (err != EFI_SUCCESS) return err; err = graphics_mode(true); if (err != EFI_SUCCESS) return err; return GraphicsOutput->Blt( GraphicsOutput, blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, dib->x, dib->y, 0); }