Basic color emoji rendering working
Still need to downscale color bitmaps to fit into cells
This commit is contained in:
parent
e4b839742c
commit
8d7515bd9b
@ -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
|
||||
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user