diff options
Diffstat (limited to 'video/out')
-rw-r--r-- | video/out/vo.c | 4 | ||||
-rw-r--r-- | video/out/vo_sixel.c | 421 |
2 files changed, 425 insertions, 0 deletions
diff --git a/video/out/vo.c b/video/out/vo.c index 27be4735ab..4cb15123ab 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -64,6 +64,7 @@ extern const struct vo_driver video_out_vaapi; extern const struct vo_driver video_out_wlshm; extern const struct vo_driver video_out_rpi; extern const struct vo_driver video_out_tct; +extern const struct vo_driver video_out_sixel; const struct vo_driver *const video_out_drivers[] = { @@ -106,6 +107,9 @@ const struct vo_driver *const video_out_drivers[] = #if HAVE_RPI_MMAL &video_out_rpi, #endif +#if HAVE_SIXEL + &video_out_sixel, +#endif &video_out_lavc, NULL }; diff --git a/video/out/vo_sixel.c b/video/out/vo_sixel.c new file mode 100644 index 0000000000..cce6a3da9d --- /dev/null +++ b/video/out/vo_sixel.c @@ -0,0 +1,421 @@ +/* + * 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 <unistd.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 + +#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 priv { + + // User specified options + int diffuse; + int width; + int height; + int reqcolors; + int fixedpal; + int threshold; + int top; + int left; + + // Internal data + sixel_output_t *output; + sixel_dither_t *dither; + sixel_dither_t *testdither; + uint8_t *buffer; + + int image_height; + int image_width; + int image_format; + + unsigned int average_r; + unsigned int average_g; + unsigned int average_b; + int previous_histgram_colors; + + struct mp_image *frame; + struct mp_sws_context *sws; +}; + +static const unsigned int depth = 3; + +static void validate_offset_values(struct vo* vo) +{ + struct priv* priv = vo->priv; + int top = priv->top; + int left = priv->left; + int terminal_width = 0; + int terminal_height = 0; + + terminal_get_size(&terminal_width, &terminal_height); + + // Make sure that the user specified top offset + // lies in the range 1 to TERMINAL_HEIGHT + // Otherwise default to the topmost row + if (top <= 0 || top > terminal_height) + priv->top = 1; + + // Make sure that the user specified left offset + // lies in the range 1 to TERMINAL_WIDTH + // Otherwise default to the leftmost column + if (left <= 0 || left > terminal_width) + priv->left = 1; +} + +static int detect_scene_change(struct vo* vo) +{ + struct priv* priv = vo->priv; + int score; + int i; + unsigned int r = 0; + unsigned int g = 0; + unsigned int b = 0; + + unsigned int average_r = priv->average_r; + unsigned int average_g = priv->average_g; + unsigned int average_b = priv->average_b; + int previous_histgram_colors = priv->previous_histgram_colors; + + int histgram_colors = 0; + int palette_colors = 0; + unsigned char const* palette; + + histgram_colors = sixel_dither_get_num_of_histogram_colors(priv->testdither); + + if (priv->dither == NULL) + goto detected; + + /* detect scene change if number of colors increses 20% */ + if (previous_histgram_colors * 6 < histgram_colors * 5) + goto detected; + + /* detect scene change if number of colors decreses 20% */ + if (previous_histgram_colors * 4 > histgram_colors * 5) + goto detected; + + palette_colors = sixel_dither_get_num_of_palette_colors(priv->testdither); + palette = sixel_dither_get_palette(priv->testdither); + + /* compare color difference between current + * palette and previous one */ + for (i = 0; i < palette_colors; i++) { + r += palette[i * 3 + 0]; + g += palette[i * 3 + 1]; + b += palette[i * 3 + 2]; + } + score = (r - average_r) * (r - average_r) + + (g - average_g) * (g - average_g) + + (b - average_b) * (b - average_b); + if (score > priv->threshold * palette_colors + * palette_colors) + goto detected; + + return 0; + +detected: + priv->previous_histgram_colors = histgram_colors; + priv->average_r = r; + priv->average_g = g; + priv->average_b = b; + return 1; +} + +static void dealloc_dithers_and_buffer(struct vo* vo) +{ + struct priv* priv = vo->priv; + + talloc_free(priv->buffer); + + 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; + + if (priv->dither) + sixel_dither_set_body_only(priv->dither, 1); + else { + priv->dither = sixel_dither_get(BUILTIN_XTERM256); + if (priv->dither == NULL) + return SIXEL_FALSE; + sixel_dither_set_diffusion_type(priv->dither, priv->diffuse); + } + return SIXEL_OK; +} + +static SIXELSTATUS prepare_dynamic_palette(struct vo *vo) +{ + SIXELSTATUS status = SIXEL_FALSE; + struct priv *priv = vo->priv; + + /* create histgram and construct color palette + * with median cut algorithm. */ + status = sixel_dither_initialize(priv->testdither, priv->buffer, + priv->width, priv->height, 3, + LARGE_NORM, REP_CENTER_BOX, + QUALITY_LOW); + if (SIXEL_FAILED(status)) + return status; + + if (detect_scene_change(vo)) { + if (priv->dither) + sixel_dither_unref(priv->dither); + + priv->dither = priv->testdither; + status = sixel_dither_new(&priv->testdither, priv->reqcolors, NULL); + + if (SIXEL_FAILED(status)) + return status; + + sixel_dither_set_diffusion_type(priv->dither, priv->diffuse); + } else + sixel_dither_set_body_only(priv->dither, 1); + + return status; +} + +static int resize(struct vo *vo) +{ + struct priv *priv = vo->priv; + + dealloc_dithers_and_buffer(vo); + + SIXELSTATUS status = sixel_dither_new(&priv->testdither, priv->reqcolors, NULL); + if (SIXEL_FAILED(status)) + return status; + + priv->buffer = + talloc_array(NULL, uint8_t, depth * priv->width * priv->height); + + return 0; +} + +static int reconfig(struct vo *vo, struct mp_image_params *params) +{ + struct priv *priv = vo->priv; + priv->image_height = params->h; + priv->image_width = params->w; + priv->image_format = params->imgfmt; + + priv->sws->src = *params; + priv->sws->dst = (struct mp_image_params) { + .imgfmt = IMGFMT, + .w = priv->width, + .h = priv->height, + .p_w = 1, + .p_h = 1, + }; + + priv->frame = mp_image_alloc(IMGFMT, priv->width, priv->height); + if (!priv->frame) + return -1; + + if (mp_sws_reinit(priv->sws) < 0) + return -1; + + printf(ESC_HIDE_CURSOR); + printf(ESC_CLEAR_SCREEN); + vo->want_redraw = true; + + return resize(vo); +} + +static void draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct priv *priv = vo->priv; + struct mp_image src = *mpi; + + // Downscale the image + mp_sws_scale(priv->sws, priv->frame, &src); + + // Copy from mpv to RGB format as required by libsixel + memcpy_pic(priv->buffer, priv->frame->planes[0], priv->width * depth, priv->height, + priv->width * depth, priv->frame->stride[0]); + + if (priv->fixedpal) + prepare_static_palette(vo); + else + prepare_dynamic_palette(vo); + + 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; + + // Go to the offset row and column, then display the image + printf(ESC_GOTOXY, priv->top, priv->left); + sixel_encode(priv->buffer, priv->width, priv->height, + PIXELFORMAT_RGB888, + priv->dither, priv->output); + fflush(stdout); +} + +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 (SIXEL_FAILED(status)) + return status; + + sixel_output_set_encode_policy(priv->output, SIXEL_ENCODEPOLICY_FAST); + + printf(ESC_HIDE_CURSOR); + + /* don't use private color registers for each frame. */ + printf(ESC_USE_GLOBAL_COLOR_REG); + + priv->dither = NULL; + status = sixel_dither_new(&priv->testdither, priv->reqcolors, NULL); + + if (SIXEL_FAILED(status)) + return status; + + priv->buffer = + talloc_array(NULL, uint8_t, depth * priv->width * priv->height); + + priv->average_r = 0; + priv->average_g = 0; + priv->average_b = 0; + priv->previous_histgram_colors = 0; + + validate_offset_values(vo); + + 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) +{ + return VO_NOTIMPL; +} + + +static void uninit(struct vo *vo) +{ + struct priv *priv = vo->priv; + + printf(ESC_RESTORE_CURSOR); + + printf(ESC_CLEAR_SCREEN); + printf(ESC_GOTOXY, 1, 1); + fflush(stdout); + + if (priv->output) { + sixel_output_unref(priv->output); + priv->output = NULL; + } + + dealloc_dithers_and_buffer(vo); +} + +#define OPT_BASE_STRUCT struct priv + +const struct vo_driver video_out_sixel = { + .name = "sixel", + .description = "libsixel", + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_image = draw_image, + .flip_page = flip_page, + .uninit = uninit, + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .diffuse = DIFFUSE_ATKINSON, + .width = 320, + .height = 240, + .reqcolors = 256, + .fixedpal = 0, + .threshold = 0, + .top = 1, + .left = 1, + }, + .options = (const m_option_t[]) { + {"diffusion", OPT_CHOICE(diffuse, + {"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})}, + {"width", OPT_INT(width)}, + {"height", OPT_INT(height)}, + {"reqcolors", OPT_INT(reqcolors)}, + {"fixedpalette", OPT_INT(fixedpal)}, + {"color-threshold", OPT_INT(threshold)}, + {"offset-top", OPT_INT(top)}, + {"offset-left", OPT_INT(left)}, + {0} + }, + .options_prefix = "vo-sixel", +}; |