Skip to content

Commit

Permalink
[Unicode placeholders] Use the third combining char for image ID
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-grechanik committed Jan 9, 2023
1 parent 8a14b8e commit b0f22e4
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 47 deletions.
44 changes: 29 additions & 15 deletions docs/graphics-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -453,31 +453,45 @@ of lines and columns::
The image will be fit to the specified rectangle, its aspect ratio preserved (in
the future more scaling modes may be added to the protocol). Now you can display
the image using the placeholder character, encoding the image ID in its
foreground color (in 256 color mode the ID must be within the range ``[1; 255]``;
in 24-bit mode the ID is defined by the formula ``r << 16 | g << 8 | b``). The
row and column values are specified with diacritics listed in
``rowcolumn-diacritics.txt``. For example, here is how you can print a 2x2
foreground color. The row and column values are specified with diacritics listed
in ``rowcolumn-diacritics.txt``. For example, here is how you can print a 2x2
placeholder for image ID 42::

printf "\e[38;5;42m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\n"
printf "\e[38;5;42m\U10EEEE\U030D\U0305\U10EEEE\U030D\U030D\n"

By using only the foreground color you are limited to 8-bit IDs in 256 color
mode and to 24-bit IDs in true color mode. If you need more bits for the image
ID, you can specify the most significant byte via the third diacritic. For
example, this is the placeholder for the image ID ``738197504 = 42 + 2 << 24``::

printf "\e[38;5;42m\U10EEEE\U0305\U0305\U030E\U10EEEE\U0305\U030D\U030E\n"
printf "\e[38;5;42m\U10EEEE\U030D\U0305\U030E\U10EEEE\U030D\U030D\U030E\n"

You can also specify a placement ID using the underline color (if it's omitted
or zero, the terminal may choose any virtual placement of the given image). The
background color is interpreted as the background color, visible if the image is
transparent. Other text attributes are reserved for future use.

Row and column diacritics may also be omitted, in which case the terminal should
guess row and column values from the surrounding cells, in particular:

- If only the row diacritic is present, and the cell to the left of the current
one is an image placeholder with the same image and placement IDs and the same
row, then the column of the current cell is the column of the cell to the left
plus one.
- If no diacritics are present, and the cell to the left of the current one is
an image placeholder with the same image and placement IDs, then the row of
the current cell is the row of the cell to the left, and the column is the
column of the cell to the left plus one.
Row, column and most significant byte diacritics may also be omitted, in which
case the placeholder cell will inherit the missing values from the placeholder
cell to the left:

- If no diacritics are present, and the previous placeholder cell has the same
foreground and underline colors, then the row of the current cell will be the
row of the cell to the left, the column will be the column of the cell to the
left plus one, and the most significant image ID byte will be the most
significant image ID byte of the cell to the left.
- If only the row diacritic is present, and the previous placeholder cell has
the same row and the same foreground and underline colors, then the column of
the current cell will be the column of the cell to the left plus one, and the
most significant image ID byte will be the most significant image ID byte of
the cell to the left.
- If only the row and column diacritics are present, and the previous
placeholder cell has the same row, the same foreground and underline colors,
and its column is one less than the current column, then the most significant
image ID byte of the current cell will be the most significant image ID byte
of the cell to the left.

