Use a faster implementation of the Color type

Now implemented in C
This commit is contained in:
Kovid Goyal 2021-10-28 11:15:13 +05:30
parent 40c046f86b
commit 2443dc135c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
21 changed files with 395 additions and 132 deletions

View File

@ -3,8 +3,8 @@
import typing import typing
from kitty.conf.utils import KeyAction, KittensKeyMap from kitty.conf.utils import KeyAction, KittensKeyMap
import kitty.conf.utils import kitty.conf.utils
from kitty.rgb import Color from kitty.fast_data_types import Color
import kitty.rgb import kitty.fast_data_types
from kitty.types import ParsedShortcut from kitty.types import ParsedShortcut
import kitty.types import kitty.types
@ -39,31 +39,31 @@ option_names = ( # {{{
class Options: class Options:
added_bg: Color = Color(red=230, green=255, blue=237) added_bg: Color = Color(230, 255, 237)
added_margin_bg: Color = Color(red=205, green=255, blue=216) added_margin_bg: Color = Color(205, 255, 216)
background: Color = Color(red=255, green=255, blue=255) background: Color = Color(255, 255, 255)
diff_cmd: str = 'auto' diff_cmd: str = 'auto'
filler_bg: Color = Color(red=250, green=251, blue=252) filler_bg: Color = Color(250, 251, 252)
foreground: Color = Color(red=0, green=0, blue=0) foreground: Color = Color(0, 0, 0)
highlight_added_bg: Color = Color(red=172, green=242, blue=189) highlight_added_bg: Color = Color(172, 242, 189)
highlight_removed_bg: Color = Color(red=253, green=184, blue=192) highlight_removed_bg: Color = Color(253, 184, 192)
hunk_bg: Color = Color(red=241, green=248, blue=255) hunk_bg: Color = Color(241, 248, 255)
hunk_margin_bg: Color = Color(red=219, green=237, blue=255) hunk_margin_bg: Color = Color(219, 237, 255)
margin_bg: Color = Color(red=250, green=251, blue=252) margin_bg: Color = Color(250, 251, 252)
margin_fg: Color = Color(red=170, green=170, blue=170) margin_fg: Color = Color(170, 170, 170)
margin_filler_bg: typing.Optional[kitty.rgb.Color] = None margin_filler_bg: typing.Optional[kitty.fast_data_types.Color] = None
num_context_lines: int = 3 num_context_lines: int = 3
pygments_style: str = 'default' pygments_style: str = 'default'
removed_bg: Color = Color(red=255, green=238, blue=240) removed_bg: Color = Color(255, 238, 240)
removed_margin_bg: Color = Color(red=255, green=220, blue=224) removed_margin_bg: Color = Color(255, 220, 224)
replace_tab_by: str = ' ' replace_tab_by: str = ' '
search_bg: Color = Color(red=68, green=68, blue=68) search_bg: Color = Color(68, 68, 68)
search_fg: Color = Color(red=255, green=255, blue=255) search_fg: Color = Color(255, 255, 255)
select_bg: Color = Color(red=180, green=213, blue=254) select_bg: Color = Color(180, 213, 254)
select_fg: typing.Optional[kitty.rgb.Color] = Color(red=0, green=0, blue=0) select_fg: typing.Optional[kitty.fast_data_types.Color] = Color(0, 0, 0)
syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'} syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'}
title_bg: Color = Color(red=255, green=255, blue=255) title_bg: Color = Color(255, 255, 255)
title_fg: Color = Color(red=0, green=0, blue=0) title_fg: Color = Color(0, 0, 0)
map: typing.List[typing.Tuple[kitty.types.ParsedShortcut, kitty.conf.utils.KeyAction]] = [] map: typing.List[typing.Tuple[kitty.types.ParsedShortcut, kitty.conf.utils.KeyAction]] = []
key_definitions: KittensKeyMap = {} key_definitions: KittensKeyMap = {}
config_paths: typing.Tuple[str, ...] = () config_paths: typing.Tuple[str, ...] = ()

View File

@ -18,7 +18,7 @@ from urllib.request import Request, urlopen
from kitty.config import atomic_save, parse_config from kitty.config import atomic_save, parse_config
from kitty.constants import cache_dir, config_dir from kitty.constants import cache_dir, config_dir
from kitty.options.types import Options as KittyOptions from kitty.options.types import Options as KittyOptions
from kitty.rgb import Color from kitty.fast_data_types import Color
from kitty.utils import reload_conf_in_all_kitties from kitty.utils import reload_conf_in_all_kitties
from ..choose.match import match from ..choose.match import match

View File

@ -6,10 +6,11 @@ from contextlib import contextmanager
from enum import Enum, auto from enum import Enum, auto
from functools import wraps from functools import wraps
from typing import ( from typing import (
IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
) )
from kitty.rgb import Color, color_as_sharp, to_color from kitty.fast_data_types import Color
from kitty.rgb import color_as_sharp, to_color
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize
from .operations_stub import CMD from .operations_stub import CMD
@ -170,7 +171,7 @@ UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
'straight double curly'.split())} 'straight double curly'.split())}
ColorSpec = Union[int, str, Tuple[int, int, int]] ColorSpec = Union[int, str, Color]
def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str: def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str:
@ -179,7 +180,7 @@ def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str:
elif isinstance(color, int): elif isinstance(color, int):
e = f'{base + 8}:5:{max(0, min(color, 255))}' e = f'{base + 8}:5:{max(0, min(color, 255))}'
else: else:
e = '{}:2:{}:{}:{}'.format(base + 8, *color) e = f'{base + 8}{color.as_sgr}'
return e return e
@ -454,7 +455,7 @@ def as_type_stub() -> str:
ans = [ ans = [
'from typing import * # noqa', 'from typing import * # noqa',
'from kitty.typing import GraphicsCommandType, ScreenSize', 'from kitty.typing import GraphicsCommandType, ScreenSize',
'from kitty.rgb import Color', 'from kitty.fast_data_types import Color',
'import kitty.rgb', 'import kitty.rgb',
'import kittens.tui.operations', 'import kittens.tui.operations',
] ]

