diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index aae9222f096..21c7795a2d0 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -1,8 +1,9 @@ #version GLSL_VERSION #define WHICH_PROGRAM #define NOT_TRANSPARENT +#define NOT_SUBPIXEL -#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) +#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) || defined(SUBPIXEL) #define NEEDS_BACKROUND #endif @@ -28,6 +29,7 @@ in vec3 foreground; in vec4 cursor_color_vec; in vec3 decoration_fg; in float colored_sprite; +in float subpixel; #endif out vec4 final_color; @@ -94,8 +96,23 @@ vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) { vec4 calculate_foreground() { // returns the effective foreground color in pre-multiplied form vec4 text_fg = texture(sprites, sprite_pos); +#ifdef SUBPIXEL + vec3 unblended_fg = mix(foreground, text_fg.rgb, colored_sprite); +#ifdef TRANSPARENT + float alpha = text_fg.g; // Cairo uses green channel to convert FreeType's subpixel buffer to ARGB + float scale_coeff = mix(1, alpha, alpha > 0); + vec3 scaled_mask = text_fg.rgb / scale_coeff; + vec3 blended_fg = foreground * scaled_mask; + float text_alpha = mix(text_fg.a, alpha, subpixel); +#else + vec3 blended_fg = mix(background, foreground, text_fg.rgb); + float text_alpha = text_fg.a; +#endif + vec3 fg = mix(unblended_fg, blended_fg, subpixel); +#else vec3 fg = mix(foreground, text_fg.rgb, colored_sprite); float text_alpha = text_fg.a; +#endif float underline_alpha = texture(sprites, underline_pos).a; float strike_alpha = texture(sprites, strike_pos).a; float cursor_alpha = texture(sprites, cursor_pos).a; diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 29d9353e578..26aaa787488 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -6,6 +6,7 @@ #define STRIKE_SHIFT {STRIKE_SHIFT} #define DIM_SHIFT {DIM_SHIFT} #define USE_SELECTION_FG +#define NOT_SUBPIXEL // Inputs {{{ layout(std140) uniform CellRenderData { @@ -35,7 +36,7 @@ const uvec2 cell_pos_map[] = uvec2[4]( // }}} -#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) +#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) || defined(SUBPIXEL) #define NEEDS_BACKROUND #endif @@ -61,6 +62,7 @@ out vec3 strike_pos; out vec3 foreground; out vec3 decoration_fg; out float colored_sprite; +out float subpixel; out float effective_text_alpha; #endif @@ -69,6 +71,7 @@ out float effective_text_alpha; const uint BYTE_MASK = uint(0xFF); const uint Z_MASK = uint(0xFFF); const uint COLOR_MASK = uint(0x4000); +const uint SUBPIXEL_MASK = uint(0x8000); const uint ZERO = uint(0); const uint ONE = uint(1); const uint TWO = uint(2); @@ -166,6 +169,7 @@ void main() { // The character sprite being rendered 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); + subpixel = float((sprite_coords.z & SUBPIXEL_MASK) >> 15); // Foreground uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); diff --git a/kitty/config_data.py b/kitty/config_data.py index 7564c943d7d..319f60d8abc 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -295,6 +295,12 @@ def box_drawing_scale(x): and very thick lines. ''')) +o('subpixel_rendering', False, long_text=_(''' +Use subpixel rendering instead of grayscale in freetype. Impacts performance, +but may look better on low DPI screens. Possible values are :code:`none`, +:code:`lcd`, and :code:`lcd_v`. +''')) + # }}} g('cursor') # {{{ diff --git a/kitty/core_text.m b/kitty/core_text.m index 352185db841..7da5e85aa0c 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -456,7 +456,7 @@ } bool -render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_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, FONTS_DATA_HANDLE fg, bool center_glyph) { +render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_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, bool UNUSED *was_subpixel, FONTS_DATA_HANDLE fg, bool center_glyph) { CTFace *self = (CTFace*)s; for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint; return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph); diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 42fc25dabb4..040c29de42e 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -50,7 +50,7 @@ pattern_as_dict(FcPattern *pat) { I(FC_SLANT, slant); I(FC_HINT_STYLE, hint_style); I(FC_INDEX, index); - I(FC_RGBA, subpixel); + I(FC_RGBA, rgba); I(FC_LCD_FILTER, lcdfilter); B(FC_HINTING, hinting); B(FC_SCALABLE, scalable); diff --git a/kitty/fonts.c b/kitty/fonts.c index 0df8dfa3888..291464e9123 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -32,7 +32,7 @@ typedef struct { typedef struct SpritePosition SpritePosition; struct SpritePosition { SpritePosition *next; - bool filled, rendered, colored; + bool filled, rendered, colored, subpixel; sprite_index x, y, z; uint8_t ligature_index; glyph_index glyph; @@ -247,6 +247,7 @@ sprite_position_for(FontGroup *fg, Font *font, glyph_index glyph, ExtraGlyphs *e s->filled = true; s->rendered = false; s->colored = false; + s->subpixel = false; s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z; do_increment(fg, error); return s; @@ -301,7 +302,7 @@ free_maps(Font *font) { void clear_sprite_map(Font *font) { -#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; +#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->subpixel = 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; @@ -606,6 +607,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { if (sp->rendered) return; sp->rendered = true; sp->colored = false; + sp->subpixel = false; PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cpu_cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0); if (ret == NULL) { PyErr_Print(); return; } uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); @@ -640,6 +642,7 @@ static inline void set_cell_sprite(GPUCell *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; + if (sp->subpixel) cell->sprite_z |= 0x8000; } static inline pixel* @@ -671,12 +674,14 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU clear_canvas(fg); bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch); - render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph); + bool was_subpixel = false; + render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, &was_subpixel, (FONTS_DATA_HANDLE)fg, center_glyph); if (PyErr_Occurred()) PyErr_Print(); for (unsigned int i = 0; i < num_cells; i++) { sprite_position[i]->rendered = true; sprite_position[i]->colored = was_colored; + sprite_position[i]->subpixel = was_subpixel; set_cell_sprite(gpu_cells + i, sprite_position[i]); pixel *buf = num_cells == 1 ? fg->canvas : extract_cell_from_canvas(fg, i, num_cells); current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); diff --git a/kitty/fonts.h b/kitty/fonts.h index 0791c633b6e..78fdde4e76e 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -26,7 +26,7 @@ bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); -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, FONTS_DATA_HANDLE, bool center_glyph); +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, bool *was_subpixel, FONTS_DATA_HANDLE, bool center_glyph); PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE); PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE); diff --git a/kitty/freetype.c b/kitty/freetype.c index 198f737b957..edda411a715 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -11,6 +11,7 @@ #include #include #include +#include #if HB_VERSION_MAJOR > 1 || (HB_VERSION_MAJOR == 1 && (HB_VERSION_MINOR > 6 || (HB_VERSION_MINOR == 6 && HB_VERSION_MICRO >= 3))) #define HARFBUZZ_HAS_CHANGE_FONT @@ -29,6 +30,7 @@ typedef struct { unsigned int units_per_EM; int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness; int hinting, hintstyle, index; + int rgba; bool is_scalable, has_color; float size_in_pts; FT_F26Dot6 char_width, char_height; @@ -90,8 +92,10 @@ static inline int get_load_flags(int hinting, int hintstyle, int base) { int flags = base; if (hinting) { - if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; - else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT; + if (!(flags & (FT_LOAD_TARGET_MONO | FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V))) { + if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; + else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT; + } } else flags |= FT_LOAD_NO_HINTING; return flags; } @@ -191,13 +195,14 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA } static inline bool -init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) { +init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, int rgba, FONTS_DATA_HANDLE fg) { #define CPY(n) self->n = self->face->n; CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); #undef CPY self->is_scalable = FT_IS_SCALABLE(self->face); self->has_color = FT_HAS_COLOR(self->face); self->hinting = hinting; self->hintstyle = hintstyle; + self->rgba = rgba; if (!set_size_for_face((PyObject*)self, 0, false, fg)) return false; self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } @@ -221,16 +226,18 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) { long index = 0; bool hinting = false; long hint_style = 0; + long rgba = 0; D(path, PyUnicode_AsUTF8, false); D(index, PyLong_AsLong, true); D(hinting, PyObject_IsTrue, true); D(hint_style, PyLong_AsLong, true); + D(rgba, PyLong_AsLong, true); #undef D Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0); if (self != NULL) { int error = FT_New_Face(library, path, index, &(self->face)); if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; } - if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, fg)) { Py_CLEAR(self); return NULL; } + if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, rgba, fg)) { Py_CLEAR(self); return NULL; } } return (PyObject*)self; } @@ -242,7 +249,7 @@ face_from_path(const char *path, int index, FONTS_DATA_HANDLE fg) { int error; error = FT_New_Face(library, path, index, &ans->face); if (error) { set_freetype_error("Failed to load face, with error:", error); ans->face = NULL; return NULL; } - if (!init_ft_face(ans, Py_None, true, 3, fg)) { Py_CLEAR(ans); return NULL; } + if (!init_ft_face(ans, Py_None, true, 3, 1, fg)) { Py_CLEAR(ans); return NULL; } return (PyObject*)ans; } @@ -328,6 +335,7 @@ typedef struct { size_t start_x, width, stride; size_t rows; FT_Pixel_Mode pixel_mode; + bool bgr; bool needs_free; unsigned int factor, right_edge; int bitmap_left, bitmap_top; @@ -341,6 +349,35 @@ free_processed_bitmap(ProcessedBitmap *bm) { } } +static inline bool +is_pixel_visible(ProcessedBitmap *ans, size_t x, size_t y) { + unsigned char *s = ans->buf + x + y * ans->stride; + float color; + switch (ans->pixel_mode) { + case FT_PIXEL_MODE_GRAY: + return s[0] > 200; + case FT_PIXEL_MODE_LCD: +#define C(red, green, blue) ((float)s[red])*0.3 + ((float)s[green])*0.59 + ((float)s[blue])*0.11 + if (!ans->bgr) + color = C(0, 1, 2); + else + color = C(2, 1, 0); +#undef C + break; + case FT_PIXEL_MODE_LCD_V: +#define C(red, green, blue) ((float)s[red])*0.3 + ((float)s[green * ans->stride])*0.59 + ((float)s[blue * ans->stride])*0.11 + if (!ans->bgr) + color = C(0, 1, 2); + else + color = C(2, 1, 0); +#undef C + break; + default: + return true; + } + return color > 50; // TODO: tweak this value +} + static inline void trim_borders(ProcessedBitmap *ans, size_t extra) { bool column_has_text = false; @@ -348,7 +385,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) { // Trim empty columns from the right side of the bitmap for (ssize_t x = ans->width - 1; !column_has_text && x > -1 && extra > 0; x--) { for (size_t y = 0; y < ans->rows && !column_has_text; y++) { - if (ans->buf[x + y * ans->stride] > 200) column_has_text = true; + if (is_pixel_visible(ans, x, y)) column_has_text = true; } if (!column_has_text) { ans->width--; extra--; } } @@ -361,21 +398,37 @@ trim_borders(ProcessedBitmap *ans, size_t extra) { static inline void populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) { ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; - ans->rows = bitmap->rows; if (copy_buf) { - ans->buf = calloc(ans->rows, ans->stride); + ans->buf = calloc(bitmap->rows, ans->stride); if (!ans->buf) fatal("Out of memory"); ans->needs_free = true; - memcpy(ans->buf, bitmap->buffer, ans->rows * ans->stride); + memcpy(ans->buf, bitmap->buffer, bitmap->rows * ans->stride); } else ans->buf = bitmap->buffer; - ans->start_x = 0; ans->width = bitmap->width; + ans->start_x = 0; ans->width = bitmap->width; ans->rows = bitmap->rows; ans->pixel_mode = bitmap->pixel_mode; ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left; + if (ans->pixel_mode == FT_PIXEL_MODE_LCD) + ans->width /= 3; + else if (ans->pixel_mode == FT_PIXEL_MODE_LCD_V) + ans->rows /= 3; } static inline bool render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) { - if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; + int flags = FT_LOAD_RENDER; + if (OPT(subpixel_rendering)) { + switch (self->rgba) { + case FC_RGBA_RGB: + case FC_RGBA_BGR: + flags |= FT_LOAD_TARGET_LCD; + break; + case FC_RGBA_VRGB: + case FC_RGBA_VBGR: + flags |= FT_LOAD_TARGET_LCD_V; + break; + } + } + if (!load_glyph(self, glyph_id, flags)) return false; unsigned int max_width = cell_width * num_cells; // Embedded bitmap glyph? @@ -403,7 +456,8 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_ if (ans->width > max_width) { size_t extra = ans->width - max_width; if (italic && extra < cell_width / 2) { - trim_borders(ans, extra); + if (ans->pixel_mode == FT_PIXEL_MODE_GRAY || extra > 2) + trim_borders(ans, extra); } else if (extra == 2 && num_cells == 1) { // there exist fonts that have bitmaps just a couple of pixels // wider than their advances, rather than rescale, which looks @@ -511,6 +565,40 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect } } +static inline void +copy_lcd_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, bool bgr) { + 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 *rgb = s + 3 * sc; +#define C(idx, shift) ( rgb[idx] << shift) + if (!bgr) + d[dc] = C(0, 24) | C(1, 16) | C(2, 8) | 0xff; + else + d[dc] = C(2, 24) | C(1, 16) | C(0, 8) | 0xff; +#undef C + } + } +} + +static inline void +copy_lcd_v_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, bool bgr) { + 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 + 3 * 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 *rgb = s + sc; +#define C(idx, shift) ( rgb[src_stride * idx] << shift) + if (!bgr) + d[dc] = C(0, 24) | C(1, 16) | C(2, 8) | 0xff; + else + d[dc] = C(2, 24) | C(1, 16) | C(0, 8) | 0xff; +#undef C + } + } +} + 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, size_t baseline) { // We want the glyph to be positioned inside the cell based on the bearingX @@ -541,13 +629,17 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size if (bm->pixel_mode == FT_PIXEL_MODE_BGRA) { copy_color_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width); + } else if (bm->pixel_mode == FT_PIXEL_MODE_LCD) { + copy_lcd_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width, bm->bgr); + } else if (bm->pixel_mode == FT_PIXEL_MODE_LCD_V) { + copy_lcd_v_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width, bm->bgr); } else render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width); } static const ProcessedBitmap EMPTY_PBM = {.factor = 1}; 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, FONTS_DATA_HANDLE fg, bool center_glyph) { +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, bool *was_subpixel, FONTS_DATA_HANDLE fg, bool center_glyph) { Face *self = (Face*)f; bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color; float x = 0.f, y = 0.f, x_offset = 0.f; @@ -555,6 +647,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf unsigned int canvas_width = cell_width * num_cells; for (unsigned int i = 0; i < num_glyphs; i++) { bm = EMPTY_PBM; + bm.bgr = (self->rgba == FC_RGBA_BGR || self->rgba == FC_RGBA_VBGR); if (*was_colored) { if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) { if (PyErr_Occurred()) PyErr_Print(); @@ -564,6 +657,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf } else { if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) return false; } + *was_subpixel = (bm.pixel_mode == FT_PIXEL_MODE_LCD || bm.pixel_mode == FT_PIXEL_MODE_LCD_V); x_offset = x + (float)positions[i].x_offset / 64.0f; y = (float)positions[i].y_offset / 64.0f; if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) { @@ -615,10 +709,28 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { FT_UInt glyph_index = FT_Get_Char_Index(self->face, text[n]); int error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT); if (error) continue; - error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL); + int flags = 0; + if (OPT(subpixel_rendering)) { + switch (self->rgba) { + case FC_RGBA_RGB: + case FC_RGBA_BGR: + flags |= FT_RENDER_MODE_LCD; + break; + case FC_RGBA_VRGB: + case FC_RGBA_VBGR: + flags |= FT_RENDER_MODE_LCD_V; + break; + default: + flags |= FT_RENDER_MODE_LCD; + } + } else { + flags |= FT_RENDER_MODE_NORMAL; + } + error = FT_Render_Glyph(self->face->glyph, flags); if (error) continue; FT_Bitmap *bitmap = &self->face->glyph->bitmap; pbm = EMPTY_PBM; + pbm.bgr = (self->rgba == FC_RGBA_BGR || self->rgba == FC_RGBA_VBGR); populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false); place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, pen_x, 0, baseline); pen_x += self->face->glyph->advance.x >> 6; diff --git a/kitty/glfw.c b/kitty/glfw.c index 8dbf485299b..a8f19dd98f6 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -555,7 +555,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { glfwShowWindow(glfw_window); } if (is_first_window) { - PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False); + PyObject *ret = PyObject_CallFunction(load_programs, "OO", is_semi_transparent ? Py_True : Py_False, OPT(subpixel_rendering) ? Py_True : Py_False); if (ret == NULL) return NULL; Py_DECREF(ret); #ifdef __APPLE__ diff --git a/kitty/main.py b/kitty/main.py index 30ea6b0a330..a7a8a4f4317 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -79,8 +79,8 @@ def talk_to_instance(args): conn.close() -def load_all_shaders(semi_transparent=0): - load_shader_programs(semi_transparent) +def load_all_shaders(semi_transparent=False, subpixel_enabled=False): + load_shader_programs(semi_transparent, subpixel_enabled) load_borders_program() diff --git a/kitty/state.c b/kitty/state.c index 31e81562827..1d7b99940c1 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -406,6 +406,7 @@ PYWRAP1(set_options) { S(macos_window_resizable, PyObject_IsTrue); S(macos_hide_from_tasks, PyObject_IsTrue); S(macos_thicken_font, PyFloat_AsDouble); + S(subpixel_rendering, PyObject_IsTrue); S(tab_bar_min_tabs, PyLong_AsUnsignedLong); S(disable_ligatures, PyLong_AsLong); S(resize_draw_strategy, PyLong_AsLong); diff --git a/kitty/state.h b/kitty/state.h index 30e3a481e26..552454adbba 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -26,6 +26,7 @@ typedef struct { double repaint_delay, input_delay; bool focus_follows_mouse, hide_window_decorations; bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen, macos_show_window_title_in_menubar; + bool subpixel_rendering; unsigned int macos_option_as_alt; float macos_thicken_font; int adjust_line_height_px, adjust_column_width_px; diff --git a/kitty/window.py b/kitty/window.py index ddd1d5df3b0..dd747021284 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -56,7 +56,7 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def load_shader_programs(semi_transparent=False): +def load_shader_programs(semi_transparent=False, subpixel_enabled=False): compile_program(BLIT_PROGRAM, *load_shaders('blit')) v, f = load_shaders('cell') @@ -77,6 +77,9 @@ def load_shader_programs(semi_transparent=False): if semi_transparent: vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + if subpixel_enabled: + vv = vv.replace('#define NOT_SUBPIXEL', '#define SUBPIXEL') + ff = ff.replace('#define NOT_SUBPIXEL', '#define SUBPIXEL') if not load_shader_programs.use_selection_fg: vv = vv.replace('#define USE_SELECTION_FG', '#define DONT_USE_SELECTION_FG') ff = ff.replace('#define USE_SELECTION_FG', '#define DONT_USE_SELECTION_FG')