From 1884cc17c193f26c94f8bec7c45807e939a3df47 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Nov 2016 10:07:47 +0530 Subject: [PATCH] A ColorProfile to manage colors --- kitty/colors.c | 100 +++++++++++++++++++++++++++++++++++++-- kitty/config.py | 31 ++++++------ kitty/data-types.c | 3 +- kitty/data-types.h | 10 ++++ kitty/tracker.py | 6 +-- kitty_tests/datatypes.py | 11 ++++- 6 files changed, 136 insertions(+), 25 deletions(-) diff --git a/kitty/colors.c b/kitty/colors.c index 2ce58d13d..bcaae1c3e 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -7,7 +7,7 @@ #include "data-types.h" -static uint32_t FG_BG_256[255] = { +static uint32_t FG_BG_256[256] = { 0x000000, // 0 0xcd0000, // 1 0x00cd00, // 2 @@ -34,8 +34,8 @@ PyObject* create_256_color_table() { uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6]; FG_BG_256[j] = (r << 16) | (g << 8) | b; } - // colors 233..253: grayscale - for(i = 1; i < 22; i++, j++) { + // colors 233..255: grayscale + for(i = 1; i < 24; i++, j++) { uint8_t v = 8 + i * 10; FG_BG_256[j] = (v << 16) | (v << 8) | v; } @@ -49,3 +49,97 @@ PyObject* create_256_color_table() { } return ans; } + +static PyObject * +new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { + ColorProfile *self; + + self = (ColorProfile *)type->tp_alloc(type, 0); + if (self != NULL) { + if (FG_BG_256[255] == 0) create_256_color_table(); + memcpy(self->color_table_256, FG_BG_256, sizeof(FG_BG_256)); + } + return (PyObject*) self; +} + +static void +dealloc(Cursor* self) { + Py_TYPE(self)->tp_free((PyObject*)self); +} + + +static PyObject* +update_ansi_color_table(ColorProfile *self, PyObject *val) { +#define update_ansi_color_table_doc "Update the 16 basic colors" + index_type i; + PyObject *t; + + if (!PyList_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a list"); return NULL; } + +#define to_color \ + t = PyList_GET_ITEM(val, i); \ + self->ansi_color_table[i] = PyLong_AsUnsignedLong(t); + + for(i = 30; i < 38; i++) { + to_color; + } + i = 39; to_color; + for(i = 90; i < 98; i++) { + to_color; + } + i = 99; to_color; + for(i = 40; i < 48; i++) { + to_color; + } + i = 49; to_color; + for(i = 100; i < 108; i++) { + to_color; + } + Py_RETURN_NONE; +} + +static PyObject* +ansi_color(ColorProfile *self, PyObject *val) { +#define ansi_color_doc "Return the color at the specified index" + if (!PyLong_Check(val)) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; } + unsigned long idx = PyLong_AsUnsignedLong(val); + if (idx >= sizeof(self->ansi_color_table) / sizeof(self->ansi_color_table[0])) { + PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; + } + return PyLong_FromUnsignedLong(self->ansi_color_table[idx]); +} + +static PyObject* +color_256(ColorProfile *self, PyObject *val) { +#define color_256_doc "Return the color at the specified 256-color index" + if (!PyLong_Check(val)) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; } + unsigned long idx = PyLong_AsUnsignedLong(val); + if (idx >= 256) { + PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; + } + return PyLong_FromUnsignedLong(self->color_table_256[idx]); +} +// Boilerplate {{{ + + +static PyMethodDef methods[] = { + METHOD(update_ansi_color_table, METH_O) + METHOD(ansi_color, METH_O) + METHOD(color_256, METH_O) + {NULL} /* Sentinel */ +}; + + +static PyTypeObject ColorProfile_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "fast_data_types.ColorProfile", + .tp_basicsize = sizeof(ColorProfile), + .tp_dealloc = (destructor)dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "ColorProfile", + .tp_methods = methods, + .tp_new = new, +}; + +INIT_TYPE(ColorProfile) +// }}} diff --git a/kitty/config.py b/kitty/config.py index 8b6c3245a..ec393d4b8 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -4,7 +4,7 @@ import re from collections import namedtuple -from typing import Tuple +from itertools import repeat key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') @@ -298,23 +298,22 @@ def load_config(path: str) -> Options: return Options(**ans) -def build_ansi_color_tables(opts: Options) -> Tuple[dict, dict]: +def build_ansi_color_table(opts: Options=defaults): + def as_int(x): + return (x[0] << 16) | (x[1] << 8) | x[2] + def col(i): - return getattr(opts, 'color{}'.format(i)) + return as_int(getattr(opts, 'color{}'.format(i))) + ans = list(repeat(0, 120)) fg = {30 + i: col(i) for i in range(8)} - fg[39] = opts.foreground + fg[39] = as_int(opts.foreground) fg.update({90 + i: col(i + 8) for i in range(8)}) - fg[99] = opts.foreground_bold + fg[99] = as_int(opts.foreground_bold) bg = {40 + i: col(i) for i in range(8)} - bg[49] = opts.background + bg[49] = as_int(opts.background) bg.update({100 + i: col(i + 8) for i in range(8)}) - build_ansi_color_tables.fg, build_ansi_color_tables.bg = fg, bg -build_ansi_color_tables(defaults) - - -def fg_color_table(): - return build_ansi_color_tables.fg - - -def bg_color_table(): - return build_ansi_color_tables.bg + for k, val in fg.items(): + ans[k] = val + for k, val in bg.items(): + ans[k] = val + return ans diff --git a/kitty/data-types.c b/kitty/data-types.c index 9d4422cd7..85f64925f 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -9,6 +9,7 @@ extern int init_LineBuf(PyObject *); extern int init_Cursor(PyObject *); extern int init_Line(PyObject *); +extern int init_ColorProfile(PyObject *); extern PyObject* create_256_color_table(); #include "gl.h" @@ -37,8 +38,8 @@ PyInit_fast_data_types(void) { if (!init_LineBuf(m)) return NULL; if (!init_Line(m)) return NULL; if (!init_Cursor(m)) return NULL; + if (!init_ColorProfile(m)) return NULL; if (!add_module_gl_constants(m)) return NULL; - if (PyModule_AddObject(m, "FG_BG_256", create_256_color_table()) != 0) return NULL; PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT); diff --git a/kitty/data-types.h b/kitty/data-types.h index 0595406eb..91757776b 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -148,6 +148,16 @@ typedef struct { } Cursor; + +typedef struct { + PyObject_HEAD + + uint32_t color_table_256[256]; + uint32_t ansi_color_table[120]; + +} ColorProfile; + + Line* alloc_line(); Cursor* alloc_cursor(); diff --git a/kitty/tracker.py b/kitty/tracker.py index fc2a37f25..78c3784a7 100644 --- a/kitty/tracker.py +++ b/kitty/tracker.py @@ -6,8 +6,6 @@ from collections import defaultdict from operator import itemgetter from typing import Set, Tuple, Iterator -from .data_types import Cursor - def merge_ranges(ranges: Set[Tuple[int]]) -> Iterator[Tuple[int]]: if ranges: @@ -44,11 +42,11 @@ class ChangeTracker: self._dirty = True self.mark_dirtied() - def cursor_changed(self, cursor: Cursor) -> None: + def cursor_changed(self, cursor) -> None: self.changed_cursor = cursor self.dirty() - def cursor_position_changed(self, cursor: Cursor) -> None: + def cursor_position_changed(self, cursor) -> None: self.changed_cursor = cursor self.dirty() diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 18821e3aa..df8ee60f2 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -6,8 +6,9 @@ import codecs from . import BaseTest, filled_line_buf, filled_cursor +from kitty.config import build_ansi_color_table, defaults from kitty.utils import is_simple_string, wcwidth, sanitize_title -from kitty.fast_data_types import LineBuf, Cursor as C, REVERSE +from kitty.fast_data_types import LineBuf, Cursor as C, REVERSE, ColorProfile def create_lbuf(*lines): @@ -259,3 +260,11 @@ class TestDataTypes(BaseTest): self.assertTrue(is_simple_string(d(s.encode('utf-8')))) self.assertFalse(is_simple_string('a1コ')) self.assertEqual(sanitize_title('a\0\01 \t\n\f\rb'), 'a b') + + def test_color_profile(self): + c = ColorProfile() + c.update_ansi_color_table(build_ansi_color_table()) + for i in range(8): + col = getattr(defaults, 'color{}'.format(i)) + self.assertEqual(c.ansi_color(30 + i), col[0] << 16 | col[1] << 8 | col[2]) + self.ae(c.color_256(255), 0xeeeeee)