Basic color emoji rendering working

Still need to downscale color bitmaps to fit into cells
This commit is contained in:
Kovid Goyal 2017-12-07 20:39:11 +05:30
parent e4b839742c
commit 8d7515bd9b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 92 additions and 34 deletions

View File

@ -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

View File

@ -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]);

View File

@ -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;

View File

@ -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);

View File

@ -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')

View File

@ -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);

View File

@ -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;