mirror of
https://github.com/tmux/tmux.git
synced 2026-10-15 07:44:24 +00:00
Merge image_merge into 4902-image-support
Replace SIXEL retention and the per-client cache with the global indexed-image cache from image_merge. Adapt its backend callbacks for Kitty and test palette quantization through both input protocols.
This commit is contained in:
78
regress/image-sixel-cache.sh
Normal file
78
regress/image-sixel-cache.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that cached colours and dithering survive clipping and client changes,
|
||||
# and that a visible crop is drawn when the whole scaled image is too large.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export TERM
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
python3 -B - "$TEST_TMUX" <<'PY'
|
||||
import sys
|
||||
from image_test import image_test, sixel, check_image
|
||||
|
||||
levels = [0, 14, 29, 43, 57, 71, 86, 100]
|
||||
palette = [(r, g, b) for r in levels for g in levels for b in levels]
|
||||
wire = sixel(32, 16, palette, list(range(512)))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
first = test.attach()
|
||||
test.emit(wire)
|
||||
original = first.images()
|
||||
assert len(original) == 1 and original[0][:2] == (32, 16)
|
||||
|
||||
# Clients with the same cell size draw identical indexed pixels.
|
||||
second = test.attach()
|
||||
test.redraw()
|
||||
assert first.images() == second.images() == original
|
||||
|
||||
# A different cell size must not replace the first clients' result.
|
||||
small = test.attach(cw=4, ch=8)
|
||||
test.redraw()
|
||||
scaled = small.images()
|
||||
assert len(scaled) == 1 and scaled[0][:2] == (16, 8)
|
||||
assert first.images() == second.images() == original
|
||||
|
||||
# Overwrite the first cell. The remaining pixels retain the full palette
|
||||
# and dithering, rather than converting the smaller rectangle again.
|
||||
test.emit(b'\033[HX')
|
||||
expected = [original[0][2][y * 32 + x]
|
||||
for y in range(16) for x in range(8, 32)]
|
||||
for client in (first, second):
|
||||
images = client.images()
|
||||
assert len(images) == 1
|
||||
check_image(images[0], 24, 16, expected)
|
||||
small_expected = [scaled[0][2][y * 16 + x]
|
||||
for y in range(8) for x in range(4, 16)]
|
||||
images = small.images()
|
||||
assert len(images) == 1
|
||||
check_image(images[0], 12, 8, small_expected)
|
||||
|
||||
# Changing back to an earlier cell size reuses the same indexed pixels.
|
||||
test.resize(small, 40, 16)
|
||||
test.redraw()
|
||||
for client in (first, second, small):
|
||||
images = client.images()
|
||||
assert len(images) == 1
|
||||
check_image(images[0], 24, 16, expected)
|
||||
|
||||
# Replacing the source must not draw pixels from the deleted image.
|
||||
test.emit(b'\033[2J\033[H' +
|
||||
sixel(8, 16, [(100, 0, 0)], [0] * (8 * 16)))
|
||||
for client in (first, second, small):
|
||||
images = client.images()
|
||||
assert len(images) == 1
|
||||
check_image(images[0], 8, 16, [(255, 0, 0)] * (8 * 16))
|
||||
|
||||
with image_test(sys.argv[1], sx=1) as test:
|
||||
client = test.attach()
|
||||
test.emit(sixel(16, 16, [(100, 0, 0)], [0] * (16 * 16)))
|
||||
# The whole image would be 12000 pixels wide, beyond the SIXEL limit.
|
||||
# Its visible first cell is still valid and must be converted directly.
|
||||
test.resize(client, 1, 16, cw=6000, ch=16)
|
||||
test.redraw()
|
||||
images = client.images()
|
||||
assert len(images) == 1
|
||||
check_image(images[0], 6000, 16, [(255, 0, 0)] * (6000 * 16))
|
||||
PY
|
||||
@@ -18,28 +18,23 @@ palette = [(r, g, b) for r in levels for g in levels for b in levels]
|
||||
expected = [tuple((c * 255 + 50) // 100 for c in p) for p in palette]
|
||||
with image_test(sys.argv[1]) as test:
|
||||
client = test.attach()
|
||||
test.emit(sixel(32, 16, palette, list(range(512))))
|
||||
images = client.images()
|
||||
assert len(images) == 1 and images[0][:2] == (32, 16)
|
||||
assert images[0][2] == expected, 'original SIXEL palette changed'
|
||||
test.redraw()
|
||||
assert client.images() == images, 'retained palette changed on redraw'
|
||||
|
||||
# Kitty input exercises RGBA quantization rather than SIXEL retention.
|
||||
rgba = bytes(c for pixel in expected for c in (*pixel, 255))
|
||||
test.emit(b'\033[2J\033[H\033_Ga=T,q=2,f=32,s=32,v=16;' +
|
||||
kitty = (b'\033_Ga=T,q=2,f=32,s=32,v=16;' +
|
||||
base64.b64encode(rgba) + b'\033\\')
|
||||
images = client.images()
|
||||
assert len(images) == 1 and images[0][:2] == (32, 16)
|
||||
actual = images[0][2]
|
||||
assert all(c is not None for c in actual), 'dithering introduced holes'
|
||||
assert 128 <= len(set(actual)) <= 256, len(set(actual))
|
||||
errors = [abs(a - e) for p, q in zip(actual, expected) for a, e in zip(p, q)]
|
||||
assert max(errors) <= 80, max(errors)
|
||||
assert sum(errors) / len(errors) <= 16, sum(errors) / len(errors)
|
||||
assert max(actual[0]) <= 30 and min(actual[-1]) >= 225, (
|
||||
'black/white extremes lost', actual[0], actual[-1])
|
||||
for _ in range(3):
|
||||
test.redraw()
|
||||
assert client.images() == images, 'palette or dithering changed on redraw'
|
||||
# Both input protocols use the shared RGBA model and quantization.
|
||||
for wire in (sixel(32, 16, palette, list(range(512))), kitty):
|
||||
test.emit(b'\033[2J\033[H' + wire)
|
||||
images = client.images()
|
||||
assert len(images) == 1 and images[0][:2] == (32, 16)
|
||||
actual = images[0][2]
|
||||
assert all(c is not None for c in actual), 'dithering introduced holes'
|
||||
assert 128 <= len(set(actual)) <= 256, len(set(actual))
|
||||
errors = [abs(a - e) for p, q in zip(actual, expected) for a, e in zip(p, q)]
|
||||
assert max(errors) <= 80, max(errors)
|
||||
assert sum(errors) / len(errors) <= 16, sum(errors) / len(errors)
|
||||
assert max(actual[0]) <= 30 and min(actual[-1]) >= 225, (
|
||||
'black/white extremes lost', actual[0], actual[-1])
|
||||
for _ in range(3):
|
||||
test.redraw()
|
||||
assert client.images() == images, 'palette or dithering changed on redraw'
|
||||
PY
|
||||
|
||||
@@ -31,7 +31,7 @@ with image_test(sys.argv[1]) as test:
|
||||
assert len(client.images()) == 1
|
||||
check_image(client.images()[0], 8, 16, [rgb] * (8 * 16))
|
||||
|
||||
# Undefined registers are black in the shared model and in retained output.
|
||||
# Undefined registers are black in the shared image model.
|
||||
test.emit(b'\033[2J\033[H\033P9;1q"1;1;8;16'
|
||||
b'#5!8~-!8~-!8N\033\\')
|
||||
assert len(client.images()) == 1
|
||||
|
||||
Reference in New Issue
Block a user