Properly scale emoji rendered in the CSD

This commit is contained in:
Kovid Goyal
2021-04-01 22:07:00 +05:30
parent b7d967b6bc
commit 6d6273af48
3 changed files with 92 additions and 25 deletions

View File

@@ -458,36 +458,38 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
return true;
}
static void
downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_height) {
int
downsample_32bit_image(uint8_t *src, unsigned src_width, unsigned src_height, unsigned src_stride, uint8_t *dest, unsigned dest_width, unsigned dest_height) {
// Downsample using a simple area averaging algorithm. Could probably do
// better with bi-cubic or lanczos, but at these small sizes I don't think
// it matters
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
float ratio = MAX((float)src_width / dest_width, (float)src_height / dest_height);
int factor = (int)ceilf(ratio);
uint8_t *dest = calloc(4, (size_t)width * cell_height);
if (dest == NULL) fatal("Out of memory");
uint8_t *d = dest;
for (unsigned int i = 0, sr = 0; i < cell_height; i++, sr += factor) {
for (unsigned int j = 0, sc = 0; j < width; j++, sc += factor, d += 4) {
for (unsigned int i = 0, sr = 0; i < dest_height; i++, sr += factor) {
for (unsigned int j = 0, sc = 0; j < dest_width; j++, sc += factor, d += 4) {
// calculate area average
unsigned int r=0, g=0, b=0, a=0, count=0;
for (unsigned int y=sr; y < MIN(sr + factor, bm->rows); y++) {
uint8_t *p = bm->buf + (y * bm->stride) + sc * 4;
for (unsigned int x=sc; x < MIN(sc + factor, bm->width); x++, count++) {
for (unsigned int y=sr; y < MIN(sr + factor, src_height); y++) {
uint8_t *p = src + (y * src_stride) + sc * 4;
for (unsigned int x=sc; x < MIN(sc + factor, src_width); x++, count++) {
b += *(p++); g += *(p++); r += *(p++); a += *(p++);
}
}
if (count) {
d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count;
}
}
}
return factor;
}
static void
downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_height) {
uint8_t *dest = calloc(4, (size_t)width * cell_height);
if (dest == NULL) fatal("Out of memory");
bm->factor = downsample_32bit_image(bm->buf, bm->width, bm->rows, bm->stride, dest, width, cell_height);
bm->buf = dest; bm->needs_free = true; bm->stride = 4 * width; bm->width = width; bm->rows = cell_height;
bm->factor = factor;
}
static inline void

View File

@@ -34,7 +34,8 @@ typedef struct {
size_t start_x, width, stride;
size_t rows;
FT_Pixel_Mode pixel_mode;
unsigned int factor, right_edge;
unsigned int left_edge, top_edge, bottom_edge, right_edge;
float factor;
int bitmap_left, bitmap_top;
} ProcessedBitmap;
@@ -102,7 +103,7 @@ width_for_char(Face *face, char_type ch) {
unsigned int ans = 0;
int glyph_index = FT_Get_Char_Index(face->freetype, ch);
if (glyph_index) {
int error = FT_Load_Glyph(face->freetype, glyph_index, FT_LOAD_DEFAULT);
int error = FT_Load_Glyph(face->freetype, glyph_index, get_load_flags(face->hinting, face->hintstyle, FT_LOAD_DEFAULT));
if (!error) {
ans = (unsigned int)ceilf((float)face->freetype->glyph->metrics.horiAdvance / 64.f);
}
@@ -144,7 +145,7 @@ choose_bitmap_size(FT_Face face, FT_UInt desired_height) {
}
static void
set_pixel_size(RenderCtx *ctx, Face *face, FT_UInt sz) {
set_pixel_size(RenderCtx *ctx, Face *face, FT_UInt sz, bool get_metrics UNUSED) {
if (sz != face->pixel_size) {
if (FT_HAS_COLOR(face->freetype)) sz = choose_bitmap_size(face->freetype, font_units_to_pixels_y(main_face.freetype, main_face.freetype->height));
else FT_Set_Pixel_Sizes(face->freetype, sz, sz);
@@ -161,6 +162,7 @@ typedef struct RenderState {
size_t output_width, output_height;
Face *current_face;
float x, y;
int y_offset;
Region src, dest;
unsigned sz_px;
bool truncated;
@@ -179,6 +181,7 @@ setup_regions(ProcessedBitmap *bm, RenderState *rs, int baseline) {
} else {
rs->dest.top = baseline - yoff;
}
rs->dest.top += rs->y_offset;
}
#define ARGB(a, r, g, b) ( (a & 0xff) << 24 ) | ( (r & 0xff) << 16) | ( (g & 0xff) << 8 ) | (b & 0xff)
@@ -205,7 +208,7 @@ static void
render_color_bitmap(ProcessedBitmap *src, RenderState *rs) {
for (size_t sr = rs->src.top, dr = rs->dest.top; sr < rs->src.bottom && dr < rs->dest.bottom; sr++, dr++) {
pixel *dest_row = rs->output + rs->output_width * dr;
uint8_t *src_px = src->buf + src->stride * sr;
uint8_t *src_px = src->buf + src->stride * sr + 4 * rs->src.left;
for (size_t sc = rs->src.left, dc = rs->dest.left; sc < rs->src.right && dc < rs->dest.right; sc++, dc++, src_px += 4) {
pixel fg = premult_pixel(ARGB(src_px[3], src_px[2], src_px[1], src_px[0]), src_px[3]);
dest_row[dc] = alpha_blend_premult(fg, dest_row[dc]);
@@ -235,6 +238,35 @@ populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBit
ans->buf = bitmap->buffer;
}
static void
detect_edges(ProcessedBitmap *ans) {
#define check const uint8_t *p = ans->buf + x * 4 + y * ans->stride; if (p[3] > 20)
ans->right_edge = 0; ans->bottom_edge = 0;
for (ssize_t x = ans->width - 1; !ans->right_edge && x > -1; x--) {
for (size_t y = 0; y < ans->rows && !ans->right_edge; y++) {
check ans->right_edge = x;
}
}
for (ssize_t y = ans->rows - 1; !ans->bottom_edge && y > -1; y--) {
for (size_t x = 0; x < ans->width && !ans->bottom_edge; x++) {
check ans->bottom_edge = y;
}
}
ans->left_edge = ans->width;
for (size_t x = 0; ans->left_edge == ans->width && x < ans->width; x++) {
for (size_t y = 0; y < ans->rows && ans->left_edge == ans->width; y++) {
check ans->left_edge = x;
}
}
ans->top_edge = ans->rows;
for (size_t y = 0; ans->top_edge == ans->rows && y < ans->rows; y++) {
for (size_t x = 0; x < ans->width && ans->top_edge == ans->rows; x++) {
check ans->top_edge = y;
}
}
#undef check
}
static bool
render_run(RenderCtx *ctx, RenderState *rs) {
hb_buffer_guess_segment_properties(hb_buffer);
@@ -245,7 +277,7 @@ render_run(RenderCtx *ctx, RenderState *rs) {
FT_Face face = rs->current_face->freetype;
bool has_color = FT_HAS_COLOR(face);
FT_UInt pixel_size = rs->sz_px;
set_pixel_size(ctx, rs->current_face, pixel_size);
set_pixel_size(ctx, rs->current_face, pixel_size, false);
hb_shape(rs->current_face->hb, hb_buffer, NULL, 0);
unsigned int len = hb_buffer_get_length(hb_buffer);
hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
@@ -281,12 +313,44 @@ render_run(RenderCtx *ctx, RenderState *rs) {
PyErr_Print();
continue;
};
ProcessedBitmap pbm = {.factor=1};
ProcessedBitmap pbm = {0};
switch(face->glyph->bitmap.pixel_mode) {
case FT_PIXEL_MODE_BGRA: {
uint8_t *buf = NULL;
unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height);
populate_processed_bitmap(face->glyph, &face->glyph->bitmap, &pbm);
unsigned bm_width = 0, bm_height = text_height;
if (pbm.rows > bm_height) {
double ratio = pbm.width / (double)pbm.rows;
bm_width = (unsigned)(ratio * bm_height);
buf = calloc(sizeof(pixel), bm_height * bm_width);
if (!buf) break;
downsample_32bit_image(pbm.buf, pbm.width, pbm.rows, pbm.stride, buf, bm_width, bm_height);
pbm.buf = buf; pbm.stride = 4 * bm_width; pbm.width = bm_width; pbm.rows = bm_height;
detect_edges(&pbm);
}
setup_regions(&pbm, rs, baseline);
if (bm_width) {
/* printf("bottom_edge: %u top_edge: %u left_edge: %u right_edge: %u\n", */
/* pbm.bottom_edge, pbm.top_edge, pbm.left_edge, pbm.right_edge); */
rs->src.top = pbm.top_edge; rs->src.bottom = pbm.bottom_edge + 1;
rs->src.left = pbm.left_edge; rs->src.right = pbm.right_edge + 1;
rs->dest.left = (int)(rs->x + 2);
positions[i].x_advance = (pbm.right_edge - pbm.left_edge + 2) * 64;
unsigned main_baseline = font_units_to_pixels_y(main_face.freetype, main_face.freetype->ascender);
unsigned symbol_height = pbm.bottom_edge - pbm.top_edge;
unsigned baseline_y = main_baseline + rs->y_offset, text_bottom_y = text_height + rs->y_offset;
if (symbol_height <= baseline_y) {
rs->dest.top = baseline_y - symbol_height + 2;
} else {
if (symbol_height <= text_bottom_y) rs->dest.top = text_bottom_y - symbol_height;
else rs->dest.top = 0;
}
rs->dest.top += main_baseline > pbm.bottom_edge ? main_baseline - pbm.bottom_edge : 0;
/* printf("symbol_height: %u baseline_y: %u\n", symbol_height, baseline_y); */
}
render_color_bitmap(&pbm, rs);
free(buf);
}
break;
case FT_PIXEL_MODE_MONO: {
@@ -376,13 +440,13 @@ render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pix
if (!unicode) { PyErr_NoMemory(); return false; }
bool ok = false;
text_len = decode_utf8_string(text, text_len, unicode);
set_pixel_size(ctx, &main_face, sz_px);
set_pixel_size(ctx, &main_face, sz_px, true);
unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height);
if (text_height < height) y_offset -= (height - text_height) / 2;
RenderState rs = {
.current_face = &main_face, .fg = fg, .bg = bg, .output_width = width, .output_height = height,
.output = (pixel*)output_buf, .x = x_offset, .y = y_offset, .sz_px = sz_px
};
if (text_height < height) rs.y_offset = (height - text_height) / 2;
for (size_t i = 0; i < text_len && rs.x < rs.output_width && !rs.truncated; i++) {
if (!process_codepoint(ctx, &rs, unicode[i], unicode[i + 1])) goto end;
@@ -424,9 +488,9 @@ release_freetype_render_context(FreeTypeRenderCtx ctx) { if (ctx) { cleanup((Ren
static PyObject*
render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
// use for testing as below
// kitty +runpy "from kitty.fast_data_types import *; open('/tmp/test.rgba', 'wb').write(freetype_render_line())" && convert -size 800x120 -depth 8 /tmp/test.rgba /tmp/test.png && icat /tmp/test.png
const char *text = "Test 猫 H🐱H rendering with ellipsis", *family = NULL;
unsigned int width = 800, height = 120;
// kitty +runpy "from kitty.fast_data_types import *; open('/tmp/test.rgba', 'wb').write(freetype_render_line())" && convert -size 800x60 -depth 8 /tmp/test.rgba /tmp/test.png && icat /tmp/test.png
const char *text = "Test 猫 H🐱🚀b rendering with ellipsis for cut off text", *family = NULL;
unsigned int width = 800, height = 60;
int bold = 0, italic = 0;
unsigned long fg = 0, bg = 0xfffefefe;
float x_offset = 0, y_offset = 0;

View File

@@ -29,3 +29,4 @@ bool fallback_font(char_type ch, const char *family, bool bold, bool italic, boo
bool freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest);
FT_Library freetype_library(void);
void set_freetype_error(const char* prefix, int err_code);
int downsample_32bit_image(uint8_t *src, unsigned src_width, unsigned src_height, unsigned src_stride, uint8_t *dest, unsigned dest_width, unsigned dest_height);