Support image decoding with OpenImageIO #453 (#534)

* fedora: dep on OpenImageIO, and use it
* fedora: dep on libqrcodegen-devel
* fedora: BuildRequires OpenEXR-devel
* tight check on USE_MULTIMEDIA
* CMake: enable notcurses-view for ffmpeg OR oiio
* notcurses-view: don't reach into libav
* oiio: ncvisual_render() #453
* oiio: need our own properly-offset ncvisual_plane()
* `visual` poc: accept optional command line argument
* oiio: work for 3-channel images #453
* oiio: destroy ncvisual's plane if we own it #453
* notcurses_visual.3: s/FFmpeg/multimedia/g
pull/542/head
Nick Black 4 years ago committed by GitHub
parent fe37d49c5d
commit 9a075ae5d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,7 +13,7 @@ rearrangements of Notcurses.
`notcurses/ncerrs.h`.
* CMake no longer uses the `USE_FFMPEG` option. Instead, the `USE_MULTIMEDIA`
option can be defined as `ffmpeg`, `oiio`, or `none`. In `cmake-gui`, this
item will now appear as an option selector.
item will now appear as an option selector. `oiio` selects OpenImageIO.
* 1.3.2 (2020-04-19)
* `ncdirect_cursor_push()`, `notcurses_cursor_pop()`, and

