summaryrefslogtreecommitdiffstats
path: root/video/out/vo_tct.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /video/out/vo_tct.c
parentInitial commit. (diff)
downloadmpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz
mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'video/out/vo_tct.c')
-rw-r--r--video/out/vo_tct.c347
1 files changed, 347 insertions, 0 deletions
diff --git a/video/out/vo_tct.c b/video/out/vo_tct.c
new file mode 100644
index 0000000..8859095
--- /dev/null
+++ b/video/out/vo_tct.c
@@ -0,0 +1,347 @@
+/*
+ * 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 <unistd.h>
+#include <config.h>
+
+#if HAVE_POSIX
+#include <sys/ioctl.h>
+#endif
+
+#include <libswscale/swscale.h>
+
+#include "options/m_config.h"
+#include "config.h"
+#include "osdep/terminal.h"
+#include "osdep/io.h"
+#include "vo.h"
+#include "sub/osd.h"
+#include "video/sws_utils.h"
+#include "video/mp_image.h"
+
+#define IMGFMT IMGFMT_BGR24
+
+#define ALGO_PLAIN 1
+#define ALGO_HALF_BLOCKS 2
+
+#define TERM_ESC_CLEAR_COLORS "\033[0m"
+#define TERM_ESC_COLOR256_BG "\033[48;5"
+#define TERM_ESC_COLOR256_FG "\033[38;5"
+#define TERM_ESC_COLOR24BIT_BG "\033[48;2"
+#define TERM_ESC_COLOR24BIT_FG "\033[38;2"
+
+#define DEFAULT_WIDTH 80
+#define DEFAULT_HEIGHT 25
+
+struct vo_tct_opts {
+ int algo;
+ int width; // 0 -> default
+ int height; // 0 -> default
+ bool term256; // 0 -> true color
+};
+
+struct lut_item {
+ char str[4];
+ int width;
+};
+
+struct priv {
+ struct vo_tct_opts opts;
+ size_t buffer_size;
+ int swidth;
+ int sheight;
+ struct mp_image *frame;
+ struct mp_rect src;
+ struct mp_rect dst;
+ struct mp_sws_context *sws;
+ struct lut_item lut[256];
+};
+
+// Convert RGB24 to xterm-256 8-bit value
+// For simplicity, assume RGB space is perceptually uniform.
+// There are 5 places where one of two outputs needs to be chosen when the
+// input is the exact middle:
+// - The r/g/b channels and the gray value: the higher value output is chosen.
+// - If the gray and color have same distance from the input - color is chosen.
+static int rgb_to_x256(uint8_t r, uint8_t g, uint8_t b)
+{
+ // Calculate the nearest 0-based color index at 16 .. 231
+# define v2ci(v) (v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40)
+ int ir = v2ci(r), ig = v2ci(g), ib = v2ci(b); // 0..5 each
+# define color_index() (36 * ir + 6 * ig + ib) /* 0..215, lazy evaluation */
+
+ // Calculate the nearest 0-based gray index at 232 .. 255
+ int average = (r + g + b) / 3;
+ int gray_index = average > 238 ? 23 : (average - 3) / 10; // 0..23
+
+ // Calculate the represented colors back from the index
+ static const int i2cv[6] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
+ int cr = i2cv[ir], cg = i2cv[ig], cb = i2cv[ib]; // r/g/b, 0..255 each
+ int gv = 8 + 10 * gray_index; // same value for r/g/b, 0..255
+
+ // Return the one which is nearer to the original input rgb value
+# define dist_square(A,B,C, a,b,c) ((A-a)*(A-a) + (B-b)*(B-b) + (C-c)*(C-c))
+ int color_err = dist_square(cr, cg, cb, r, g, b);
+ int gray_err = dist_square(gv, gv, gv, r, g, b);
+ return color_err <= gray_err ? 16 + color_index() : 232 + gray_index;
+}
+
+static void print_seq3(struct lut_item *lut, const char* prefix,
+ uint8_t r, uint8_t g, uint8_t b)
+{
+// The fwrite implementation is about 25% faster than the printf code
+// (even if we use *.s with the lut values), however,
+// on windows we need to use printf in order to translate escape sequences and
+// UTF8 output for the console.
+#ifndef _WIN32
+ fputs(prefix, stdout);
+ fwrite(lut[r].str, lut[r].width, 1, stdout);
+ fwrite(lut[g].str, lut[g].width, 1, stdout);
+ fwrite(lut[b].str, lut[b].width, 1, stdout);
+ fputc('m', stdout);
+#else
+ printf("%s;%d;%d;%dm", prefix, (int)r, (int)g, (int)b);
+#endif
+}
+
+static void print_seq1(struct lut_item *lut, const char* prefix, uint8_t c)
+{
+#ifndef _WIN32
+ fputs(prefix, stdout);
+ fwrite(lut[c].str, lut[c].width, 1, stdout);
+ fputc('m', stdout);
+#else
+ printf("%s;%dm", prefix, (int)c);
+#endif
+}
+
+
+static void write_plain(
+ const int dwidth, const int dheight,
+ const int swidth, const int sheight,
+ const unsigned char *source, const int source_stride,
+ bool term256, struct lut_item *lut)
+{
+ assert(source);
+ const int tx = (dwidth - swidth) / 2;
+ const int ty = (dheight - sheight) / 2;
+ for (int y = 0; y < sheight; y++) {
+ const unsigned char *row = source + y * source_stride;
+ printf(TERM_ESC_GOTO_YX, ty + y, tx);
+ for (int x = 0; x < swidth; x++) {
+ unsigned char b = *row++;
+ unsigned char g = *row++;
+ unsigned char r = *row++;
+ if (term256) {
+ print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r, g, b));
+ } else {
+ print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r, g, b);
+ }
+ printf(" ");
+ }
+ printf(TERM_ESC_CLEAR_COLORS);
+ }
+ printf("\n");
+}
+
+static void write_half_blocks(
+ const int dwidth, const int dheight,
+ const int swidth, const int sheight,
+ unsigned char *source, int source_stride,
+ bool term256, struct lut_item *lut)
+{
+ assert(source);
+ const int tx = (dwidth - swidth) / 2;
+ const int ty = (dheight - sheight) / 2;
+ for (int y = 0; y < sheight * 2; y += 2) {
+ const unsigned char *row_up = source + y * source_stride;
+ const unsigned char *row_down = source + (y + 1) * source_stride;
+ printf(TERM_ESC_GOTO_YX, ty + y / 2, tx);
+ for (int x = 0; x < swidth; x++) {
+ unsigned char b_up = *row_up++;
+ unsigned char g_up = *row_up++;
+ unsigned char r_up = *row_up++;
+ unsigned char b_down = *row_down++;
+ unsigned char g_down = *row_down++;
+ unsigned char r_down = *row_down++;
+ if (term256) {
+ print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up));
+ print_seq1(lut, TERM_ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down));
+ } else {
+ print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r_up, g_up, b_up);
+ print_seq3(lut, TERM_ESC_COLOR24BIT_FG, r_down, g_down, b_down);
+ }
+ printf("\xe2\x96\x84"); // UTF8 bytes of U+2584 (lower half block)
+ }
+ printf(TERM_ESC_CLEAR_COLORS);
+ }
+ printf("\n");
+}
+
+static void get_win_size(struct vo *vo, int *out_width, int *out_height) {
+ struct priv *p = vo->priv;
+ *out_width = DEFAULT_WIDTH;
+ *out_height = DEFAULT_HEIGHT;
+
+ terminal_get_size(out_width, out_height);
+
+ if (p->opts.width > 0)
+ *out_width = p->opts.width;
+ if (p->opts.height > 0)
+ *out_height = p->opts.height;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct priv *p = vo->priv;
+
+ get_win_size(vo, &vo->dwidth, &vo->dheight);
+
+ struct mp_osd_res osd;
+ vo_get_src_dst_rects(vo, &p->src, &p->dst, &osd);
+ p->swidth = p->dst.x1 - p->dst.x0;
+ p->sheight = p->dst.y1 - p->dst.y0;
+
+ p->sws->src = *params;
+ p->sws->dst = (struct mp_image_params) {
+ .imgfmt = IMGFMT,
+ .w = p->swidth,
+ .h = p->sheight,
+ .p_w = 1,
+ .p_h = 1,
+ };
+
+ const int mul = (p->opts.algo == ALGO_PLAIN ? 1 : 2);
+ if (p->frame)
+ talloc_free(p->frame);
+ p->frame = mp_image_alloc(IMGFMT, p->swidth, p->sheight * mul);
+ if (!p->frame)
+ return -1;
+
+ if (mp_sws_reinit(p->sws) < 0)
+ return -1;
+
+ printf(TERM_ESC_CLEAR_SCREEN);
+
+ vo->want_redraw = true;
+ return 0;
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct priv *p = vo->priv;
+ struct mp_image *src = frame->current;
+ if (!src)
+ return;
+ // XXX: pan, crop etc.
+ mp_sws_scale(p->sws, p->frame, src);
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ int width, height;
+ get_win_size(vo, &width, &height);
+
+ if (vo->dwidth != width || vo->dheight != height)
+ reconfig(vo, vo->params);
+
+ if (p->opts.algo == ALGO_PLAIN) {
+ write_plain(
+ vo->dwidth, vo->dheight, p->swidth, p->sheight,
+ p->frame->planes[0], p->frame->stride[0],
+ p->opts.term256, p->lut);
+ } else {
+ write_half_blocks(
+ vo->dwidth, vo->dheight, p->swidth, p->sheight,
+ p->frame->planes[0], p->frame->stride[0],
+ p->opts.term256, p->lut);
+ }
+ fflush(stdout);
+}
+
+static void uninit(struct vo *vo)
+{
+ printf(TERM_ESC_RESTORE_CURSOR);
+ printf(TERM_ESC_NORMAL_SCREEN);
+ struct priv *p = vo->priv;
+ if (p->frame)
+ talloc_free(p->frame);
+}
+
+static int preinit(struct vo *vo)
+{
+ // most terminal characters aren't 1:1, so we default to 2:1.
+ // if user passes their own value of choice, it'll be scaled accordingly.
+ vo->monitor_par = vo->opts->monitor_pixel_aspect * 2;
+
+ struct priv *p = vo->priv;
+ p->sws = mp_sws_alloc(vo);
+ p->sws->log = vo->log;
+ mp_sws_enable_cmdline_opts(p->sws, vo->global);
+
+ for (int i = 0; i < 256; ++i) {
+ char buff[8];
+ p->lut[i].width = snprintf(buff, sizeof(buff), ";%d", i);
+ memcpy(p->lut[i].str, buff, 4); // some strings may not end on a null byte, but that's ok.
+ }
+
+ printf(TERM_ESC_HIDE_CURSOR);
+ printf(TERM_ESC_ALT_SCREEN);
+
+ 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;
+}
+
+#define OPT_BASE_STRUCT struct priv
+
+const struct vo_driver video_out_tct = {
+ .name = "tct",
+ .description = "true-color terminals",
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
+ .uninit = uninit,
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .opts.algo = ALGO_HALF_BLOCKS,
+ },
+ .options = (const m_option_t[]) {
+ {"algo", OPT_CHOICE(opts.algo,
+ {"plain", ALGO_PLAIN},
+ {"half-blocks", ALGO_HALF_BLOCKS})},
+ {"width", OPT_INT(opts.width)},
+ {"height", OPT_INT(opts.height)},
+ {"256", OPT_BOOL(opts.term256)},
+ {0}
+ },
+ .options_prefix = "vo-tct",
+};