/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Toomas Soome */ /* * Graphics support for loader emulation. * The interface in loader and here needs some more development. * We can get colormap from gfx_private, but loader is currently * relying on tem fg/bg colors for drawing, once the menu code * will get some facelift, we would need to provide colors as menu component * attributes and stop depending on tem. */ #include #include #include #include #include #include #include #include #include #include #include "gfx_fb.h" struct framebuffer fb; #define max(x, y) ((x) >= (y) ? (x) : (y)) static void gfx_fb_cons_display(uint32_t, uint32_t, uint32_t, uint32_t, uint8_t *); /* This colormap should be replaced by colormap query from kernel */ typedef struct { uint8_t red[16]; uint8_t green[16]; uint8_t blue[16]; } text_cmap_t; text_cmap_t cmap4_to_24 = { /* BEGIN CSTYLED */ /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Wh+ Bk Bl Gr Cy Rd Mg Br Wh Bk+ Bl+ Gr+ Cy+ Rd+ Mg+ Yw */ .red = {0xff,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x40,0x00,0x00,0x00,0xff,0xff,0xff}, .green = {0xff,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x80,0x40,0x00,0xff,0xff,0x00,0x00,0xff}, .blue = {0xff,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x40,0xff,0x00,0xff,0x00,0xff,0x00} /* END CSTYLED */ }; const uint8_t solaris_color_to_pc_color[16] = { 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; void gfx_framework_init(void) { struct fbgattr attr; struct gfxfb_info *gfxfb_info; char buf[10]; fb.fd = open("/dev/fb", O_RDWR); if (fb.fd < 0) return; /* make sure we have GFX framebuffer */ if (ioctl(fb.fd, VIS_GETIDENTIFIER, &fb.ident) < 0 || strcmp(fb.ident.name, "illumos_fb") != 0) { close(fb.fd); fb.fd = -1; return; } if (ioctl(fb.fd, FBIOGATTR, &attr) < 0) { close(fb.fd); fb.fd = -1; return; } gfxfb_info = (struct gfxfb_info *)attr.sattr.dev_specific; fb.fb_height = attr.fbtype.fb_height; fb.fb_width = attr.fbtype.fb_width; fb.fb_depth = attr.fbtype.fb_depth; fb.fb_size = attr.fbtype.fb_size; fb.fb_bpp = attr.fbtype.fb_depth >> 3; if (attr.fbtype.fb_depth == 15) fb.fb_bpp = 2; fb.fb_pitch = gfxfb_info->pitch; fb.terminal_origin_x = gfxfb_info->terminal_origin_x; fb.terminal_origin_y = gfxfb_info->terminal_origin_y; fb.font_width = gfxfb_info->font_width; fb.font_height = gfxfb_info->font_height; fb.red_mask_size = gfxfb_info->red_mask_size; fb.red_field_position = gfxfb_info->red_field_position; fb.green_mask_size = gfxfb_info->green_mask_size; fb.green_field_position = gfxfb_info->green_field_position; fb.blue_mask_size = gfxfb_info->blue_mask_size; fb.blue_field_position = gfxfb_info->blue_field_position; fb.fb_addr = (uint8_t *)mmap(0, fb.fb_size, (PROT_READ | PROT_WRITE), MAP_SHARED, fb.fd, 0); if (fb.fb_addr == NULL) { close(fb.fd); fb.fd = -1; return; } snprintf(buf, sizeof (buf), "%d", fb.fb_height); setenv("screen-height", buf, 1); snprintf(buf, sizeof (buf), "%d", fb.fb_width); setenv("screen-width", buf, 1); } void gfx_framework_fini(void) { if (fb.fd < 0) return; (void) munmap((caddr_t)fb.fb_addr, fb.fb_size); close(fb.fd); fb.fd = -1; } static int isqrt(int num) { int res = 0; int bit = 1 << 30; /* "bit" starts at the highest power of four <= the argument. */ while (bit > num) bit >>= 2; while (bit != 0) { if (num >= res + bit) { num -= res + bit; res = (res >> 1) + bit; } else { res >>= 1; } bit >>= 2; } return (res); } void gfx_fb_setpixel(uint32_t x, uint32_t y) { uint32_t c, offset; if (fb.fd < 0) return; c = 0; /* black */ if (x >= fb.fb_width || y >= fb.fb_height) return; offset = y * fb.fb_pitch + x * fb.fb_bpp; switch (fb.fb_depth) { case 8: fb.fb_addr[offset] = c & 0xff; break; case 15: case 16: *(uint16_t *)(fb.fb_addr + offset) = c & 0xffff; break; case 24: fb.fb_addr[offset] = (c >> 16) & 0xff; fb.fb_addr[offset + 1] = (c >> 8) & 0xff; fb.fb_addr[offset + 2] = c & 0xff; break; case 32: *(uint32_t *)(fb.fb_addr + offset) = c; break; } } void gfx_fb_drawrect(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t fill) { int x, y; if (fb.fd < 0) return; for (y = y1; y <= y2; y++) { if (fill || (y == y1) || (y == y2)) { for (x = x1; x <= x2; x++) gfx_fb_setpixel(x, y); } else { gfx_fb_setpixel(x1, y); gfx_fb_setpixel(x2, y); } } } void gfx_term_drawrect(uint32_t row1, uint32_t col1, uint32_t row2, uint32_t col2) { int x1, y1, x2, y2; int xshift, yshift; int width, i; if (fb.fd < 0) return; width = fb.font_width / 4; /* line width */ xshift = (fb.font_width - width) / 2; yshift = (fb.font_height - width) / 2; /* Terminal coordinates start from (1,1) */ row1--; col1--; row2--; col2--; /* * Draw horizontal lines width points thick, shifted from outer edge. */ x1 = (row1 + 1) * fb.font_width + fb.terminal_origin_x; y1 = col1 * fb.font_height + fb.terminal_origin_y + yshift; x2 = row2 * fb.font_width + fb.terminal_origin_x; gfx_fb_drawrect(x1, y1, x2, y1 + width, 1); y2 = col2 * fb.font_height + fb.terminal_origin_y; y2 += fb.font_height - yshift - width; gfx_fb_drawrect(x1, y2, x2, y2 + width, 1); /* * Draw vertical lines width points thick, shifted from outer edge. */ x1 = row1 * fb.font_width + fb.terminal_origin_x + xshift; y1 = col1 * fb.font_height + fb.terminal_origin_y; y1 += fb.font_height; y2 = col2 * fb.font_height + fb.terminal_origin_y; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); x1 = row2 * fb.font_width + fb.terminal_origin_x; x1 += fb.font_width - xshift - width; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); /* Draw upper left corner. */ x1 = row1 * fb.font_width + fb.terminal_origin_x + xshift; y1 = col1 * fb.font_height + fb.terminal_origin_y; y1 += fb.font_height; x2 = row1 * fb.font_width + fb.terminal_origin_x; x2 += fb.font_width; y2 = col1 * fb.font_height + fb.terminal_origin_y + yshift; for (i = 0; i <= width; i++) gfx_fb_bezier(x1 + i, y1, x1 + i, y2 + i, x2, y2 + i, width-i); /* Draw lower left corner. */ x1 = row1 * fb.font_width + fb.terminal_origin_x; x1 += fb.font_width; y1 = col2 * fb.font_height + fb.terminal_origin_y; y1 += fb.font_height - yshift; x2 = row1 * fb.font_width + fb.terminal_origin_x + xshift; y2 = col2 * fb.font_height + fb.terminal_origin_y; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); /* Draw upper right corner. */ x1 = row2 * fb.font_width + fb.terminal_origin_x; y1 = col1 * fb.font_height + fb.terminal_origin_y + yshift; x2 = row2 * fb.font_width + fb.terminal_origin_x; x2 += fb.font_width - xshift - width; y2 = col1 * fb.font_height + fb.terminal_origin_y; y2 += fb.font_height; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 + i, x2 + i, y1 + i, x2 + i, y2, width-i); /* Draw lower right corner. */ x1 = row2 * fb.font_width + fb.terminal_origin_x; y1 = col2 * fb.font_height + fb.terminal_origin_y; y1 += fb.font_height - yshift; x2 = row2 * fb.font_width + fb.terminal_origin_x; x2 += fb.font_width - xshift - width; y2 = col2 * fb.font_height + fb.terminal_origin_y; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); } void gfx_fb_line(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t width) { int dx, sx, dy, sy; int err, e2, x2, y2, ed; if (fb.fd < 0) return; sx = x0 < x1? 1 : -1; sy = y0 < y1? 1 : -1; dx = abs(x1 - x0); dy = abs(y1 - y0); err = dx - dy; ed = dx + dy == 0 ? 1 : isqrt(dx * dx + dy * dy); if (dx != 0 && dy != 0) width = (width + 1) >> 1; for (;;) { gfx_fb_setpixel(x0, y0); e2 = err; x2 = x0; if ((e2 << 1) >= -dx) { /* x step */ e2 += dy; y2 = y0; while (e2 < ed * width && (y1 != y2 || dx > dy)) { y2 += sy; gfx_fb_setpixel(x0, y2); e2 += dx; } if (x0 == x1) break; e2 = err; err -= dy; x0 += sx; } if ((e2 << 1) <= dy) { /* y step */ e2 = dx-e2; while (e2 < ed * width && (x1 != x2 || dx < dy)) { x2 += sx; gfx_fb_setpixel(x2, y0); e2 += dy; } if (y0 == y1) break; err += dx; y0 += sy; } } } void gfx_fb_bezier(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t wd) { int sx, sy, xx, yy, xy, width; int dx, dy, err, curvature; int i; if (fb.fd < 0) return; width = wd; sx = x2 - x1; sy = y2 - y1; xx = x0 - x1; yy = y0 - y1; curvature = xx*sy - yy*sx; if (sx * sx + sy * sy > xx * xx + yy * yy) { x2 = x0; x0 = sx + x1; y2 = y0; y0 = sy + y1; curvature = -curvature; } if (curvature != 0) { xx += sx; sx = x0 < x2? 1 : -1; xx *= sx; yy += sy; sy = y0 < y2? 1 : -1; yy *= sy; xy = 2 * xx * yy; xx *= xx; yy *= yy; if (curvature * sx * sy < 0) { xx = -xx; yy = -yy; xy = -xy; curvature = -curvature; } dx = 4 * sy * curvature * (x1 - x0) + xx - xy; dy = 4 * sx * curvature * (y0 - y1) + yy - xy; xx += xx; yy += yy; err = dx + dy + xy; do { for (i = 0; i <= width; i++) gfx_fb_setpixel(x0 + i, y0); if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */ y1 = 2 * err < dx; if (2 * err > dy) { x0 += sx; dx -= xy; dy += yy; err += dy; } if (y1 != 0) { y0 += sy; dy -= xy; dx += xx; err += dx; } } while (dy < dx); /* gradient negates -> algorithm fails */ } gfx_fb_line(x0, y0, x2, y2, width); } #define FL_PUTIMAGE_BORDER 0x1 #define FL_PUTIMAGE_NOSCROLL 0x2 #define FL_PUTIMAGE_DEBUG 0x80 int gfx_fb_putimage(png_t *png, uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2, uint32_t flags) { uint32_t i, j, x, y, fheight, fwidth, color; uint8_t r, g, b, a, *p, *data; bool scale = false; bool trace = false; trace = (flags & FL_PUTIMAGE_DEBUG) != 0; if (fb.fd < 0) { if (trace) printf("Framebuffer not active.\n"); return (1); } if (png->color_type != PNG_TRUECOLOR_ALPHA) { if (trace) printf("Not truecolor image.\n"); return (1); } if (ux1 > fb.fb_width || uy1 > fb.fb_height) { if (trace) printf("Top left coordinate off screen.\n"); return (1); } if (png->width > UINT16_MAX || png->height > UINT16_MAX) { if (trace) printf("Image too large.\n"); return (1); } if (png->width < 1 || png->height < 1) { if (trace) printf("Image too small.\n"); return (1); } /* * If 0 was passed for either ux2 or uy2, then calculate the missing * part of the bottom right coordinate. */ scale = true; if (ux2 == 0 && uy2 == 0) { /* Both 0, use the native resolution of the image */ ux2 = ux1 + png->width; uy2 = uy1 + png->height; scale = false; } else if (ux2 == 0) { /* Set ux2 from uy2/uy1 to maintain aspect ratio */ ux2 = ux1 + (png->width * (uy2 - uy1)) / png->height; } else if (uy2 == 0) { /* Set uy2 from ux2/ux1 to maintain aspect ratio */ uy2 = uy1 + (png->height * (ux2 - ux1)) / png->width; } if (ux2 > fb.fb_width || uy2 > fb.fb_height) { if (trace) printf("Bottom right coordinate off screen.\n"); return (1); } fwidth = ux2 - ux1; fheight = uy2 - uy1; /* * If the original image dimensions have been passed explicitly, * disable scaling. */ if (fwidth == png->width && fheight == png->height) scale = false; if (ux1 == 0) { /* * No top left X co-ordinate (real coordinates start at 1), * place as far right as it will fit. */ ux2 = fb.fb_width - fb.terminal_origin_x; ux1 = ux2 - fwidth; } if (uy1 == 0) { /* * No top left Y co-ordinate (real coordinates start at 1), * place as far down as it will fit. */ uy2 = fb.fb_height - fb.terminal_origin_y; uy1 = uy2 - fheight; } if (ux1 >= ux2 || uy1 >= uy2) { if (trace) printf("Image dimensions reversed.\n"); return (1); } if (fwidth < 2 || fheight < 2) { if (trace) printf("Target area too small\n"); return (1); } if (trace) printf("Image %ux%u -> %ux%u @%ux%u\n", png->width, png->height, fwidth, fheight, ux1, uy1); if ((flags & FL_PUTIMAGE_BORDER)) gfx_fb_drawrect(ux1, uy1, ux2, uy2, 0); data = malloc(fwidth * fheight * fb.fb_bpp); if (data == NULL) { if (trace) printf("Out of memory.\n"); return (1); } /* * Build image for our framebuffer. */ /* Helper to calculate the pixel index from the source png */ #define GETPIXEL(xx, yy) (((yy) * png->width + (xx)) * png->bpp) /* * For each of the x and y directions, calculate the number of pixels * in the source image that correspond to a single pixel in the target. * Use fixed-point arithmetic with 16-bits for each of the integer and * fractional parts. */ const uint32_t wcstep = ((png->width - 1) << 16) / (fwidth - 1); const uint32_t hcstep = ((png->height - 1) << 16) / (fheight - 1); uint32_t hc = 0; for (y = 0; y < fheight; y++) { uint32_t hc2 = (hc >> 9) & 0x7f; uint32_t hc1 = 0x80 - hc2; uint32_t offset_y = hc >> 16; uint32_t offset_y1 = offset_y + 1; uint32_t wc = 0; for (x = 0; x < fwidth; x++) { uint32_t wc2 = (wc >> 9) & 0x7f; uint32_t wc1 = 0x80 - wc2; uint32_t offset_x = wc >> 16; uint32_t offset_x1 = offset_x + 1; /* Target pixel index */ j = (y * fwidth + x) * fb.fb_bpp; if (!scale) { i = GETPIXEL(x, y); r = png->image[i]; g = png->image[i + 1]; b = png->image[i + 2]; a = png->image[i + 3]; } else { uint8_t pixel[4]; uint32_t p00 = GETPIXEL(offset_x, offset_y); uint32_t p01 = GETPIXEL(offset_x, offset_y1); uint32_t p10 = GETPIXEL(offset_x1, offset_y); uint32_t p11 = GETPIXEL(offset_x1, offset_y1); /* * Given a 2x2 array of pixels in the source * image, combine them to produce a single * value for the pixel in the target image. * Each column of pixels is combined using * a weighted average where the top and bottom * pixels contribute hc1 and hc2 respectively. * The calculation for bottom pixel pB and * top pixel pT is: * (pT * hc1 + pB * hc2) / (hc1 + hc2) * Once the values are determined for the two * columns of pixels, then the columns are * averaged together in the same way but using * wc1 and wc2 for the weightings. * * Since hc1 and hc2 are chosen so that * hc1 + hc2 == 128 (and same for wc1 + wc2), * the >> 14 below is a quick way to divide by * (hc1 + hc2) * (wc1 + wc2) */ for (i = 0; i < 4; i++) pixel[i] = ( (png->image[p00 + i] * hc1 + png->image[p01 + i] * hc2) * wc1 + (png->image[p10 + i] * hc1 + png->image[p11 + i] * hc2) * wc2) >> 14; r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3]; } color = r >> (8 - fb.red_mask_size) << fb.red_field_position | g >> (8 - fb.green_mask_size) << fb.green_field_position | b >> (8 - fb.blue_mask_size) << fb.blue_field_position; switch (fb.fb_depth) { case 8: { uint32_t best, dist, k; int diff; color = 0; best = 256 * 256 * 256; for (k = 0; k < 16; k++) { diff = r - cmap4_to_24.red[k]; dist = diff * diff; diff = g - cmap4_to_24.green[k]; dist += diff * diff; diff = b - cmap4_to_24.blue[k]; dist += diff * diff; if (dist < best) { color = k; best = dist; if (dist == 0) break; } } data[j] = solaris_color_to_pc_color[color]; break; } case 15: case 16: *(uint16_t *)(data+j) = color; break; case 24: p = (uint8_t *)&color; data[j] = p[0]; data[j+1] = p[1]; data[j+2] = p[2]; break; case 32: color |= a << 24; *(uint32_t *)(data+j) = color; break; } wc += wcstep; } hc += hcstep; } gfx_fb_cons_display(uy1, ux1, fwidth, fheight, data); free(data); return (0); } /* * Implements alpha blending for RGBA data, could use pixels for arguments, * but byte stream seems more generic. * The generic alpha blending is: * blend = alpha * fg + (1.0 - alpha) * bg. * Since our alpha is not from range [0..1], we scale appropriately. */ static uint8_t alpha_blend(uint8_t fg, uint8_t bg, uint8_t alpha) { uint16_t blend, h, l; /* trivial corner cases */ if (alpha == 0) return (bg); if (alpha == 0xFF) return (fg); blend = (alpha * fg + (0xFF - alpha) * bg); /* Division by 0xFF */ h = blend >> 8; l = blend & 0xFF; if (h + l >= 0xFF) h++; return (h); } /* Copy memory to framebuffer or to memory. */ static void bitmap_cpy(uint8_t *dst, uint8_t *src, uint32_t len, int bpp) { uint32_t i; uint8_t a; switch (bpp) { case 4: for (i = 0; i < len; i += bpp) { a = src[i+3]; dst[i] = alpha_blend(src[i], dst[i], a); dst[i+1] = alpha_blend(src[i+1], dst[i+1], a); dst[i+2] = alpha_blend(src[i+2], dst[i+2], a); dst[i+3] = a; } break; default: (void) memcpy(dst, src, len); break; } } /* * gfx_fb_cons_display implements direct draw on frame buffer memory. * It is needed till we have way to send bitmaps to tem, tem already has * function to send data down to framebuffer. */ static void gfx_fb_cons_display(uint32_t row, uint32_t col, uint32_t width, uint32_t height, uint8_t *data) { uint32_t size; /* write size per scanline */ uint8_t *fbp; /* fb + calculated offset */ int i; /* make sure we will not write past FB */ if (col >= fb.fb_width || row >= fb.fb_height || col + width > fb.fb_width || row + height > fb.fb_height) return; size = width * fb.fb_bpp; fbp = fb.fb_addr + col * fb.fb_bpp + row * fb.fb_pitch; /* write all scanlines in rectangle */ for (i = 0; i < height; i++) { uint8_t *dest = fbp + i * fb.fb_pitch; uint8_t *src = data + i * size; bitmap_cpy(dest, src, size, fb.fb_bpp); } }