// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2015 Google, Inc */ #include #include #include #include #include #include #include #include #include #define BMP_RLE8_ESCAPE 0 #define BMP_RLE8_EOL 0 #define BMP_RLE8_EOBMP 1 #define BMP_RLE8_DELTA 2 /** * get_bmp_col_16bpp() - Convert a colour-table entry into a 16bpp pixel value * * Return: value to write to the 16bpp frame buffer for this palette entry */ static uint get_bmp_col_16bpp(struct bmp_color_table_entry cte) { return ((cte.red << 8) & 0xf800) | ((cte.green << 3) & 0x07e0) | ((cte.blue >> 3) & 0x001f); } /** * get_bmp_col_x2r10g10b10() - Convert a colour-table entry into a x2r10g10b10 pixel value * * Return: value to write to the x2r10g10b10 frame buffer for this palette entry */ static u32 get_bmp_col_x2r10g10b10(struct bmp_color_table_entry *cte) { return ((cte->red << 22U) | (cte->green << 12U) | (cte->blue << 2U)); } /** * write_pix8() - Write a pixel from a BMP image into the framebuffer * * This handles frame buffers with 8, 16, 24 or 32 bits per pixel * * @fb: Place in frame buffer to update * @bpix: Frame buffer bits-per-pixel, which controls how many bytes are written * @palette: BMP palette table * @bmap: Pointer to BMP bitmap position to write. This contains a single byte * which is either written directly (bpix == 8) or used to look up the * palette to get a colour to write */ static void write_pix8(u8 *fb, uint bpix, enum video_format eformat, struct bmp_color_table_entry *palette, u8 *bmap) { if (bpix == 8) { *fb++ = *bmap; } else if (bpix == 16) { *(u16 *)fb = get_bmp_col_16bpp(palette[*bmap]); } else { /* Only support big endian */ struct bmp_color_table_entry *cte = &palette[*bmap]; if (bpix == 24) { *fb++ = cte->red; *fb++ = cte->green; *fb++ = cte->blue; } else if (eformat == VIDEO_X2R10G10B10) { *(u32 *)fb = get_bmp_col_x2r10g10b10(cte); } else { *fb++ = cte->blue; *fb++ = cte->green; *fb++ = cte->red; *fb++ = 0; } } } static void draw_unencoded_bitmap(u8 **fbp, uint bpix, enum video_format eformat, uchar *bmap, struct bmp_color_table_entry *palette, int cnt) { u8 *fb = *fbp; while (cnt > 0) { write_pix8(fb, bpix, eformat, palette, bmap++); fb += bpix / 8; cnt--; } *fbp = fb; } static void draw_encoded_bitmap(u8 **fbp, uint bpix, enum video_format eformat, struct bmp_color_table_entry *palette, u8 *bmap, int cnt) { u8 *fb = *fbp; while (cnt > 0) { write_pix8(fb, bpix, eformat, palette, bmap); fb += bpix / 8; cnt--; } *fbp = fb; } static void video_display_rle8_bitmap(struct udevice *dev, struct bmp_image *bmp, uint bpix, struct bmp_color_table_entry *palette, uchar *fb, int x_off, int y_off, ulong width, ulong height) { struct video_priv *priv = dev_get_uclass_priv(dev); uchar *bmap; ulong cnt, runlen; int x, y; int decode = 1; uint bytes_per_pixel = bpix / 8; enum video_format eformat = priv->format; debug("%s\n", __func__); bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); x = 0; y = height - 1; while (decode) { if (bmap[0] == BMP_RLE8_ESCAPE) { switch (bmap[1]) { case BMP_RLE8_EOL: /* end of line */ bmap += 2; x = 0; y--; fb -= width * bytes_per_pixel + priv->line_length; break; case BMP_RLE8_EOBMP: /* end of bitmap */ decode = 0; break; case BMP_RLE8_DELTA: /* delta run */ x += bmap[2]; y -= bmap[3]; fb = (uchar *)(priv->fb + (y + y_off - 1) * priv->line_length + (x + x_off) * bytes_per_pixel); bmap += 4; break; default: /* unencoded run */ runlen = bmap[1]; bmap += 2; if (y < height) { if (x < width) { if (x + runlen > width) cnt = width - x; else cnt = runlen; draw_unencoded_bitmap( &fb, bpix, eformat, bmap, palette, cnt); } x += runlen; } bmap += runlen; if (runlen & 1) bmap++; } } else { /* encoded run */ if (y < height) { runlen = bmap[0]; if (x < width) { /* aggregate the same code */ while (bmap[0] == 0xff && bmap[2] != BMP_RLE8_ESCAPE && bmap[1] == bmap[3]) { runlen += bmap[2]; bmap += 2; } if (x + runlen > width) cnt = width - x; else cnt = runlen; draw_encoded_bitmap(&fb, bpix, eformat, palette, &bmap[1], cnt); } x += runlen; } bmap += 2; } } } /** * video_splash_align_axis() - Align a single coordinate * *- if a coordinate is 0x7fff then the image will be centred in * that direction *- if a coordinate is -ve then it will be offset to the * left/top of the centre by that many pixels *- if a coordinate is positive it will be used unchnaged. * * @axis: Input and output coordinate * @panel_size: Size of panel in pixels for that axis * @picture_size: Size of bitmap in pixels for that axis */ static void video_splash_align_axis(int *axis, unsigned long panel_size, unsigned long picture_size) { long panel_picture_delta = panel_size - picture_size; long axis_alignment; if (*axis == BMP_ALIGN_CENTER) axis_alignment = panel_picture_delta / 2; else if (*axis < 0) axis_alignment = panel_picture_delta + *axis + 1; else return; *axis = max(0, (int)axis_alignment); } int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y, bool align) { struct video_priv *priv = dev_get_uclass_priv(dev); int i, j; uchar *start, *fb; struct bmp_image *bmp = map_sysmem(bmp_image, 0); uchar *bmap; ushort padded_width; unsigned long width, height, byte_width; unsigned long pwidth = priv->xsize; unsigned colours, bpix, bmp_bpix; enum video_format eformat; struct bmp_color_table_entry *palette; int hdr_size; int ret; if (!bmp || !(bmp->header.signature[0] == 'B' && bmp->header.signature[1] == 'M')) { printf("Error: no valid bmp image at %lx\n", bmp_image); return -EINVAL; } width = get_unaligned_le32(&bmp->header.width); height = get_unaligned_le32(&bmp->header.height); bmp_bpix = get_unaligned_le16(&bmp->header.bit_count); hdr_size = get_unaligned_le16(&bmp->header.size); debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix); palette = (void *)bmp + 14 + hdr_size; colours = 1 << bmp_bpix; bpix = VNBITS(priv->bpix); eformat = priv->format; if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) { printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", bpix, bmp_bpix); return -EINVAL; } /* * We support displaying 8bpp and 24bpp BMPs on 16bpp LCDs * and displaying 24bpp BMPs on 32bpp LCDs */ if (bpix != bmp_bpix && !(bmp_bpix == 8 && bpix == 16) && !(bmp_bpix == 8 && bpix == 24) && !(bmp_bpix == 8 && bpix == 32) && !(bmp_bpix == 24 && bpix == 16) && !(bmp_bpix == 24 && bpix == 32)) { printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", bpix, get_unaligned_le16(&bmp->header.bit_count)); return -EPERM; } debug("Display-bmp: %d x %d with %d colours, display %d\n", (int)width, (int)height, (int)colours, 1 << bpix); padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width); if (align) { video_splash_align_axis(&x, priv->xsize, width); video_splash_align_axis(&y, priv->ysize, height); } if ((x + width) > pwidth) width = pwidth - x; if ((y + height) > priv->ysize) height = priv->ysize - y; bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); start = (uchar *)(priv->fb + (y + height) * priv->line_length + x * bpix / 8); /* Move back to the final line to be drawn */ fb = start - priv->line_length; switch (bmp_bpix) { case 1: case 8: if (IS_ENABLED(CONFIG_VIDEO_BMP_RLE8)) { u32 compression = get_unaligned_le32( &bmp->header.compression); debug("compressed %d %d\n", compression, BMP_BI_RLE8); if (compression == BMP_BI_RLE8) { video_display_rle8_bitmap(dev, bmp, bpix, palette, fb, x, y, width, height); break; } } /* Not compressed */ byte_width = width * (bpix / 8); if (!byte_width) byte_width = width; for (i = 0; i < height; ++i) { schedule(); for (j = 0; j < width; j++) { write_pix8(fb, bpix, eformat, palette, bmap); bmap++; fb += bpix / 8; } bmap += (padded_width - width); fb -= byte_width + priv->line_length; } break; case 16: if (IS_ENABLED(CONFIG_BMP_16BPP)) { for (i = 0; i < height; ++i) { schedule(); for (j = 0; j < width; j++) { *fb++ = *bmap++; *fb++ = *bmap++; } bmap += (padded_width - width); fb -= width * 2 + priv->line_length; } } break; case 24: if (IS_ENABLED(CONFIG_BMP_24BPP)) { for (i = 0; i < height; ++i) { for (j = 0; j < width; j++) { if (bpix == 16) { /* 16bit 565RGB format */ *(u16 *)fb = ((bmap[2] >> 3) << 11) | ((bmap[1] >> 2) << 5) | (bmap[0] >> 3); bmap += 3; fb += 2; } else if (eformat == VIDEO_X2R10G10B10) { u32 pix; pix = *bmap++ << 2U; pix |= *bmap++ << 12U; pix |= *bmap++ << 22U; *fb++ = pix & 0xff; *fb++ = (pix >> 8) & 0xff; *fb++ = (pix >> 16) & 0xff; *fb++ = pix >> 24; } else { *fb++ = *bmap++; *fb++ = *bmap++; *fb++ = *bmap++; *fb++ = 0; } } fb -= priv->line_length + width * (bpix / 8); bmap += (padded_width - width); } } break; case 32: if (IS_ENABLED(CONFIG_BMP_32BPP)) { for (i = 0; i < height; ++i) { for (j = 0; j < width; j++) { if (eformat == VIDEO_X2R10G10B10) { u32 pix; pix = *bmap++ << 2U; pix |= *bmap++ << 12U; pix |= *bmap++ << 22U; pix |= (*bmap++ >> 6) << 30U; *fb++ = pix & 0xff; *fb++ = (pix >> 8) & 0xff; *fb++ = (pix >> 16) & 0xff; *fb++ = pix >> 24; } else { *fb++ = *bmap++; *fb++ = *bmap++; *fb++ = *bmap++; *fb++ = *bmap++; } } fb -= priv->line_length + width * (bpix / 8); } } break; default: break; }; /* Find the position of the top left of the image in the framebuffer */ fb = (uchar *)(priv->fb + y * priv->line_length + x * bpix / 8); ret = video_sync_copy(dev, start, fb); if (ret) return log_ret(ret); return video_sync(dev, false); }