From 8d7515bd9b7bebdfc0c24966df62e2ab8f4e28d5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 7 Dec 2017 20:39:11 +0530 Subject: [PATCH] Basic color emoji rendering working Still need to downscale color bitmaps to fit into cells --- kitty/cell_fragment.glsl | 11 +++++--- kitty/cell_vertex.glsl | 7 +++-- kitty/fonts.c | 28 +++++++++--------- kitty/fonts.h | 3 +- kitty/fonts/render.py | 12 +++++--- kitty/freetype.c | 61 ++++++++++++++++++++++++++++++++++++---- kitty/state.c | 4 +-- 7 files changed, 92 insertions(+), 34 deletions(-) diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 89d183bbc..1011c5098 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -24,6 +24,7 @@ in vec3 underline_pos; in vec3 strike_pos; in vec3 foreground; in vec3 decoration_fg; +in float colored_sprite; #endif out vec4 final_color; @@ -51,13 +52,15 @@ vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alp #ifdef NEEDS_FOREGROUND vec4 calculate_foreground() { - float text_alpha = texture(sprites, sprite_pos).r; - float underline_alpha = texture(sprites, underline_pos).r; - float strike_alpha = texture(sprites, strike_pos).r; + vec4 text_fg = texture(sprites, sprite_pos); + vec3 fg = mix(foreground, text_fg.rgb, colored_sprite); + float text_alpha = text_fg.a; + float underline_alpha = texture(sprites, underline_pos).a; + float strike_alpha = texture(sprites, strike_pos).a; // Since strike and text are the same color, we simply add the alpha values float combined_alpha = min(text_alpha + strike_alpha, 1.0f); // Underline color might be different, so alpha blend - return alpha_blend(decoration_fg, underline_alpha, foreground, combined_alpha); + return alpha_blend(decoration_fg, underline_alpha, fg, combined_alpha); } #endif diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index f798649f6..e076773ff 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -52,12 +52,14 @@ out vec3 underline_pos; out vec3 strike_pos; out vec3 foreground; out vec3 decoration_fg; +out float colored_sprite; #endif // Utility functions {{{ const uint BYTE_MASK = uint(0xFF); -const uint SHORT_MASK = uint(0xFFFF); +const uint Z_MASK = uint(0xFFF); +const uint COLOR_MASK = uint(0x4000); const uint ZERO = uint(0); const uint ONE = uint(1); const uint TWO = uint(2); @@ -150,7 +152,8 @@ void main() { #ifdef NEEDS_FOREGROUND // The character sprite being rendered - sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & SHORT_MASK); + sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & Z_MASK); + colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14); // Foreground uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); diff --git a/kitty/fonts.c b/kitty/fonts.c index 904cccc1e..d11834df5 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -7,6 +7,7 @@ #include "fonts.h" #include "state.h" +#include "emoji.h" #define MISSING_GLYPH 4 #define MAX_NUM_EXTRA_GLYPHS 8 @@ -17,7 +18,6 @@ send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; static PyObject *python_send_to_gpu_impl = NULL; extern PyTypeObject Line_Type; -typedef struct SpritePosition SpritePosition; typedef struct SpecialGlyphCache SpecialGlyphCache; enum {NO_FONT=-3, MISSING_FONT=-2, BLANK_FONT=-1, BOX_FONT=0}; @@ -26,15 +26,17 @@ typedef struct { glyph_index data[MAX_NUM_EXTRA_GLYPHS]; } ExtraGlyphs; +typedef struct SpritePosition SpritePosition; struct SpritePosition { SpritePosition *next; - bool filled, rendered; + bool filled, rendered, colored; sprite_index x, y, z; uint8_t ligature_index; glyph_index glyph; ExtraGlyphs extra_glyphs; }; + struct SpecialGlyphCache { SpecialGlyphCache *next; glyph_index glyph; @@ -93,7 +95,7 @@ sprite_map_set_error(int error) { void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len) { sprite_tracker.max_texture_size = max_texture_size; - sprite_tracker.max_array_len = max_array_len; + sprite_tracker.max_array_len = MIN(0xfff, max_array_len); } static inline void @@ -143,6 +145,7 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui s->ligature_index = ligature_index; s->filled = true; s->rendered = false; + s->colored = false; s->x = sprite_tracker.x; s->y = sprite_tracker.y; s->z = sprite_tracker.z; do_increment(error); return s; @@ -195,7 +198,7 @@ free_maps(Font *font) { void clear_sprite_map(Font *font) { -#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; memset(&s->extra_glyphs, 0, sizeof(ExtraGlyphs)); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0; +#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->glyph = 0; memset(&s->extra_glyphs, 0, sizeof(ExtraGlyphs)); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0; SpritePosition *s; for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) { s = font->sprite_map + i; @@ -437,9 +440,7 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { pixel val = d[dc]; uint8_t alpha = s[sc]; -#define C(shift) ((MIN(0xff, alpha + ((val >> shift) & 0xff))) << shift) - d[dc] = C(24) | C(16) | C(8) | MIN(0xff, alpha + (val & 0xff)); -#undef C + d[dc] = 0xffffff00 | MIN(0xff, alpha + (val & 0xff)); } } } @@ -458,6 +459,7 @@ render_box_cell(Cell *cell) { set_sprite(cell, sp->x, sp->y, sp->z); if (sp->rendered) return; sp->rendered = true; + sp->colored = false; PyObject *ret = PyObject_CallFunction(box_drawing_function, "I", cell->ch); if (ret == NULL) { PyErr_Print(); return; } uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); @@ -493,6 +495,7 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) { static inline void set_cell_sprite(Cell *cell, SpritePosition *sp) { cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z; + if (sp->colored) cell->sprite_z |= 0x4000; } static inline pixel* @@ -518,10 +521,12 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl } clear_canvas(); - render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline); + bool was_colored = is_emoji(cells->ch); + render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored); for (unsigned int i = 0; i < num_cells; i++) { sprite_position[i]->rendered = true; + sprite_position[i]->colored = was_colored; set_cell_sprite(cells + i, sprite_position[i]); pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells); current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); @@ -882,13 +887,6 @@ concat_cells(PyObject UNUSED *self, PyObject *args) { rgba[1] = (src[i] >> 16) & 0xff; rgba[2] = (src[i] >> 8) & 0xff; rgba[3] = src[i] & 0xff; - // pre-multiplied, so de-multiply - if (rgba[3]) { - float f = 255.f / (float)rgba[3]; - rgba[0] = MAX(255.f, ((float)rgba[0] * f)); - rgba[1] = MAX(255.f, ((float)rgba[1] * f)); - rgba[2] = MAX(255.f, ((float)rgba[2] * f)); - } } } else { uint8_t *src = (uint8_t*)s + cell_width * r; diff --git a/kitty/fonts.h b/kitty/fonts.h index 1d05b7a7d..45830e2d5 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -13,13 +13,12 @@ #pragma GCC diagnostic pop - unsigned int glyph_id_for_codepoint(PyObject *, char_type); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z); -bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline); +bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored); void render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride); void render_line(Line *line); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index d67193cd8..afa08e7be 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -11,8 +11,9 @@ from kitty.config import defaults from kitty.constants import is_macos from kitty.fast_data_types import ( Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites, - set_font, set_font_size, set_logical_dpi, set_send_sprite_to_gpu, - sprite_map_set_limits, test_render_line, test_shape + set_font, set_font_size, set_logical_dpi, set_options, + set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line, + test_shape ) from kitty.fonts.box_drawing import render_box_char, render_missing_glyph @@ -169,6 +170,7 @@ def render_box_drawing(codepoint): def setup_for_testing(family='monospace', size=11.0, dpi=96.0): from kitty.utils import get_logical_dpi opts = defaults._replace(font_family=family) + set_options(opts) sprites = {} def send_to_gpu(x, y, z, data): @@ -195,7 +197,9 @@ def render_string(text, family='monospace', size=11.0, dpi=96.0): cells = [] found_content = False for i in reversed(range(s.columns)): - sp = line.sprite_at(i) + sp = list(line.sprite_at(i)) + sp[2] &= 0xfff + sp = tuple(sp) if sp == (0, 0, 0) and not found_content: continue found_content = True @@ -260,5 +264,5 @@ def showcase(): f = 'monospace' if is_macos else 'Liberation Mono' test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f) test_render_string('δ½ ε₯½,δΈ–η•Œ', family=f) - test_render_string('|\U0001F601|\U0001F64f|\U0001F63a|', family=f) + test_render_string('|😁|πŸ™|😺|', family=f) test_render_string('A=>>B!=C', family='Fira Code') diff --git a/kitty/freetype.c b/kitty/freetype.c index b15382bed..f09077d34 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -117,7 +117,7 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt } if (strike_index > -1) { error = FT_Select_Size(self->face, strike_index); - if (error) { set_freetype_error("Failed to set char size for non-scaleable font, with error:", error); return false; } + if (error) { set_freetype_error("Failed to set char size for non-scalable font, with error:", error); return false; } return true; } } @@ -316,6 +316,7 @@ typedef struct { unsigned char* buf; size_t start_x, width, stride; size_t rows; + FT_Pixel_Mode pixel_mode; } ProcessedBitmap; @@ -336,7 +337,6 @@ trim_borders(ProcessedBitmap *ans, size_t extra) { } - static inline bool render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic, bool rescale) { if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; @@ -346,6 +346,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_ ans->start_x = 0; ans->width = bitmap->width; ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; ans->rows = bitmap->rows; + ans->pixel_mode = bitmap->pixel_mode; if (ans->width > max_width) { size_t extra = bitmap->width - max_width; if (italic && extra < cell_width / 2) { @@ -362,6 +363,49 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_ return true; } + +static inline bool +render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic) { + unsigned short best = 0, diff = USHRT_MAX; + for (short i = 0; i < self->face->num_fixed_sizes; i++) { + unsigned short w = self->face->available_sizes[i].width; + unsigned short d = w > (unsigned short)cell_width ? w - (unsigned short)cell_width : (unsigned short)cell_width - w; + if (d < diff) { + diff = d; + best = i; + } + } + FT_Error error = FT_Select_Size(self->face, best); + if (error) { set_freetype_error("Failed to set char size for non-scalable font, with error:", error); return false; } + if (!load_glyph(self, glyph_id, FT_LOAD_COLOR)) return false; + FT_Bitmap *bitmap = &self->face->glyph->bitmap; + if (bitmap->pixel_mode != FT_PIXEL_MODE_BGRA) return render_bitmap(self, glyph_id, ans, cell_width, num_cells, bold, italic, true); + ans->buf = bitmap->buffer; + ans->start_x = 0; ans->width = bitmap->width; + ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; + ans->rows = bitmap->rows; + ans->pixel_mode = bitmap->pixel_mode; + return true; +} + + +static inline void +copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { + for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { + pixel *d = dest + dest_stride * dr; + uint8_t *s = src + src_stride * sr; + for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { + uint8_t *bgra = s + 4 * sc; + // TODO: Convert from sRGB to linear color space? + if (bgra[3]) { +#define C(idx, shift) ( (uint8_t)(((float)bgra[idx] / (float)bgra[3]) * 255) << shift) + d[dc] = C(2, 24) | C(1, 16) | C(0, 8) | bgra[3]; +#undef C + } else d[dc] = 0; + } + } +} + static inline void place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) { // We want the glyph to be positioned inside the cell based on the bearingX @@ -392,7 +436,9 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size /* printf("x_offset: %f bearing_x: %f y_offset: %f bearing_y: %f src_start_row: %zu src_start_column: %zu dest_start_row: %zu dest_start_column: %zu bm_width: %lu bitmap_rows: %lu\n", x_offset, bearing_x, y_offset, bearing_y, src_start_row, src_start_column, dest_start_row, dest_start_column, bm->width, bm->rows); */ - render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width); + if (bm->pixel_mode == FT_PIXEL_MODE_BGRA) { + copy_color_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width); + } else render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width); } static inline void @@ -406,13 +452,18 @@ right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { } bool -render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { +render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) { Face *self = (Face*)f; + bool is_emoji = *was_colored; *was_colored = is_emoji && self->face->num_fixed_sizes > 0 && self->has_color; float x = 0.f, y = 0.f, x_offset = 0.f; ProcessedBitmap bm; unsigned int canvas_width = cell_width * num_cells; for (unsigned int i = 0; i < num_glyphs; i++) { - if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return false; + if (*was_colored) { + if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic)) return false; + } else { + if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return false; + } x_offset = x + (float)positions[i].x_offset / 64.0f; y = (float)positions[i].y_offset / 64.0f; if (self->face->glyph->metrics.width > 0 && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline); diff --git a/kitty/state.c b/kitty/state.c index b024c3e98..310f78ed6 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -297,8 +297,8 @@ PYWRAP1(handle_for_window_id) { PYWRAP1(set_options) { PyObject *ret, *opts; - int is_wayland, debug_gl = 0; - PA("Op|p", &opts, &is_wayland, &debug_gl); + int is_wayland = 0, debug_gl = 0; + PA("O|pp", &opts, &is_wayland, &debug_gl); global_state.is_wayland = is_wayland ? true : false; global_state.debug_gl = debug_gl ? true : false; #define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;