summaryrefslogtreecommitdiffstats
path: root/video/out/vo_sixel.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_sixel.c')
-rw-r--r--video/out/vo_sixel.c235
1 files changed, 142 insertions, 93 deletions
diff --git a/video/out/vo_sixel.c b/video/out/vo_sixel.c
index c9cc1573cb..c7df67f17f 100644
--- a/video/out/vo_sixel.c
+++ b/video/out/vo_sixel.c
@@ -35,41 +35,41 @@
#include "video/sws_utils.h"
#include "video/mp_image.h"
+#if HAVE_POSIX
+#include <unistd.h>
+#endif
+
#define IMGFMT IMGFMT_RGB24
+#define TERM_ESC_USE_GLOBAL_COLOR_REG "\033[?1070l"
+
#define TERMINAL_FALLBACK_COLS 80
#define TERMINAL_FALLBACK_ROWS 25
#define TERMINAL_FALLBACK_PX_WIDTH 320
#define TERMINAL_FALLBACK_PX_HEIGHT 240
-#define ESC_HIDE_CURSOR "\033[?25l"
-#define ESC_RESTORE_CURSOR "\033[?25h"
-#define ESC_CLEAR_SCREEN "\033[2J"
-#define ESC_GOTOXY "\033[%d;%df"
-#define ESC_USE_GLOBAL_COLOR_REG "\033[?1070l"
+struct vo_sixel_opts {
+ int diffuse;
+ int reqcolors;
+ bool fixedpal;
+ int threshold;
+ int width, height, top, left;
+ int pad_y, pad_x;
+ int rows, cols;
+ bool config_clear, alt_screen;
+ bool buffered;
+};
struct priv {
-
// User specified options
- int opt_diffuse;
- int opt_width;
- int opt_height;
- int opt_reqcolors;
- int opt_fixedpal;
- int opt_threshold;
- int opt_top;
- int opt_left;
- int opt_pad_y;
- int opt_pad_x;
- int opt_rows;
- int opt_cols;
- int opt_clear;
+ struct vo_sixel_opts opts;
// Internal data
sixel_output_t *output;
sixel_dither_t *dither;
sixel_dither_t *testdither;
uint8_t *buffer;
+ char *sixel_output_buf;
bool skip_frame_draw;
int left, top; // image origin cell (1 based)
@@ -77,7 +77,7 @@ struct priv {
int num_cols, num_rows; // terminal size in cells
int canvas_ok; // whether canvas vo->dwidth and vo->dheight are positive
- int previous_histgram_colors;
+ int previous_histogram_colors;
struct mp_rect src_rect;
struct mp_rect dst_rect;
@@ -91,23 +91,23 @@ static const unsigned int depth = 3;
static int detect_scene_change(struct vo* vo)
{
struct priv* priv = vo->priv;
- int previous_histgram_colors = priv->previous_histgram_colors;
- int histgram_colors = 0;
+ int previous_histogram_colors = priv->previous_histogram_colors;
+ int histogram_colors = 0;
// If threshold is set negative, then every frame must be a scene change
- if (priv->dither == NULL || priv->opt_threshold < 0)
+ if (priv->dither == NULL || priv->opts.threshold < 0)
return 1;
- histgram_colors = sixel_dither_get_num_of_histogram_colors(priv->testdither);
+ histogram_colors = sixel_dither_get_num_of_histogram_colors(priv->testdither);
- int color_difference_count = previous_histgram_colors - histgram_colors;
+ int color_difference_count = previous_histogram_colors - histogram_colors;
color_difference_count = (color_difference_count > 0) ? // abs value
color_difference_count : -color_difference_count;
if (100 * color_difference_count >
- priv->opt_threshold * previous_histgram_colors)
+ priv->opts.threshold * previous_histogram_colors)
{
- priv->previous_histgram_colors = histgram_colors; // update history
+ priv->previous_histogram_colors = histogram_colors; // update history
return 1;
} else {
return 0;
@@ -149,7 +149,7 @@ static SIXELSTATUS prepare_static_palette(struct vo* vo)
if (priv->dither == NULL)
return SIXEL_FALSE;
- sixel_dither_set_diffusion_type(priv->dither, priv->opt_diffuse);
+ sixel_dither_set_diffusion_type(priv->dither, priv->opts.diffuse);
}
sixel_dither_set_body_only(priv->dither, 0);
@@ -161,7 +161,7 @@ static SIXELSTATUS prepare_dynamic_palette(struct vo *vo)
SIXELSTATUS status = SIXEL_FALSE;
struct priv *priv = vo->priv;
- /* create histgram and construct color palette
+ /* create histogram and construct color palette
* with median cut algorithm. */
status = sixel_dither_initialize(priv->testdither, priv->buffer,
priv->width, priv->height,
@@ -178,12 +178,12 @@ static SIXELSTATUS prepare_dynamic_palette(struct vo *vo)
}
priv->dither = priv->testdither;
- status = sixel_dither_new(&priv->testdither, priv->opt_reqcolors, NULL);
+ status = sixel_dither_new(&priv->testdither, priv->opts.reqcolors, NULL);
if (SIXEL_FAILED(status))
return status;
- sixel_dither_set_diffusion_type(priv->dither, priv->opt_diffuse);
+ sixel_dither_set_diffusion_type(priv->dither, priv->opts.diffuse);
} else {
if (priv->dither == NULL)
return SIXEL_FALSE;
@@ -206,8 +206,8 @@ static void update_canvas_dimensions(struct vo *vo)
terminal_get_size2(&num_rows, &num_cols, &total_px_width, &total_px_height);
// If the user has specified rows/cols use them for further calculations
- num_rows = (priv->opt_rows > 0) ? priv->opt_rows : num_rows;
- num_cols = (priv->opt_cols > 0) ? priv->opt_cols : num_cols;
+ num_rows = (priv->opts.rows > 0) ? priv->opts.rows : num_rows;
+ num_cols = (priv->opts.cols > 0) ? priv->opts.cols : num_cols;
// If the pad value is set in between 0 and width/2 - 1, then we
// subtract from the detected width. Otherwise, we assume that the width
@@ -215,17 +215,17 @@ static void update_canvas_dimensions(struct vo *vo)
// total_width to be an integer multiple of num_cols. So in case the padding
// added by terminal is less than the number of cells in that axis, then rounding
// down will take care of correcting the detected width and remove padding.
- if (priv->opt_width > 0) {
+ if (priv->opts.width > 0) {
// option - set by the user, hard truth
- total_px_width = priv->opt_width;
+ total_px_width = priv->opts.width;
} else {
if (total_px_width <= 0) {
// ioctl failed to read terminal width
total_px_width = TERMINAL_FALLBACK_PX_WIDTH;
} else {
- if (priv->opt_pad_x >= 0 && priv->opt_pad_x < total_px_width / 2) {
+ if (priv->opts.pad_x >= 0 && priv->opts.pad_x < total_px_width / 2) {
// explicit padding set by the user
- total_px_width -= (2 * priv->opt_pad_x);
+ total_px_width -= (2 * priv->opts.pad_x);
} else {
// rounded "auto padding"
total_px_width = total_px_width / num_cols * num_cols;
@@ -233,14 +233,14 @@ static void update_canvas_dimensions(struct vo *vo)
}
}
- if (priv->opt_height > 0) {
- total_px_height = priv->opt_height;
+ if (priv->opts.height > 0) {
+ total_px_height = priv->opts.height;
} else {
if (total_px_height <= 0) {
total_px_height = TERMINAL_FALLBACK_PX_HEIGHT;
} else {
- if (priv->opt_pad_y >= 0 && priv->opt_pad_y < total_px_height / 2) {
- total_px_height -= (2 * priv->opt_pad_y);
+ if (priv->opts.pad_y >= 0 && priv->opts.pad_y < total_px_height / 2) {
+ total_px_height -= (2 * priv->opts.pad_y);
} else {
total_px_height = total_px_height / num_rows * num_rows;
}
@@ -279,9 +279,9 @@ static void set_sixel_output_parameters(struct vo *vo)
// top/left values must be greater than 1. If it is set, then
// the image will be rendered from there and no further centering is done.
- priv->top = (priv->opt_top > 0) ? priv->opt_top :
+ priv->top = (priv->opts.top > 0) ? priv->opts.top :
priv->num_rows * priv->dst_rect.y0 / vo->dheight + 1;
- priv->left = (priv->opt_left > 0) ? priv->opt_left :
+ priv->left = (priv->opts.left > 0) ? priv->opts.left :
priv->num_cols * priv->dst_rect.x0 / vo->dwidth + 1;
}
@@ -310,9 +310,9 @@ static int update_sixel_swscaler(struct vo *vo, struct mp_image_params *params)
return -1;
// create testdither only if dynamic palette mode is set
- if (!priv->opt_fixedpal) {
+ if (!priv->opts.fixedpal) {
SIXELSTATUS status = sixel_dither_new(&priv->testdither,
- priv->opt_reqcolors, NULL);
+ priv->opts.reqcolors, NULL);
if (SIXEL_FAILED(status)) {
MP_ERR(vo, "update_sixel_swscaler: Failed to create new dither: %s\n",
sixel_helper_format_error(status));
@@ -326,6 +326,45 @@ static int update_sixel_swscaler(struct vo *vo, struct mp_image_params *params)
return 0;
}
+static inline int sixel_buffer(char *data, int size, void *priv) {
+ char **out = (char **)priv;
+ *out = talloc_strndup_append_buffer(*out, data, size);
+ return size;
+}
+
+static inline int sixel_write(char *data, int size, void *priv)
+{
+ FILE *p = (FILE *)priv;
+ // On POSIX platforms, write() is the fastest method. It also is the only
+ // one that allows atomic writes so mpv’s output will not be interrupted
+ // by other processes or threads that write to stdout, which would cause
+ // screen corruption. POSIX does not guarantee atomicity for writes
+ // exceeding PIPE_BUF, but at least Linux does seem to implement it that
+ // way.
+#if HAVE_POSIX
+ int remain = size;
+
+ while (remain > 0) {
+ ssize_t written = write(fileno(p), data, remain);
+ if (written < 0)
+ return written;
+ remain -= written;
+ data += written;
+ }
+
+ return size;
+#else
+ int ret = fwrite(data, 1, size, p);
+ fflush(p);
+ return ret;
+#endif
+}
+
+static inline void sixel_strwrite(char *s)
+{
+ sixel_write(s, strlen(s), stdout);
+}
+
static int reconfig(struct vo *vo, struct mp_image_params *params)
{
struct priv *priv = vo->priv;
@@ -336,7 +375,8 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
ret = update_sixel_swscaler(vo, params);
}
- printf(ESC_CLEAR_SCREEN);
+ if (priv->opts.config_clear)
+ sixel_strwrite(TERM_ESC_CLEAR_SCREEN);
vo->want_redraw = true;
return ret;
@@ -365,7 +405,8 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
// with a failed reconfig.
update_sixel_swscaler(vo, vo->params);
- printf(ESC_CLEAR_SCREEN);
+ if (priv->opts.config_clear)
+ sixel_strwrite(TERM_ESC_CLEAR_SCREEN);
resized = true;
}
@@ -407,7 +448,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
// they should try to re-initialize the dithers, so it shouldn't dereference
// any NULL pointers. flip_page also has a check to make sure dither is not
// NULL before drawing, so failure in these functions should still be okay.
- if (priv->opt_fixedpal) {
+ if (priv->opts.fixedpal) {
status = prepare_static_palette(vo);
} else {
status = prepare_dynamic_palette(vo);
@@ -422,11 +463,6 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
talloc_free(mpi);
}
-static int sixel_write(char *data, int size, void *priv)
-{
- return fwrite(data, 1, size, (FILE *)priv);
-}
-
static void flip_page(struct vo *vo)
{
struct priv* priv = vo->priv;
@@ -442,24 +478,36 @@ static void flip_page(struct vo *vo)
return;
// Go to the offset row and column, then display the image
- printf(ESC_GOTOXY, priv->top, priv->left);
+ priv->sixel_output_buf = talloc_asprintf(NULL, TERM_ESC_GOTO_YX,
+ priv->top, priv->left);
+ if (!priv->opts.buffered)
+ sixel_strwrite(priv->sixel_output_buf);
+
sixel_encode(priv->buffer, priv->width, priv->height,
depth, priv->dither, priv->output);
- fflush(stdout);
+
+ if (priv->opts.buffered)
+ sixel_write(priv->sixel_output_buf,
+ ta_get_size(priv->sixel_output_buf), stdout);
+
+ talloc_free(priv->sixel_output_buf);
}
static int preinit(struct vo *vo)
{
struct priv *priv = vo->priv;
SIXELSTATUS status = SIXEL_FALSE;
- FILE* sixel_output_file = stdout;
// Parse opts set by CLI or conf
priv->sws = mp_sws_alloc(vo);
priv->sws->log = vo->log;
mp_sws_enable_cmdline_opts(priv->sws, vo->global);
- status = sixel_output_new(&priv->output, sixel_write, sixel_output_file, NULL);
+ if (priv->opts.buffered)
+ status = sixel_output_new(&priv->output, sixel_buffer,
+ &priv->sixel_output_buf, NULL);
+ else
+ status = sixel_output_new(&priv->output, sixel_write, stdout, NULL);
if (SIXEL_FAILED(status)) {
MP_ERR(vo, "preinit: Failed to create output file: %s\n",
sixel_helper_format_error(status));
@@ -468,16 +516,20 @@ static int preinit(struct vo *vo)
sixel_output_set_encode_policy(priv->output, SIXEL_ENCODEPOLICY_FAST);
- printf(ESC_HIDE_CURSOR);
+ if (priv->opts.alt_screen)
+ sixel_strwrite(TERM_ESC_ALT_SCREEN);
+
+ sixel_strwrite(TERM_ESC_HIDE_CURSOR);
+ terminal_set_mouse_input(true);
/* don't use private color registers for each frame. */
- printf(ESC_USE_GLOBAL_COLOR_REG);
+ sixel_strwrite(TERM_ESC_USE_GLOBAL_COLOR_REG);
priv->dither = NULL;
// create testdither only if dynamic palette mode is set
- if (!priv->opt_fixedpal) {
- status = sixel_dither_new(&priv->testdither, priv->opt_reqcolors, NULL);
+ if (!priv->opts.fixedpal) {
+ status = sixel_dither_new(&priv->testdither, priv->opts.reqcolors, NULL);
if (SIXEL_FAILED(status)) {
MP_ERR(vo, "preinit: Failed to create new dither: %s\n",
sixel_helper_format_error(status));
@@ -485,7 +537,7 @@ static int preinit(struct vo *vo)
}
}
- priv->previous_histgram_colors = 0;
+ priv->previous_histogram_colors = 0;
return 0;
}
@@ -507,12 +559,11 @@ static void uninit(struct vo *vo)
{
struct priv *priv = vo->priv;
- printf(ESC_RESTORE_CURSOR);
+ sixel_strwrite(TERM_ESC_RESTORE_CURSOR);
+ terminal_set_mouse_input(false);
- if (priv->opt_clear) {
- printf(ESC_CLEAR_SCREEN);
- printf(ESC_GOTOXY, 1, 1);
- }
+ if (priv->opts.alt_screen)
+ sixel_strwrite(TERM_ESC_NORMAL_SCREEN);
fflush(stdout);
if (priv->output) {
@@ -537,22 +588,17 @@ const struct vo_driver video_out_sixel = {
.uninit = uninit,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
- .opt_diffuse = DIFFUSE_AUTO,
- .opt_width = 0,
- .opt_height = 0,
- .opt_reqcolors = 256,
- .opt_threshold = -1,
- .opt_fixedpal = 1,
- .opt_top = 0,
- .opt_left = 0,
- .opt_pad_y = -1,
- .opt_pad_x = -1,
- .opt_rows = 0,
- .opt_cols = 0,
- .opt_clear = 1,
+ .opts.diffuse = DIFFUSE_AUTO,
+ .opts.reqcolors = 256,
+ .opts.threshold = -1,
+ .opts.fixedpal = true,
+ .opts.pad_y = -1,
+ .opts.pad_x = -1,
+ .opts.config_clear = true,
+ .opts.alt_screen = true,
},
.options = (const m_option_t[]) {
- {"dither", OPT_CHOICE(opt_diffuse,
+ {"dither", OPT_CHOICE(opts.diffuse,
{"auto", DIFFUSE_AUTO},
{"none", DIFFUSE_NONE},
{"atkinson", DIFFUSE_ATKINSON},
@@ -562,18 +608,21 @@ const struct vo_driver video_out_sixel = {
{"burkes", DIFFUSE_BURKES},
{"arithmetic", DIFFUSE_A_DITHER},
{"xor", DIFFUSE_X_DITHER})},
- {"width", OPT_INT(opt_width)},
- {"height", OPT_INT(opt_height)},
- {"reqcolors", OPT_INT(opt_reqcolors)},
- {"fixedpalette", OPT_FLAG(opt_fixedpal)},
- {"threshold", OPT_INT(opt_threshold)},
- {"top", OPT_INT(opt_top)},
- {"left", OPT_INT(opt_left)},
- {"pad-y", OPT_INT(opt_pad_y)},
- {"pad-x", OPT_INT(opt_pad_x)},
- {"rows", OPT_INT(opt_rows)},
- {"cols", OPT_INT(opt_cols)},
- {"exit-clear", OPT_FLAG(opt_clear), },
+ {"width", OPT_INT(opts.width)},
+ {"height", OPT_INT(opts.height)},
+ {"reqcolors", OPT_INT(opts.reqcolors)},
+ {"fixedpalette", OPT_BOOL(opts.fixedpal)},
+ {"threshold", OPT_INT(opts.threshold)},
+ {"top", OPT_INT(opts.top)},
+ {"left", OPT_INT(opts.left)},
+ {"pad-y", OPT_INT(opts.pad_y)},
+ {"pad-x", OPT_INT(opts.pad_x)},
+ {"rows", OPT_INT(opts.rows)},
+ {"cols", OPT_INT(opts.cols)},
+ {"config-clear", OPT_BOOL(opts.config_clear), },
+ {"alt-screen", OPT_BOOL(opts.alt_screen), },
+ {"buffered", OPT_BOOL(opts.buffered), },
+ {"exit-clear", OPT_REPLACED("vo-sixel-alt-screen")},
{0}
},
.options_prefix = "vo-sixel",