mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Merge branch 'sixel-optimize'
This commit is contained in:
commit
5650120e08
4 changed files with 218 additions and 65 deletions
|
|
@ -82,6 +82,7 @@
|
|||
(https://codeberg.org/dnkl/foot/issues/376).
|
||||
* The minimum version requirement for the libxkbcommon dependency is
|
||||
now 1.0.0.
|
||||
* Empty pixel rows at the bottom of a sixel is now trimmed.
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
|
|
|||
|
|
@ -158,20 +158,37 @@ def main():
|
|||
reset_actions = ['\033[m', '\033[39m', '\033[49m']
|
||||
out.write(random.choice(reset_actions))
|
||||
|
||||
# Leave alt screen
|
||||
out.write('\033[m\033[r\033[?1049l')
|
||||
# Reset colors
|
||||
out.write('\033[m\033[r')
|
||||
|
||||
if opts.sixel:
|
||||
# The sixel 'alphabet'
|
||||
sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
|
||||
|
||||
for _ in range(200):
|
||||
# Offset image
|
||||
out.write(' ' * (random.randrange(cols // 2)))
|
||||
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.
|
||||
|
|
@ -179,29 +196,36 @@ def main():
|
|||
# (except 'hue' which is 0..360)
|
||||
out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}')
|
||||
|
||||
# Randomize image width/height
|
||||
six_height = random.randrange(height // 2)
|
||||
six_width = random.randrange(width // 2)
|
||||
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)}')
|
||||
|
||||
# Sixel size. Without this, sixels will be
|
||||
# auto-resized on cell-boundaries. We expect programs
|
||||
# to emit this sequence since otherwise you cannot get
|
||||
# correctly sized images.
|
||||
out.write(f'"0;0;{six_width};{six_height}')
|
||||
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
|
||||
|
||||
for row in range(six_height // 6): # Each sixel is 6 pixels
|
||||
# Choose a random color
|
||||
out.write(f'#{random.randrange(256)}')
|
||||
|
||||
for col in range(six_width):
|
||||
out.write(f'{random.choice(sixels)}')
|
||||
|
||||
# Next line
|
||||
out.write('-')
|
||||
# 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())
|
||||
|
|
|
|||
211
sixel.c
211
sixel.c
|
|
@ -37,8 +37,9 @@ sixel_init(struct terminal *term)
|
|||
|
||||
term->sixel.state = SIXEL_DECSIXEL;
|
||||
term->sixel.pos = (struct coord){0, 0};
|
||||
term->sixel.max_non_empty_row_no = 0;
|
||||
term->sixel.row_byte_ofs = 0;
|
||||
term->sixel.color_idx = 0;
|
||||
term->sixel.max_col = 0;
|
||||
term->sixel.param = 0;
|
||||
term->sixel.param_idx = 0;
|
||||
memset(term->sixel.params, 0, sizeof(term->sixel.params));
|
||||
|
|
@ -695,6 +696,13 @@ sixel_reflow(struct terminal *term)
|
|||
void
|
||||
sixel_unhook(struct terminal *term)
|
||||
{
|
||||
if (term->sixel.image.height > term->sixel.max_non_empty_row_no + 1) {
|
||||
LOG_DBG(
|
||||
"last row only partially filled, reducing image height: %d -> %d",
|
||||
term->sixel.image.height, term->sixel.max_non_empty_row_no + 1);
|
||||
term->sixel.image.height = term->sixel.max_non_empty_row_no + 1;
|
||||
}
|
||||
|
||||
int pixel_row_idx = 0;
|
||||
int pixel_rows_left = term->sixel.image.height;
|
||||
const int stride = term->sixel.image.width * sizeof(uint32_t);
|
||||
|
|
@ -825,7 +833,6 @@ sixel_unhook(struct terminal *term)
|
|||
term->sixel.image.data = NULL;
|
||||
term->sixel.image.width = 0;
|
||||
term->sixel.image.height = 0;
|
||||
term->sixel.max_col = 0;
|
||||
term->sixel.pos = (struct coord){0, 0};
|
||||
|
||||
free(term->sixel.private_palette);
|
||||
|
|
@ -837,6 +844,88 @@ sixel_unhook(struct terminal *term)
|
|||
render_refresh(term);
|
||||
}
|
||||
|
||||
static bool
|
||||
resize_horizontally(struct terminal *term, int new_width)
|
||||
{
|
||||
LOG_DBG("resizing image horizontally: %dx(%d) -> %dx(%d)",
|
||||
term->sixel.image.width, term->sixel.image.height,
|
||||
new_width, term->sixel.image.height);
|
||||
|
||||
if (unlikely(new_width > term->sixel.max_width)) {
|
||||
LOG_WARN("maximum image dimensions reached");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t *old_data = term->sixel.image.data;
|
||||
const int old_width = term->sixel.image.width;
|
||||
const int height = term->sixel.image.height;
|
||||
|
||||
int alloc_height = (height + 6 - 1) / 6 * 6;
|
||||
|
||||
xassert(new_width > 0);
|
||||
xassert(alloc_height > 0);
|
||||
|
||||
/* Width (and thus stride) change - need to allocate a new buffer */
|
||||
uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t));
|
||||
|
||||
/* Copy old rows, and initialize new columns to background color */
|
||||
for (int r = 0; r < height; r++) {
|
||||
memcpy(&new_data[r * new_width],
|
||||
&old_data[r * old_width],
|
||||
old_width * sizeof(uint32_t));
|
||||
|
||||
for (int c = old_width; c < new_width; c++)
|
||||
new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
|
||||
}
|
||||
|
||||
free(old_data);
|
||||
|
||||
term->sixel.image.data = new_data;
|
||||
term->sixel.image.width = new_width;
|
||||
term->sixel.row_byte_ofs = term->sixel.pos.row * new_width;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
resize_vertically(struct terminal *term, int new_height)
|
||||
{
|
||||
LOG_DBG("resizing image vertically: (%d)x%d -> (%d)x%d",
|
||||
term->sixel.image.width, term->sixel.image.height,
|
||||
term->sixel.image.width, new_height);
|
||||
|
||||
if (unlikely(new_height > term->sixel.max_height)) {
|
||||
LOG_WARN("maximum image dimensions reached");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t *old_data = term->sixel.image.data;
|
||||
const int width = term->sixel.image.width;
|
||||
const int old_height = term->sixel.image.height;
|
||||
|
||||
int alloc_height = (new_height + 6 - 1) / 6 * 6;
|
||||
|
||||
xassert(width > 0);
|
||||
xassert(new_height > 0);
|
||||
|
||||
uint32_t *new_data = realloc(
|
||||
old_data, width * alloc_height * sizeof(uint32_t));
|
||||
|
||||
if (new_data == NULL) {
|
||||
LOG_ERRNO("failed to reallocate sixel image buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Initialize new rows to background color */
|
||||
for (int r = old_height; r < new_height; r++) {
|
||||
for (int c = 0; c < width; c++)
|
||||
new_data[r * width + c] = color_with_alpha(term, term->colors.bg);
|
||||
}
|
||||
|
||||
term->sixel.image.data = new_data;
|
||||
term->sixel.image.height = new_height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
resize(struct terminal *term, int new_width, int new_height)
|
||||
{
|
||||
|
|
@ -844,6 +933,13 @@ resize(struct terminal *term, int new_width, int new_height)
|
|||
term->sixel.image.width, term->sixel.image.height,
|
||||
new_width, new_height);
|
||||
|
||||
if (new_width > term->sixel.max_width ||
|
||||
new_height > term->sixel.max_height)
|
||||
{
|
||||
LOG_WARN("maximum image dimensions reached");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t *old_data = term->sixel.image.data;
|
||||
const int old_width = term->sixel.image.width;
|
||||
const int old_height = term->sixel.image.height;
|
||||
|
|
@ -892,47 +988,55 @@ resize(struct terminal *term, int new_width, int new_height)
|
|||
term->sixel.image.data = new_data;
|
||||
term->sixel.image.width = new_width;
|
||||
term->sixel.image.height = new_height;
|
||||
term->sixel.row_byte_ofs = term->sixel.pos.row * new_width;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sixel_add(struct terminal *term, uint32_t color, uint8_t sixel)
|
||||
sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t sixel)
|
||||
{
|
||||
//LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color);
|
||||
xassert(term->sixel.pos.col < term->sixel.image.width);
|
||||
xassert(term->sixel.pos.row < term->sixel.image.height);
|
||||
|
||||
if (term->sixel.pos.col >= term->sixel.max_width ||
|
||||
term->sixel.pos.row * 6 + 5 >= term->sixel.max_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
size_t ofs = term->sixel.row_byte_ofs + col;
|
||||
uint32_t *data = &term->sixel.image.data[ofs];
|
||||
|
||||
if (term->sixel.pos.col >= term->sixel.image.width ||
|
||||
term->sixel.pos.row * 6 + 5 >= (term->sixel.image.height + 6 - 1) / 6 * 6)
|
||||
{
|
||||
int width = max(
|
||||
term->sixel.image.width,
|
||||
max(term->sixel.max_col, term->sixel.pos.col + 1));
|
||||
int max_non_empty_row = 0;
|
||||
int row = term->sixel.pos.row;
|
||||
|
||||
int height = max(
|
||||
term->sixel.image.height,
|
||||
(term->sixel.pos.row + 1) * 6);
|
||||
|
||||
if (!resize(term, width, height))
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++, sixel >>= 1) {
|
||||
for (int i = 0; i < 6; i++, sixel >>= 1, data += width) {
|
||||
if (sixel & 1) {
|
||||
size_t pixel_row = term->sixel.pos.row * 6 + i;
|
||||
size_t stride = term->sixel.image.width;
|
||||
size_t idx = pixel_row * stride + term->sixel.pos.col;
|
||||
term->sixel.image.data[idx] = color_with_alpha(term, color);
|
||||
*data = color;
|
||||
max_non_empty_row = row + i;
|
||||
}
|
||||
}
|
||||
|
||||
xassert(sixel == 0);
|
||||
term->sixel.pos.col++;
|
||||
|
||||
term->sixel.max_non_empty_row_no = max(
|
||||
term->sixel.max_non_empty_row_no,
|
||||
max_non_empty_row);
|
||||
}
|
||||
|
||||
static void
|
||||
sixel_add_many(struct terminal *term, uint8_t c, unsigned count)
|
||||
{
|
||||
uint32_t color = term->sixel.palette[term->sixel.color_idx];
|
||||
|
||||
int col = term->sixel.pos.col;
|
||||
int width = term->sixel.image.width;
|
||||
|
||||
if (unlikely(col + count - 1 >= width)) {
|
||||
width = col + count;
|
||||
if (unlikely(!resize_horizontally(term, width)))
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < count; i++, col++)
|
||||
sixel_add(term, col, width, color, c);
|
||||
|
||||
term->sixel.pos.col = col;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -959,16 +1063,26 @@ decsixel(struct terminal *term, uint8_t c)
|
|||
break;
|
||||
|
||||
case '$':
|
||||
if (term->sixel.pos.col > term->sixel.max_col)
|
||||
term->sixel.max_col = term->sixel.pos.col;
|
||||
term->sixel.pos.col = 0;
|
||||
if (likely(term->sixel.pos.col <= term->sixel.max_width)) {
|
||||
/*
|
||||
* We set, and keep, ‘col’ outside the image boundary when
|
||||
* we’ve reached the maximum image height, to avoid also
|
||||
* having to check the row vs image height in the common
|
||||
* path in sixel_add().
|
||||
*/
|
||||
term->sixel.pos.col = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
if (term->sixel.pos.col > term->sixel.max_col)
|
||||
term->sixel.max_col = term->sixel.pos.col;
|
||||
term->sixel.pos.row++;
|
||||
term->sixel.pos.row += 6;
|
||||
term->sixel.pos.col = 0;
|
||||
term->sixel.row_byte_ofs += term->sixel.image.width * 6;
|
||||
|
||||
if (term->sixel.pos.row >= term->sixel.image.height) {
|
||||
if (!resize_vertically(term, term->sixel.pos.row + 6))
|
||||
term->sixel.pos.col = term->sixel.max_width + 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E':
|
||||
|
|
@ -981,7 +1095,7 @@ decsixel(struct terminal *term, uint8_t c)
|
|||
case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v':
|
||||
case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}':
|
||||
case '~':
|
||||
sixel_add(term, term->sixel.palette[term->sixel.color_idx], c - 63);
|
||||
sixel_add_many(term, c - 63, 1);
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
|
|
@ -1031,6 +1145,7 @@ decgra(struct terminal *term, uint8_t c)
|
|||
ph <= term->sixel.max_height && pv <= term->sixel.max_width)
|
||||
{
|
||||
resize(term, ph, pv);
|
||||
term->sixel.max_non_empty_row_no = pv - 1;
|
||||
}
|
||||
|
||||
term->sixel.state = SIXEL_DECSIXEL;
|
||||
|
|
@ -1050,13 +1165,23 @@ decgri(struct terminal *term, uint8_t c)
|
|||
term->sixel.param += c - '0';
|
||||
break;
|
||||
|
||||
default:
|
||||
//LOG_DBG("repeating '%c' %u times", c, term->sixel.param);
|
||||
for (unsigned i = 0; i < term->sixel.param; i++)
|
||||
decsixel(term, c);
|
||||
case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E':
|
||||
case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
|
||||
case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S':
|
||||
case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
|
||||
case '[': case '\\': case ']': case '^': case '_': case '`': case 'a':
|
||||
case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
|
||||
case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o':
|
||||
case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v':
|
||||
case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}':
|
||||
case '~': {
|
||||
unsigned count = term->sixel.param;
|
||||
if (likely(count > 0))
|
||||
sixel_add_many(term, c - 63, count);
|
||||
term->sixel.state = SIXEL_DECSIXEL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1114,7 +1239,8 @@ decgci(struct terminal *term, uint8_t c)
|
|||
LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)",
|
||||
term->sixel.color_idx, hue, lum, sat, rgb);
|
||||
|
||||
term->sixel.palette[term->sixel.color_idx] = rgb;
|
||||
term->sixel.palette[term->sixel.color_idx] =
|
||||
color_with_alpha(term, rgb);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1126,7 +1252,8 @@ decgci(struct terminal *term, uint8_t c)
|
|||
LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu",
|
||||
term->sixel.color_idx, r, g, b);
|
||||
|
||||
term->sixel.palette[term->sixel.color_idx] = r << 16 | g << 8 | b;
|
||||
term->sixel.palette[term->sixel.color_idx] =
|
||||
color_with_alpha(term, r << 16 | g << 8 | b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -521,8 +521,9 @@ struct terminal {
|
|||
} state;
|
||||
|
||||
struct coord pos; /* Current sixel coordinate */
|
||||
int max_non_empty_row_no;
|
||||
size_t row_byte_ofs; /* Byte position into image, for current row */
|
||||
int color_idx; /* Current palette index */
|
||||
int max_col; /* Largest column index we've seen (aka the image width) */
|
||||
uint32_t *private_palette; /* Private palette, used when private mode 1070 is enabled */
|
||||
uint32_t *shared_palette; /* Shared palette, used when private mode 1070 is disabled */
|
||||
uint32_t *palette; /* Points to either private_palette or shared_palette */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue