A ColorProfile to manage colors

This commit is contained in:
Kovid Goyal 2016-11-10 10:07:47 +05:30
parent 32e4de1c79
commit 1884cc17c1
6 changed files with 136 additions and 25 deletions

View File

@ -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)
// }}}

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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()

View File

@ -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)