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

View File

@ -6,10 +6,11 @@ from contextlib import contextmanager
from enum import Enum, auto
from functools import wraps
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 .operations_stub import CMD
@ -170,7 +171,7 @@ UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
'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:
@ -179,7 +180,7 @@ def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str:
elif isinstance(color, int):
e = f'{base + 8}:5:{max(0, min(color, 255))}'
else:
e = '{}:2:{}:{}:{}'.format(base + 8, *color)
e = f'{base + 8}{color.as_sgr}'
return e
@ -454,7 +455,7 @@ def as_type_stub() -> str:
ans = [
'from typing import * # noqa',
'from kitty.typing import GraphicsCommandType, ScreenSize',
'from kitty.rgb import Color',
'from kitty.fast_data_types import Color',
'import kitty.rgb',
'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,
ring_bell, safe_pipe, set_application_quit_request, set_background_image,
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 .keys import get_shortcut, shortcut_matches
@ -45,7 +45,7 @@ from .notify import notification_activated
from .options.types import Options
from .options.utils import MINIMUM_FONT_SIZE, SubSequenceMap
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 .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager

View File

@ -7,8 +7,8 @@
#include "state.h"
#include <structmember.h>
#include "colors.h"
PyTypeObject ColorProfile_Type;
static uint32_t FG_BG_256[256] = {
0x000000, // 0
@ -60,7 +60,7 @@ PyObject* create_256_color_table() {
}
static PyObject *
new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
new_cp(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
ColorProfile *self;
self = (ColorProfile *)type->tp_alloc(type, 0);
@ -77,14 +77,14 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
}
static void
dealloc(ColorProfile* self) {
dealloc_cp(ColorProfile* self) {
if (self->color_stack) free(self->color_stack);
Py_TYPE(self)->tp_free((PyObject*)self);
}
ColorProfile*
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;
uint8_t r;
uint32_t col = 0;
PyObject *ans = NULL;
switch(t) {
case 1:
r = (entry >> 8) & 0xff;
@ -258,10 +257,11 @@ as_color(ColorProfile *self, PyObject *val) {
col = entry >> 8;
break;
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));
return ans;
Color *ans = PyObject_New(Color, &Color_Type);
if (ans) ans->color.rgb = col;
return (PyObject*)ans;
}
static PyObject*
@ -412,8 +412,9 @@ CGETSET(cursor_color)
CGETSET(cursor_text_color)
CGETSET(highlight_fg)
CGETSET(highlight_bg)
#undef CGETSET
static PyGetSetDef getsetters[] = {
static PyGetSetDef cp_getsetters[] = {
GETSET(default_fg)
GETSET(default_bg)
GETSET(cursor_color)
@ -424,11 +425,11 @@ static PyGetSetDef getsetters[] = {
};
static PyMemberDef members[] = {
static PyMemberDef cp_members[] = {
{NULL}
};
static PyMethodDef methods[] = {
static PyMethodDef cp_methods[] = {
METHOD(update_ansi_color_table, METH_O)
METHOD(reset_color_table, METH_NOARGS)
METHOD(as_dict, METH_NOARGS)
@ -445,14 +446,151 @@ PyTypeObject ColorProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.ColorProfile",
.tp_basicsize = sizeof(ColorProfile),
.tp_dealloc = (destructor)dealloc,
.tp_dealloc = (destructor)dealloc_cp,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "ColorProfile",
.tp_members = members,
.tp_methods = methods,
.tp_getset = getsetters,
.tp_new = new,
.tp_members = cp_members,
.tp_methods = cp_methods,
.tp_getset = cp_getsetters,
.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[] = {
METHODB(default_color_table, METH_NOARGS),
@ -465,6 +603,11 @@ int init_ColorProfile(PyObject *module) {\
if (PyType_Ready(&ColorProfile_Type) < 0) return 0;
if (PyModule_AddObject(module, "ColorProfile", (PyObject *)&ColorProfile_Type) != 0) return 0;
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;
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
)
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 ..typing import Protocol
from ..utils import expandvars, log_error

View File

@ -30,6 +30,7 @@
#define MIN(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
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 str(s) #s
#define arraysz(x) (sizeof(x)/sizeof(x[0]))

View File

@ -20,10 +20,10 @@ from .conf.utils import KeyAction
from .constants import (
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.utils import MouseMap
from .rgb import Color, color_as_sharp
from .rgb import color_as_sharp
from .types import MouseEvent, SingleKey
from .typing import SequenceMap

View File

@ -619,6 +619,61 @@ def patch_global_colors(spec: Dict[str, Optional[int]], configured: bool) -> Non
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:
default_bg: int
@ -626,7 +681,7 @@ class ColorProfile:
def as_dict(self) -> Dict[str, int]:
pass
def as_color(self, val: int) -> Tuple[int, int, int]:
def as_color(self, val: int) -> Optional[Color]:
pass
def set_color(self, num: int, val: int) -> None:

View File

@ -7,6 +7,7 @@
#pragma once
#include "../state.h"
#include "../colors.h"
static inline float
PyFloat_AsFloat(PyObject *o) {
@ -15,10 +16,9 @@ PyFloat_AsFloat(PyObject *o) {
static inline color_type
color_as_int(PyObject *color) {
if (!PyTuple_Check(color)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return 0; }
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
#undef I
if (!PyObject_TypeCheck(color, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color object"); return 0; }
Color *c = (Color*)color;
return c->color.val & 0xffffff;
}
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
from kitty.constants import is_macos
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
import kitty.options.utils
from kitty.rgb import Color
import kitty.rgb
from kitty.types import FloatEdges, SingleKey
import kitty.types
@ -441,23 +441,23 @@ option_names = ( # {{{
class Options:
active_border_color: typing.Optional[kitty.rgb.Color] = Color(red=0, green=255, blue=0)
active_tab_background: Color = Color(red=238, green=238, blue=238)
active_border_color: typing.Optional[kitty.fast_data_types.Color] = Color(0, 255, 0)
active_tab_background: Color = Color(238, 238, 238)
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
adjust_baseline: typing.Union[int, float] = 0
adjust_column_width: typing.Union[int, float] = 0
adjust_line_height: typing.Union[int, float] = 0
allow_hyperlinks: int = 1
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_layout: choices_for_background_image_layout = 'tiled'
background_image_linear: bool = False
background_opacity: float = 1.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_path: typing.Optional[str] = None
bold_font: str = 'auto'
@ -472,12 +472,12 @@ class Options:
command_on_bell: typing.List[str] = ['none']
confirm_os_window_close: int = 0
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_blink_interval: float = -1.0
cursor_shape: int = 1
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
default_pointer_shape: choices_for_default_pointer_shape = 'beam'
detect_urls: bool = True
@ -493,12 +493,12 @@ class Options:
font_family: str = 'monospace'
font_size: float = 11.0
force_ltr: bool = False
foreground: Color = Color(red=221, green=221, blue=221)
foreground: Color = Color(221, 221, 221)
hide_window_decorations: int = 0
inactive_border_color: Color = Color(red=204, green=204, blue=204)
inactive_tab_background: Color = Color(red=153, green=153, blue=153)
inactive_border_color: Color = Color(204, 204, 204)
inactive_tab_background: Color = Color(153, 153, 153)
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
initial_window_height: typing.Tuple[int, str] = (400, 'px')
initial_window_width: typing.Tuple[int, str] = (640, 'px')
@ -516,12 +516,12 @@ class Options:
macos_titlebar_color: int = 0
macos_traditional_fullscreen: bool = False
macos_window_resizable: bool = True
mark1_background: Color = Color(red=152, green=211, blue=203)
mark1_foreground: Color = Color(red=0, green=0, blue=0)
mark2_background: Color = Color(red=242, green=220, blue=211)
mark2_foreground: Color = Color(red=0, green=0, blue=0)
mark3_background: Color = Color(red=242, green=116, blue=188)
mark3_foreground: Color = Color(red=0, green=0, blue=0)
mark1_background: Color = Color(152, 211, 203)
mark1_foreground: Color = Color(0, 0, 0)
mark2_background: Color = Color(242, 220, 211)
mark2_foreground: Color = Color(0, 0, 0)
mark3_background: Color = Color(242, 116, 188)
mark3_foreground: Color = Color(0, 0, 0)
mouse_hide_wait: float = 0.0 if is_macos else 3.0
open_url_with: typing.List[str] = ['default']
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_history_size: int = 0
select_by_word_characters: str = '@-./_~?&=%+#'
selection_background: Color = Color(red=255, green=250, blue=205)
selection_foreground: typing.Optional[kitty.rgb.Color] = Color(red=0, green=0, blue=0)
selection_background: Color = Color(255, 250, 205)
selection_foreground: typing.Optional[kitty.fast_data_types.Color] = Color(0, 0, 0)
shell: str = '.'
shell_integration: str = 'enabled'
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
tab_activity_symbol: typing.Optional[str] = None
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_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_width: float = 0
tab_bar_min_tabs: int = 2
@ -562,7 +562,7 @@ class Options:
term: str = 'xterm-kitty'
touch_scroll_multiplier: float = 1.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_prefixes: typing.Tuple[str, ...] = ('http', 'https', 'file', 'ftp', 'gemini', 'irc', 'gopher', 'mailto', 'news', 'git')
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
)
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.key_names import (
character_key_name_aliases, functional_key_name_aliases,
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.utils import expandvars, log_error

View File

@ -3,7 +3,8 @@
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 .base import (

View File

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

37
kitty/rgb.py generated
View File

@ -3,37 +3,8 @@
import re
from contextlib import suppress
from typing import NamedTuple, Optional, Tuple
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))
from typing import Optional
from .fast_data_types import Color
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:
return '#{:02x}{:02x}{:02x}'.format(*x)
return x.as_sharp
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]:

View File

@ -12,9 +12,9 @@ from .config import build_ansi_color_table
from .constants import config_dir
from .fast_data_types import (
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 .typing import EdgeLiteral, PowerlineStyle
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,
supports_primary_selection
)
from .rgb import Color, to_color
from .rgb import to_color
from .types import run_once
from .typing import AddressFamily, PopenType, Socket, StartupCtx
from .fast_data_types import Color
if TYPE_CHECKING:
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))
def color_as_int(val: Tuple[int, int, int]) -> int:
return val[0] << 16 | val[1] << 8 | val[2]
def color_as_int(val: Color) -> int:
return int(val) & 0xffffff
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:
q = to_color(spec)
if q is not None:
r, g, b = q
yield c, r << 16 | g << 8 | b
yield c, int(q) & 0xffffff
except Exception:
continue

View File

@ -22,7 +22,7 @@ from .constants import appname, is_macos, wakeup
from .fast_data_types import (
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM,
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,
MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE,
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 .notify import NotificationCommand, handle_notification_cmd
from .options.types import Options
from .rgb import Color, to_color
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
@ -813,7 +813,9 @@ class Window:
changed = False
for c, val in parse_color_set(value):
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:
changed = True
cp.set_color(c, val)

View File

@ -6,7 +6,7 @@ import tempfile
from kitty.config import build_ansi_color_table, defaults
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
)
from kitty.rgb import to_color
@ -32,8 +32,12 @@ class TestDataTypes(BaseTest):
for x in 'xxx #12 #1234 rgb:a/b'.split():
self.assertIsNone(to_color(x))
def c(spec, r=0, g=0, b=0):
self.ae(tuple(to_color(spec)), (r, g, b))
def c(spec, r=0, g=0, b=0, a=0):
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('#234567', 0x23, 0x45, 0x67)
@ -42,6 +46,15 @@ class TestDataTypes(BaseTest):
c('rgb:23/45/67', 0x23, 0x45, 0x67)
c('rgb:abc/abc/def', 0xab, 0xab, 0xde)
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):
old = filled_line_buf(2, 3, filled_cursor())
@ -423,8 +436,8 @@ class TestDataTypes(BaseTest):
c.update_ansi_color_table(build_ansi_color_table())
for i in range(8):
col = getattr(defaults, f'color{i}')
self.assertEqual(c.as_color(i << 8 | 1), (col[0], col[1], col[2]))
self.ae(c.as_color(255 << 8 | 1), (0xee, 0xee, 0xee))
self.assertEqual(c.as_color(i << 8 | 1), col)
self.ae(c.as_color(255 << 8 | 1), Color(0xee, 0xee, 0xee))
def test_historybuf(self):
lb = filled_line_buf()

View File

@ -5,6 +5,7 @@
from . import BaseTest
from kitty.utils import log_error
from kitty.options.utils import DELETE_ENV_VAR
from kitty.fast_data_types import Color
class TestConfParsing(BaseTest):
@ -37,7 +38,7 @@ class TestConfParsing(BaseTest):
opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red')
self.ae(opts.font_size, 11.37)
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)
opts = p('clear_all_shortcuts y', 'map f1 next_window')
self.ae(len(opts.keymap), 1)