diff --git a/kitty/data-types.h b/kitty/data-types.h index 70ab51379..dc0ff3c62 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -31,6 +31,7 @@ typedef unsigned long long id_type; typedef uint32_t char_type; typedef uint32_t color_type; typedef uint32_t combining_type; +typedef uint32_t pixel; typedef unsigned int index_type; typedef uint16_t sprite_index; typedef uint16_t attrs_type; @@ -127,6 +128,10 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape; #define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop") #endif +typedef struct { + uint32_t left, top, right, bottom; +} Region; + typedef struct { char_type ch; color_type fg, bg, decoration_fg; diff --git a/kitty/fonts.c b/kitty/fonts.c index 6e2fb06f0..904cccc1e 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -12,7 +12,7 @@ #define MAX_NUM_EXTRA_GLYPHS 8 typedef uint16_t glyph_index; -typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, uint8_t*); +typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*); send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; static PyObject *python_send_to_gpu_impl = NULL; extern PyTypeObject Line_Type; @@ -261,15 +261,15 @@ del_font(Font *f) { } static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0; -static uint8_t *canvas = NULL; +static pixel *canvas = NULL; #define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3) static inline void -clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height); } +clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); } static void -python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) { +python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) { if (python_send_to_gpu_impl != NULL && python_send_to_gpu_impl != Py_None) { - PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, cell_width * cell_height)); + PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * cell_width * cell_height)); if (ret == NULL) PyErr_Print(); else Py_DECREF(ret); } @@ -296,7 +296,7 @@ update_cell_metrics() { } sprite_tracker_set_layout(cell_width, cell_height); global_state.cell_width = cell_width; global_state.cell_height = cell_height; - free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height); + free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); if (canvas == NULL) return PyErr_NoMemory(); for (ssize_t i = 0, j = fonts.first_symbol_font_idx; i < (ssize_t)fonts.symbol_map_fonts_count; i++, j++) { CALL(j, cell_height, true); @@ -429,6 +429,21 @@ END_ALLOW_CASE_RANGE static PyObject* box_drawing_function = NULL; +void +render_alpha_mask(uint8_t *alpha_mask, 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 = alpha_mask + src_stride * sr; + 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 + } + } +} + static void render_box_cell(Cell *cell) { int error = 0; @@ -445,7 +460,11 @@ render_box_cell(Cell *cell) { sp->rendered = true; PyObject *ret = PyObject_CallFunction(box_drawing_function, "I", cell->ch); if (ret == NULL) { PyErr_Print(); return; } - current_send_sprite_to_gpu(sp->x, sp->y, sp->z, PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0))); + uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); + clear_canvas(); + Region r = { .right = cell_width, .bottom = cell_height }; + render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width); + current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas); Py_DECREF(ret); } @@ -476,11 +495,11 @@ set_cell_sprite(Cell *cell, SpritePosition *sp) { cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z; } -static inline uint8_t* +static inline pixel* extract_cell_from_canvas(unsigned int i, unsigned int num_cells) { - uint8_t *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width); + pixel *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width); unsigned int stride = cell_width * num_cells; - for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width); + for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width * sizeof(pixel)); return ans; } @@ -504,7 +523,7 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl for (unsigned int i = 0; i < num_cells; i++) { sprite_position[i]->rendered = true; set_cell_sprite(cells + i, sprite_position[i]); - uint8_t *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells); + 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); } @@ -640,8 +659,8 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) { unsigned int num_glyphs = shape(first_cell, num_cells, font->hb_font, &info, &positions); #if 0 // You can also generate this easily using hb-shape --show-flags --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text - hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, CELLS_IN_CANVAS * cell_width * cell_height, NULL, font->hb_font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS | HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS); - printf("\n%s\n", canvas); + hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, font->hb_font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS | HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS); + printf("\n%s\n", (char*)canvas); clear_canvas(); #endif unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; @@ -815,7 +834,11 @@ send_prerendered_sprites(PyObject UNUSED *s, PyObject *args) { x = sprite_tracker.x; y = sprite_tracker.y; z = sprite_tracker.z; do_increment(&error); if (error != 0) { sprite_map_set_error(error); return NULL; } - current_send_sprite_to_gpu(x, y, z, PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i))); + uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i)); + clear_canvas(); + Region r = { .right = cell_width, .bottom = cell_height }; + render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width); + current_send_sprite_to_gpu(x, y, z, canvas); } return Py_BuildValue("H", x); } @@ -839,18 +862,41 @@ test_render_line(PyObject UNUSED *self, PyObject *args) { static PyObject* concat_cells(PyObject UNUSED *self, PyObject *args) { + // Concatenate cells returning RGBA data unsigned int cell_width, cell_height; + int is_32_bit; PyObject *cells; - if (!PyArg_ParseTuple(args, "IIO!", &cell_width, &cell_height, &PyTuple_Type, &cells)) return NULL; + if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL; size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i; - PyObject *ans = PyBytes_FromStringAndSize(NULL, 3 * cell_width * cell_height * num_cells); + PyObject *ans = PyBytes_FromStringAndSize(NULL, 4 * cell_width * cell_height * num_cells); if (ans == NULL) return PyErr_NoMemory(); - uint8_t *dest = (uint8_t*)PyBytes_AS_STRING(ans), *src; + pixel *dest = (pixel*)PyBytes_AS_STRING(ans); for (r = 0; r < cell_height; r++) { for (c = 0; c < num_cells; c++) { - src = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c))) + cell_width * r; - for (i = 0; i < cell_width; i++, dest += 3) { - dest[0] = src[i]; dest[1] = src[i]; dest[2] = src[i]; + void *s = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c))); + if (is_32_bit) { + pixel *src = (pixel*)s + cell_width * r; + for (i = 0; i < cell_width; i++, dest++) { + uint8_t *rgba = (uint8_t*)dest; + rgba[0] = (src[i] >> 24) & 0xff; + 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; + for (i = 0; i < cell_width; i++, dest++) { + uint8_t *rgba = (uint8_t*)dest; + if (src[i]) { memset(rgba, 0xff, 3); rgba[3] = src[i]; } + else *dest = 0; + } } } diff --git a/kitty/fonts.h b/kitty/fonts.h index 033b1b75d..1d05b7a7d 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -19,7 +19,8 @@ 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, uint8_t *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); +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); void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height); diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 50c72b67f..5ef073ded 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -435,7 +435,7 @@ def render_missing_glyph(buf, width, height): draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0) -def test_drawing(sz=32, family='monospace'): +def test_drawing(sz=48, family='monospace'): from .render import display_bitmap, setup_for_testing from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu @@ -444,7 +444,7 @@ def test_drawing(sz=32, family='monospace'): def join_cells(cells): cells = tuple(bytes(x) for x in cells) - return concat_cells(width, height, cells) + return concat_cells(width, height, False, cells) def render_chr(ch): if ch in box_chars: @@ -469,7 +469,7 @@ def test_drawing(sz=32, family='monospace'): rgb_data = b''.join(rows) width *= 32 height *= len(rows) - assert len(rgb_data) == width * height * 3, '{} != {}'.format(len(rgb_data), width * height * 3) + assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4) display_bitmap(rgb_data, width, height) finally: set_send_sprite_to_gpu(None) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 66bbd47c4..d67193cd8 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -220,16 +220,17 @@ def display_bitmap(rgb_data, width, height): if not hasattr(display_bitmap, 'detected') and not detect_support(): raise SystemExit('Your terminal does not support the graphics protocol') display_bitmap.detected = True - with NamedTemporaryFile(delete=False) as f: + with NamedTemporaryFile(suffix='.rgba', delete=False) as f: f.write(rgb_data) - show(f.name, width, height, 24) + assert len(rgb_data) == 4 * width * height + show(f.name, width, height, 32) def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi=96.0): from kitty.fast_data_types import concat_cells, current_fonts cell_width, cell_height, cells = render_string(text, family, size, dpi) - rgb_data = concat_cells(cell_width, cell_height, tuple(cells)) + rgb_data = concat_cells(cell_width, cell_height, True, tuple(cells)) cf = current_fonts() fonts = [cf['medium'].display_name()] fonts.extend(f.display_name() for f in cf['fallback']) diff --git a/kitty/freetype.c b/kitty/freetype.c index a43f7519f..b15382bed 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -363,57 +363,50 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_ } static inline void -place_bitmap_in_canvas(unsigned char *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) { +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 // and bearingY values, making sure that it does not overflow the cell. + Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width }, dest = { .bottom = cell_height, .right = cell_width }; + // Calculate column bounds float bearing_x = (float)metrics->horiBearingX / 64.f; - ssize_t xoff = (ssize_t)(x_offset + bearing_x); - size_t src_start_column = bm->start_x, dest_start_column = 0, extra; - if (xoff < 0) src_start_column += -xoff; - else dest_start_column = xoff; + int32_t xoff = (ssize_t)(x_offset + bearing_x); + uint32_t extra; + if (xoff < 0) src.left += -xoff; + else dest.left = xoff; // Move the dest start column back if the width overflows because of it - if (dest_start_column > 0 && dest_start_column + bm->width > cell_width) { - extra = dest_start_column + bm->width - cell_width; - dest_start_column = extra > dest_start_column ? 0 : dest_start_column - extra; + if (dest.left > 0 && dest.left + bm->width > cell_width) { + extra = dest.left + bm->width - cell_width; + dest.left = extra > dest.left ? 0 : dest.left - extra; } // Calculate row bounds float bearing_y = (float)metrics->horiBearingY / 64.f; - ssize_t yoff = (ssize_t)(y_offset + bearing_y); - size_t src_start_row, dest_start_row; + int32_t yoff = (ssize_t)(y_offset + bearing_y); if (yoff > 0 && (size_t)yoff > baseline) { - src_start_row = 0; - dest_start_row = 0; + dest.top = 0; } else { - src_start_row = 0; - dest_start_row = baseline - yoff; + dest.top = baseline - yoff; } /* 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); */ - for (size_t sr = src_start_row, dr = dest_start_row; sr < bm->rows && dr < cell_height; sr++, dr++) { - for(size_t sc = src_start_column, dc = dest_start_column; sc < bm->width && dc < cell_width; sc++, dc++) { - uint16_t val = cell[dr * cell_width + dc]; - val = MIN(255, ((uint16_t)val + (uint16_t)bm->buf[sr * bm->stride + sc])); - cell[dr * cell_width + dc] = val; - } - } + render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width); } static inline void -right_shift_canvas(uint8_t *canvas, size_t width, size_t height, size_t amt) { - uint8_t *src; +right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { + pixel *src; size_t r; for (r = 0, src = canvas; r < height; r++, src += width) { - memmove(src + amt, src, width - amt); - memset(src, 0, amt); + memmove(src + amt, src, sizeof(pixel) * (width - amt)); + memset(src, 0, sizeof(pixel) * 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, uint8_t *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) { Face *self = (Face*)f; float x = 0.f, y = 0.f, x_offset = 0.f; ProcessedBitmap bm; diff --git a/kitty/shaders.c b/kitty/shaders.c index 021cda501..7563ee4ee 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -32,15 +32,14 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int copy_image_warned = true; fprintf(stderr, "WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation.\n"); } - uint8_t *src = malloc(5 * width * height * num_levels); + size_t sz = width * height * num_levels; + pixel *src = malloc(sz * sizeof(pixel)); if (src == NULL) { fatal("Out of memory."); } - uint8_t *dest = src + (4 * width * height * num_levels); glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - for(size_t i = 0; i < width * height * num_levels; i++) dest[i] = src[4*i]; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RGBA, GL_UNSIGNED_BYTE, src); free(src); } else { glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); @@ -63,7 +62,7 @@ realloc_sprite_texture() { sprite_tracker_current_layout(&xnum, &ynum, &z); znum = z + 1; width = xnum * global_state.cell_width; height = ynum * global_state.cell_height; - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R8, width, height, znum); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum); if (sprite_map.texture_id) { // need to re-alloc src_ynum = MAX(1, sprite_map.last_ynum); @@ -86,14 +85,14 @@ ensure_sprite_map() { } void -send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t *buf) { +send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel *buf) { unsigned int xnum, ynum, znum; sprite_tracker_current_layout(&xnum, &ynum, &znum); if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture(); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); x *= global_state.cell_width; y *= global_state.cell_height; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, buf); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); } void diff --git a/kitty/state.h b/kitty/state.h index f851e20f4..7a757b939 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -165,4 +165,4 @@ void draw_cursor(CursorRenderInfo *, bool); void update_surface_size(int, int, uint32_t); void free_texture(uint32_t*); void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool); -void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, uint8_t*); +void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, pixel*);