View File

@ -36,7 +36,7 @@ from .fast_data_types import (
os_window_font_size, patch_global_colors, redirect_mouse_handling, os_window_font_size, patch_global_colors, redirect_mouse_handling,
ring_bell, safe_pipe, set_application_quit_request, set_background_image, ring_bell, safe_pipe, set_application_quit_request, set_background_image,
set_boss, set_clipboard_string, set_in_sequence_mode, set_options, set_boss, set_clipboard_string, set_in_sequence_mode, set_options,
set_os_window_size, thread_write, toggle_fullscreen, toggle_maximized set_os_window_size, thread_write, toggle_fullscreen, toggle_maximized, Color
) )
from .key_encoding import get_name_to_functional_number_map from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches from .keys import get_shortcut, shortcut_matches
@ -45,7 +45,7 @@ from .notify import notification_activated
from .options.types import Options from .options.types import Options
from .options.utils import MINIMUM_FONT_SIZE, SubSequenceMap from .options.utils import MINIMUM_FONT_SIZE, SubSequenceMap
from .os_window_size import initial_window_size_func from .os_window_size import initial_window_size_func
from .rgb import Color, color_from_int from .rgb import color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import ( from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager

View File

@ -7,8 +7,8 @@
#include "state.h" #include "state.h"
#include <structmember.h> #include <structmember.h>
#include "colors.h"
PyTypeObject ColorProfile_Type;
static uint32_t FG_BG_256[256] = { static uint32_t FG_BG_256[256] = {
0x000000, // 0 0x000000, // 0
@ -60,7 +60,7 @@ PyObject* create_256_color_table() {
} }
static PyObject * static PyObject *
new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { new_cp(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
ColorProfile *self; ColorProfile *self;
self = (ColorProfile *)type->tp_alloc(type, 0); self = (ColorProfile *)type->tp_alloc(type, 0);
@ -77,14 +77,14 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
} }
static void static void
dealloc(ColorProfile* self) { dealloc_cp(ColorProfile* self) {
if (self->color_stack) free(self->color_stack); if (self->color_stack) free(self->color_stack);
Py_TYPE(self)->tp_free((PyObject*)self); Py_TYPE(self)->tp_free((PyObject*)self);
} }
ColorProfile* ColorProfile*
alloc_color_profile() { alloc_color_profile() {
return (ColorProfile*)new(&ColorProfile_Type, NULL, NULL); return (ColorProfile*)new_cp(&ColorProfile_Type, NULL, NULL);
} }
@ -248,7 +248,6 @@ as_color(ColorProfile *self, PyObject *val) {
unsigned int t = entry & 0xFF; unsigned int t = entry & 0xFF;
uint8_t r; uint8_t r;
uint32_t col = 0; uint32_t col = 0;
PyObject *ans = NULL;
switch(t) { switch(t) {
case 1: case 1:
r = (entry >> 8) & 0xff; r = (entry >> 8) & 0xff;
@ -258,10 +257,11 @@ as_color(ColorProfile *self, PyObject *val) {
col = entry >> 8; col = entry >> 8;
break; break;
default: default:
ans = Py_None; Py_INCREF(Py_None); Py_RETURN_NONE;
} }
if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF)); Color *ans = PyObject_New(Color, &Color_Type);
return ans; if (ans) ans->color.rgb = col;
return (PyObject*)ans;
} }
static PyObject* static PyObject*
@ -412,8 +412,9 @@ CGETSET(cursor_color)
CGETSET(cursor_text_color) CGETSET(cursor_text_color)
CGETSET(highlight_fg) CGETSET(highlight_fg)
CGETSET(highlight_bg) CGETSET(highlight_bg)
#undef CGETSET
static PyGetSetDef getsetters[] = { static PyGetSetDef cp_getsetters[] = {
GETSET(default_fg) GETSET(default_fg)
GETSET(default_bg) GETSET(default_bg)
GETSET(cursor_color) GETSET(cursor_color)
@ -424,11 +425,11 @@ static PyGetSetDef getsetters[] = {
}; };
static PyMemberDef members[] = { static PyMemberDef cp_members[] = {
{NULL} {NULL}
}; };
static PyMethodDef methods[] = { static PyMethodDef cp_methods[] = {
METHOD(update_ansi_color_table, METH_O) METHOD(update_ansi_color_table, METH_O)
METHOD(reset_color_table, METH_NOARGS) METHOD(reset_color_table, METH_NOARGS)
METHOD(as_dict, METH_NOARGS) METHOD(as_dict, METH_NOARGS)
@ -445,14 +446,151 @@ PyTypeObject ColorProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.ColorProfile", .tp_name = "fast_data_types.ColorProfile",
.tp_basicsize = sizeof(ColorProfile), .tp_basicsize = sizeof(ColorProfile),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc_cp,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "ColorProfile", .tp_doc = "ColorProfile",
.tp_members = members, .tp_members = cp_members,
.tp_methods = methods, .tp_methods = cp_methods,
.tp_getset = getsetters, .tp_getset = cp_getsetters,
.tp_new = new, .tp_new = new_cp,
}; };
// }}}
static PyObject *
new_color(PyTypeObject *type, PyObject *args, PyObject *kwds) {
static const char* kwlist[] = {"red", "green", "blue", "alpha", NULL};
Color *self;
unsigned char r = 0, g = 0, b = 0, a = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|BBBB", (char**)kwlist, &r, &g, &b, &a)) return NULL;
self = (Color *)type->tp_alloc(type, 0);
if (self != NULL) {
self->color.r = r; self->color.g = g; self->color.b = b; self->color.a = a;
}
return (PyObject*) self;
}
static PyObject*
color_as_int(Color *self) {
return PyLong_FromUnsignedLong(self->color.val);
}
static PyObject*
color_truediv(Color *self, PyObject *divisor) {
DECREF_AFTER_FUNCTION PyObject *o = PyNumber_Float(divisor);
if (o == NULL) return NULL;
double r = self->color.r, g = self->color.g, b = self->color.b, a = self->color.a;
double d = PyFloat_AS_DOUBLE(o) * 255.;
return Py_BuildValue("dddd", r/d, g/d, b/d, a/d);
}
static PyNumberMethods color_number_methods = {
.nb_int = (unaryfunc)color_as_int,
.nb_true_divide = (binaryfunc)color_truediv,
};
#define CGETSET(name) \
static PyObject* name##_get(Color *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(self->color.name); }
CGETSET(red)
CGETSET(green)
CGETSET(blue)
CGETSET(alpha)
#undef CGETSET
static PyObject*
rgb_get(Color *self, void *closure UNUSED) {
return PyLong_FromUnsignedLong(self->color.rgb);
}
static PyObject*
luminance_get(Color *self, void *closure UNUSED) {
return PyFloat_FromDouble(rgb_luminance(self->color));
}
static PyObject*
sgr_get(Color* self, void *closure UNUSED) {
char buf[32];
int sz = snprintf(buf, sizeof(buf), ":2:%u:%u:%u", self->color.r, self->color.g, self->color.b);
return PyUnicode_FromStringAndSize(buf, sz);
}
static PyObject*
sharp_get(Color* self, void *closure UNUSED) {
char buf[32];
int sz;
if (self->color.alpha) sz = snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", self->color.a, self->color.r, self->color.g, self->color.b);
else sz = snprintf(buf, sizeof(buf), "#%02x%02x%02x", self->color.r, self->color.g, self->color.b);
return PyUnicode_FromStringAndSize(buf, sz);
}
static PyObject*
color_cmp(PyObject *self, PyObject *other, int op) {
if (op != Py_EQ && op != Py_NE) return Py_NotImplemented;
if (!PyObject_TypeCheck(other, &Color_Type)) {
if (op == Py_EQ) Py_RETURN_FALSE;
Py_RETURN_TRUE;
}
Color *a = (Color*)self, *b = (Color*)other;
switch (op) {
case Py_EQ: { if (a->color.val == b->color.val) { Py_RETURN_TRUE; } Py_RETURN_FALSE; }
case Py_NE: { if (a->color.val != b->color.val) { Py_RETURN_TRUE; } Py_RETURN_FALSE; }
default:
return Py_NotImplemented;
}
}
static PyGetSetDef color_getsetters[] = {
{"rgb", (getter) rgb_get, NULL, "rgb", NULL},
{"red", (getter) red_get, NULL, "red", NULL},
{"green", (getter) green_get, NULL, "green", NULL},
{"blue", (getter) blue_get, NULL, "blue", NULL},
{"alpha", (getter) alpha_get, NULL, "alpha", NULL},
{"luminance", (getter) luminance_get, NULL, "luminance", NULL},
{"as_sgr", (getter) sgr_get, NULL, "as_sgr", NULL},
{"as_sharp", (getter) sharp_get, NULL, "as_sharp", NULL},
{NULL} /* Sentinel */
};
static PyObject*
contrast(Color* self, PyObject *o) {
if (!PyObject_TypeCheck(o, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color"); return NULL; }
Color *other = (Color*) o;
return PyFloat_FromDouble(rgb_contrast(self->color, other->color));
}
static PyMethodDef color_methods[] = {
METHODB(contrast, METH_O),
{NULL} /* Sentinel */
};
static PyObject *
repr(Color *self) {
if (self->color.alpha) return PyUnicode_FromFormat("Color(red=%u, green=%u, blue=%u, alpha=%u)", self->color.r, self->color.g, self->color.b, self->color.a);
return PyUnicode_FromFormat("Color(%u, %u, %u)", self->color.r, self->color.g, self->color.b);
}
static Py_hash_t
color_hash(PyObject *x) {
return ((Color*)x)->color.val;
}
PyTypeObject Color_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "kitty.fast_data_types.Color",
.tp_basicsize = sizeof(Color),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Color",
.tp_new = new_color,
.tp_getset = color_getsetters,
.tp_as_number = &color_number_methods,
.tp_methods = color_methods,
.tp_repr = (reprfunc)repr,
.tp_hash = color_hash,
.tp_richcompare = color_cmp,
};
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
METHODB(default_color_table, METH_NOARGS), METHODB(default_color_table, METH_NOARGS),
@ -465,6 +603,11 @@ int init_ColorProfile(PyObject *module) {\
if (PyType_Ready(&ColorProfile_Type) < 0) return 0; if (PyType_Ready(&ColorProfile_Type) < 0) return 0;
if (PyModule_AddObject(module, "ColorProfile", (PyObject *)&ColorProfile_Type) != 0) return 0; if (PyModule_AddObject(module, "ColorProfile", (PyObject *)&ColorProfile_Type) != 0) return 0;
Py_INCREF(&ColorProfile_Type); Py_INCREF(&ColorProfile_Type);
if (PyType_Ready(&Color_Type) < 0) return 0;
if (PyModule_AddObject(module, "Color", (PyObject *)&Color_Type) != 0) return 0;
Py_INCREF(&Color_Type);
if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return 1; return 1;
} }

75
kitty/colors.h Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "data-types.h"
typedef union ARGB32 {
color_type val;
struct {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t b: 8;
uint8_t g: 8;
uint8_t r: 8;
uint8_t a: 8;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t a: 8;
uint8_t r: 8;
uint8_t g: 8;
uint8_t b: 8;
#else
#error "Unsupported endianness"
#endif
};
struct {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t blue: 8;
uint8_t green: 8;
uint8_t red: 8;
uint8_t alpha: 8;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t alpha: 8;
uint8_t red: 8;
uint8_t green: 8;
uint8_t blue: 8;
#else
#error "Unsupported endianness"
#endif
};
struct {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
color_type rgb: 24;
uint8_t _ignore_me: 8;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t _ignore_me: 8;
color_type rgb: 24;
#else
#error "Unsupported endianness"
#endif
};
} ARGB32;
typedef struct {
PyObject_HEAD
ARGB32 color;
} Color;
extern PyTypeObject ColorProfile_Type;
extern PyTypeObject Color_Type;
static inline double
rgb_luminance(ARGB32 c) {
return 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
}
static inline double
rgb_contrast(ARGB32 a, ARGB32 b) {
double al = rgb_luminance(a), bl = rgb_luminance(b);
if (al < bl) SWAP(al, bl);
return (al + 0.05) / (bl + 0.05);
}

View File

@ -9,7 +9,8 @@ from typing import (
Sequence, Set, Tuple, TypeVar, Union, Generic Sequence, Set, Tuple, TypeVar, Union, Generic
) )
from ..rgb import Color, to_color as as_color from ..rgb import to_color as as_color
from ..fast_data_types import Color
from ..types import ConvertibleToNumbers, ParsedShortcut from ..types import ConvertibleToNumbers, ParsedShortcut
from ..typing import Protocol from ..typing import Protocol
from ..utils import expandvars, log_error from ..utils import expandvars, log_error