@ -30,6 +30,8 @@ if(${USE_MULTIMEDIA} STREQUAL "ffmpeg")
set(USE_FFMPEG ON)
elseif(${USE_MULTIMEDIA} STREQUAL "oiio")
set(USE_OIIO ON)
elseif(NOT ${USE_MULTIMEDIA} STREQUAL "none")
message(FATAL_ERROR "USE_MULTIMEDIA must be one of 'oiio', 'ffmpeg', 'none' (was '${USE_MULTIMEDIA}')")
endif()
find_package(PkgConfig REQUIRED)
@ -504,8 +506,8 @@ target_compile_definitions(notcurses-tetris
)
# notcurses-view
if(${USE_FFMPEG} OR ${USE_OIIO})
file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp)
if(${USE_FFMPEG})
add_executable(notcurses-view ${VIEWSRCS})
target_include_directories(notcurses-view
PRIVATE
@ -696,7 +698,7 @@ install(FILES
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
if(${USE_FFMPEG})
if(${USE_FFMPEG} OR ${USE_OIIO})
file(GLOB TESTDATA CONFIGURE_DEPENDS data/*)
# Don't install source materia for self-originated multimedia
list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$")
@ -717,7 +719,7 @@ if(${USE_TESTS})
install(TARGETS notcurses-tester DESTINATION bin)
endif()
install(TARGETS notcurses-tetris DESTINATION bin)
if(${USE_FFMPEG})
if(${USE_FFMPEG} OR ${USE_OIIO})
install(TARGETS notcurses-view DESTINATION bin)
endif()
install(TARGETS notcurses notcurses-static

@ -57,7 +57,7 @@ result in a zero-area rendering.
# RETURN VALUES
**notcurses_canopen** returns true if this functionality is enabled, or false
if Notcurses was not built with FFmpeg support. **ncplane_visual_open** and
if Notcurses was not built with multimedia support. **ncplane_visual_open** and
**ncvisual_open_plane** return an **ncvisual** object on success, or **NULL**
on failure. Success from these functions indicates that the specified **file**
was opened, and enough data was read to make a firm codec identification. It

@ -5,6 +5,7 @@
static inline bool
ffmpeg_trans_p(bool bgr, unsigned char alpha){
if(!bgr && alpha < 192){
//fprintf(stderr, "TRANSPARENT!\n");
return true;
}
return false;

@ -37,11 +37,11 @@ typedef struct ncvisual {
#endif
} ncvisual;
#ifdef USE_FFMPEG
ncplane* ncvisual_plane(ncvisual* ncv){
return ncv->ncp;
}
#ifdef USE_FFMPEG
void ncvisual_destroy(ncvisual* ncv){
if(ncv){
avcodec_close(ncv->codecctx);

@ -4,13 +4,12 @@
#include <OpenImageIO/imageio.h>
#include "internal.h"
extern "C" {
typedef struct ncvisual {
int packet_outstanding;
int dstwidth, dstheight;
float timescale; // scale frame duration by this value
std::unique_ptr<OIIO::ImageInput> image; // must be close()d
std::unique_ptr<uint32_t[]> frame;
ncplane* ncp;
// if we're creating the plane based off the first frame's dimensions, these
// describe where the plane ought be placed, and how it ought be sized. this
@ -20,6 +19,12 @@ typedef struct ncvisual {
struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane
} ncvisual;
extern "C" {
ncplane* ncvisual_plane(ncvisual* ncv){
return ncv->ncp;
}
bool notcurses_canopen(const notcurses* nc __attribute__ ((unused))){
return true;
}
@ -38,6 +43,7 @@ ncvisual_create(float timescale){
ret->placex = ret->placey = 0;
ret->style = NCSCALE_NONE;
ret->ncobj = nullptr;
ret->frame = nullptr;
return ret;
}
@ -54,6 +60,9 @@ ncvisual_open(const char* filename, nc_err_e* err){
*err = NCERR_DECODE;
return nullptr;
}
/*const auto &spec = ncv->image->spec();
std::cout << "Opened " << filename << ": " << spec.height << "x" <<
spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/
return ncv;
}
@ -67,6 +76,7 @@ ncvisual* ncplane_visual_open(ncplane* nc, const char* filename, nc_err_e* ncerr
ncv->dstheight *= 2;
ncv->ncp = nc;
ncv->style = NCSCALE_STRETCH;
ncv->ncobj = nullptr;
return ncv;
}
@ -81,31 +91,158 @@ ncvisual* ncvisual_open_plane(notcurses* nc, const char* filename,
ncv->style = style;
ncv->ncobj = nc;
ncv->ncp = nullptr;
ncv->ncobj = nc;
return ncv;
}
nc_err_e ncvisual_decode(ncvisual* nc){
(void)nc; // FIXME
return NCERR_DECODE;
if(nc->frame){
return NCERR_EOF; // FIXME
}
const auto &spec = nc->image->spec();
auto pixels = spec.width * spec.height;// * spec.nchannels;
if(spec.nchannels < 3 || spec.nchannels > 4){
return NCERR_DECODE; // FIXME get some to test with
}
nc->frame = std::make_unique<uint32_t[]>(pixels);
if(spec.nchannels == 3){
std::fill(nc->frame.get(), nc->frame.get() + pixels, 0xfffffffful);
}
if(!nc->image->read_image(0, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->frame.get(), 4)){
return NCERR_DECODE;
}
/*for(int i = 0 ; i < pixels ; ++i){
//fprintf(stderr, "%06d %02x %02x %02x %02x\n", i,
fprintf(stderr, "%06d %d %d %d %d\n", i,
(nc->frame[i]) & 0xff,
(nc->frame[i] >> 8) & 0xff,
(nc->frame[i] >> 16) & 0xff,
nc->frame[i] >> 24
);
}*/
int rows, cols;
if(nc->ncp == nullptr){ // create plane
if(nc->style == NCSCALE_NONE){
rows = spec.height / 2;
cols = spec.width;
}else{ // FIXME differentiate between scale/stretch
notcurses_term_dim_yx(nc->ncobj, &rows, &cols);
if(nc->placey >= rows || nc->placex >= cols){
return NCERR_DECODE;
}
rows -= nc->placey;
cols -= nc->placex;
}
nc->dstwidth = cols;
nc->dstheight = rows * 2;
nc->ncp = ncplane_new(nc->ncobj, rows, cols, nc->placey, nc->placex, nullptr);
nc->placey = 0;
nc->placex = 0;
if(nc->ncp == nullptr){
return NCERR_NOMEM;
}
}else{ // check for resize
ncplane_dim_yx(nc->ncp, &rows, &cols);
if(rows != nc->dstheight / 2 || cols != nc->dstwidth){
nc->dstheight = rows * 2;
nc->dstwidth = cols;
}
}
return NCERR_SUCCESS;
}
int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx){
(void)ncv;
(void)begy;
(void)begx;
(void)leny;
(void)lenx;
return -1;
//fprintf(stderr, "render %dx%d+%dx%d\n", begy, begx, leny, lenx);
if(begy < 0 || begx < 0 || lenx < -1 || leny < -1){
return -1;
}
if(ncv->frame == nullptr){
return -1;
}
const auto &spec = ncv->image->spec();
//fprintf(stderr, "render %d/%d to %dx%d+%dx%d\n", f->height, f->width, begy, begx, leny, lenx);
if(begx >= spec.width || begy >= spec.height){
return -1;
}
if(lenx == -1){ // -1 means "to the end"; use all space available
lenx = spec.width - begx;
}
if(leny == -1){
leny = spec.height - begy;
}
if(lenx == 0 || leny == 0){ // no need to draw zero-size object, exit
return 0;
}
if(begx + lenx > spec.width || begy + leny > spec.height){
return -1;
}
int dimy, dimx;
ncplane_dim_yx(ncv->ncp, &dimy, &dimx);
ncplane_cursor_move_yx(ncv->ncp, 0, 0);
// y and x are actual plane coordinates. each row corresponds to two rows of
// the input (scaled) frame (columns are 1:1). we track the row of the
// visual via visy.
//fprintf(stderr, "render: %dx%d:%d+%d of %d/%d -> %dx%d\n", begy, begx, leny, lenx, f->height, f->width, dimy, dimx);
const int linesize = spec.width * 4;
int ret = ncblit_rgba(ncv->ncp, ncv->placey, ncv->placex, linesize,
ncv->frame.get(), begy, begx, leny, lenx);
//av_frame_unref(ncv->oframe);
return ret;
}
int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv, nc_err_e* ncerr,
float timespec, streamcb streamer, void* curry){
(void)nc;
(void)ncv;
(void)ncerr;
(void)timespec;
(void)streamer;
(void)curry;
float timescale, streamcb streamer, void* curry){
int frame = 1;
ncv->timescale = timescale;
struct timespec begin; // time we started
clock_gettime(CLOCK_MONOTONIC, &begin);
uint64_t nsbegin = timespec_to_ns(&begin);
bool usets = false;
// each frame has a pkt_duration in milliseconds. keep the aggregate, in case
// we don't have PTS available.
uint64_t sum_duration = 0;
while((*ncerr = ncvisual_decode(ncv)) == NCERR_SUCCESS){
/* codecctx seems to be off by a factor of 2 regularly. instead, go with
// the time_base from the avformatctx.
double tbase = av_q2d(ncv->fmtctx->streams[ncv->stream_index]->time_base);
int64_t ts = ncv->oframe->best_effort_timestamp;
if(frame == 1 && ts){
usets = true;
}*/
if(ncvisual_render(ncv, 0, 0, -1, -1) < 0){
return -1;
}
if(streamer){
int r = streamer(nc, ncv, curry);
if(r){
return r;
}
}
++frame;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t nsnow = timespec_to_ns(&now);
struct timespec interval;
/*uint64_t duration = ncv->oframe->pkt_duration * tbase * NANOSECS_IN_SEC;
sum_duration += (duration * ncv->timescale);
//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->codecctx->time_base), av_q2d(ncv->fmtctx->streams[ncv->stream_index]->time_base));
double schedns = nsbegin;
if(usets){
if(tbase == 0){
tbase = duration;
}
schedns += ts * (tbase * ncv->timescale) * NANOSECS_IN_SEC;
}else{
schedns += sum_duration;
}
if(nsnow < schedns){
ns_to_timespec(schedns - nsnow, &interval);
nanosleep(&interval, nullptr);
}*/
}
if(*ncerr == NCERR_EOF){
return 0;
}
return -1;
}
@ -122,7 +259,10 @@ int ncvisual_init(int loglevel){
void ncvisual_destroy(ncvisual* ncv){
if(ncv){
ncv->image->close();
free(ncv);
if(ncv->ncobj){
ncplane_destroy(ncv->ncp);
}
delete ncv;
}
}

@ -6,7 +6,14 @@
#include <notcurses/notcurses.h>
int main(int argc, char** argv){
const char* file = "../data/changes.jpg";
setlocale(LC_ALL, "");
if(argc > 2){
fprintf(stderr, "usage: visual [ file ]\n");
return EXIT_FAILURE;
}else if(argc == 2){
file = argv[1];
}
notcurses_options opts{};
opts.inhibit_alternate_screen = true;
struct notcurses* nc;
@ -18,7 +25,7 @@ int main(int argc, char** argv){
ncplane_dim_yx(n, &dimy, &dimx);
nc_err_e ncerr;
auto ncv = ncplane_visual_open(n, "../data/changes.jpg", &ncerr);
auto ncv = ncplane_visual_open(n, file, &ncerr);
if(!ncv){
goto err;
}

@ -1,4 +1,5 @@
#include <array>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <clocale>
@ -7,15 +8,8 @@
#include <libgen.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <ncpp/NotCurses.hh>
#include <ncpp/Visual.hh>
extern "C" {
#include <libavutil/pixdesc.h>
#include <libavutil/avconfig.h>
#include <libavcodec/avcodec.h> // ffmpeg doesn't reliably "C"-guard itself
}
#include <ncpp/NotCurses.hh>
using namespace ncpp;
@ -170,7 +164,6 @@ int main(int argc, char** argv){
int dimy, dimx;
nc.get_term_dim(&dimy, &dimx);
for(auto i = nonopt ; i < argc ; ++i){
std::array<char, 128> errbuf;
int frames = 0;
nc_err_e err;
std::unique_ptr<Visual> ncv;
@ -183,9 +176,8 @@ int main(int argc, char** argv){
}
int r = ncv->stream(&err, timescale, perframe, &frames);
if(r < 0){ // positive is intentional abort
av_make_error_string(errbuf.data(), errbuf.size(), err);
nc.stop();
std::cerr << "Error decoding " << argv[i] << ": " << errbuf.data() << std::endl;
std::cerr << "Error decoding " << argv[i] << ": " << nc_strerror(err) << std::endl;
return EXIT_FAILURE;
}else if(r == 0){
std::unique_ptr<Plane> stdn(nc.get_stdplane());

@ -1,9 +1,4 @@
#include "main.h"
#ifdef USE_MULTIMEDIA
#include <libavutil/pixdesc.h>
#include <libavutil/avconfig.h>
#include <libavcodec/avcodec.h>
#endif
TEST_CASE("Multimedia") {
if(getenv("TERM") == nullptr){

@ -11,6 +11,9 @@ Source2: https://dank.qemfd.net/dankamongmen.gpg
BuildRequires: gnupg2
BuildRequires: cmake
BuildRequires: gcc-c++
BuildRequires: libqrcodegen-devel
BuildRequires: OpenEXR-devel
BuildRequires: OpenImageIO-devel
BuildRequires: pandoc
BuildRequires: python3-devel
BuildRequires: python3-cffi
@ -53,7 +56,7 @@ Python wrappers and a demonstration script for the notcurses library.
%build
mkdir build
cd build
%cmake -DUSE_FFMPEG=off -DUSE_TESTS=off ..
%cmake -DUSE_MULTIMEDIA=oiio -DUSE_TESTS=off ..
%make_build
cd python
%py3_build

Loading…
Cancel
Save