diff --git a/doc/config.md b/doc/config.md index cab0b10b..3aafca42 100644 --- a/doc/config.md +++ b/doc/config.md @@ -96,12 +96,13 @@ This document describes the fields that can be found within a configuration file - **regX_text**: Text displayed in OSD region X. - **regX_font**: Font used for text in OSD region X. - **regX_opal**: Opacity of OSD region X. -- **regX_posx**: X position of OSD region X. +- **regX_posx**: X position of OSD region X; `-1` centers it on the main stream. - **regX_posy**: Y position of OSD region X. - **regX_size**: Size of the text or image in OSD region X. - **regX_color**: Color of the text or image in OSD region X. - **regX_outl**: Outline color of the text in OSD region X. - **regX_thick**: Thickness of the text outline in OSD region X. +- **regX_bgcolor**: Background of the text in OSD region X, in the same 15-bit format as the colors with the top bit set to show it: `0x8000` is an opaque black box, `0` none (default: `0`). Through `/api/osd/X`, `bgcolor=#RGB`, `#RRGGBB` or `none`, other values leave it unchanged. ## JPEG section diff --git a/doc/endpoints.md b/doc/endpoints.md index 2e9c9fd4..3fa27f7d 100644 --- a/doc/endpoints.md +++ b/doc/endpoints.md @@ -241,10 +241,11 @@ Configures text or image overlays by their ID (0-9 at the moment). | GET | `color` | Font color (hex format, RGB555 format) | | GET | `opal` | Opacity level (0-255) | | GET | `pos` | Position on main stream \[x,y\] | -| GET | `posx` | X coordinate (write-only) | +| GET | `posx` | X coordinate, -1 to center (write-only) | | GET | `posy` | Y coordinate (write-only) | | GET | `outl` | Outline color (hex format, RGB555 format) | | GET | `thick` | Outline thickness (0 to disable) | +| GET | `bgcolor` | Background box color (hex format), `none` for none | | POST | `file` | Bitmap or PNG image to upload (replaces text) | **Response** @@ -258,7 +259,8 @@ Configures text or image overlays by their ID (0-9 at the moment). "size": 15.0, "text": "Backyard (%T)", "outl": "#8000", - "thick": 0.0 + "thick": 0.0, + "bgcolor": "none" } ``` diff --git a/doc/overlays.md b/doc/overlays.md index 3e366dac..758cf4d0 100644 --- a/doc/overlays.md +++ b/doc/overlays.md @@ -20,6 +20,12 @@ curl http://192.168.1.17/api/osd/1?color=%23FFFF&outl=%238000&thick=1.0 ``` N.B. Hashtags have to be espaced with %23 in curl URL syntaxes +A box behind the text keeps it readable over bright scenes, and a negative X centers a region: +``` +curl "http://192.168.1.17/api/osd/1?text=Backyard&bgcolor=%23000000&posx=-1&posy=0" +curl http://192.168.1.17/api/osd/1?bgcolor=none +``` + Empty strings are used to clear the regions: ``` curl http://192.168.1.17/api/osd/1?text= diff --git a/src/app_config.c b/src/app_config.c index ed828771..c23d18e5 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -180,6 +180,7 @@ int app_config_save(void) { fprintf(file, " reg%d_size: %.1f\n", i, osds[i].size); fprintf(file, " reg%d_color: %#04x\n", i, osds[i].color); fprintf(file, " reg%d_outl: %#04x\n", i, osds[i].outl); + fprintf(file, " reg%d_bgcolor: %#04x\n", i, osds[i].bgcolor); fprintf(file, " reg%d_thick: %.1f\n", i, osds[i].thick); } @@ -380,6 +381,7 @@ enum ConfigError app_config_parse(void) { parse_bool(&ini, "mdns", "enable", &app_config.mdns_enable); + region_defaults(); parse_bool(&ini, "osd", "enable", &app_config.osd_enable); if (app_config.osd_enable) { for (char i = 0; i < MAX_OSD; i++) { @@ -395,7 +397,7 @@ enum ConfigError app_config_parse(void) { err = parse_int(&ini, "osd", param, 0, UCHAR_MAX, &val); if (err == CONFIG_OK) osds[i].opal = (unsigned char)val; sprintf(param, "reg%d_posx", i); - err = parse_int(&ini, "osd", param, 0, SHRT_MAX, &val); + err = parse_int(&ini, "osd", param, -1, SHRT_MAX, &val); if (err == CONFIG_OK) osds[i].posx = (short)val; sprintf(param, "reg%d_posy", i); err = parse_int(&ini, "osd", param, 0, SHRT_MAX, &val); @@ -406,9 +408,11 @@ enum ConfigError app_config_parse(void) { parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].color); sprintf(param, "reg%d_outl", i); parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].outl); + sprintf(param, "reg%d_bgcolor", i); + parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].bgcolor); sprintf(param, "reg%d_thick", i); parse_double(&ini, "osd", param, 0, UCHAR_MAX, &osds[i].thick); - osds[i].updt = 1; + osds[i].updt = !EMPTY(osds[i].text) || !EMPTY(osds[i].img); } } diff --git a/src/hal/config.c b/src/hal/config.c index f5f13e14..57b82d02 100644 --- a/src/hal/config.c +++ b/src/hal/config.c @@ -103,7 +103,8 @@ enum ConfigError parse_param_value_n( } param_value[res] = 0; - if (res >= 2 && param_value[0] == '"' && param_value[res - 1] == '"') { + if (res >= 2 && (param_value[0] == '"' || param_value[0] == '\'') && + param_value[res - 1] == param_value[0]) { memmove(param_value, param_value + 1, res - 2); param_value[res - 2] = '\0'; res -= 2; diff --git a/src/hal/star/i6_hal.c b/src/hal/star/i6_hal.c index 1c48fd0c..6b79d33c 100644 --- a/src/hal/star/i6_hal.c +++ b/src/hal/star/i6_hal.c @@ -379,7 +379,23 @@ void i6_pipeline_destroy(void) i6_snr.fnDisable(_i6_snr_index); } -int i6_region_create(char handle, hal_rect rect, short opacity) +// A text rendered with a proportional font, like a clock, changes its width +// from one second to the next. Recreating the region each time frees and +// reallocates its MMA blocks between the VPE frame buffers, which cycle +// through the heap, until a port buffer no longer fits and frames drop. +// The canvas is rounded up and only grows, the bitmap is padded to it +#define I6_RGN_CANVAS_W 32 +#define I6_RGN_CANVAS_H 16 + +// The padding stays within the room left to the frame's right edge, a +// bitmap that does not fit there on its own keeps its exact width +static unsigned int i6_region_width(unsigned int canvas, short width, short room) +{ + if (room < width) room = width; + return MIN(canvas, (unsigned int)room); +} + +int i6_region_create(char handle, hal_rect rect, short opacity, short room) { int ret; @@ -390,18 +406,21 @@ int i6_region_create(char handle, hal_rect rect, short opacity) region.type = I6_RGN_TYPE_OSD; region.pixFmt = I6_RGN_PIXFMT_ARGB1555; - region.size.width = rect.width; - region.size.height = rect.height; + region.size.width = i6_region_width( + (rect.width + I6_RGN_CANVAS_W - 1) & ~(I6_RGN_CANVAS_W - 1), rect.width, room); + region.size.height = (rect.height + I6_RGN_CANVAS_H - 1) & ~(I6_RGN_CANVAS_H - 1); if (i6_rgn.fnGetRegionConfig(handle, ®ionCurr)) { - HAL_INFO("i6_rgn", "Creating region %d...\n", handle); + HAL_INFO("i6_rgn", "Creating region %d (%ux%u)...\n", handle, + region.size.width, region.size.height); if (ret = i6_rgn.fnCreateRegion(handle, ®ion)) return ret; } else if (regionCurr.type != region.type || regionCurr.size.height != region.size.height || - regionCurr.size.width != region.size.width) { + regionCurr.size.width < region.size.width || + regionCurr.size.width > i6_region_width(regionCurr.size.width, rect.width, room)) { HAL_INFO("i6_rgn", "Parameters are different, recreating " - "region %d...\n", handle); + "region %d (%ux%u)...\n", handle, region.size.width, region.size.height); for (char i = 0; i < I6_VENC_CHN_NUM; i++) { if (!i6_state[i].enable) continue; dest.port = i; @@ -412,19 +431,6 @@ int i6_region_create(char handle, hal_rect rect, short opacity) return ret; } - if (i6_rgn.fnGetChannelConfig(handle, &dest, &attribCurr)) - HAL_INFO("i6_rgn", "Attaching region %d...\n", handle); - else if (attribCurr.point.x != rect.x || attribCurr.point.y != rect.y || - attribCurr.osd.bgFgAlpha[1] != opacity) { - HAL_INFO("i6_rgn", "Parameters are different, reattaching " - "region %d...\n", handle); - for (char i = 0; i < I6_VENC_CHN_NUM; i++) { - if (!i6_state[i].enable) continue; - dest.port = i; - i6_rgn.fnDetachChannel(handle, &dest); - } - } - memset(&attrib, 0, sizeof(attrib)); attrib.show = 1; attrib.point.x = rect.x; @@ -434,18 +440,41 @@ int i6_region_create(char handle, hal_rect rect, short opacity) attrib.osd.bgFgAlpha[0] = 0; attrib.osd.bgFgAlpha[1] = opacity; + // Each port is checked on its own, so one enabled after the region was + // attached still gets it + ret = EXIT_SUCCESS; for (char i = 0; i < I6_VENC_CHN_NUM; i++) { if (!i6_state[i].enable) continue; + // On SSC32x the snapshot port is YUYV, where a region comes out + // stretched with every glyph doubled + if (series == 0xEF && i6_state[i].payload == HAL_VIDCODEC_JPG) continue; dest.port = i; - i6_rgn.fnAttachChannel(handle, &dest, &attrib); + if (!i6_rgn.fnGetChannelConfig(handle, &dest, &attribCurr)) { + if (attribCurr.point.x == rect.x && attribCurr.point.y == rect.y && + attribCurr.osd.bgFgAlpha[1] == opacity) + continue; + HAL_INFO("i6_rgn", "Parameters are different, reattaching " + "region %d to VPE port %d...\n", handle, i); + i6_rgn.fnDetachChannel(handle, &dest); + } else + HAL_INFO("i6_rgn", "Attaching region %d to VPE port %d...\n", handle, i); + int err = i6_rgn.fnAttachChannel(handle, &dest, &attrib); + if (err) { + HAL_DANGER("i6_rgn", "Attaching region %d to VPE port %d failed " + "with %#x!\n", handle, i, err); + ret = err; + } } - return EXIT_SUCCESS; + return ret; } +static char _i6_rgn_ready = 0; + void i6_region_deinit(void) { i6_rgn.fnDeinit(); + _i6_rgn_ready = 0; } void i6_region_destroy(char handle) @@ -463,16 +492,78 @@ void i6_region_destroy(char handle) void i6_region_init(void) { + if (_i6_rgn_ready) return; + i6_rgn_pal palette = {{{0, 0, 0, 0}}}; i6_rgn.fnInit(&palette); + _i6_rgn_ready = 1; +} + +int i6_region_prepare(char handle, short width, short height, short room) +{ + i6_rgn_cnf region; + + i6_region_init(); + + memset(®ion, 0, sizeof(region)); + region.type = I6_RGN_TYPE_OSD; + region.pixFmt = I6_RGN_PIXFMT_ARGB1555; + // A quarter more than the text needs, so a clock or a counter can grow + region.size.width = i6_region_width( + (width + width / 4 + I6_RGN_CANVAS_W - 1) & ~(I6_RGN_CANVAS_W - 1), width, room); + region.size.height = (height + I6_RGN_CANVAS_H - 1) & ~(I6_RGN_CANVAS_H - 1); + + HAL_INFO("i6_rgn", "Reserving region %d (%ux%u)...\n", handle, + region.size.width, region.size.height); + return i6_rgn.fnCreateRegion(handle, ®ion); } int i6_region_setbitmap(int handle, hal_bitmap *bitmap) { - i6_rgn_bmp nativeBmp = { .data = bitmap->data, .pixFmt = I6_RGN_PIXFMT_ARGB1555, - .size.height = bitmap->dim.height, .size.width = bitmap->dim.width }; + int ret; + i6_rgn_cnf region; + + if (ret = i6_rgn.fnGetRegionConfig(handle, ®ion)) { + HAL_DANGER("i6_rgn", "Reading the size of region %d failed " + "with %#x!\n", handle, ret); + return ret; + } + if (bitmap->dim.width > region.size.width || + bitmap->dim.height > region.size.height) { + HAL_DANGER("i6_rgn", "Bitmap %ux%u exceeds the %ux%u canvas " + "of region %d!\n", bitmap->dim.width, bitmap->dim.height, + region.size.width, region.size.height, handle); + return EXIT_FAILURE; + } + + // ARGB1555 zero is a transparent pixel, the margin stays invisible + unsigned short *canvas = bitmap->data; + if (bitmap->dim.width != region.size.width || + bitmap->dim.height != region.size.height) { + canvas = calloc(region.size.width * region.size.height, sizeof(*canvas)); + if (!canvas) { + HAL_DANGER("i6_rgn", "Allocating the canvas of region %d failed!\n", handle); + return EXIT_FAILURE; + } + for (unsigned int y = 0; y < bitmap->dim.height; y++) + memcpy(canvas + y * region.size.width, + (unsigned short*)bitmap->data + y * bitmap->dim.width, + bitmap->dim.width * sizeof(*canvas)); + } - return i6_rgn.fnSetBitmap(handle, &nativeBmp); + i6_rgn_bmp nativeBmp = { .data = canvas, .pixFmt = I6_RGN_PIXFMT_ARGB1555, + .size.height = region.size.height, .size.width = region.size.width }; + + ret = i6_rgn.fnSetBitmap(handle, &nativeBmp); + if (ret) + HAL_DANGER("i6_rgn", "Setting the bitmap of region %d (%ux%u) " + "failed with %#x!\n", handle, nativeBmp.size.width, + nativeBmp.size.height, ret); + + if (canvas != bitmap->data) + free(canvas); + + return ret; } int i6_sensor_exposure(unsigned int micros) diff --git a/src/hal/star/i6_hal.h b/src/hal/star/i6_hal.h index 75e83c48..abae584e 100644 --- a/src/hal/star/i6_hal.h +++ b/src/hal/star/i6_hal.h @@ -38,10 +38,11 @@ int i6_config_load(char *path); int i6_pipeline_create(char index, short width, short height, char mirror, char flip, char framerate); void i6_pipeline_destroy(void); -int i6_region_create(char handle, hal_rect rect, short opacity); +int i6_region_create(char handle, hal_rect rect, short opacity, short room); void i6_region_deinit(void); void i6_region_destroy(char handle); void i6_region_init(void); +int i6_region_prepare(char handle, short width, short height, short room); int i6_region_setbitmap(int handle, hal_bitmap *bitmap); int i6_sensor_exposure(unsigned int micros); diff --git a/src/hal/tools.c b/src/hal/tools.c index 83ec44af..c5d4fd93 100644 --- a/src/hal/tools.c +++ b/src/hal/tools.c @@ -97,7 +97,7 @@ int color_parse(const char *str) { int b = hex_to_int(str[3]); if (r >= 0 && g >= 0 && b >= 0) - return (1 << 16) | (r << 11) | (g << 6) | (b << 1); + return (1 << 15) | (r << 11) | (g << 6) | (b << 1); } // 8-bit hex format "#RRGGBB" diff --git a/src/media.c b/src/media.c index e9cb510c..558ec411 100644 --- a/src/media.c +++ b/src/media.c @@ -1,4 +1,5 @@ #include "media.h" +#include "region.h" char audioOn = 0, udpOn = 0; pthread_mutex_t aencMtx, chnMtx, mp4Mtx; @@ -874,6 +875,8 @@ int sdk_start(void) { HAL_ERROR("media", "System initialization failed with %#x!\n%s\n", ret, errstr(ret)); + region_prepare(); + if (app_config.audio_enable) { ret = media_audio_enable(); if (ret) diff --git a/src/region.c b/src/region.c index 23785663..bf115c42 100644 --- a/src/region.c +++ b/src/region.c @@ -326,18 +326,9 @@ int region_prepare_bitmap(char *path, hal_bitmap *bitmap) { return EXIT_SUCCESS; } -void *region_thread(void) { - switch (plat) { -#if defined(__ARM_PCS_VFP) - case HAL_PLATFORM_I6: i6_region_init(); break; - case HAL_PLATFORM_I6C: i6c_region_init(); break; - case HAL_PLATFORM_M6: m6_region_init(); break; -#endif - } - +void region_defaults(void) { for (char id = 0; id < MAX_OSD; id++) { - if (!EMPTY(osds[id].text) || !EMPTY(osds[id].img)) continue; osds[id].hand = -1; osds[id].color = DEF_COLOR; osds[id].opal = DEF_OPAL; @@ -345,12 +336,77 @@ void *region_thread(void) { osds[id].posx = DEF_POSX; osds[id].posy = DEF_POSY + (DEF_SIZE * 3 / 2) * id; osds[id].outl = DEF_OUTL; + osds[id].bgcolor = 0; osds[id].thick = DEF_THICK; osds[id].updt = 0; strncpy(osds[id].font, DEF_FONT, sizeof(osds[id].font) - 1); osds[id].text[0] = '\0'; osds[id].img[0] = '\0'; } +} + +static short region_frame(void) { + return app_config.mp4_enable ? app_config.mp4_width : app_config.mjpeg_width; +} + +// A negative X centers the region on the main stream, the width is rounded +// so a clock does not move, and get reattached, every second +static short region_posx(char id, short width) { + if (osds[id].posx >= 0) return osds[id].posx; + + return MAX(region_frame() - ((width + 31) & ~31), 0) / 2 & ~1; +} + +static int region_font(const char *name, char *path) { + char *dirs[] = { + ".", + "/oem/usr/share", + "/usr/local/share/fonts", + "/usr/share/fonts/truetype", + "/usr/share/fonts", + NULL}; + + for (char **dir = dirs; *dir; dir++) { + sprintf(path, "%s/%s.ttf", *dir, name); + if (!access(path, F_OK)) return EXIT_SUCCESS; + sprintf(path, "%s/%s2.ttf", *dir, name); + if (!access(path, F_OK)) return EXIT_SUCCESS; + } + + return EXIT_FAILURE; +} + +// On infinity6 the text canvases have to exist before the video pipeline: made +// later, they land in the MMA gaps the encoder's frame buffers come and go +// from, and the main stream loses a third of its frames +void region_prepare(void) { + if (!app_config.osd_enable || plat != HAL_PLATFORM_I6) return; + +#if defined(__ARM_PCS_VFP) + for (char id = 0; id < MAX_OSD; id++) { + char font[256], out[80] = {0}; + + if (EMPTY(osds[id].text) || region_font(osds[id].font, font)) continue; + + strncpy(out, osds[id].text, sizeof(out) - 1); + region_fill_formatted(out); + hal_bitmap bitmap = text_create_rendered(font, osds[id].size, out, osds[id].color, + osds[id].outl, osds[id].thick, osds[id].bgcolor); + i6_region_prepare(id, bitmap.dim.width, bitmap.dim.height, + region_frame() - region_posx(id, bitmap.dim.width)); + free(bitmap.data); + } +#endif +} + +void *region_thread(void) { + switch (plat) { +#if defined(__ARM_PCS_VFP) + case HAL_PLATFORM_I6: i6_region_init(); break; + case HAL_PLATFORM_I6C: i6c_region_init(); break; + case HAL_PLATFORM_M6: m6_region_init(); break; +#endif + } while (keepRunning) { for (char id = 0; id < MAX_OSD; id++) { @@ -366,31 +422,18 @@ void *region_thread(void) { if (osds[id].updt) { char font[256]; - char *dirs[] = { - ".", - "/oem/usr/share", - "/usr/local/share/fonts", - "/usr/share/fonts/truetype", - "/usr/share/fonts", - NULL}; - char **dir = dirs; - while (*dir) { - sprintf(font, "%s/%s.ttf", *dir, osds[id].font); - if (!access(font, F_OK)) goto found_font; - sprintf(font, "%s/%s2.ttf", *dir++, osds[id].font); - if (!access(font, F_OK)) goto found_font; + if (region_font(osds[id].font, font)) { + HAL_DANGER("region", "Font \"%s\" not found!\n", osds[id].font); + continue; } - HAL_DANGER("region", "Font \"%s\" not found!\n", osds[id].font); - continue; -found_font:; hal_bitmap bitmap = text_create_rendered(font, osds[id].size, out, osds[id].color, - osds[id].outl, osds[id].thick); + osds[id].outl, osds[id].thick, osds[id].bgcolor); hal_rect rect = { .height = bitmap.dim.height, .width = bitmap.dim.width, - .x = osds[id].posx, .y = osds[id].posy }; + .x = region_posx(id, bitmap.dim.width), .y = osds[id].posy }; switch (plat) { #if defined(__ARM_PCS_VFP) case HAL_PLATFORM_I6: - i6_region_create(id, rect, osds[id].opal); + i6_region_create(id, rect, osds[id].opal, region_frame() - rect.x); i6_region_setbitmap(id, &bitmap); break; case HAL_PLATFORM_I6C: @@ -453,11 +496,11 @@ found_font:; if (!ret) { hal_rect rect = { .height = bitmap.dim.height, .width = bitmap.dim.width, - .x = osds[id].posx, .y = osds[id].posy }; + .x = region_posx(id, bitmap.dim.width), .y = osds[id].posy }; switch (plat) { #if defined(__ARM_PCS_VFP) case HAL_PLATFORM_I6: - i6_region_create(id, rect, osds[id].opal); + i6_region_create(id, rect, osds[id].opal, region_frame() - rect.x); i6_region_setbitmap(id, &bitmap); break; case HAL_PLATFORM_I6C: diff --git a/src/region.h b/src/region.h index 50a3148b..bb0dd5ff 100644 --- a/src/region.h +++ b/src/region.h @@ -66,12 +66,14 @@ typedef struct { char font[32]; char text[80]; char img[64]; - int outl; + int outl, bgcolor; double thick; } osd; extern osd osds[MAX_OSD]; extern char timefmt[64]; +void region_defaults(void); +void region_prepare(void); int region_start(); void region_stop(); diff --git a/src/server.c b/src/server.c index 56c06497..a7d98a5e 100644 --- a/src/server.c +++ b/src/server.c @@ -1454,12 +1454,26 @@ void respond_request(http_request_t *req) { if (remain == value) continue; osds[id].thick = result; } + else if (EQUALS(key, "bgcolor")) { + size_t len = strlen(value); + if (EMPTY(value) || EQUALS(value, "none") || EQUALS(value, "0")) + osds[id].bgcolor = 0; + else if (value[0] == '#' && (len == 4 || len == 7) && + strspn(value + 1, "0123456789abcdefABCDEF") == len - 1) + osds[id].bgcolor = color_parse(value); + } } osds[id].updt = 1; } int color = (((osds[id].color >> 10) & 0x1F) * 255 / 31) << 16 | (((osds[id].color >> 5) & 0x1F) * 255 / 31) << 8 | ((osds[id].color & 0x1F) * 255 / 31); + char bgcolor[8] = "none"; + if (osds[id].bgcolor & 0x8000) + sprintf(bgcolor, "#%06x", + (((osds[id].bgcolor >> 10) & 0x1F) * 255 / 31) << 16 | + (((osds[id].bgcolor >> 5) & 0x1F) * 255 / 31) << 8 | + ((osds[id].bgcolor & 0x1F) * 255 / 31)); respLen = sprintf(response, "HTTP/1.1 200 OK\r\n" "Content-Type: application/json;charset=UTF-8\r\n" @@ -1467,10 +1481,10 @@ void respond_request(http_request_t *req) { "\r\n" "{\"id\":%d,\"color\":\"#%x\",\"opal\":%d,\"pos\":[%d,%d]," "\"font\":\"%s\",\"size\":%.1f,\"text\":\"%s\",\"img\":\"%s\"," - "\"outl\":\"#%x\",\"thick\":%.1f}", + "\"outl\":\"#%x\",\"thick\":%.1f,\"bgcolor\":\"%s\"}", id, color, osds[id].opal, osds[id].posx, osds[id].posy, osds[id].font, osds[id].size, osds[id].text, osds[id].img, - osds[id].outl, osds[id].thick); + osds[id].outl, osds[id].thick, bgcolor); send_and_close(req->clntFd, response, respLen); return; } diff --git a/src/text.c b/src/text.c index a4d190ae..9d6da595 100644 --- a/src/text.c +++ b/src/text.c @@ -172,18 +172,20 @@ void text_dim_rendered(double *margin, double *height, double *width, const char } hal_bitmap text_create_rendered(const char *font, double size, const char *text, - int color, int outline, double thick) + int color, int outline, double thick, int background) { text_load_font(&sft, font, size, &lmtx); double margin, height, width; text_dim_rendered(&margin, &height, &width, text); - text_new_rendered(&canvas, (CEILING(width) + 3) & ~3, CEILING(height), 0); + // A box around the text gets some room on either side + int pad = background ? CEILING(size / 4) : 0; + text_new_rendered(&canvas, (CEILING(width) + 2 * pad + 3) & ~3, CEILING(height), background); unsigned cps[strlen(text) + 1]; int n = utf8_to_utf32(text, cps, strlen(text) + 1); - double x = margin; + double x = margin + pad; double y = margin + lmtx.ascender + lmtx.lineGap; SFT_Glyph ogid = 0; for (int k = 0; k < n; k++) @@ -191,7 +193,7 @@ hal_bitmap text_create_rendered(const char *font, double size, const char *text, if (cps[k] == '\\' && cps[k + 1] == 'n') { k++; - x = margin; + x = margin + pad; y += lmtx.ascender - lmtx.descender + lmtx.lineGap; ogid = 0; continue; diff --git a/src/text.h b/src/text.h index 8ad294c9..a4f65425 100644 --- a/src/text.h +++ b/src/text.h @@ -5,5 +5,5 @@ #include "hal/support.h" #include "lib/schrift.h" -hal_bitmap text_create_rendered(const char *font, double size, const char *text, - int color, int outline, double thick); \ No newline at end of file +hal_bitmap text_create_rendered(const char *font, double size, const char *text, + int color, int outline, double thick, int background); \ No newline at end of file