View File

@ -30,6 +30,7 @@
#define MIN(x, y) __extension__ ({ \ #define MIN(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \ __typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a < b ? a : b;}) a < b ? a : b;})
#define SWAP(x, y) do { __typeof__(x) _sw_ = y; y = x; x = _sw_; } while(0)
#define xstr(s) str(s) #define xstr(s) str(s)
#define str(s) #s #define str(s) #s
#define arraysz(x) (sizeof(x)/sizeof(x[0])) #define arraysz(x) (sizeof(x)/sizeof(x[0]))

View File

@ -20,10 +20,10 @@ from .conf.utils import KeyAction
from .constants import ( from .constants import (
extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path
) )
from .fast_data_types import num_users from .fast_data_types import num_users, Color
from .options.types import Options as KittyOpts, defaults from .options.types import Options as KittyOpts, defaults
from .options.utils import MouseMap from .options.utils import MouseMap
from .rgb import Color, color_as_sharp from .rgb import color_as_sharp
from .types import MouseEvent, SingleKey from .types import MouseEvent, SingleKey
from .typing import SequenceMap from .typing import SequenceMap

View File

@ -619,6 +619,61 @@ def patch_global_colors(spec: Dict[str, Optional[int]], configured: bool) -> Non
pass pass
class Color:
@property
def rgb(self) -> int:
pass
@property
def red(self) -> int:
pass
@property
def green(self) -> int:
pass
@property
def blue(self) -> int:
pass
@property
def alpha(self) -> int:
pass
@property
def luminance(self) -> float:
pass
@property
def as_sgr(self) -> str:
pass
@property
def as_sharp(self) -> str:
pass
def __init__(self, red: int = 0, green: int = 0, blue: int = 0, alpha: int = 0) -> None:
pass
def __truediv__(self, divisor: float) -> Tuple[float, float, float, float]: # (r, g, b, a)
pass
def __int__(self) -> int:
pass
def __hash__(self) -> int:
pass
def __eq__(self, other: Any) -> bool:
pass
def __ne__(self, other: Any) -> bool:
pass
def contrast(self, other: 'Color') -> float:
pass
class ColorProfile: class ColorProfile:
default_bg: int default_bg: int
@ -626,7 +681,7 @@ class ColorProfile:
def as_dict(self) -> Dict[str, int]: def as_dict(self) -> Dict[str, int]:
pass pass
def as_color(self, val: int) -> Tuple[int, int, int]: def as_color(self, val: int) -> Optional[Color]:
pass pass
def set_color(self, num: int, val: int) -> None: def set_color(self, num: int, val: int) -> None:

