Merge branch 'sixel-optimize'

This commit is contained in:
Daniel Eklöf 2021-03-11 17:33:41 +01:00
commit 5650120e08
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
4 changed files with 218 additions and 65 deletions

View file

@ -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

View file

@ -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
View file

@ -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
* weve 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;
}
}

View file

@ -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 */