/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "graphics.h" #include "splash.h" #include "util.h" struct bmp_file { CHAR8 signature[2]; UINT32 size; UINT16 reserved[2]; UINT32 offset; } __attribute__((packed)); /* we require at least BITMAPINFOHEADER, later versions are accepted, but their features ignored */ struct bmp_dib { UINT32 size; UINT32 x; UINT32 y; UINT16 planes; UINT16 depth; UINT32 compression; UINT32 image_size; INT32 x_pixel_meter; INT32 y_pixel_meter; UINT32 colors_used; UINT32 colors_important; } __attribute__((packed)); struct bmp_map { UINT8 blue; UINT8 green; UINT8 red; UINT8 reserved; } __attribute__((packed)); EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, struct bmp_map **ret_map, UINT8 **pixmap) { struct bmp_file *file; struct bmp_dib *dib; struct bmp_map *map; UINTN row_size; if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib)) return EFI_INVALID_PARAMETER; /* check file header */ 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 */ dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file)); if (dib->size < sizeof(struct 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; } row_size = ((UINTN) 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 */ 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 map_count; UINTN map_size; if (dib->colors_used) map_count = dib->colors_used; else { switch (dib->depth) { case 1: case 4: case 8: map_count = 1 << dib->depth; break; default: map_count = 0; break; } } 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; } static VOID pixel_blend(UINT32 *dst, const UINT32 source) { UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; alpha = (source & 0xff); /* convert src from RGBA to XRGB */ src = source >> 8; /* decompose into RB and G components */ src_rb = (src & 0xff00ff); src_g = (src & 0x00ff00); dst_rb = (*dst & 0xff00ff); dst_g = (*dst & 0x00ff00); /* blend */ rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff; g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00; *dst = (rb | g); } EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, struct bmp_dib *dib, struct bmp_map *map, UINT8 *pixmap) { UINT8 *in; UINTN y; /* transform and copy pixels */ in = pixmap; for (y = 0; y < dib->y; y++) { EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out; UINTN row_size; UINTN x; out = &buf[(dib->y - y - 1) * dib->x]; for (x = 0; x < dib->x; x++, in++, out++) { switch (dib->depth) { case 1: { UINTN i; for (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: { UINTN i; 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 16: { UINT16 i = *(UINT16 *) in; out->Red = (i & 0x7c00) >> 7; out->Green = (i & 0x3e0) >> 2; out->Blue = (i & 0x1f) << 3; in += 1; break; } case 24: out->Red = in[2]; out->Green = in[1]; out->Blue = in[0]; in += 2; break; case 32: { UINT32 i = *(UINT32 *) in; pixel_blend((UINT32 *)out, i); in += 3; break; } } } /* add row padding; new lines always start at 32 bit boundary */ row_size = in - pixmap; in += ((row_size + 3) & ~3) - row_size; } return EFI_SUCCESS; } EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) { EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {}; EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; struct bmp_dib *dib; struct bmp_map *map; UINT8 *pixmap; UINT64 blt_size; _cleanup_freepool_ VOID *blt = NULL; UINTN x_pos = 0; UINTN y_pos = 0; EFI_STATUS err; if (!background) { if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) { pixel.Red = 0xc0; pixel.Green = 0xc0; pixel.Blue = 0xc0; } background = &pixel; } err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); if (EFI_ERROR(err)) return err; err = bmp_parse_header(content, len, &dib, &map, &pixmap); if (EFI_ERROR(err)) 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; uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background, EfiBltVideoFill, 0, 0, 0, 0, GraphicsOutput->Mode->Info->HorizontalResolution, GraphicsOutput->Mode->Info->VerticalResolution, 0); /* EFI buffer */ blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); blt = AllocatePool(blt_size); if (!blt) return EFI_OUT_OF_RESOURCES; err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, dib->x, dib->y, 0); if (EFI_ERROR(err)) return err; err = bmp_to_blt(blt, dib, map, pixmap); if (EFI_ERROR(err)) return err; err = graphics_mode(TRUE); if (EFI_ERROR(err)) return err; return uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, dib->x, dib->y, 0); }