mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
This fixes an issue where there was a relatively high chance of not emitting any color bands at all, causing the final image size to be too small.
231 lines
8.2 KiB
Python
Executable file
231 lines
8.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import argparse
|
|
import enum
|
|
import fcntl
|
|
import struct
|
|
import random
|
|
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
|
|
|
|
try:
|
|
lines, cols, height, width = struct.unpack(
|
|
'HHHH',
|
|
fcntl.ioctl(sys.stdout.fileno(),
|
|
termios.TIOCGWINSZ,
|
|
struct.pack('HHHH', 0, 0, 0, 0)))
|
|
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')
|
|
|
|
# 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(100):
|
|
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 // 2), random.randrange(width // 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
|
|
out.write('\033Pq')
|
|
|
|
# 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())
|