Merge branch 'master' of github.com:dankamongmen/notcurses

dankamongmen/thirdpass
nick black 4 years ago
commit 16603dda27
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -119,6 +119,7 @@ if(NOT "${HAVE_QRCODEGEN_H}")
endif()
set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen)
endif()
find_library(LIBM m)
find_library(LIBRT rt)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -163,6 +164,7 @@ target_link_libraries(notcurses-core
PRIVATE
"${TERMINFO_LIBRARIES}"
"${READLINE_LIBRARIES}"
"${LIBM}"
"${LIBRT}"
unistring
PUBLIC
@ -172,6 +174,7 @@ target_link_libraries(notcurses-core-static
PRIVATE
"${TERMINFO_STATIC_LIBRARIES}"
"${READLINE_STATIC_LIBRARIES}"
"${LIBM}"
"${LIBRT}"
unistring
PUBLIC

@ -103,14 +103,14 @@ portability, you should by all means use that fine library.
Minimum versions generally indicate the oldest version I've tested with; it
may well be possible to use still older versions. Let me know of any successes!
* (build) A C11 and a C++17 compiler
* (build) CMake 3.14.0+
* (build) CMake 3.14.0+ and a C11 compiler
* (OPTIONAL) (OpenImageIO, testing, C++ bindings): A C++17 compiler
* (build+runtime) From [NCURSES](https://invisible-island.net/ncurses/announce.html): terminfo 6.1+
* (build+runtime) GNU [libunistring](https://www.gnu.org/software/libunistring/) 0.9.10+
* (build+runtime) GNU [Readline](https://www.gnu.org/software/readline/) 8.0+
* (OPTIONAL) (build+runtime) From QR-Code-generator: [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+
* (OPTIONAL) (build+runtime) From [FFmpeg](https://www.ffmpeg.org/): libswscale 5.0+, libavformat 57.0+, libavutil 56.0+
* (OPTIONAL) (build+runtime) [OpenImageIO](https://github.com/OpenImageIO/oiio) 2.15.0+
* (OPTIONAL) (build+runtime) [OpenImageIO](https://github.com/OpenImageIO/oiio) 2.15.0+, requires C++
* (OPTIONAL) (testing) [Doctest](https://github.com/onqtam/doctest) 2.3.5+
* (OPTIONAL) (documentation) [pandoc](https://pandoc.org/index.html) 1.19.2+
* (OPTIONAL) (python bindings): Python 3.7+, [CFFI](https://pypi.org/project/cffi/) 1.13.2+, [pypandoc](https://pypi.org/project/pypandoc/) 1.5+

@ -19,11 +19,11 @@ relies on the font. Patches to correct/complete this table are very welcome!
| --------------- | ------------------ | ----- | ------ | ----------------------- | ----- |
| [Alacritty](https://github.com/alacritty/alacritty) | ✅ | ✅ |❌ |`TERM=alacritty` `COLORTERM=24bit` | |
| [FBterm](https://github.com/zhangyuanwei/fbterm) | ❌ | ? |? |`TERM=fbterm` | 256 colors, no RGB color. |
| [foot](https://codeberg.org/dnkl/foot) | ✅ | ✅ |✅ |`TERM=foot` | |
| [foot](https://codeberg.org/dnkl/foot) | ✅ | ✅ |✅ |`TERM=foot` | Sixel support. |
| [Gnome Terminal](https://gitlab.gnome.org/GNOME/gnome-terminal) | | ❌ |✅ |`TERM=gnome` `COLORTERM=24bit` | `ccc` support *is* available when run with `vte-256color`. |
| [Guake](https://github.com/Guake/guake) | | ? |? | | |
| [ITerm2](https://github.com/gnachman/iTerm2) | | ? |? | | |
| [Kitty](https://github.com/kovidgoyal/kitty) | ✅ | ✅ |✅ |`TERM=xterm-kitty` | |
| [Kitty](https://github.com/kovidgoyal/kitty) | ✅ | ✅ |✅ |`TERM=xterm-kitty` | See below. |
| [kmscon](https://github.com/dvdhrm/kmscon) | | ? |? |`TERM=xterm-256color` | No RGB color AFAICT, nor any distinct terminfo entry. |
| [Konsole](https://invent.kde.org/utilities/konsole) | ❌ | ❌ |? |`TERM=konsole-direct` | |
| Linux console | ❌ | ✅ |see [below](#the-linux-console) |`TERM=linux` `COLORTERM=24bit` | 8 (512 glyph fonts) or 16 (256 glyph fonts) colors max, but RGB values are downsampled to a 256-index palette. See below. |
@ -37,7 +37,7 @@ relies on the font. Patches to correct/complete this table are very welcome!
| Terminology | ❌ | ? |? | ? | |
| [Tilda](https://github.com/lanoxx/tilda) | | ? |? | ? | |
| [tmux](https://github.com/tmux/tmux/wiki) | | ❌ |n/a |`TERM=tmux-256color` `COLORTERM=24bit` | `tmux.conf` must apply `Tc`; see below. `bce` is available with the `tmux-256color-bce` definition. |
| [wezterm](https://github.com/wez/wezterm) | | ✅ |? |`TERM=wezterm` `COLORTERM=24bit` | |
| [wezterm](https://github.com/wez/wezterm) | | ✅ |? |`TERM=wezterm` `COLORTERM=24bit` | See below. |
| [Windows Terminal](https://github.com/microsoft/terminal)| | ? |? | ? | |
| [wterm](https://github.com/majestrate/wterm) | | ? |? | ? | |
| [XFCE4 Terminal](https://gitlab.xfce.org/apps/xfce4-terminal) | ❌ | ✅ |✅ |`TERM=xfce` `COLORTERM=24bit` | No `xfce-direct` variant exists. |
@ -45,7 +45,8 @@ relies on the font. Patches to correct/complete this table are very welcome!
| [Yakuake](https://github.com/KDE/yakuake) | | ? |? | ? | |
Note that `xfce4-terminal`, `gnome-terminal`, etc. are essentially skinning
atop the common VTE ("Virtual TErminal") library.
atop the common GNOME [VTE ("Virtual
TErminal")](https://gitlab.gnome.org/GNOME/vte) library.
## Kitty
@ -63,6 +64,11 @@ be demonstrated with the `kittyzapper` binary (`src/poc/kittyzapper.c`).
Kitty is furthermore the only terminal I know to lack the `bce` (Background
Color Erase) capability, but Notcurses never relies on `bce` behavior.
## Wezterm
Wezterm [implements](https://wezfurlong.org/wezterm/escape-sequences.html) some
interesting underline options, and the iTerm2 graphic protocol.
## GNU screen
GNU screen does have 24-bit color support, but only in the 5.X series. Note

@ -0,0 +1,487 @@
#include <math.h>
#include <float.h>
#include <limits.h>
#include <string.h>
#include "internal.h"
#define MAXWIDTH 2
#define CREATE(T, X) \
typedef struct nc##X##plot { \
T* slots; \
T miny, maxy; \
ncplane* ncp; \
/* sloutcount-element circular buffer of samples. the newest one (rightmost) \
is at slots[slotstart]; they get older as you go back (and around). \
elements. slotcount is max(columns, rangex), less label room. */ \
int64_t slotx; /* x value corresponding to slots[slotstart] (newest x) */ \
uint64_t maxchannels; \
uint64_t minchannels; \
uint16_t legendstyle; \
bool vertical_indep; /* not yet implemented FIXME */ \
const struct blitset* bset; \
char* title; \
/* requested number of slots. 0 for automatically setting the number of slots \
to span the horizontal area. if there are more slots than there are \
columns, we prefer showing more recent slots to less recent. if there are \
fewer slots than there are columns, they prefer the left side. */ \
int rangex; \
/* domain minimum and maximum. if detectdomain is true, these are \
progressively enlarged/shrunk to fit the sample set. if not, samples \
outside these bounds are counted, but the displayed range covers only this. */ \
int slotcount; \
int slotstart; /* index of most recently-written slot */ \
bool labelaxisd; /* label dependent axis (consumes PREFIXCOLUMNS columns) */ \
bool exponentiali; /* exponential independent axis */ \
bool detectdomain; /* is domain detection in effect (stretch the domain)? */ \
bool detectonlymax; /* domain detection applies only to max, not min */ \
bool printsample; /* print the most recent sample */ \
} nc##X##plot; \
\
int redraw_plot_##T(nc##X##plot* ncp){ \
ncplane_erase(ncp->ncp); \
const int scale = ncp->bset->width; \
int dimy, dimx; \
ncplane_dim_yx(ncp->ncp, &dimy, &dimx); \
const int scaleddim = dimx * scale; \
/* each transition is worth this much change in value */ \
const size_t states = ncp->bset->height + 1; \
/* FIXME can we not rid ourselves of this meddlesome double? either way, the \
interval is one row's range (for linear plots), or the base (base^slots== \
maxy-miny) of the range (for exponential plots). */ \
double interval; \
if(ncp->exponentiali){ \
if(ncp->maxy > ncp->miny){ \
interval = pow(ncp->maxy - ncp->miny, (double)1 / (dimy * states)); \
/* fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states); */ \
}else{ \
interval = 0; \
} \
}else{ \
interval = ncp->maxy < ncp->miny ? 0 : (ncp->maxy - ncp->miny) / ((double)dimy * states); \
} \
const int startx = ncp->labelaxisd ? PREFIXCOLUMNS : 0; /* plot cols begin here */ \
/* if we want fewer slots than there are available columns, our final column \
will be other than the plane's final column. most recent x goes here. */ \
const int finalx = (ncp->slotcount < scaleddim - 1 - (startx * scale) ? startx + (ncp->slotcount / scale) - 1 : dimx - 1); \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \
if(ncp->labelaxisd){ \
/* show the *top* of each interval range */ \
for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, y, 0, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
char buf[PREFIXSTRLEN + 1]; \
if(ncp->exponentiali){ \
if(y == dimy - 1){ /* we cheat on the top row to exactly match maxy */ \
ncmetric(ncp->maxy * 100, 100, buf, 0, 1000, '\0'); \
}else{ \
ncmetric(pow(interval, (y + 1) * states) * 100, 100, buf, 0, 1000, '\0'); \
} \
}else{ \
ncmetric((ncp->maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0, 1000, '\0'); \
} \
if(y == dimy - 1 && strlen(ncp->title)){ \
ncplane_printf_yx(ncp->ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, ncp->title); \
}else{ \
ncplane_printf_yx(ncp->ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf); \
} \
} \
}else if(strlen(ncp->title)){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, dimy - 1, 0, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
ncplane_printf_yx(ncp->ncp, 0, PREFIXCOLUMNS - strlen(ncp->title), "%s", ncp->title); \
} \
ncplane_set_styles(ncp->ncp, NCSTYLE_NONE); \
if(finalx < startx){ /* exit on pathologically narrow planes */ \
return 0; \
} \
if(!interval){ \
interval = 1; \
} \
int idx = ncp->slotstart; /* idx holds the real slot index; we move backwards */ \
for(int x = finalx ; x >= startx ; --x){ \
/* a single column might correspond to more than 1 ('scale', up to \
MAXWIDTH) slot's worth of samples. prepare the working gval set. */ \
T gvals[MAXWIDTH]; \
/* load it retaining the same ordering we have in the actual array */ \
for(int i = scale - 1 ; i >= 0 ; --i){ \
gvals[i] = ncp->slots[idx]; /* clip the value at the limits of the graph */ \
if(gvals[i] < ncp->miny){ \
gvals[i] = ncp->miny; \
} \
if(gvals[i] > ncp->maxy){ \
gvals[i] = ncp->maxy; \
} \
/* FIXME if there are an odd number, only go up through the valid ones... */ \
if(--idx < 0){ \
idx = ncp->slotcount - 1; \
} \
} \
/* starting from the least-significant row, progress in the more significant \
direction, drawing egcs from the grid specification, aborting early if \
we can't draw anything in a given cell. */ \
T intervalbase = ncp->miny; \
const wchar_t* egc = ncp->bset->egcs; \
bool done = !ncp->bset->fill; \
for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, y, x, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
size_t egcidx = 0, sumidx = 0; \
/* if we've got at least one interval's worth on the number of positions \
times the number of intervals per position plus the starting offset, \
we're going to print *something* */ \
for(int i = 0 ; i < scale ; ++i){ \
sumidx *= states; \
if(intervalbase < gvals[i]){ \
if(ncp->exponentiali){ \
/* we want the log-base-interval of gvals[i] */ \
double scaled = log(gvals[i] - ncp->miny) / log(interval); \
double sival = intervalbase ? log(intervalbase) / log(interval) : 0; \
egcidx = scaled - sival; \
}else{ \
egcidx = (gvals[i] - intervalbase) / interval; \
} \
if(egcidx >= states){ \
egcidx = states - 1; \
done = false; \
} \
sumidx += egcidx; \
}else{ \
egcidx = 0; \
} \
/* printf(stderr, "y: %d i(scale): %d gvals[%d]: %ju egcidx: %zu sumidx: %zu interval: %f intervalbase: %ju\n", y, i, i, gvals[i], egcidx, sumidx, interval, intervalbase); */ \
} \
/* if we're not UTF8, we can only arrive here via NCBLIT_1x1 (otherwise \
we would have errored out during construction). even then, however, \
we need handle ASCII differently, since it can't print full block. \
in ASCII mode, sumidx != 0 means swap colors and use space. in all \
modes, sumidx == 0 means don't do shit, since we erased earlier. */ \
/* if(sumidx)fprintf(stderr, "dimy: %d y: %d x: %d sumidx: %zu egc[%zu]: %lc\n", dimy, y, x, sumidx, sumidx, egc[sumidx]); */ \
if(sumidx){ \
if(notcurses_canutf8(ncplane_notcurses(ncp->ncp))){ \
char utf8[MB_CUR_MAX + 1]; \
int bytes = wctomb(utf8, egc[sumidx]); \
if(bytes < 0){ \
return -1; \
} \
utf8[bytes] = '\0'; \
nccell* c = ncplane_cell_ref_yx(ncp->ncp, dimy - y - 1, x); \
cell_set_bchannel(c, channels_bchannel(channels)); \
cell_set_fchannel(c, channels_fchannel(channels)); \
cell_set_styles(c, NCSTYLE_NONE); \
if(pool_blit_direct(&ncp->ncp->pool, c, utf8, bytes, 1) <= 0){ \
return -1; \
} \
}else{ \
const uint64_t swapbg = channels_bchannel(channels); \
const uint64_t swapfg = channels_fchannel(channels); \
channels_set_bchannel(&channels, swapfg); \
channels_set_fchannel(&channels, swapbg); \
ncplane_set_channels(ncp->ncp, channels); \
if(ncplane_putchar_yx(ncp->ncp, dimy - y - 1, x, ' ') <= 0){ \
return -1; \
} \
channels_set_bchannel(&channels, swapbg); \
channels_set_fchannel(&channels, swapfg); \
ncplane_set_channels(ncp->ncp, channels); \
} \
} \
if(done){ \
break; \
} \
if(ncp->exponentiali){ \
intervalbase = ncp->miny + pow(interval, (y + 1) * states - 1); \
}else{ \
intervalbase += (states * interval); \
} \
} \
} \
if(ncp->printsample){ \
int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \
ncplane_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)ncp->slots[lastslot]); \
} \
ncplane_home(ncp->ncp); \
return 0; \
} \
\
static bool \
create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T miny, const T maxy, \
const T trueminy, const T truemaxy){ \
ncplot_options zeroed = {}; \
if(!opts){ \
opts = &zeroed; \
} \
if(opts->flags >= (NCPLOT_OPTION_PRINTSAMPLE << 1u)){ \
logwarn(ncplane_notcurses(n), "Provided unsupported flags %016jx\n", (uintmax_t)opts->flags); \
} \
const notcurses* nc = ncplane_notcurses_const(n); \
/* if miny == maxy (enabling domain detection), they both must be equal to 0 */ \
if(miny == maxy && miny){ \
ncplane_destroy(n); \
return false; \
} \
if(opts->rangex < 0){ \
logerror(nc, "Supplied negative independent range %d\n", opts->rangex); \
ncplane_destroy(n); \
return false; \
} \
if(maxy < miny){ \
ncplane_destroy(n); \
return false; \
} \
/* DETECTMAXONLY can't be used without domain detection */ \
if(opts->flags & NCPLOT_OPTION_DETECTMAXONLY && (miny != maxy)){ \
logerror(nc, "Supplied DETECTMAXONLY without domain detection"); \
ncplane_destroy(n); \
return false; \
} \
const notcurses* notc = ncplane_notcurses(n); \
ncblitter_e blitfxn = opts ? opts->gridtype : NCBLIT_DEFAULT; \
if(blitfxn == NCBLIT_DEFAULT){ \
blitfxn = ncplot_defblitter(notc); \
} \
bool degrade_blitter = !(opts && (opts->flags & NCPLOT_OPTION_NODEGRADE)); \
const struct blitset* bset = lookup_blitset(&notc->tcache, blitfxn, degrade_blitter); \
if(bset == NULL){ \
ncplane_destroy(n); \
return false; \
} \
int sdimy, sdimx; \
ncplane_dim_yx(n, &sdimy, &sdimx); \
if(sdimx <= 0){ \
ncplane_destroy(n); \
return false; \
} \
int dimx = sdimx; \
ncpp->title = strdup(opts->title ? opts->title : ""); \
ncpp->rangex = opts->rangex; \
/* if we're sizing the plot based off the plane dimensions, scale it by the \
plot geometry's width for all calculations */ \
const int scaleddim = dimx * bset->width; \
const int scaledprefixlen = PREFIXCOLUMNS * bset->width; \
if((ncpp->slotcount = ncpp->rangex) == 0){ \
ncpp->slotcount = scaleddim; \
} \
if(dimx < ncpp->rangex){ \
ncpp->slotcount = scaleddim; \
} \
ncpp->legendstyle = opts->legendstyle; \
if( (ncpp->labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){ \
if(ncpp->slotcount + scaledprefixlen > scaleddim){ \
if(scaleddim > scaledprefixlen){ \
ncpp->slotcount = scaleddim - scaledprefixlen; \
} \
} \
} \
size_t slotsize = sizeof(*ncpp->slots) * ncpp->slotcount; \
ncpp->slots = malloc(slotsize); \
if(ncpp->slots == NULL){ \
ncplane_destroy(n); \
return false; \
} \
memset(ncpp->slots, 0, slotsize); \
ncpp->ncp = n; \
ncpp->maxchannels = opts->maxchannels; \
ncpp->minchannels = opts->minchannels; \
ncpp->bset = bset; \
ncpp->miny = miny; \
ncpp->maxy = maxy; \
ncpp->vertical_indep = opts->flags & NCPLOT_OPTION_VERTICALI; \
ncpp->exponentiali = opts->flags & NCPLOT_OPTION_EXPONENTIALD; \
ncpp->detectonlymax = opts->flags & NCPLOT_OPTION_DETECTMAXONLY; \
ncpp->printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE; \
if( (ncpp->detectdomain = (miny == maxy)) ){ \
ncpp->maxy = trueminy; \
ncpp->miny = truemaxy; \
} \
ncpp->slotstart = 0; \
ncpp->slotx = 0; \
redraw_plot_##T(ncpp); \
return true; \
} \
/* if x is less than the window, return -1, as the sample will be thrown away. \
if the x is within the current window, find the proper slot and update it. \
otherwise, the x is the newest sample. if it is obsoletes all existing slots, \
reset them, and write the new sample anywhere. otherwise, write it to the \
proper slot based on the current newest slot. */ \
int window_slide_##T(nc##X##plot* ncp, int64_t x){ \
if(x < ncp->slotx - (ncp->slotcount - 1)){ /* x is behind window, won't be counted */ \
return -1; \
}else if(x <= ncp->slotx){ /* x is within window, do nothing */ \
return 0; \
} /* x is newest; we might be keeping some, might not */ \
int64_t xdiff = x - ncp->slotx; /* the raw amount we're advancing */ \
ncp->slotx = x; \
if(xdiff >= ncp->slotcount){ /* we're throwing away all old samples, write to 0 */ \
memset(ncp->slots, 0, sizeof(*ncp->slots) * ncp->slotcount); \
ncp->slotstart = 0; \
return 0; \
} \
/* we're throwing away only xdiff slots, which is less than slotcount. \
first, we'll try to clear to the right...number to reset on the right of \
the circular buffer. min of (available at current or to right, xdiff) */ \
int slotsreset = ncp->slotcount - ncp->slotstart - 1; \
if(slotsreset > xdiff){ \
slotsreset = xdiff; \
} \
if(slotsreset){ \
memset(ncp->slots + ncp->slotstart + 1, 0, slotsreset * sizeof(*ncp->slots)); \
} \
ncp->slotstart = (ncp->slotstart + xdiff) % ncp->slotcount; \
xdiff -= slotsreset; \
if(xdiff){ /* throw away some at the beginning */ \
memset(ncp->slots, 0, xdiff * sizeof(*ncp->slots)); \
} \
return 0; \
} \
\
/* x must be within n's window at this point */ \
inline void update_sample_##T(nc##X##plot* ncp, int64_t x, T y, bool reset){ \
const int64_t diff = ncp->slotx - x; /* amount behind */ \
const int idx = (ncp->slotstart + ncp->slotcount - diff) % ncp->slotcount; \
if(reset){ \
ncp->slots[idx] = y; \
}else{ \
ncp->slots[idx] += y; \
} \
} \
\
/* if we're doing domain detection, update the domain to reflect the value we \
just set. if we're not, check the result against the known ranges, and \
return -1 if the value is outside of that range. */ \
int update_domain_##T(nc##X##plot* ncp, uint64_t x){ \
const T val = ncp->slots[x % ncp->slotcount]; \
if(ncp->detectdomain){ \
if(val > ncp->maxy){ \
ncp->maxy = val; \
} \
if(!ncp->detectonlymax){ \
if(val < ncp->miny){ \
ncp->miny = val; \
} \
} \
return 0; \
} \
if(val > ncp->maxy || val < ncp->miny){ \
return -1; \
} \
return 0; \
} \
int set_sample_##T(nc##X##plot* ncpp, uint64_t x, T y){ \
if(window_slide_##T(ncpp, x)){ \
return -1; \
} \
update_sample_##T(ncpp, x, y, true); \
if(update_domain_##T(ncpp, x)){ \
return -1; \
} \
return redraw_plot_##T(ncpp); \
} \
/* Add to or set the value corresponding to this x. If x is beyond the current \
x window, the x window is advanced to include x, and values passing beyond \
the window are lost. The first call will place the initial window. The plot \
will be redrawn, but notcurses_render() is not called. */ \
int add_sample_##T(nc##X##plot* ncpp, uint64_t x, T y){ \
if(window_slide_##T(ncpp, x)){ \
return -1; \
} \
update_sample_##T(ncpp, x, y, false); \
if(update_domain_##T(ncpp, x)){ \
return -1; \
} \
return redraw_plot_##T(ncpp); \
} \
int sample_##T(const nc##X##plot* ncp, int64_t x, T* y){ \
if(x < ncp->slotx - (ncp->slotcount - 1)){ /* x is behind window */ \
return -1; \
}else if(x > ncp->slotx){ /* x is ahead of window */ \
return -1; \
} \
*y = ncp->slots[x % ncp->slotcount]; \
return 0; \
} \
void destroy_##T(nc##X##plot* ncpp){ \
free(ncpp->slots); \
ncplane_destroy(ncpp->ncp); \
}
CREATE(uint64_t, u)
CREATE(double, d)
ncuplot* ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy){
ncuplot* ret = malloc(sizeof(*ret));
if(ret){
memset(ret, 0, sizeof(*ret));
if(create_uint64_t(ret, n, opts, miny, maxy, 0, UINT64_MAX)){
return ret;
}
free(ret);
ret = NULL;
}
return ret;
}
ncplane* ncuplot_plane(ncuplot* n){
return n->ncp;
}
int ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y){
return add_sample_uint64_t(n, x, y);
}
int ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y){
return set_sample_uint64_t(n, x, y);
}
void ncuplot_destroy(ncuplot* n){
if(n){
destroy_uint64_t(n);
free(n);
}
}
ncdplot* ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy){
ncdplot* ret = malloc(sizeof(*ret));
if(ret){
memset(ret, 0, sizeof(*ret));
if(create_double(ret, n, opts, miny, maxy, -DBL_MAX, DBL_MAX)){
return ret;
}
free(ret);
ret = NULL;
}
return ret;
}
ncplane* ncdplot_plane(ncdplot* n){
return n->ncp;
}
int ncdplot_add_sample(ncdplot* n, uint64_t x, double y){
return add_sample_double(n, x, y);
}
int ncdplot_set_sample(ncdplot* n, uint64_t x, double y){
return set_sample_double(n, x, y);
}
int ncuplot_sample(const ncuplot* n, uint64_t x, uint64_t* y){
return sample_uint64_t(n, x, y);
}
int ncdplot_sample(const ncdplot* n, uint64_t x, double* y){
return sample_double(n, x, y);
}
void ncdplot_destroy(ncdplot* n) {
if(n){
destroy_double(n);
free(n);
}
}

@ -1,504 +0,0 @@
#include <cmath>
#include <array>
#include <limits>
#include <string>
#include "internal.h"
template<typename T>
class ncppplot {
public:
// these were all originally plain C, sorry for the non-idiomatic usage FIXME
static bool create(ncppplot<T>* ncpp, ncplane* n, const ncplot_options* opts, T miny, T maxy) {
ncplot_options zeroed = {};
if(!opts){
opts = &zeroed;
}
if(opts->flags >= (NCPLOT_OPTION_PRINTSAMPLE << 1u)){
logwarn(ncplane_notcurses(n), "Provided unsupported flags %016jx\n", (uintmax_t)opts->flags);
}
auto nc = ncplane_notcurses(n);
// if miny == maxy (enabling domain detection), they both must be equal to 0
if(miny == maxy && miny){
ncplane_destroy(n);
return false;
}
if(opts->rangex < 0){
logerror(nc, "Supplied negative independent range %d\n", opts->rangex);
ncplane_destroy(n);
return false;
}
if(maxy < miny){
ncplane_destroy(n);
return false;
}
// DETECTMAXONLY can't be used without domain detection
if(opts->flags & NCPLOT_OPTION_DETECTMAXONLY && (miny != maxy)){
logerror(nc, "Supplied DETECTMAXONLY without domain detection");
ncplane_destroy(n);
return false;
}
const notcurses* notc = ncplane_notcurses(n);
ncblitter_e blitfxn = opts ? opts->gridtype : NCBLIT_DEFAULT;
if(blitfxn == NCBLIT_DEFAULT){
blitfxn = ncplot_defblitter(notc);
}
bool degrade_blitter = !(opts && (opts->flags & NCPLOT_OPTION_NODEGRADE));
auto bset = lookup_blitset(&notc->tcache, blitfxn, degrade_blitter);
if(bset == nullptr){
ncplane_destroy(n);
return false;
}
int sdimy, sdimx;
ncplane_dim_yx(n, &sdimy, &sdimx);
if(sdimx <= 0){
ncplane_destroy(n);
return false;
}
int dimx = sdimx;
if(opts->title){
ncpp->title = std::string(opts->title);
}
ncpp->rangex = opts->rangex;
// if we're sizing the plot based off the plane dimensions, scale it by the
// plot geometry's width for all calculations
const int scaleddim = dimx * bset->width;
const int scaledprefixlen = PREFIXCOLUMNS * bset->width;
if((ncpp->slotcount = ncpp->rangex) == 0){
ncpp->slotcount = scaleddim;
}
if(dimx < ncpp->rangex){
ncpp->slotcount = scaleddim;
}
ncpp->legendstyle = opts->legendstyle;
if( (ncpp->labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){
if(ncpp->slotcount + scaledprefixlen > scaleddim){
if(scaleddim > scaledprefixlen){
ncpp->slotcount = scaleddim - scaledprefixlen;
}
}
}
size_t slotsize = sizeof(*ncpp->slots) * ncpp->slotcount;
ncpp->slots = static_cast<T*>(malloc(slotsize));
if(ncpp->slots == nullptr){
ncplane_destroy(n);
return false;
}
memset(ncpp->slots, 0, slotsize);
ncpp->ncp = n;
ncpp->maxchannels = opts->maxchannels;
ncpp->minchannels = opts->minchannels;
ncpp->bset = bset;
ncpp->miny = miny;
ncpp->maxy = maxy;
ncpp->vertical_indep = opts->flags & NCPLOT_OPTION_VERTICALI;
ncpp->exponentiali = opts->flags & NCPLOT_OPTION_EXPONENTIALD;
ncpp->detectonlymax = opts->flags & NCPLOT_OPTION_DETECTMAXONLY;
ncpp->printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE;
if( (ncpp->detectdomain = (miny == maxy)) ){
ncpp->maxy = 0;
ncpp->miny = std::numeric_limits<T>::max();
}
ncpp->slotstart = 0;
ncpp->slotx = 0;
ncpp->redraw_plot();
return true;
}
void destroy(){
free(slots);
ncplane_destroy(ncp);
}
auto redraw_plot() -> int {
ncplane_erase(ncp);
const int scale = bset->width;
int dimy, dimx;
ncplane_dim_yx(ncp, &dimy, &dimx);
const int scaleddim = dimx * scale;
// each transition is worth this much change in value
const size_t states = bset->height + 1;
// FIXME can we not rid ourselves of this meddlesome double? either way, the
// interval is one row's range (for linear plots), or the base (base^slots==
// maxy-miny) of the range (for exponential plots).
double interval;
if(exponentiali){
if(maxy > miny){
interval = pow(maxy - miny, (double)1 / (dimy * states));
//fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states);
}else{
interval = 0;
}
}else{
interval = maxy < miny ? 0 : (maxy - miny) / ((double)dimy * states);
}
const int startx = labelaxisd ? PREFIXCOLUMNS : 0; // plot cols begin here
// if we want fewer slots than there are available columns, our final column
// will be other than the plane's final column. most recent x goes here.
const int finalx = (slotcount < scaleddim - 1 - (startx * scale) ? startx + (slotcount / scale) - 1 : dimx - 1);
ncplane_set_styles(ncp, legendstyle);
if(labelaxisd){
// show the *top* of each interval range
for(int y = 0 ; y < dimy ; ++y){
uint64_t channels = 0;
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, y, 0, dimy, dimx);
ncplane_set_channels(ncp, channels);
char buf[PREFIXSTRLEN + 1];
if(exponentiali){
if(y == dimy - 1){ // we cheat on the top row to exactly match maxy
ncmetric(maxy * 100, 100, buf, 0, 1000, '\0');
}else{
ncmetric(pow(interval, (y + 1) * states) * 100, 100, buf, 0, 1000, '\0');
}
}else{
ncmetric((maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0, 1000, '\0');
}
if(y == dimy - 1 && !title.empty()){
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, title.c_str());
}else{
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf);
}
}
}else if(!title.empty()){
uint64_t channels = 0;
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, dimy - 1, 0, dimy, dimx);
ncplane_set_channels(ncp, channels);
ncplane_printf_yx(ncp, 0, PREFIXCOLUMNS - title.length(), "%s", title.c_str());
}
ncplane_set_styles(ncp, NCSTYLE_NONE);
if(finalx < startx){ // exit on pathologically narrow planes
return 0;
}
if(!interval){
interval = 1;
}
#define MAXWIDTH 2
int idx = slotstart; // idx holds the real slot index; we move backwards
for(int x = finalx ; x >= startx ; --x){
// a single column might correspond to more than 1 ('scale', up to
// MAXWIDTH) slot's worth of samples. prepare the working gval set.
T gvals[MAXWIDTH];
// load it retaining the same ordering we have in the actual array
for(int i = scale - 1 ; i >= 0 ; --i){
gvals[i] = slots[idx]; // clip the value at the limits of the graph
if(gvals[i] < miny){
gvals[i] = miny;
}
if(gvals[i] > maxy){
gvals[i] = maxy;
}
// FIXME if there are an odd number, only go up through the valid ones...
if(--idx < 0){
idx = slotcount - 1;
}
}
// starting from the least-significant row, progress in the more significant
// direction, drawing egcs from the grid specification, aborting early if
// we can't draw anything in a given cell.
T intervalbase = miny;
const wchar_t* egc = bset->egcs;
bool done = !bset->fill;
for(int y = 0 ; y < dimy ; ++y){
uint64_t channels = 0;
calc_gradient_channels(&channels, minchannels, minchannels,
maxchannels, maxchannels, y, x, dimy, dimx);
ncplane_set_channels(ncp, channels);
size_t egcidx = 0, sumidx = 0;
// if we've got at least one interval's worth on the number of positions
// times the number of intervals per position plus the starting offset,
// we're going to print *something*
for(int i = 0 ; i < scale ; ++i){
sumidx *= states;
if(intervalbase < gvals[i]){
if(exponentiali){
// we want the log-base-interval of gvals[i]
double scaled = log(gvals[i] - miny) / log(interval);
double sival = intervalbase ? log(intervalbase) / log(interval) : 0;
egcidx = scaled - sival;
}else{
egcidx = (gvals[i] - intervalbase) / interval;
}
if(egcidx >= states){
egcidx = states - 1;
done = false;
}
sumidx += egcidx;
}else{
egcidx = 0;
}
//fprintf(stderr, "y: %d i(scale): %d gvals[%d]: %ju egcidx: %zu sumidx: %zu interval: %f intervalbase: %ju\n", y, i, i, gvals[i], egcidx, sumidx, interval, intervalbase);
}
// if we're not UTF8, we can only arrive here via NCBLIT_1x1 (otherwise
// we would have errored out during construction). even then, however,
// we need handle ASCII differently, since it can't print full block.
// in ASCII mode, sumidx != 0 means swap colors and use space. in all
// modes, sumidx == 0 means don't do shit, since we erased earlier.
//if(sumidx)fprintf(stderr, "dimy: %d y: %d x: %d sumidx: %zu egc[%zu]: %lc\n", dimy, y, x, sumidx, sumidx, egc[sumidx]);
if(sumidx){
if(notcurses_canutf8(ncplane_notcurses(ncp))){
char utf8[MB_CUR_MAX + 1];
int bytes = wctomb(utf8, egc[sumidx]);
if(bytes < 0){
return -1;
}
utf8[bytes] = '\0';
nccell* c = ncplane_cell_ref_yx(ncp, dimy - y - 1, x);
cell_set_bchannel(c, channels_bchannel(channels));
cell_set_fchannel(c, channels_fchannel(channels));
cell_set_styles(c, NCSTYLE_NONE);
if(pool_blit_direct(&ncp->pool, c, utf8, bytes, 1) <= 0){
return -1;
}
}else{
const uint64_t swapbg = channels_bchannel(channels);
const uint64_t swapfg = channels_fchannel(channels);
channels_set_bchannel(&channels, swapfg);
channels_set_fchannel(&channels, swapbg);
ncplane_set_channels(ncp, channels);
if(ncplane_putchar_yx(ncp, dimy - y - 1, x, ' ') <= 0){
return -1;
}
channels_set_bchannel(&channels, swapbg);
channels_set_fchannel(&channels, swapfg);
ncplane_set_channels(ncp, channels);
}
}
if(done){
break;
}
if(exponentiali){
intervalbase = miny + pow(interval, (y + 1) * states - 1);
}else{
intervalbase += (states * interval);
}
}
}
if(printsample){
int lastslot = slotstart ? slotstart - 1 : slotcount - 1;
ncplane_set_styles(ncp, legendstyle);
ncplane_printf_aligned(ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)slots[lastslot]);
}
ncplane_home(ncp);
return 0;
}
// Add to or set the value corresponding to this x. If x is beyond the current
// x window, the x window is advanced to include x, and values passing beyond
// the window are lost. The first call will place the initial window. The plot
// will be redrawn, but notcurses_render() is not called.
auto add_sample(uint64_t x, T y) -> int {
if(window_slide(x)){
return -1;
}
update_sample(x, y, false);
if(update_domain(x)){
return -1;
}
return redraw_plot();
}
auto set_sample(uint64_t x, T y) -> int {
if(window_slide(x)){
return -1;
}
update_sample(x, y, true);
if(update_domain(x)){
return -1;
}
return redraw_plot();
}
auto sample(int64_t x, T* y) const -> int {
if(x < slotx - (slotcount - 1)){ // x is behind window
return -1;
}else if(x > slotx){ // x is ahead of window
return -1;
}
*y = slots[x % slotcount];
return 0;
}
// if we're doing domain detection, update the domain to reflect the value we
// just set. if we're not, check the result against the known ranges, and
// return -1 if the value is outside of that range.
auto update_domain(uint64_t x) -> int {
const T val = slots[x % slotcount];
if(detectdomain){
if(val > maxy){
maxy = val;
}
if(!detectonlymax){
if(val < miny){
miny = val;
}
}
return 0;
}
if(val > maxy || val < miny){
return -1;
}
return 0;
}
// if x is less than the window, return -1, as the sample will be thrown away.
// if the x is within the current window, find the proper slot and update it.
// otherwise, the x is the newest sample. if it is obsoletes all existing slots,
// reset them, and write the new sample anywhere. otherwise, write it to the
// proper slot based on the current newest slot.
auto window_slide(int64_t x) -> int {
if(x < slotx - (slotcount - 1)){ // x is behind window, won't be counted
return -1;
}else if(x <= slotx){ // x is within window, do nothing
return 0;
} // x is newest; we might be keeping some, might not
int64_t xdiff = x - slotx; // the raw amount we're advancing
slotx = x;
if(xdiff >= slotcount){ // we're throwing away all old samples, write to 0
memset(slots, 0, sizeof(*slots) * slotcount); // FIXME need a STL operation?
slotstart = 0;
return 0;
}
// we're throwing away only xdiff slots, which is less than slotcount.
// first, we'll try to clear to the right...number to reset on the right of
// the circular buffer. min of (available at current or to right, xdiff)
int slotsreset = slotcount - slotstart - 1;
if(slotsreset > xdiff){
slotsreset = xdiff;
}
if(slotsreset){
memset(slots + slotstart + 1, 0, slotsreset * sizeof(*slots));
}
slotstart = (slotstart + xdiff) % slotcount;
xdiff -= slotsreset;
if(xdiff){ // throw away some at the beginning
memset(slots, 0, xdiff * sizeof(*slots));
}
return 0;
}
// x must be within n's window at this point
inline void update_sample(int64_t x, T y, bool reset){
const int64_t diff = slotx - x; // amount behind
const int idx = (slotstart + slotcount - diff) % slotcount;
if(reset){
slots[idx] = y;
}else{
slots[idx] += y;
}
}
ncplane* ncp;
// sloutcount-element circular buffer of samples. the newest one (rightmost)
// is at slots[slotstart]; they get older as you go back (and around).
// elements. slotcount is max(columns, rangex), less label room.
T* slots;
int64_t slotx; // x value corresponding to slots[slotstart] (newest x)
private:
uint64_t maxchannels;
uint64_t minchannels;
uint16_t legendstyle;
bool vertical_indep; // not yet implemented FIXME
const struct blitset* bset;
std::string title;
// requested number of slots. 0 for automatically setting the number of slots
// to span the horizontal area. if there are more slots than there are
// columns, we prefer showing more recent slots to less recent. if there are
// fewer slots than there are columns, they prefer the left side.
int rangex;
// domain minimum and maximum. if detectdomain is true, these are
// progressively enlarged/shrunk to fit the sample set. if not, samples
// outside these bounds are counted, but the displayed range covers only this.
T miny, maxy;
int slotcount;
int slotstart; // index of most recently-written slot
bool labelaxisd; // label dependent axis (consumes PREFIXCOLUMNS columns)
bool exponentiali; // exponential independent axis
bool detectdomain; // is domain detection in effect (stretch the domain)?
bool detectonlymax; // domain detection applies only to max, not min
bool printsample; // print the most recent sample
};
using ncuplot = struct ncuplot {
ncppplot<uint64_t> n;
};
using ncdplot = struct ncdplot {
ncppplot<double> n;
};
extern "C" {
auto ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy) -> ncuplot* {
auto ret = new ncuplot;
if(ret){
if(ncppplot<uint64_t>::create(&ret->n, n, opts, miny, maxy)){
return ret;
}
delete ret;
}
return nullptr;
}
auto ncuplot_plane(ncuplot* n) -> ncplane* {
return n->n.ncp;
}
auto ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y) -> int {
return n->n.add_sample(x, y);
}
auto ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y) -> int {
return n->n.set_sample(x, y);
}
void ncuplot_destroy(ncuplot* n) {
if(n){
n->n.destroy();
delete n;
}
}
auto ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy) -> ncdplot* {
auto ret = new ncdplot;
if(ret){
if(ncppplot<double>::create(&ret->n, n, opts, miny, maxy)){
return ret;
}
delete ret;
}
return nullptr;
}
auto ncdplot_plane(ncdplot* n) -> ncplane* {
return n->n.ncp;
}
auto ncdplot_add_sample(ncdplot* n, uint64_t x, double y) -> int {
return n->n.add_sample(x, y);
}
auto ncdplot_set_sample(ncdplot* n, uint64_t x, double y) -> int {
return n->n.set_sample(x, y);
}
auto ncuplot_sample(const ncuplot* n, uint64_t x, uint64_t* y) -> int {
return n->n.sample(x, y);
}
auto ncdplot_sample(const ncdplot* n, uint64_t x, double* y) -> int {
return n->n.sample(x, y);
}
void ncdplot_destroy(ncdplot* n) {
if(n){
n->n.destroy();
delete n;
}
}
}

@ -24,11 +24,11 @@ typedef struct ncvisual {
} ncvisual;
static inline void
ncvisual_set_data(ncvisual* ncv, uint32_t* data, bool owned){
ncvisual_set_data(ncvisual* ncv, void* data, bool owned){
if(ncv->owndata){
free(ncv->data);
}
ncv->data = data;
ncv->data = (uint32_t*)data;
ncv->owndata = owned;
}

@ -1,6 +1,5 @@
#include "builddef.h"
#ifdef USE_FFMPEG
extern "C" {
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
@ -11,7 +10,6 @@ extern "C" {
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
}
#include "internal.h"
#include "visual-details.h"
@ -42,9 +40,9 @@ typedef struct ncvisual_details {
#define IMGALLOCALIGN 32
/*static void
print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) {
print_frame_summary(const AVCodecContext* cctx, const AVFrame* f){
char pfmt[128];
av_get_pix_fmt_string(pfmt, sizeof(pfmt), static_cast<enum AVPixelFormat>(f->format));
av_get_pix_fmt_string(pfmt, sizeof(pfmt), f->format);
fprintf(stderr, "Frame %05d (%d? %d?) %dx%d pfmt %d (%s)\n",
cctx->frame_number,
f->coded_picture_number,
@ -80,12 +78,12 @@ print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) {
}*/
static char*
deass(const char* ass) {
deass(const char* ass){
// SSA/ASS formats:
// Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
// FIXME more
if(strncmp(ass, "Dialogue:", strlen("Dialogue:"))){
return nullptr;
return NULL;
}
const char* delim = strchr(ass, ',');
int commas = 0; // we want 8
@ -94,7 +92,7 @@ deass(const char* ass) {
++commas;
}
if(!delim){
return nullptr;
return NULL;
}
// handle ASS syntax...\i0, \b0, etc.
char* dup = strdup(delim + 1);
@ -112,16 +110,16 @@ deass(const char* ass) {
return dup;
}
auto ffmpeg_subtitle(const ncvisual* ncv) -> char* {
char* ffmpeg_subtitle(const ncvisual* ncv){
for(unsigned i = 0 ; i < ncv->details->subtitle.num_rects ; ++i){
const AVSubtitleRect* rect = ncv->details->subtitle.rects[i];
if(rect->type == SUBTITLE_ASS){
return deass(rect->ass);
}else if(rect->type == SUBTITLE_TEXT) {;
}else if(rect->type == SUBTITLE_TEXT){;
return strdup(rect->text);
}
}
return nullptr;
return NULL;
}
static int
@ -135,7 +133,7 @@ averr2ncerr(int averr){
}
int ffmpeg_decode(ncvisual* nc){
if(nc->details->fmtctx == nullptr){ // not a file-backed ncvisual
if(nc->details->fmtctx == NULL){ // not a file-backed ncvisual
return -1;
}
bool have_frame = false;
@ -192,30 +190,30 @@ int ffmpeg_decode(ncvisual* nc){
nc->cols = nc->details->frame->width;
nc->rows = nc->details->frame->height;
//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details->frame->height, nc->details->frame->width, nc->rowstride, f->data);
ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(f->data[0]), false);
ncvisual_set_data(nc, f->data[0], false);
return 0;
}
// resize frame to oframe, converting to RGBA (if necessary) along the way
int ffmpeg_resize(ncvisual* nc, int rows, int cols) {
int ffmpeg_resize(ncvisual* nc, int rows, int cols){
const int targformat = AV_PIX_FMT_RGBA;
AVFrame* inf = nc->details->oframe ? nc->details->oframe : nc->details->frame;
//fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inf->format, nc->rows, nc->cols, targformat, rows, cols);
if(inf->format == targformat && nc->rows == rows && nc->cols == cols){
return 0;
}
auto swsctx = sws_getContext(inf->width,
inf->height,
static_cast<AVPixelFormat>(inf->format),
cols, rows,
static_cast<AVPixelFormat>(targformat),
SWS_LANCZOS, nullptr, nullptr, nullptr);
if(swsctx == nullptr){
struct SwsContext* swsctx = sws_getContext(inf->width,
inf->height,
inf->format,
cols, rows,
targformat,
SWS_LANCZOS, NULL, NULL, NULL);
if(swsctx == NULL){
//fprintf(stderr, "Error retrieving swsctx\n");
return -1;
}
AVFrame* sframe;
if((sframe = av_frame_alloc()) == nullptr){
if((sframe = av_frame_alloc()) == NULL){
// fprintf(stderr, "Couldn't allocate frame for %s\n", filename);
sws_freeContext(swsctx);
return -1; // no need to free swsctx
@ -227,15 +225,14 @@ int ffmpeg_resize(ncvisual* nc, int rows, int cols) {
//fprintf(stderr, "SIZE DECODED: %d %d (%d) (want %d %d)\n", nc->rows, nc->cols, inf->linesize[0], rows, cols);
int size = av_image_alloc(sframe->data, sframe->linesize,
sframe->width, sframe->height,
static_cast<AVPixelFormat>(sframe->format),
IMGALLOCALIGN);
sframe->format, IMGALLOCALIGN);
if(size < 0){
//fprintf(stderr, "Error allocating visual data (%d)\n", size);
av_freep(&sframe);
sws_freeContext(swsctx);
return -1;
}
int height = sws_scale(swsctx, inf->data,
int height = sws_scale(swsctx, (const uint8_t * const*)inf->data,
inf->linesize, 0,
inf->height, sframe->data,
sframe->linesize);
@ -247,7 +244,7 @@ int ffmpeg_resize(ncvisual* nc, int rows, int cols) {
return -1;
}
const AVFrame* f = sframe;
int bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(static_cast<AVPixelFormat>(f->format)));
int bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(f->format));
if(bpp != 32){
//fprintf(stderr, "Bad bits-per-pixel (wanted 32, got %d)\n", bpp);
av_freep(sframe->data);
@ -257,7 +254,7 @@ int ffmpeg_resize(ncvisual* nc, int rows, int cols) {
nc->rowstride = sframe->linesize[0];
nc->rows = rows;
nc->cols = cols;
ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(sframe->data[0]), true);
ncvisual_set_data(nc, sframe->data[0], true);
if(nc->details->oframe){
//av_freep(nc->details->oframe->data);
av_freep(&nc->details->oframe);
@ -268,40 +265,46 @@ int ffmpeg_resize(ncvisual* nc, int rows, int cols) {
return 0;
}
auto ffmpeg_details_init(void) -> ncvisual_details* {
auto deets = new ncvisual_details{};
deets->stream_index = -1;
deets->sub_stream_index = -1;
if((deets->frame = av_frame_alloc()) == nullptr){
delete deets;
return nullptr;
ncvisual_details* ffmpeg_details_init(void){
ncvisual_details* deets = malloc(sizeof(*deets));
if(deets){
memset(deets, 0, sizeof(*deets));
deets->stream_index = -1;
deets->sub_stream_index = -1;
if((deets->frame = av_frame_alloc()) == NULL){
free(deets);
return NULL;
}
}
return deets;
}
auto ffmpeg_create() -> ncvisual* {
auto nc = new ncvisual{};
if((nc->details = ffmpeg_details_init()) == nullptr){
delete nc;
return nullptr;
ncvisual* ffmpeg_create(){
ncvisual* nc = malloc(sizeof(*nc));
if(nc){
memset(nc, 0, sizeof(*nc));
if((nc->details = ffmpeg_details_init()) == NULL){
free(nc);
return NULL;
}
}
return nc;
}
ncvisual* ffmpeg_from_file(const char* filename) {
ncvisual* ffmpeg_from_file(const char* filename){
AVStream* st;
ncvisual* ncv = ffmpeg_create();
if(ncv == nullptr){
if(ncv == NULL){
// fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
return nullptr;
return NULL;
}
//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
int averr = avformat_open_input(&ncv->details->fmtctx, filename, nullptr, nullptr);
int averr = avformat_open_input(&ncv->details->fmtctx, filename, NULL, NULL);
if(averr < 0){
//fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr);
goto err;
}
averr = avformat_find_stream_info(ncv->details->fmtctx, nullptr);
averr = avformat_find_stream_info(ncv->details->fmtctx, NULL);
if(averr < 0){
//fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr);
goto err;
@ -309,12 +312,12 @@ ncvisual* ffmpeg_from_file(const char* filename) {
//av_dump_format(ncv->details->fmtctx, 0, filename, false);
if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details->subtcodec, 0)) >= 0){
ncv->details->sub_stream_index = averr;
if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == nullptr){
if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == NULL){
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
goto err;
}
// FIXME do we need avcodec_parameters_to_context() here?
if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, nullptr) < 0){
if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, NULL) < 0){
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
goto err;
}
@ -322,7 +325,7 @@ ncvisual* ffmpeg_from_file(const char* filename) {
ncv->details->sub_stream_index = -1;
}
//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
if((ncv->details->packet = av_packet_alloc()) == nullptr){
if((ncv->details->packet = av_packet_alloc()) == NULL){
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
goto err;
}
@ -331,23 +334,23 @@ ncvisual* ffmpeg_from_file(const char* filename) {
goto err;
}
ncv->details->stream_index = averr;
if(ncv->details->codec == nullptr){
if(ncv->details->codec == NULL){
//fprintf(stderr, "Couldn't find decoder for %s\n", filename);
goto err;
}
st = ncv->details->fmtctx->streams[ncv->details->stream_index];
if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == nullptr){
if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == NULL){
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
goto err;
}
if(avcodec_parameters_to_context(ncv->details->codecctx, st->codecpar) < 0){
goto err;
}
if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, nullptr) < 0){
if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, NULL) < 0){
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
goto err;
}
/*if((ncv->cparams = avcodec_parameters_alloc()) == nullptr){
/*if((ncv->cparams = avcodec_parameters_alloc()) == NULL){
//fprintf(stderr, "Couldn't allocate codec params for %s\n", filename);
goto err;
}
@ -366,7 +369,7 @@ ncvisual* ffmpeg_from_file(const char* filename) {
err:
ncvisual_destroy(ncv);
return nullptr;
return NULL;
}
// iterate over the decoded frames, calling streamer() with curry for each.
@ -375,7 +378,7 @@ err:
// up playback.
int ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale,
streamcb streamer, const struct ncvisual_options* vopts,
void* curry) {
void* curry){
int frame = 1;
struct timespec begin; // time we started
clock_gettime(CLOCK_MONOTONIC, &begin);
@ -385,7 +388,7 @@ int ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale,
// we don't have PTS available.
uint64_t sum_duration = 0;
ncplane* newn = NULL;
ncvisual_options activevopts;
struct ncvisual_options activevopts;
memcpy(&activevopts, vopts, sizeof(*vopts));
int ncerr;
do{
@ -399,7 +402,7 @@ int ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale,
}
// decay the blitter explicitly, so that the callback knows the blitter it
// was actually rendered with
auto bset = rgba_blitter(nc, &activevopts);
const struct blitset* bset = rgba_blitter(nc, &activevopts);
if(bset){
activevopts.blitter = bset->geom;
}
@ -457,29 +460,29 @@ int ffmpeg_decode_loop(ncvisual* ncv){
int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
const struct blitset* bset,
int leny, int lenx, const blitterargs* bargs) {
int leny, int lenx, const blitterargs* bargs){
const AVFrame* inframe = ncv->details->oframe ? ncv->details->oframe : ncv->details->frame;
//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details->oframe, ncv->details->frame);
void* data = nullptr;
void* data = NULL;
int stride = 0;
AVFrame* sframe = nullptr;
AVFrame* sframe = NULL;
const int targformat = AV_PIX_FMT_RGBA;
//fprintf(stderr, "got format: %d want format: %d\n", inframe->format, targformat);
if(inframe && (cols != inframe->width || rows != inframe->height || inframe->format != targformat)){
//fprintf(stderr, "resize+render: %d/%d->%d/%d (%dX%d @ %dX%d, %d/%d)\n", inframe->height, inframe->width, rows, cols, begy, begx, placey, placex, leny, lenx);
sframe = av_frame_alloc();
if(sframe == nullptr){
if(sframe == NULL){
//fprintf(stderr, "Couldn't allocate output frame for scaled frame\n");
return -1;
}
//fprintf(stderr, "WHN NCV: %d/%d\n", inframe->width, inframe->height);
ncv->details->swsctx = sws_getCachedContext(ncv->details->swsctx,
ncv->cols, ncv->rows,
static_cast<AVPixelFormat>(inframe->format),
inframe->format,
cols, rows,
static_cast<AVPixelFormat>(targformat),
SWS_LANCZOS, nullptr, nullptr, nullptr);
if(ncv->details->swsctx == nullptr){
targformat,
SWS_LANCZOS, NULL, NULL, NULL);
if(ncv->details->swsctx == NULL){
//fprintf(stderr, "Error retrieving details->swsctx\n");
return -1;
}
@ -489,7 +492,7 @@ int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
sframe->height = rows;
int size = av_image_alloc(sframe->data, sframe->linesize,
sframe->width, sframe->height,
static_cast<AVPixelFormat>(sframe->format),
sframe->format,
IMGALLOCALIGN);
if(size < 0){
//fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width);
@ -525,10 +528,10 @@ int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
return 0;
}
auto ffmpeg_details_seed(ncvisual* ncv) -> void {
assert(nullptr == ncv->details->oframe);
ncv->details->frame->data[0] = reinterpret_cast<uint8_t*>(ncv->data);
ncv->details->frame->data[1] = nullptr;
void ffmpeg_details_seed(ncvisual* ncv){
assert(NULL == ncv->details->oframe);
ncv->details->frame->data[0] = (uint8_t*)ncv->data;
ncv->details->frame->data[1] = NULL;
ncv->details->frame->linesize[0] = ncv->rowstride;
ncv->details->frame->linesize[1] = 0;
ncv->details->frame->width = ncv->cols;
@ -536,7 +539,7 @@ auto ffmpeg_details_seed(ncvisual* ncv) -> void {
ncv->details->frame->format = AV_PIX_FMT_RGBA;
}
auto ffmpeg_log_level(int level) -> int{
int ffmpeg_log_level(int level){
switch(level){
case NCLOGLEVEL_SILENT: return AV_LOG_QUIET;
case NCLOGLEVEL_PANIC: return AV_LOG_PANIC;
@ -553,7 +556,7 @@ auto ffmpeg_log_level(int level) -> int{
return AV_LOG_TRACE;
}
auto ffmpeg_init(int loglevel) -> int {
int ffmpeg_init(int loglevel){
av_log_set_level(ffmpeg_log_level(loglevel));
// FIXME could also use av_log_set_callback() and capture the message...
return 0;
@ -566,7 +569,7 @@ void ffmpeg_printbanner(const notcurses* nc __attribute__ ((unused))){
LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO);
}
auto ffmpeg_details_destroy(ncvisual_details* deets) -> void {
void ffmpeg_details_destroy(ncvisual_details* deets){
avcodec_close(deets->codecctx);
avcodec_free_context(&deets->subtcodecctx);
avcodec_free_context(&deets->codecctx);
@ -577,16 +580,16 @@ auto ffmpeg_details_destroy(ncvisual_details* deets) -> void {
av_packet_free(&deets->packet);
avformat_close_input(&deets->fmtctx);
avsubtitle_free(&deets->subtitle);
delete deets;
free(deets);
}
auto ffmpeg_destroy(ncvisual* ncv) -> void {
void ffmpeg_destroy(ncvisual* ncv){
if(ncv){
ffmpeg_details_destroy(ncv->details);
if(ncv->owndata){
free(ncv->data);
}
delete ncv;
free(ncv);
}
}
Loading…
Cancel
Save