2020-11-01 11:13:31 +00:00
|
|
|
|
/*
|
|
|
|
|
* Sixel mpv output device implementation based on ffmpeg libavdevice implementation
|
|
|
|
|
* by Hayaki Saito
|
|
|
|
|
* https://github.com/saitoha/FFmpeg-SIXEL/blob/sixel/libavdevice/sixel.c
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2014 Hayaki Saito
|
|
|
|
|
*
|
|
|
|
|
* This file is part of mpv.
|
|
|
|
|
*
|
|
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* mpv is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU Lesser General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
|
#include <sixel.h>
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#include "options/m_config.h"
|
|
|
|
|
#include "osdep/terminal.h"
|
|
|
|
|
#include "sub/osd.h"
|
|
|
|
|
#include "vo.h"
|
|
|
|
|
#include "video/sws_utils.h"
|
|
|
|
|
#include "video/mp_image.h"
|
|
|
|
|
|
|
|
|
|
#define IMGFMT IMGFMT_RGB24
|
|
|
|
|
|
2022-12-17 04:42:58 +00:00
|
|
|
|
#define TERM_ESC_USE_GLOBAL_COLOR_REG "\033[?1070l"
|
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
#define TERMINAL_FALLBACK_COLS 80
|
|
|
|
|
#define TERMINAL_FALLBACK_ROWS 25
|
|
|
|
|
#define TERMINAL_FALLBACK_PX_WIDTH 320
|
|
|
|
|
#define TERMINAL_FALLBACK_PX_HEIGHT 240
|
2020-11-09 14:34:56 +00:00
|
|
|
|
|
2022-12-17 05:21:45 +00:00
|
|
|
|
struct vo_sixel_opts {
|
|
|
|
|
int diffuse;
|
|
|
|
|
int reqcolors;
|
2023-02-20 03:32:50 +00:00
|
|
|
|
bool fixedpal;
|
2022-12-17 05:21:45 +00:00
|
|
|
|
int threshold;
|
|
|
|
|
int width, height, top, left;
|
|
|
|
|
int pad_y, pad_x;
|
|
|
|
|
int rows, cols;
|
2023-02-20 03:32:50 +00:00
|
|
|
|
bool config_clear, alt_screen;
|
|
|
|
|
bool buffered;
|
2022-12-17 05:21:45 +00:00
|
|
|
|
};
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2022-12-17 05:21:45 +00:00
|
|
|
|
struct priv {
|
2020-11-01 11:13:31 +00:00
|
|
|
|
// User specified options
|
2022-12-17 05:21:45 +00:00
|
|
|
|
struct vo_sixel_opts opts;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
// Internal data
|
|
|
|
|
sixel_output_t *output;
|
|
|
|
|
sixel_dither_t *dither;
|
|
|
|
|
sixel_dither_t *testdither;
|
|
|
|
|
uint8_t *buffer;
|
2022-12-17 06:52:44 +00:00
|
|
|
|
char *sixel_output_buf;
|
2020-11-26 16:05:20 +00:00
|
|
|
|
bool skip_frame_draw;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-12-02 10:39:57 +00:00
|
|
|
|
int left, top; // image origin cell (1 based)
|
|
|
|
|
int width, height; // actual image px size - always reflects dst_rect.
|
|
|
|
|
int num_cols, num_rows; // terminal size in cells
|
|
|
|
|
int canvas_ok; // whether canvas vo->dwidth and vo->dheight are positive
|
2020-11-11 14:28:00 +00:00
|
|
|
|
|
2023-03-28 15:16:42 +00:00
|
|
|
|
int previous_histogram_colors;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
struct mp_rect src_rect;
|
|
|
|
|
struct mp_rect dst_rect;
|
|
|
|
|
struct mp_osd_res osd;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
struct mp_image *frame;
|
|
|
|
|
struct mp_sws_context *sws;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const unsigned int depth = 3;
|
|
|
|
|
|
|
|
|
|
static int detect_scene_change(struct vo* vo)
|
|
|
|
|
{
|
|
|
|
|
struct priv* priv = vo->priv;
|
2023-03-28 15:16:42 +00:00
|
|
|
|
int previous_histogram_colors = priv->previous_histogram_colors;
|
|
|
|
|
int histogram_colors = 0;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// If threshold is set negative, then every frame must be a scene change
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->dither == NULL || priv->opts.threshold < 0)
|
2020-11-12 10:37:33 +00:00
|
|
|
|
return 1;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2023-03-28 15:16:42 +00:00
|
|
|
|
histogram_colors = sixel_dither_get_num_of_histogram_colors(priv->testdither);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2023-03-28 15:16:42 +00:00
|
|
|
|
int color_difference_count = previous_histogram_colors - histogram_colors;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
color_difference_count = (color_difference_count > 0) ? // abs value
|
|
|
|
|
color_difference_count : -color_difference_count;
|
|
|
|
|
|
|
|
|
|
if (100 * color_difference_count >
|
2023-03-28 15:16:42 +00:00
|
|
|
|
priv->opts.threshold * previous_histogram_colors)
|
2020-11-12 10:37:33 +00:00
|
|
|
|
{
|
2023-03-28 15:16:42 +00:00
|
|
|
|
priv->previous_histogram_colors = histogram_colors; // update history
|
2020-11-12 10:37:33 +00:00
|
|
|
|
return 1;
|
|
|
|
|
} else {
|
|
|
|
|
return 0;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-29 12:07:43 +00:00
|
|
|
|
static void dealloc_dithers_and_buffers(struct vo* vo)
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{
|
|
|
|
|
struct priv* priv = vo->priv;
|
|
|
|
|
|
2020-11-09 14:23:22 +00:00
|
|
|
|
if (priv->buffer) {
|
|
|
|
|
talloc_free(priv->buffer);
|
|
|
|
|
priv->buffer = NULL;
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-29 12:07:43 +00:00
|
|
|
|
if (priv->frame) {
|
|
|
|
|
talloc_free(priv->frame);
|
|
|
|
|
priv->frame = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
if (priv->dither) {
|
|
|
|
|
sixel_dither_unref(priv->dither);
|
|
|
|
|
priv->dither = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->testdither) {
|
|
|
|
|
sixel_dither_unref(priv->testdither);
|
|
|
|
|
priv->testdither = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static SIXELSTATUS prepare_static_palette(struct vo* vo)
|
|
|
|
|
{
|
|
|
|
|
struct priv* priv = vo->priv;
|
|
|
|
|
|
2020-11-26 21:25:07 +00:00
|
|
|
|
if (!priv->dither) {
|
2020-11-01 11:13:31 +00:00
|
|
|
|
priv->dither = sixel_dither_get(BUILTIN_XTERM256);
|
|
|
|
|
if (priv->dither == NULL)
|
|
|
|
|
return SIXEL_FALSE;
|
2020-11-16 14:43:50 +00:00
|
|
|
|
|
2022-12-17 05:21:45 +00:00
|
|
|
|
sixel_dither_set_diffusion_type(priv->dither, priv->opts.diffuse);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
2020-11-26 21:25:07 +00:00
|
|
|
|
|
|
|
|
|
sixel_dither_set_body_only(priv->dither, 0);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
return SIXEL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static SIXELSTATUS prepare_dynamic_palette(struct vo *vo)
|
|
|
|
|
{
|
|
|
|
|
SIXELSTATUS status = SIXEL_FALSE;
|
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
|
2023-03-27 20:42:17 +00:00
|
|
|
|
/* create histogram and construct color palette
|
2020-11-01 11:13:31 +00:00
|
|
|
|
* with median cut algorithm. */
|
|
|
|
|
status = sixel_dither_initialize(priv->testdither, priv->buffer,
|
2020-11-27 10:10:42 +00:00
|
|
|
|
priv->width, priv->height,
|
|
|
|
|
SIXEL_PIXELFORMAT_RGB888,
|
2020-11-01 11:13:31 +00:00
|
|
|
|
LARGE_NORM, REP_CENTER_BOX,
|
|
|
|
|
QUALITY_LOW);
|
|
|
|
|
if (SIXEL_FAILED(status))
|
|
|
|
|
return status;
|
|
|
|
|
|
|
|
|
|
if (detect_scene_change(vo)) {
|
2020-11-16 14:43:50 +00:00
|
|
|
|
if (priv->dither) {
|
2020-11-01 11:13:31 +00:00
|
|
|
|
sixel_dither_unref(priv->dither);
|
2020-11-16 14:43:50 +00:00
|
|
|
|
priv->dither = NULL;
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
priv->dither = priv->testdither;
|
2022-12-17 05:21:45 +00:00
|
|
|
|
status = sixel_dither_new(&priv->testdither, priv->opts.reqcolors, NULL);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
if (SIXEL_FAILED(status))
|
|
|
|
|
return status;
|
|
|
|
|
|
2022-12-17 05:21:45 +00:00
|
|
|
|
sixel_dither_set_diffusion_type(priv->dither, priv->opts.diffuse);
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
2020-11-26 21:25:07 +00:00
|
|
|
|
if (priv->dither == NULL)
|
2020-11-16 14:43:50 +00:00
|
|
|
|
return SIXEL_FALSE;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-26 21:25:07 +00:00
|
|
|
|
sixel_dither_set_body_only(priv->dither, 0);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 13:14:12 +00:00
|
|
|
|
static void update_canvas_dimensions(struct vo *vo)
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// this function sets the vo canvas size in pixels vo->dwidth, vo->dheight,
|
2020-11-30 13:14:12 +00:00
|
|
|
|
// and the number of rows and columns available in priv->num_rows/cols
|
2020-11-12 10:37:33 +00:00
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
int num_rows = TERMINAL_FALLBACK_ROWS;
|
|
|
|
|
int num_cols = TERMINAL_FALLBACK_COLS;
|
|
|
|
|
int total_px_width = 0;
|
|
|
|
|
int total_px_height = 0;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
terminal_get_size2(&num_rows, &num_cols, &total_px_width, &total_px_height);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// If the user has specified rows/cols use them for further calculations
|
2022-12-17 05:21:45 +00:00
|
|
|
|
num_rows = (priv->opts.rows > 0) ? priv->opts.rows : num_rows;
|
|
|
|
|
num_cols = (priv->opts.cols > 0) ? priv->opts.cols : num_cols;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// output must be a integer multiple of num_cols and accordingly set
|
|
|
|
|
// 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.
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->opts.width > 0) {
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// option - set by the user, hard truth
|
2022-12-17 05:21:45 +00:00
|
|
|
|
total_px_width = priv->opts.width;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
if (total_px_width <= 0) {
|
|
|
|
|
// ioctl failed to read terminal width
|
|
|
|
|
total_px_width = TERMINAL_FALLBACK_PX_WIDTH;
|
|
|
|
|
} else {
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->opts.pad_x >= 0 && priv->opts.pad_x < total_px_width / 2) {
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// explicit padding set by the user
|
2022-12-17 05:21:45 +00:00
|
|
|
|
total_px_width -= (2 * priv->opts.pad_x);
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
// rounded "auto padding"
|
|
|
|
|
total_px_width = total_px_width / num_cols * num_cols;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->opts.height > 0) {
|
|
|
|
|
total_px_height = priv->opts.height;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
if (total_px_height <= 0) {
|
|
|
|
|
total_px_height = TERMINAL_FALLBACK_PX_HEIGHT;
|
|
|
|
|
} else {
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->opts.pad_y >= 0 && priv->opts.pad_y < total_px_height / 2) {
|
|
|
|
|
total_px_height -= (2 * priv->opts.pad_y);
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
total_px_height = total_px_height / num_rows * num_rows;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
// use n-1 rows for height
|
|
|
|
|
// The last row can't be used for encoding image, because after sixel encode
|
|
|
|
|
// the terminal moves the cursor to next line below the image, causing the
|
|
|
|
|
// last line to be empty instead of displaying image data.
|
|
|
|
|
// TODO: Confirm if the output height must be a multiple of 6, if not, remove
|
|
|
|
|
// the / 6 * 6 part which is setting the height to be a multiple of 6.
|
|
|
|
|
vo->dheight = total_px_height * (num_rows - 1) / num_rows / 6 * 6;
|
|
|
|
|
vo->dwidth = total_px_width;
|
|
|
|
|
|
2020-11-30 13:14:12 +00:00
|
|
|
|
priv->num_rows = num_rows;
|
|
|
|
|
priv->num_cols = num_cols;
|
2020-12-02 10:39:57 +00:00
|
|
|
|
|
|
|
|
|
priv->canvas_ok = vo->dwidth > 0 && vo->dheight > 0;
|
2020-11-30 13:14:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void set_sixel_output_parameters(struct vo *vo)
|
|
|
|
|
{
|
|
|
|
|
// This function sets output scaled size in priv->width, priv->height
|
|
|
|
|
// and the scaling rectangles in pixels priv->src_rect, priv->dst_rect
|
|
|
|
|
// as well as image positioning in cells priv->top, priv->left.
|
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
|
2020-11-12 10:37:33 +00:00
|
|
|
|
vo_get_src_dst_rects(vo, &priv->src_rect, &priv->dst_rect, &priv->osd);
|
|
|
|
|
|
|
|
|
|
// priv->width and priv->height are the width and height of dst_rect
|
|
|
|
|
// and they are not changed anywhere else outside this function.
|
|
|
|
|
// It is the sixel image output dimension which is output by libsixel.
|
|
|
|
|
priv->width = priv->dst_rect.x1 - priv->dst_rect.x0;
|
|
|
|
|
priv->height = priv->dst_rect.y1 - priv->dst_rect.y0;
|
|
|
|
|
|
|
|
|
|
// 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.
|
2022-12-17 05:21:45 +00:00
|
|
|
|
priv->top = (priv->opts.top > 0) ? priv->opts.top :
|
2020-11-30 13:14:12 +00:00
|
|
|
|
priv->num_rows * priv->dst_rect.y0 / vo->dheight + 1;
|
2022-12-17 05:21:45 +00:00
|
|
|
|
priv->left = (priv->opts.left > 0) ? priv->opts.left :
|
2020-11-30 13:14:12 +00:00
|
|
|
|
priv->num_cols * priv->dst_rect.x0 / vo->dwidth + 1;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 13:14:12 +00:00
|
|
|
|
static int update_sixel_swscaler(struct vo *vo, struct mp_image_params *params)
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{
|
|
|
|
|
struct priv *priv = vo->priv;
|
2020-11-11 10:49:13 +00:00
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
priv->sws->src = *params;
|
2020-11-12 10:37:33 +00:00
|
|
|
|
priv->sws->src.w = mp_rect_w(priv->src_rect);
|
|
|
|
|
priv->sws->src.h = mp_rect_h(priv->src_rect);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
priv->sws->dst = (struct mp_image_params) {
|
|
|
|
|
.imgfmt = IMGFMT,
|
|
|
|
|
.w = priv->width,
|
|
|
|
|
.h = priv->height,
|
|
|
|
|
.p_w = 1,
|
|
|
|
|
.p_h = 1,
|
|
|
|
|
};
|
|
|
|
|
|
2020-11-29 12:07:43 +00:00
|
|
|
|
dealloc_dithers_and_buffers(vo);
|
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
priv->frame = mp_image_alloc(IMGFMT, priv->width, priv->height);
|
|
|
|
|
if (!priv->frame)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (mp_sws_reinit(priv->sws) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
|
2020-11-27 10:25:51 +00:00
|
|
|
|
// create testdither only if dynamic palette mode is set
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (!priv->opts.fixedpal) {
|
2020-11-27 10:25:51 +00:00
|
|
|
|
SIXELSTATUS status = sixel_dither_new(&priv->testdither,
|
2022-12-17 05:21:45 +00:00
|
|
|
|
priv->opts.reqcolors, NULL);
|
2020-11-27 10:25:51 +00:00
|
|
|
|
if (SIXEL_FAILED(status)) {
|
2020-11-30 13:14:12 +00:00
|
|
|
|
MP_ERR(vo, "update_sixel_swscaler: Failed to create new dither: %s\n",
|
2020-11-27 10:25:51 +00:00
|
|
|
|
sixel_helper_format_error(status));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2020-11-16 14:43:50 +00:00
|
|
|
|
}
|
2020-11-12 10:37:33 +00:00
|
|
|
|
|
|
|
|
|
priv->buffer =
|
|
|
|
|
talloc_array(NULL, uint8_t, depth * priv->width * priv->height);
|
|
|
|
|
|
|
|
|
|
return 0;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-17 06:52:44 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 09:06:49 +00:00
|
|
|
|
static inline int sixel_write(char *data, int size, void *priv)
|
2022-12-17 06:10:48 +00:00
|
|
|
|
{
|
2022-12-20 09:06:49 +00:00
|
|
|
|
FILE *p = (FILE *)priv;
|
2022-12-17 06:10:48 +00:00
|
|
|
|
// On POSIX platforms, write() is the fastest method. It also is the only
|
2022-12-20 09:45:36 +00:00
|
|
|
|
// 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.
|
2022-12-17 06:10:48 +00:00
|
|
|
|
#if HAVE_POSIX
|
2022-12-20 09:45:36 +00:00
|
|
|
|
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;
|
2022-12-17 06:10:48 +00:00
|
|
|
|
#else
|
2022-12-20 09:06:49 +00:00
|
|
|
|
int ret = fwrite(data, 1, size, p);
|
|
|
|
|
fflush(p);
|
2022-12-17 06:10:48 +00:00
|
|
|
|
return ret;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-17 06:52:44 +00:00
|
|
|
|
static inline void sixel_strwrite(char *s)
|
2022-12-17 06:10:48 +00:00
|
|
|
|
{
|
2022-12-20 09:06:49 +00:00
|
|
|
|
sixel_write(s, strlen(s), stdout);
|
2022-12-17 06:10:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 13:14:12 +00:00
|
|
|
|
static int reconfig(struct vo *vo, struct mp_image_params *params)
|
|
|
|
|
{
|
2020-12-02 10:39:57 +00:00
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
int ret = 0;
|
2020-11-30 13:14:12 +00:00
|
|
|
|
update_canvas_dimensions(vo);
|
2020-12-02 10:39:57 +00:00
|
|
|
|
if (priv->canvas_ok) { // if too small - succeed but skip the rendering
|
|
|
|
|
set_sixel_output_parameters(vo);
|
|
|
|
|
ret = update_sixel_swscaler(vo, params);
|
|
|
|
|
}
|
2020-11-30 13:14:12 +00:00
|
|
|
|
|
2022-12-20 09:29:49 +00:00
|
|
|
|
if (priv->opts.config_clear)
|
2022-12-17 06:52:44 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_CLEAR_SCREEN);
|
2020-11-30 13:14:12 +00:00
|
|
|
|
vo->want_redraw = true;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-26 16:05:20 +00:00
|
|
|
|
static void draw_frame(struct vo *vo, struct vo_frame *frame)
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{
|
|
|
|
|
struct priv *priv = vo->priv;
|
2020-11-16 14:43:50 +00:00
|
|
|
|
SIXELSTATUS status;
|
2020-11-26 16:05:20 +00:00
|
|
|
|
struct mp_image *mpi = NULL;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-12-02 09:05:39 +00:00
|
|
|
|
int prev_rows = priv->num_rows;
|
|
|
|
|
int prev_cols = priv->num_cols;
|
|
|
|
|
int prev_height = vo->dheight;
|
|
|
|
|
int prev_width = vo->dwidth;
|
|
|
|
|
bool resized = false;
|
|
|
|
|
update_canvas_dimensions(vo);
|
2020-12-02 10:39:57 +00:00
|
|
|
|
if (!priv->canvas_ok)
|
|
|
|
|
return;
|
2020-12-02 09:05:39 +00:00
|
|
|
|
|
|
|
|
|
if (prev_rows != priv->num_rows || prev_cols != priv->num_cols ||
|
|
|
|
|
prev_width != vo->dwidth || prev_height != vo->dheight)
|
|
|
|
|
{
|
|
|
|
|
set_sixel_output_parameters(vo);
|
|
|
|
|
// Not checking for vo->config_ok because draw_frame is never called
|
|
|
|
|
// with a failed reconfig.
|
|
|
|
|
update_sixel_swscaler(vo, vo->params);
|
|
|
|
|
|
2022-12-20 09:29:49 +00:00
|
|
|
|
if (priv->opts.config_clear)
|
2022-12-17 06:52:44 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_CLEAR_SCREEN);
|
2020-12-02 09:05:39 +00:00
|
|
|
|
resized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (frame->repeat && !frame->redraw && !resized) {
|
2020-11-26 16:05:20 +00:00
|
|
|
|
// Frame is repeated, and no need to update OSD either
|
|
|
|
|
priv->skip_frame_draw = true;
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
// Either frame is new, or OSD has to be redrawn
|
|
|
|
|
priv->skip_frame_draw = false;
|
|
|
|
|
}
|
2020-11-12 10:37:33 +00:00
|
|
|
|
|
2020-11-26 16:05:20 +00:00
|
|
|
|
// Normal case where we have to draw the frame and the image is not NULL
|
|
|
|
|
if (frame->current) {
|
|
|
|
|
mpi = mp_image_new_ref(frame->current);
|
|
|
|
|
struct mp_rect src_rc = priv->src_rect;
|
|
|
|
|
src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x);
|
|
|
|
|
src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y);
|
|
|
|
|
mp_image_crop_rc(mpi, src_rc);
|
|
|
|
|
|
|
|
|
|
// scale/pan to our dest rect
|
|
|
|
|
mp_sws_scale(priv->sws, priv->frame, mpi);
|
|
|
|
|
} else {
|
|
|
|
|
// Image is NULL, so need to clear image and draw OSD
|
|
|
|
|
mp_image_clear(priv->frame, 0, 0, priv->width, priv->height);
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-16 15:14:16 +00:00
|
|
|
|
struct mp_osd_res dim = {
|
|
|
|
|
.w = priv->width,
|
|
|
|
|
.h = priv->height
|
|
|
|
|
};
|
|
|
|
|
osd_draw_on_image(vo->osd, dim, mpi ? mpi->pts : 0, 0, priv->frame);
|
2020-11-26 16:05:20 +00:00
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
// Copy from mpv to RGB format as required by libsixel
|
2020-11-26 16:05:20 +00:00
|
|
|
|
memcpy_pic(priv->buffer, priv->frame->planes[0], priv->width * depth,
|
|
|
|
|
priv->height, priv->width * depth, priv->frame->stride[0]);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-16 14:43:50 +00:00
|
|
|
|
// Even if either of these prepare palette functions fail, on re-running them
|
|
|
|
|
// 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.
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (priv->opts.fixedpal) {
|
2020-11-16 14:43:50 +00:00
|
|
|
|
status = prepare_static_palette(vo);
|
2020-11-12 10:37:33 +00:00
|
|
|
|
} else {
|
2020-11-16 14:43:50 +00:00
|
|
|
|
status = prepare_dynamic_palette(vo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SIXEL_FAILED(status)) {
|
2020-11-26 16:05:20 +00:00
|
|
|
|
MP_WARN(vo, "draw_frame: prepare_palette returned error: %s\n",
|
2020-11-16 14:43:50 +00:00
|
|
|
|
sixel_helper_format_error(status));
|
2020-11-12 10:37:33 +00:00
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-26 16:05:20 +00:00
|
|
|
|
if (mpi)
|
|
|
|
|
talloc_free(mpi);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void flip_page(struct vo *vo)
|
|
|
|
|
{
|
|
|
|
|
struct priv* priv = vo->priv;
|
2020-12-02 10:39:57 +00:00
|
|
|
|
if (!priv->canvas_ok)
|
|
|
|
|
return;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2020-11-26 16:05:20 +00:00
|
|
|
|
// If frame is repeated and no update required, then we skip encoding
|
|
|
|
|
if (priv->skip_frame_draw)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-11-09 14:23:22 +00:00
|
|
|
|
// Make sure that image and dither are valid before drawing
|
|
|
|
|
if (priv->buffer == NULL || priv->dither == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
// Go to the offset row and column, then display the image
|
2022-12-17 06:52:44 +00:00
|
|
|
|
priv->sixel_output_buf = talloc_asprintf(NULL, TERM_ESC_GOTO_YX,
|
|
|
|
|
priv->top, priv->left);
|
2022-12-20 09:06:49 +00:00
|
|
|
|
if (!priv->opts.buffered)
|
|
|
|
|
sixel_strwrite(priv->sixel_output_buf);
|
|
|
|
|
|
2020-11-01 11:13:31 +00:00
|
|
|
|
sixel_encode(priv->buffer, priv->width, priv->height,
|
2020-11-27 10:10:42 +00:00
|
|
|
|
depth, priv->dither, priv->output);
|
2022-12-20 09:06:49 +00:00
|
|
|
|
|
|
|
|
|
if (priv->opts.buffered)
|
|
|
|
|
sixel_write(priv->sixel_output_buf,
|
|
|
|
|
ta_get_size(priv->sixel_output_buf), stdout);
|
2022-12-17 06:52:44 +00:00
|
|
|
|
|
|
|
|
|
talloc_free(priv->sixel_output_buf);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int preinit(struct vo *vo)
|
|
|
|
|
{
|
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
SIXELSTATUS status = SIXEL_FALSE;
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2022-12-20 09:06:49 +00:00
|
|
|
|
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);
|
2020-11-16 14:43:50 +00:00
|
|
|
|
if (SIXEL_FAILED(status)) {
|
|
|
|
|
MP_ERR(vo, "preinit: Failed to create output file: %s\n",
|
|
|
|
|
sixel_helper_format_error(status));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
sixel_output_set_encode_policy(priv->output, SIXEL_ENCODEPOLICY_FAST);
|
|
|
|
|
|
2022-12-20 09:22:51 +00:00
|
|
|
|
if (priv->opts.alt_screen)
|
2022-12-20 09:26:53 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_ALT_SCREEN);
|
2022-12-17 06:52:44 +00:00
|
|
|
|
|
|
|
|
|
sixel_strwrite(TERM_ESC_HIDE_CURSOR);
|
2024-03-31 19:30:26 +00:00
|
|
|
|
terminal_set_mouse_input(true);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
/* don't use private color registers for each frame. */
|
2022-12-17 06:52:44 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_USE_GLOBAL_COLOR_REG);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
priv->dither = NULL;
|
|
|
|
|
|
2020-11-27 10:25:51 +00:00
|
|
|
|
// create testdither only if dynamic palette mode is set
|
2022-12-17 05:21:45 +00:00
|
|
|
|
if (!priv->opts.fixedpal) {
|
|
|
|
|
status = sixel_dither_new(&priv->testdither, priv->opts.reqcolors, NULL);
|
2020-11-27 10:25:51 +00:00
|
|
|
|
if (SIXEL_FAILED(status)) {
|
|
|
|
|
MP_ERR(vo, "preinit: Failed to create new dither: %s\n",
|
|
|
|
|
sixel_helper_format_error(status));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2020-11-16 14:43:50 +00:00
|
|
|
|
}
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2023-03-28 15:16:42 +00:00
|
|
|
|
priv->previous_histogram_colors = 0;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int query_format(struct vo *vo, int format)
|
|
|
|
|
{
|
|
|
|
|
return format == IMGFMT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int control(struct vo *vo, uint32_t request, void *data)
|
|
|
|
|
{
|
2020-11-30 13:14:12 +00:00
|
|
|
|
if (request == VOCTRL_SET_PANSCAN)
|
|
|
|
|
return (vo->config_ok && !reconfig(vo, vo->params)) ? VO_TRUE : VO_FALSE;
|
|
|
|
|
return VO_NOTIMPL;
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void uninit(struct vo *vo)
|
|
|
|
|
{
|
|
|
|
|
struct priv *priv = vo->priv;
|
|
|
|
|
|
2022-12-17 06:52:44 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_RESTORE_CURSOR);
|
2024-03-31 19:30:26 +00:00
|
|
|
|
terminal_set_mouse_input(false);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
|
2022-12-20 09:22:51 +00:00
|
|
|
|
if (priv->opts.alt_screen)
|
2022-12-20 09:26:53 +00:00
|
|
|
|
sixel_strwrite(TERM_ESC_NORMAL_SCREEN);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
|
|
if (priv->output) {
|
|
|
|
|
sixel_output_unref(priv->output);
|
|
|
|
|
priv->output = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-29 12:07:43 +00:00
|
|
|
|
dealloc_dithers_and_buffers(vo);
|
2020-11-01 11:13:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define OPT_BASE_STRUCT struct priv
|
|
|
|
|
|
|
|
|
|
const struct vo_driver video_out_sixel = {
|
|
|
|
|
.name = "sixel",
|
2020-12-01 16:03:46 +00:00
|
|
|
|
.description = "terminal graphics using sixels",
|
2020-11-01 11:13:31 +00:00
|
|
|
|
.preinit = preinit,
|
|
|
|
|
.query_format = query_format,
|
|
|
|
|
.reconfig = reconfig,
|
|
|
|
|
.control = control,
|
2020-11-26 16:05:20 +00:00
|
|
|
|
.draw_frame = draw_frame,
|
2020-11-01 11:13:31 +00:00
|
|
|
|
.flip_page = flip_page,
|
|
|
|
|
.uninit = uninit,
|
|
|
|
|
.priv_size = sizeof(struct priv),
|
|
|
|
|
.priv_defaults = &(const struct priv) {
|
2022-12-17 05:21:45 +00:00
|
|
|
|
.opts.diffuse = DIFFUSE_AUTO,
|
|
|
|
|
.opts.reqcolors = 256,
|
|
|
|
|
.opts.threshold = -1,
|
2023-02-20 03:32:50 +00:00
|
|
|
|
.opts.fixedpal = true,
|
2022-12-17 05:21:45 +00:00
|
|
|
|
.opts.pad_y = -1,
|
|
|
|
|
.opts.pad_x = -1,
|
2023-02-20 03:32:50 +00:00
|
|
|
|
.opts.config_clear = true,
|
|
|
|
|
.opts.alt_screen = true,
|
2020-11-01 11:13:31 +00:00
|
|
|
|
},
|
|
|
|
|
.options = (const m_option_t[]) {
|
2022-12-17 05:21:45 +00:00
|
|
|
|
{"dither", OPT_CHOICE(opts.diffuse,
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{"auto", DIFFUSE_AUTO},
|
|
|
|
|
{"none", DIFFUSE_NONE},
|
|
|
|
|
{"atkinson", DIFFUSE_ATKINSON},
|
|
|
|
|
{"fs", DIFFUSE_FS},
|
|
|
|
|
{"jajuni", DIFFUSE_JAJUNI},
|
|
|
|
|
{"stucki", DIFFUSE_STUCKI},
|
|
|
|
|
{"burkes", DIFFUSE_BURKES},
|
|
|
|
|
{"arithmetic", DIFFUSE_A_DITHER},
|
|
|
|
|
{"xor", DIFFUSE_X_DITHER})},
|
2022-12-17 05:21:45 +00:00
|
|
|
|
{"width", OPT_INT(opts.width)},
|
|
|
|
|
{"height", OPT_INT(opts.height)},
|
|
|
|
|
{"reqcolors", OPT_INT(opts.reqcolors)},
|
2023-02-20 03:32:50 +00:00
|
|
|
|
{"fixedpalette", OPT_BOOL(opts.fixedpal)},
|
2022-12-17 05:21:45 +00:00
|
|
|
|
{"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)},
|
2023-02-20 03:32:50 +00:00
|
|
|
|
{"config-clear", OPT_BOOL(opts.config_clear), },
|
|
|
|
|
{"alt-screen", OPT_BOOL(opts.alt_screen), },
|
|
|
|
|
{"buffered", OPT_BOOL(opts.buffered), },
|
2020-11-01 11:13:31 +00:00
|
|
|
|
{0}
|
|
|
|
|
},
|
|
|
|
|
.options_prefix = "vo-sixel",
|
|
|
|
|
};
|