Improve anti-aliasing of triangular box drawing characters, noticeable on low-resolution screens

Fixes #2844
This commit is contained in:
Kovid Goyal 2020-07-11 23:08:28 +05:30
parent 2cfcefd9eb
commit e17c96b20f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 41 additions and 9 deletions

View File

@ -12,6 +12,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
keymaps on new keyboard events that only change geometry (:iss:`2787`). keymaps on new keyboard events that only change geometry (:iss:`2787`).
Better handling of multiple keyboards with incompatible layouts (:iss:`2726`) Better handling of multiple keyboards with incompatible layouts (:iss:`2726`)
- Improve anti-aliasing of triangular box drawing characters, noticeable on
low-resolution screens (:iss:`2844`)
- macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the - macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the
language and country code do not form a valid locale (:iss:`1233`) language and country code do not form a valid locale (:iss:`1233`)

View File

@ -8,10 +8,10 @@
# #
import math import math
from functools import partial as p from functools import partial as p, wraps
from itertools import repeat from itertools import repeat
from typing import ( from typing import (
Callable, Dict, Generator, Iterable, List, MutableSequence, Optional, Any, Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
Sequence, Tuple, cast Sequence, Tuple, cast
) )
@ -146,18 +146,45 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
half_vline(buf, width, height, level=d, which='bottom') half_vline(buf, width, height, level=d, which='bottom')
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
src_width = 4 * dest_width
def average_intensity_in_src(dest_x: int, dest_y: int) -> int:
src_y = dest_y * factor
src_x = dest_x * factor
total = 0
for y in range(src_y, src_y + factor):
offset = src_width * y
for x in range(src_x, src_x + factor):
total += src[offset + x]
return total // (factor * factor)
for y in range(dest_height):
offset = dest_width * y
for x in range(dest_width):
dest[offset + x] = average_intensity_in_src(x, y)
def supersampled(supersample_factor: int = 4) -> Callable:
# Anti-alias the drawing performed by the wrapped function by
# using supersampling
def create_wrapper(f: Callable) -> Callable:
@wraps(f)
def supersampled_wrapper(buf: BufType, width: int, height: int, *args: Any, **kw: Any) -> None:
w, h = supersample_factor * width, supersample_factor * height
ssbuf = bytearray(w * h)
f(ssbuf, w, h, *args, **kw)
downsample(ssbuf, buf, width, height, factor=supersample_factor)
return supersampled_wrapper
return create_wrapper
def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]]) -> None: def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]]) -> None:
for y in range(height): for y in range(height):
offset = y * width offset = y * width
for x, (upper, lower) in enumerate(xlimits): for x, (upper, lower) in enumerate(xlimits):
buf[x + offset] = 255 if upper <= y <= lower else 0 buf[x + offset] = 255 if upper <= y <= lower else 0
# Anti-alias the boundary, simple y-axis anti-aliasing
for x, limits in enumerate(xlimits):
for yf in limits:
for ypx in range(int(math.floor(yf)), int(math.ceil(yf)) + 1):
if 0 <= ypx < height:
off = ypx * width + x
buf[off] = min(255, buf[off] + int((1 - abs(yf - ypx)) * 255))
def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]: def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
@ -170,6 +197,7 @@ def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
return y return y
@supersampled()
def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None: def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
ay1, by1, y2 = 0, height - 1, height // 2 ay1, by1, y2 = 0, height - 1, height // 2
if left: if left:
@ -182,6 +210,7 @@ def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
fill_region(buf, width, height, xlimits) fill_region(buf, width, height, xlimits)
@supersampled()
def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None: def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
if corner == 'top-right' or corner == 'bottom-left': if corner == 'top-right' or corner == 'bottom-left':
diagonal_y = line_equation(0, 0, width - 1, height - 1) diagonal_y = line_equation(0, 0, width - 1, height - 1)