foot/scripts/generate-alt-random-writes.py
Daniel Eklöf fc46087ce9
scripts: generate-alt-random: set P2=1 when emitting sixels
P2=1 means "empty sixels remain at their current color". This is
usually the case with modern sixel encoders.
2023-06-29 15:40:00 +02:00

256 lines
9.1 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import argparse
import enum
import fcntl
import random
import signal
import struct
import sys
import termios
class ColorVariant(enum.IntEnum):
NONE = enum.auto()
REGULAR = enum.auto()
BRIGHT = enum.auto()
CUBE = enum.auto()
RGB = enum.auto()
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file')
parser.add_argument('--cols', type=int)
parser.add_argument('--rows', type=int)
parser.add_argument('--colors-regular', action='store_true')
parser.add_argument('--colors-bright', action='store_true')
parser.add_argument('--colors-256', action='store_true')
parser.add_argument('--colors-rgb', action='store_true')
parser.add_argument('--scroll', action='store_true')
parser.add_argument('--scroll-region', action='store_true')
parser.add_argument('--attr-bold', action='store_true')
parser.add_argument('--attr-italic', action='store_true')
parser.add_argument('--attr-underline', action='store_true')
parser.add_argument('--sixel', action='store_true')
parser.add_argument('--seed', type=int)
opts = parser.parse_args()
out = opts.out if opts.out is not None else sys.stdout
if opts.rows is None or opts.cols is None:
try:
def dummy(*args):
"""Need a handler installed for sigwait() to trigger."""
pass
signal.signal(signal.SIGWINCH, dummy)
while True:
with open('/dev/tty', 'rb') as pty:
lines, cols, height, width = struct.unpack(
'HHHH',
fcntl.ioctl(pty,
termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
if width > 0 and height > 0:
break
# Were early; the foot window hasnt been mapped yet. Or,
# to be more precise, fonts havent yet been loaded,
# meaning it doesnt have any cell geometry yet.
signal.sigwait([signal.SIGWINCH])
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
except OSError:
lines = None
cols = None
height = None
width = None
if opts.rows is not None:
lines = opts.rows
height = 15 * lines # PGO helper binary hardcodes cell height to 15px
if opts.cols is not None:
cols = opts.cols
width = 8 * cols # PGO help binary hardcodes cell width to 8px
if lines is None or cols is None or height is None or width is None:
raise Exception('could not get terminal width/height; use --rows and --cols')
assert lines > 0, f'{lines}'
assert cols > 0, f'{cols}'
assert width > 0, f'{width}'
assert height > 0, f'{height}'
# Number of characters to write to screen
count = 256 * 1024**1
# Characters to choose from
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789 öäå 👨👩🧒👩🏽‍🔬🇸🇪'
color_variants = ([ColorVariant.NONE] +
([ColorVariant.REGULAR] if opts.colors_regular else []) +
([ColorVariant.BRIGHT] if opts.colors_bright else []) +
([ColorVariant.CUBE] if opts.colors_256 else []) +
([ColorVariant.RGB] if opts.colors_rgb else []))
# Enter alt screen
out.write('\033[?1049h')
# uses system time or /dev/urandom if available if opt.seed == None
# pin seeding method to make seeding stable across future versions
random.seed(a=opts.seed, version=2)
for _ in range(count):
if opts.scroll and random.randrange(256) == 0:
out.write('\033[m')
if opts.scroll_region and random.randrange(256) == 0:
top = random.randrange(3)
bottom = random.randrange(3)
out.write(f'\033[{top};{lines - bottom}r')
lines_to_scroll = random.randrange(lines - 1)
rev = random.randrange(2)
if not rev and random.randrange(2):
out.write(f'\033[{lines};{cols}H')
out.write('\n' * lines_to_scroll)
else:
out.write(f'\033[{lines_to_scroll + 1}{"T" if rev == 1 else "S"}')
continue
# Generate a random location and a random character
row = random.randrange(lines)
col = random.randrange(cols)
c = random.choice(alphabet)
repeat = random.randrange((cols - col) + 1)
assert col + repeat <= cols
color_variant = random.choice(color_variants)
# Position cursor
out.write(f'\033[{row + 1};{col + 1}H')
if color_variant in [ColorVariant.REGULAR, ColorVariant.BRIGHT]:
do_bg = random.randrange(2)
base = 40 if do_bg else 30
base += 60 if color_variant == ColorVariant.BRIGHT else 0
idx = random.randrange(8)
out.write(f'\033[{base + idx}m')
elif color_variant == ColorVariant.CUBE:
do_bg = random.randrange(2)
base = 48 if do_bg else 38
idx = random.randrange(256)
if random.randrange(2):
# Old-style
out.write(f'\033[{base};5;{idx}m')
else:
# New-style (sub-parameter based)
out.write(f'\033[{base}:5:{idx}m')
elif color_variant == ColorVariant.RGB:
do_bg = random.randrange(2)
base = 48 if do_bg else 38
# use list comprehension in favor of randbytes(n)
# which is only available for Python >= 3.9
rgb = [random.randrange(256) for _ in range(3)]
if random.randrange(2):
# Old-style
out.write(f'\033[{base};2;{rgb[0]};{rgb[1]};{rgb[2]}m')
else:
# New-style (sub-parameter based)
out.write(f'\033[{base}:2::{rgb[0]}:{rgb[1]}:{rgb[2]}m')
if opts.attr_bold and random.randrange(5) == 0:
out.write('\033[1m')
if opts.attr_italic and random.randrange(5) == 0:
out.write('\033[3m')
if opts.attr_underline and random.randrange(5) == 0:
out.write('\033[4m')
out.write(c * repeat)
do_sgr_reset = random.randrange(2)
if do_sgr_reset:
reset_actions = ['\033[m', '\033[39m', '\033[49m']
out.write(random.choice(reset_actions))
# Reset colors
out.write('\033[m\033[r')
if opts.sixel:
# The sixel 'alphabet'
sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
last_pos = None
last_size = None
for _ in range(20):
if last_pos is not None and random.randrange(2):
# Overwrite last sixel. I.e. use same position and
# size as last sixel
pass
else:
# Random origin in upper left quadrant
last_pos = random.randrange(lines // 2) + 1, random.randrange(cols // 2) + 1
last_size = random.randrange((height + 1) // 2), random.randrange((width + 1) // 2)
out.write(f'\033[{last_pos[0]};{last_pos[1]}H')
six_height, six_width = last_size
six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels
# Begin sixel (with P2=1 - empty sixels are transparent)
out.write('\033P;1q')
# Sixel size. Without this, sixels will be
# auto-resized on cell-boundaries.
out.write(f'"1;1;{six_width};{six_height}')
# Set up 256 random colors
for idx in range(256):
# param 2: 1=HLS, 2=RGB.
# param 3/4/5: HLS/RGB values in range 0-100
# (except 'hue' which is 0..360)
out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}')
for row in range(six_rows):
band_count = random.randrange(4, 33)
for band in range(band_count):
# Choose a random color
out.write(f'#{random.randrange(256)}')
if random.randrange(2):
for col in range(six_width):
out.write(f'{random.choice(sixels)}')
else:
pix_left = six_width
while pix_left > 0:
repeat_count = random.randrange(1, pix_left + 1)
out.write(f'!{repeat_count}{random.choice(sixels)}')
pix_left -= repeat_count
# Next line
if band + 1 < band_count:
# Move cursor to beginning of current row
out.write('$')
elif row + 1 < six_rows:
# Newline
out.write('-')
# End sixel
out.write('\033\\')
# Leave alt screen
out.write('\033[?1049l')
if __name__ == '__main__':
sys.exit(main())