These rules are applied left-to-right, which allows specifying only row
diacritics of the first column, i.e. here is a 2 rows by 3 columns placeholder::
Expand Down
31 changes: 19 additions & 12 deletions kittens/icat/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ def write_unicode_placeholder(place: Optional['Place'], cmd: GraphicsCommand, al
start_x = place.left + 1
start_y = place.top + 1
color = f'\033[38;2;{(cmd.i >> 16) & 255};{(cmd.i >> 8) & 255};{cmd.i & 255}m'.encode('ascii')
id_higher_8bits = (cmd.i >> 24) & 255
for row in range(cmd.r):
if place:
sys.stdout.buffer.write(f'\033[{start_y + row};{start_x}H'.encode('ascii'))
Expand All @@ -270,6 +271,7 @@ def write_unicode_placeholder(place: Optional['Place'], cmd: GraphicsCommand, al
sys.stdout.buffer.write(placeholder_char)
sys.stdout.buffer.write(get_rowcolumn_diacritic(row))
sys.stdout.buffer.write(get_rowcolumn_diacritic(col))
sys.stdout.buffer.write(get_rowcolumn_diacritic(id_higher_8bits))
sys.stdout.buffer.write(b'\033[39m')
if not place:
sys.stdout.buffer.write(b'\n')
Expand Down Expand Up @@ -414,6 +416,19 @@ class ParsedOpts:
unicode_placeholder = False


def generate_random_image_id() -> int:
import struct
# Generate a 32-bit image id using rejection sampling such that the most
# significant byte and the two bytes in the middle are non-zero to avoid
# collisions with applications that cannot represent non-zero most
# significant bytes (which is represented by the third combining character)
# or two non-zero bytes in the middle (which requires 24-bit color mode).
while True:
num = struct.unpack('@I', os.urandom(4))[0]
if (num & 0xFF000000 != 0) and (num & 0x00FFFF00 != 0):
return int(num)


def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
m = identify(path)
ss = get_screen_size()
Expand Down Expand Up @@ -447,26 +462,18 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
use_number = 0
use_image_id = 0
if parsed_opts.unicode_placeholder or needs_frame_uploading:
import struct
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
if parsed_opts.unicode_placeholder:
# If we are using the unicode placeholder approach, we need to know the
# image id of the image. We use a random image id, which doesn't
# guarantee uniqueness, but should be good enough. We avoid using image
# ids below 1024 to minimize the probability of collision with images
# displayed using other methods. Also the image id must be less than
# 2**24 to be representable as a foreground color.
min_image_id = 1024
use_image_id = min_image_id + (use_number % (2**24 - min_image_id))
use_number = 0
use_image_id = generate_random_image_id()
show(
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
align=args.align, place=parsed_opts.place, use_number=use_number,
use_image_id=use_image_id,
unicode_placeholder=parsed_opts.unicode_placeholder
)
if needs_frame_uploading:
if not use_image_id:
import struct
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
show_frames(frame_data, use_number, use_image_id, args.loop)
if not can_transfer_with_files:
for fr in frame_data.frames:
Expand Down
59 changes: 39 additions & 20 deletions kitty/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -2294,58 +2294,77 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row, bool had_imag
return;
index_type i;
uint32_t run_length = 0;
// Note that all these values are base-1, zero means unknown or incorrect.
uint32_t prev_image_id = 0;
uint32_t prev_img_id_lower24bits = 0;
uint32_t prev_placement_id = 0;
// Note that the following values are 1-based, zero means unknown or incorrect.
uint32_t prev_img_id_higher8bits = 0;
uint32_t prev_img_row = 0;
uint32_t prev_img_col = 0;
for (i = 0; i < line->xnum; i++) {
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
uint32_t cur_image_id = 0;
uint32_t cur_img_id_lower24bits = 0;
uint32_t cur_placement_id = 0;
uint32_t cur_img_id_higher8bits = 0;
uint32_t cur_img_row = 0;
uint32_t cur_img_col = 0;
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
// The image id is encoded in the foreground color, and the
// placement id is (optionally) in the underline color.
cur_image_id = color_to_id(gpu_cell->fg);
// The lower 24 bits of the image id are encoded in the foreground
// color, and the placement id is (optionally) in the underline color.
cur_img_id_lower24bits = color_to_id(gpu_cell->fg);
cur_placement_id = color_to_id(gpu_cell->decoration_fg);
// If the char has diacritics, use them as row and column indices.
if (cpu_cell->cc_idx[1])
cur_img_col = diacritic_to_rowcolumn(cpu_cell->cc_idx[1]);
if (cpu_cell->cc_idx[0])
cur_img_row = diacritic_to_rowcolumn(cpu_cell->cc_idx[0]);
if (cpu_cell->cc_idx[1])
cur_img_col = diacritic_to_rowcolumn(cpu_cell->cc_idx[1]);
// The third diacritic is used to encode the higher 8 bits of the
// image id (optional).
if (cpu_cell->cc_idx[2])
cur_img_id_higher8bits = diacritic_to_rowcolumn(cpu_cell->cc_idx[2]);
}
if (cur_image_id == prev_image_id && cur_placement_id == prev_placement_id &&
// The current run is continued if the lower 24 bits of the image id and
// the placement id are the same as in the previous cell and everything
// else is unknown or compatible with the previous cell.
if (run_length > 0 && cur_img_id_lower24bits == prev_img_id_lower24bits &&
cur_placement_id == prev_placement_id &&
(!cur_img_row || cur_img_row == prev_img_row) &&
(!cur_img_col || cur_img_col == prev_img_col + 1)) {
(!cur_img_col || cur_img_col == prev_img_col + 1) &&
(!cur_img_id_higher8bits || cur_img_id_higher8bits == prev_img_id_higher8bits)) {
// This cell continues the current run.
run_length++;
// If some values are unknown, infer them from the previous cell.
cur_img_row = prev_img_row ? prev_img_row : 1;
cur_img_row = MAX(prev_img_row, 1u);
cur_img_col = prev_img_col + 1;
cur_img_id_higher8bits = MAX(prev_img_id_higher8bits, 1u);
} else {
if (prev_image_id) {
// This cell breaks the current run. Render the current run.
// This cell breaks the current run. Render the current run if it
// has a non-zero length.
if (run_length > 0) {
uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24;
grman_put_cell_image(
self->grman, row, i - run_length, prev_image_id,
self->grman, row, i - run_length, img_id,
prev_placement_id, prev_img_col - run_length,
prev_img_row - 1, run_length, 1, self->cell_size);
}
// Start a new run.
run_length = 1;
if (!cur_img_col) cur_img_col = 1;
if (!cur_img_row) cur_img_row = 1;
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
run_length = 1;
if (!cur_img_col) cur_img_col = 1;
if (!cur_img_row) cur_img_row = 1;
if (!cur_img_id_higher8bits) cur_img_id_higher8bits = 1;
}
}
prev_image_id = cur_image_id;
prev_img_id_lower24bits = cur_img_id_lower24bits;
prev_img_id_higher8bits = cur_img_id_higher8bits;
prev_placement_id = cur_placement_id;
prev_img_row = cur_img_row;
prev_img_col = cur_img_col;
}
if (prev_image_id) {
if (run_length > 0) {
// Render the last run.
grman_put_cell_image(self->grman, row, i - run_length, prev_image_id,
uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24;
grman_put_cell_image(self->grman, row, i - run_length, img_id,
prev_placement_id, prev_img_col - run_length,
prev_img_row - 1, run_length, 1, self->cell_size);
}
Expand Down

0 comments on commit b0f22e4

Please sign in to comment.