View File

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "../state.h" #include "../state.h"
#include "../colors.h"
static inline float static inline float
PyFloat_AsFloat(PyObject *o) { PyFloat_AsFloat(PyObject *o) {
@ -15,10 +16,9 @@ PyFloat_AsFloat(PyObject *o) {
static inline color_type static inline color_type
color_as_int(PyObject *color) { color_as_int(PyObject *color) {
if (!PyTuple_Check(color)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return 0; } if (!PyObject_TypeCheck(color, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color object"); return 0; }
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s) Color *c = (Color*)color;
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff; return c->color.val & 0xffffff;
#undef I
} }
static inline color_type static inline color_type

48
kitty/options/types.py generated
View File

@ -6,10 +6,10 @@ from kitty.conf.utils import KeyAction
import kitty.conf.utils import kitty.conf.utils
from kitty.constants import is_macos from kitty.constants import is_macos
import kitty.constants import kitty.constants
from kitty.fast_data_types import Color
import kitty.fast_data_types
from kitty.options.utils import KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap, TabBarMarginHeight from kitty.options.utils import KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap, TabBarMarginHeight
import kitty.options.utils import kitty.options.utils
from kitty.rgb import Color
import kitty.rgb
from kitty.types import FloatEdges, SingleKey from kitty.types import FloatEdges, SingleKey
import kitty.types import kitty.types
@ -441,23 +441,23 @@ option_names = ( # {{{
class Options: class Options:
active_border_color: typing.Optional[kitty.rgb.Color] = Color(red=0, green=255, blue=0) active_border_color: typing.Optional[kitty.fast_data_types.Color] = Color(0, 255, 0)
active_tab_background: Color = Color(red=238, green=238, blue=238) active_tab_background: Color = Color(238, 238, 238)
active_tab_font_style: typing.Tuple[bool, bool] = (True, True) active_tab_font_style: typing.Tuple[bool, bool] = (True, True)
active_tab_foreground: Color = Color(red=0, green=0, blue=0) active_tab_foreground: Color = Color(0, 0, 0)
active_tab_title_template: typing.Optional[str] = None active_tab_title_template: typing.Optional[str] = None
adjust_baseline: typing.Union[int, float] = 0 adjust_baseline: typing.Union[int, float] = 0
adjust_column_width: typing.Union[int, float] = 0 adjust_column_width: typing.Union[int, float] = 0
adjust_line_height: typing.Union[int, float] = 0 adjust_line_height: typing.Union[int, float] = 0
allow_hyperlinks: int = 1 allow_hyperlinks: int = 1
allow_remote_control: str = 'n' allow_remote_control: str = 'n'
background: Color = Color(red=0, green=0, blue=0) background: Color = Color(0, 0, 0)
background_image: typing.Optional[str] = None background_image: typing.Optional[str] = None
background_image_layout: choices_for_background_image_layout = 'tiled' background_image_layout: choices_for_background_image_layout = 'tiled'
background_image_linear: bool = False background_image_linear: bool = False
background_opacity: float = 1.0 background_opacity: float = 1.0
background_tint: float = 0 background_tint: float = 0
bell_border_color: Color = Color(red=255, green=90, blue=0) bell_border_color: Color = Color(255, 90, 0)
bell_on_tab: bool = True bell_on_tab: bool = True
bell_path: typing.Optional[str] = None bell_path: typing.Optional[str] = None
bold_font: str = 'auto' bold_font: str = 'auto'
@ -472,12 +472,12 @@ class Options:
command_on_bell: typing.List[str] = ['none'] command_on_bell: typing.List[str] = ['none']
confirm_os_window_close: int = 0 confirm_os_window_close: int = 0
copy_on_select: str = '' copy_on_select: str = ''
cursor: typing.Optional[kitty.rgb.Color] = Color(red=204, green=204, blue=204) cursor: typing.Optional[kitty.fast_data_types.Color] = Color(204, 204, 204)
cursor_beam_thickness: float = 1.5 cursor_beam_thickness: float = 1.5
cursor_blink_interval: float = -1.0 cursor_blink_interval: float = -1.0
cursor_shape: int = 1 cursor_shape: int = 1
cursor_stop_blinking_after: float = 15.0 cursor_stop_blinking_after: float = 15.0
cursor_text_color: typing.Optional[kitty.rgb.Color] = Color(red=17, green=17, blue=17) cursor_text_color: typing.Optional[kitty.fast_data_types.Color] = Color(17, 17, 17)
cursor_underline_thickness: float = 2.0 cursor_underline_thickness: float = 2.0
default_pointer_shape: choices_for_default_pointer_shape = 'beam' default_pointer_shape: choices_for_default_pointer_shape = 'beam'
detect_urls: bool = True detect_urls: bool = True
@ -493,12 +493,12 @@ class Options:
font_family: str = 'monospace' font_family: str = 'monospace'
font_size: float = 11.0 font_size: float = 11.0
force_ltr: bool = False force_ltr: bool = False
foreground: Color = Color(red=221, green=221, blue=221) foreground: Color = Color(221, 221, 221)
hide_window_decorations: int = 0 hide_window_decorations: int = 0
inactive_border_color: Color = Color(red=204, green=204, blue=204) inactive_border_color: Color = Color(204, 204, 204)
inactive_tab_background: Color = Color(red=153, green=153, blue=153) inactive_tab_background: Color = Color(153, 153, 153)
inactive_tab_font_style: typing.Tuple[bool, bool] = (False, False) inactive_tab_font_style: typing.Tuple[bool, bool] = (False, False)
inactive_tab_foreground: Color = Color(red=68, green=68, blue=68) inactive_tab_foreground: Color = Color(68, 68, 68)
inactive_text_alpha: float = 1.0 inactive_text_alpha: float = 1.0
initial_window_height: typing.Tuple[int, str] = (400, 'px') initial_window_height: typing.Tuple[int, str] = (400, 'px')
initial_window_width: typing.Tuple[int, str] = (640, 'px') initial_window_width: typing.Tuple[int, str] = (640, 'px')
@ -516,12 +516,12 @@ class Options:
macos_titlebar_color: int = 0 macos_titlebar_color: int = 0
macos_traditional_fullscreen: bool = False macos_traditional_fullscreen: bool = False
macos_window_resizable: bool = True macos_window_resizable: bool = True
mark1_background: Color = Color(red=152, green=211, blue=203) mark1_background: Color = Color(152, 211, 203)
mark1_foreground: Color = Color(red=0, green=0, blue=0) mark1_foreground: Color = Color(0, 0, 0)
mark2_background: Color = Color(red=242, green=220, blue=211) mark2_background: Color = Color(242, 220, 211)
mark2_foreground: Color = Color(red=0, green=0, blue=0) mark2_foreground: Color = Color(0, 0, 0)
mark3_background: Color = Color(red=242, green=116, blue=188) mark3_background: Color = Color(242, 116, 188)
mark3_foreground: Color = Color(red=0, green=0, blue=0) mark3_foreground: Color = Color(0, 0, 0)
mouse_hide_wait: float = 0.0 if is_macos else 3.0 mouse_hide_wait: float = 0.0 if is_macos else 3.0
open_url_with: typing.List[str] = ['default'] open_url_with: typing.List[str] = ['default']
placement_strategy: choices_for_placement_strategy = 'center' placement_strategy: choices_for_placement_strategy = 'center'
@ -537,8 +537,8 @@ class Options:
scrollback_pager: typing.List[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER'] scrollback_pager: typing.List[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER']
scrollback_pager_history_size: int = 0 scrollback_pager_history_size: int = 0
select_by_word_characters: str = '@-./_~?&=%+#' select_by_word_characters: str = '@-./_~?&=%+#'
selection_background: Color = Color(red=255, green=250, blue=205) selection_background: Color = Color(255, 250, 205)
selection_foreground: typing.Optional[kitty.rgb.Color] = Color(red=0, green=0, blue=0) selection_foreground: typing.Optional[kitty.fast_data_types.Color] = Color(0, 0, 0)
shell: str = '.' shell: str = '.'
shell_integration: str = 'enabled' shell_integration: str = 'enabled'
single_window_margin_width: FloatEdges = FloatEdges(left=-1.0, top=-1.0, right=-1.0, bottom=-1.0) single_window_margin_width: FloatEdges = FloatEdges(left=-1.0, top=-1.0, right=-1.0, bottom=-1.0)
@ -547,9 +547,9 @@ class Options:
sync_to_monitor: bool = True sync_to_monitor: bool = True
tab_activity_symbol: typing.Optional[str] = None tab_activity_symbol: typing.Optional[str] = None
tab_bar_align: choices_for_tab_bar_align = 'left' tab_bar_align: choices_for_tab_bar_align = 'left'
tab_bar_background: typing.Optional[kitty.rgb.Color] = None tab_bar_background: typing.Optional[kitty.fast_data_types.Color] = None
tab_bar_edge: int = 3 tab_bar_edge: int = 3
tab_bar_margin_color: typing.Optional[kitty.rgb.Color] = None tab_bar_margin_color: typing.Optional[kitty.fast_data_types.Color] = None
tab_bar_margin_height: TabBarMarginHeight = TabBarMarginHeight(outer=0, inner=0) tab_bar_margin_height: TabBarMarginHeight = TabBarMarginHeight(outer=0, inner=0)
tab_bar_margin_width: float = 0 tab_bar_margin_width: float = 0
tab_bar_min_tabs: int = 2 tab_bar_min_tabs: int = 2
@ -562,7 +562,7 @@ class Options:
term: str = 'xterm-kitty' term: str = 'xterm-kitty'
touch_scroll_multiplier: float = 1.0 touch_scroll_multiplier: float = 1.0
update_check_interval: float = 24.0 update_check_interval: float = 24.0
url_color: Color = Color(red=0, green=135, blue=189) url_color: Color = Color(0, 135, 189)
url_excluded_characters: str = '' url_excluded_characters: str = ''
url_prefixes: typing.Tuple[str, ...] = ('http', 'https', 'file', 'ftp', 'gemini', 'irc', 'gopher', 'mailto', 'news', 'git') url_prefixes: typing.Tuple[str, ...] = ('http', 'https', 'file', 'ftp', 'gemini', 'irc', 'gopher', 'mailto', 'news', 'git')
url_style: int = 3 url_style: int = 3

View File

@ -16,13 +16,13 @@ from kitty.conf.utils import (
python_string, to_bool, to_cmdline, to_color, uniq, unit_float python_string, to_bool, to_cmdline, to_color, uniq, unit_float
) )
from kitty.constants import config_dir, is_macos from kitty.constants import config_dir, is_macos
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, Color
from kitty.fonts import FontFeature from kitty.fonts import FontFeature
from kitty.key_names import ( from kitty.key_names import (
character_key_name_aliases, functional_key_name_aliases, character_key_name_aliases, functional_key_name_aliases,
get_key_name_lookup get_key_name_lookup
) )
from kitty.rgb import Color, color_as_int from kitty.rgb import color_as_int
from kitty.types import FloatEdges, MouseEvent, SingleKey from kitty.types import FloatEdges, MouseEvent, SingleKey
from kitty.utils import expandvars, log_error from kitty.utils import expandvars, log_error

View File

@ -3,7 +3,8 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.rgb import Color, color_as_sharp, color_from_int from kitty.fast_data_types import Color
from kitty.rgb import color_as_sharp, color_from_int
from kitty.utils import natsort_ints from kitty.utils import natsort_ints
from .base import ( from .base import (

View File

@ -6,8 +6,7 @@ import os
from typing import TYPE_CHECKING, Dict, Iterable, Optional from typing import TYPE_CHECKING, Dict, Iterable, Optional
from kitty.config import parse_config from kitty.config import parse_config
from kitty.fast_data_types import patch_color_profiles from kitty.fast_data_types import patch_color_profiles, Color
from kitty.rgb import Color
from .base import ( from .base import (
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, ParsingOfArgsFailed, MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, ParsingOfArgsFailed,

37
kitty/rgb.py generated
View File

@ -3,37 +3,8 @@
import re import re
from contextlib import suppress from contextlib import suppress
from typing import NamedTuple, Optional, Tuple from typing import Optional
from .fast_data_types import Color
class Color(NamedTuple):
red: int = 0
green: int = 0
blue: int = 0
def __truediv__(self, denom: float) -> Tuple[float, float, float]:
return self.red / denom, self.green / denom, self.blue / denom
def as_sgr(self) -> str:
return ':2:{}:{}:{}'.format(*self)
def luminance(self) -> float:
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
def contrast(self, other: 'Color') -> float:
a = self.luminance()
b = other.luminance()
if a < b:
a, b = b, a
return (a + 0.05) / (b + 0.05)
def __int__(self) -> int:
return self.red << 16 | self.green << 8 | self.blue
def as_bytearray(self, alpha: Optional[int] = None) -> bytearray:
if alpha is None:
return bytearray((self.red, self.green, self.blue))
return bytearray((self.red, self.green, self.blue, alpha))
def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int: def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int:
@ -78,11 +49,11 @@ def color_as_int(x: Color) -> int:
def color_as_sharp(x: Color) -> str: def color_as_sharp(x: Color) -> str:
return '#{:02x}{:02x}{:02x}'.format(*x) return x.as_sharp
def color_as_sgr(x: Color) -> str: def color_as_sgr(x: Color) -> str:
return x.as_sgr() return x.as_sgr
def to_color(raw: str, validate: bool = False) -> Optional[Color]: def to_color(raw: str, validate: bool = False) -> Optional[Color]:

View File

@ -12,9 +12,9 @@ from .config import build_ansi_color_table
from .constants import config_dir from .constants import config_dir
from .fast_data_types import ( from .fast_data_types import (
DECAWM, Region, Screen, cell_size_for_window, get_options, pt_to_px, DECAWM, Region, Screen, cell_size_for_window, get_options, pt_to_px,
set_tab_bar_render_data, viewport_for_window set_tab_bar_render_data, viewport_for_window, Color
) )
from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color from .rgb import alpha_blend, color_as_sgr, color_from_int, to_color
from .types import WindowGeometry, run_once from .types import WindowGeometry, run_once
from .typing import EdgeLiteral, PowerlineStyle from .typing import EdgeLiteral, PowerlineStyle
from .utils import color_as_int, log_error from .utils import color_as_int, log_error

View File

@ -21,9 +21,10 @@ from .constants import (
appname, is_macos, is_wayland, read_kitty_resource, shell_path, appname, is_macos, is_wayland, read_kitty_resource, shell_path,
supports_primary_selection supports_primary_selection
) )
from .rgb import Color, to_color from .rgb import to_color
from .types import run_once from .types import run_once
from .typing import AddressFamily, PopenType, Socket, StartupCtx from .typing import AddressFamily, PopenType, Socket, StartupCtx
from .fast_data_types import Color
if TYPE_CHECKING: if TYPE_CHECKING:
from .fast_data_types import OSWindowSize from .fast_data_types import OSWindowSize
@ -95,8 +96,8 @@ def sanitize_title(x: str) -> str:
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19\x80-\x9f]', '', x)) return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19\x80-\x9f]', '', x))
def color_as_int(val: Tuple[int, int, int]) -> int: def color_as_int(val: Color) -> int:
return val[0] << 16 | val[1] << 8 | val[2] return int(val) & 0xffffff
def color_from_int(val: int) -> Color: def color_from_int(val: int) -> Color:
@ -118,8 +119,7 @@ def parse_color_set(raw: str) -> Generator[Tuple[int, Optional[int]], None, None
else: else:
q = to_color(spec) q = to_color(spec)
if q is not None: if q is not None:
r, g, b = q yield c, int(q) & 0xffffff
yield c, r << 16 | g << 8 | b
except Exception: except Exception:
continue continue

View File

@ -22,7 +22,7 @@ from .constants import appname, is_macos, wakeup
from .fast_data_types import ( from .fast_data_types import (
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM,
CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK,
CURSOR_UNDERLINE, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, CURSOR_UNDERLINE, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, Color,
GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE,
SCROLL_PAGE, SEVEN_SEGMENT_PROGRAM, STRIKETHROUGH, TINT_PROGRAM, KeyEvent, SCROLL_PAGE, SEVEN_SEGMENT_PROGRAM, STRIKETHROUGH, TINT_PROGRAM, KeyEvent,
@ -36,7 +36,7 @@ from .fast_data_types import (
from .keys import keyboard_mode_name, mod_mask from .keys import keyboard_mode_name, mod_mask
from .notify import NotificationCommand, handle_notification_cmd from .notify import NotificationCommand, handle_notification_cmd
from .options.types import Options from .options.types import Options
from .rgb import Color, to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
@ -813,7 +813,9 @@ class Window:
changed = False changed = False
for c, val in parse_color_set(value): for c, val in parse_color_set(value):
if val is None: # color query if val is None: # color query
self.report_color(f'4;{c}', *self.screen.color_profile.as_color((c << 8) | 1)) qc = self.screen.color_profile.as_color((c << 8) | 1)
assert qc is not None
self.report_color(f'4;{c}', qc.red, qc.green, qc.blue)
else: else:
changed = True changed = True
cp.set_color(c, val) cp.set_color(c, val)

View File

@ -6,7 +6,7 @@ import tempfile
from kitty.config import build_ansi_color_table, defaults from kitty.config import build_ansi_color_table, defaults
from kitty.fast_data_types import ( from kitty.fast_data_types import (
ColorProfile, Cursor as C, HistoryBuf, LineBuf, ColorProfile, Cursor as C, HistoryBuf, LineBuf, Color,
parse_input_from_terminal, truncate_point_for_length, wcswidth, wcwidth parse_input_from_terminal, truncate_point_for_length, wcswidth, wcwidth
) )
from kitty.rgb import to_color from kitty.rgb import to_color
@ -32,8 +32,12 @@ class TestDataTypes(BaseTest):
for x in 'xxx #12 #1234 rgb:a/b'.split(): for x in 'xxx #12 #1234 rgb:a/b'.split():
self.assertIsNone(to_color(x)) self.assertIsNone(to_color(x))
def c(spec, r=0, g=0, b=0): def c(spec, r=0, g=0, b=0, a=0):
self.ae(tuple(to_color(spec)), (r, g, b)) c = to_color(spec)
self.ae(c.red, r)
self.ae(c.green, g)
self.ae(c.blue, b)
self.ae(c.alpha, a)
c('#eee', 0xee, 0xee, 0xee) c('#eee', 0xee, 0xee, 0xee)
c('#234567', 0x23, 0x45, 0x67) c('#234567', 0x23, 0x45, 0x67)
@ -42,6 +46,15 @@ class TestDataTypes(BaseTest):
c('rgb:23/45/67', 0x23, 0x45, 0x67) c('rgb:23/45/67', 0x23, 0x45, 0x67)
c('rgb:abc/abc/def', 0xab, 0xab, 0xde) c('rgb:abc/abc/def', 0xab, 0xab, 0xde)
c('red', 0xff) c('red', 0xff)
self.ae(int(Color(1, 2, 3)), 0x10203)
base = Color(12, 12, 12)
a = Color(23, 23, 23)
b = Color(100, 100, 100)
self.assertLess(base.contrast(a), base.contrast(b))
self.ae(Color(1, 2, 3).as_sgr, ':2:1:2:3')
self.ae(Color(1, 2, 3).as_sharp, '#010203')
self.ae(Color(1, 2, 3, 4).as_sharp, '#04010203')
self.ae(Color(1, 2, 3, 4).rgb, 0x10203)
def test_linebuf(self): def test_linebuf(self):
old = filled_line_buf(2, 3, filled_cursor()) old = filled_line_buf(2, 3, filled_cursor())
@ -423,8 +436,8 @@ class TestDataTypes(BaseTest):
c.update_ansi_color_table(build_ansi_color_table()) c.update_ansi_color_table(build_ansi_color_table())
for i in range(8): for i in range(8):
col = getattr(defaults, f'color{i}') col = getattr(defaults, f'color{i}')
self.assertEqual(c.as_color(i << 8 | 1), (col[0], col[1], col[2])) self.assertEqual(c.as_color(i << 8 | 1), col)
self.ae(c.as_color(255 << 8 | 1), (0xee, 0xee, 0xee)) self.ae(c.as_color(255 << 8 | 1), Color(0xee, 0xee, 0xee))
def test_historybuf(self): def test_historybuf(self):
lb = filled_line_buf() lb = filled_line_buf()

View File

@ -5,6 +5,7 @@
from . import BaseTest from . import BaseTest
from kitty.utils import log_error from kitty.utils import log_error
from kitty.options.utils import DELETE_ENV_VAR from kitty.options.utils import DELETE_ENV_VAR
from kitty.fast_data_types import Color
class TestConfParsing(BaseTest): class TestConfParsing(BaseTest):
@ -37,7 +38,7 @@ class TestConfParsing(BaseTest):
opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red') opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red')
self.ae(opts.font_size, 11.37) self.ae(opts.font_size, 11.37)
self.ae(opts.mouse_hide_wait, 0 if is_macos else 3) self.ae(opts.mouse_hide_wait, 0 if is_macos else 3)
self.ae(tuple(opts.color23), (255, 0, 0)) self.ae(opts.color23, Color(255, 0, 0))
self.assertFalse(opts.keymap) self.assertFalse(opts.keymap)
opts = p('clear_all_shortcuts y', 'map f1 next_window') opts = p('clear_all_shortcuts y', 'map f1 next_window')
self.ae(len(opts.keymap), 1) self.ae(len(opts.keymap), 1)