mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-11-11 13:11:20 +00:00
Lots of ncvisual work
Unify ffmpeg/oiio/null implementations, where possible. This effectively required placing all three in the same file, which meant they're all now C++. Update FFmpeg implemenation to be C++-usable. Implement ncvisual_rotate_cw() and ncvisual_rotate_ccw() #515. Move most of tetris over to Visual from Plane #558. Add bgra_to_rgba(), necessary for creating ncvisual from BGRA memory. Implement ncvisual_from_rgba() and ncvisual_from_bgra() #557. Add unit tests on ncvisual rotation.
This commit is contained in:
parent
f7e002d913
commit
f63325db60
14
CHANGELOG.md
14
CHANGELOG.md
@ -3,10 +3,18 @@ rearrangements of Notcurses.
|
||||
|
||||
* 1.3.4 (not yet released)
|
||||
* `notcurses_lex_margins()` has been added to lex margins expressed in either
|
||||
of two canonical formats. Hopefully this will lead to more programs
|
||||
supporting margins.
|
||||
of two canonical formats. Hopefully this will lead to more programs
|
||||
supporting margins.
|
||||
* `ncvisual_open_plane()` has been renamed `ncvisual_from_file()`. The former
|
||||
has been retained as a deprecated alias. It will be removed by 1.6/2.0.
|
||||
has been retained as a deprecated alias. It will be removed by 1.6/2.0.
|
||||
* `ncvisual_from_rgba()` and `ncvisual_from_bgra()` have been added to
|
||||
support creation of `ncvisual`s from memory, requiring no file.
|
||||
* `ncvisual_rotate_cw()` and `ncvisual_rotate_ccw()` have been added, having
|
||||
the same semantics as `ncplane_rotate_cw()` and `ncplane_rotate_ccw()`.
|
||||
They will likely be augmented in the future to support arbitrary rotations.
|
||||
* `ncvisual_from_plane()` has been added to support "promotion" of an
|
||||
`ncplane` to an `ncvisual`. The source plane may contain only spaces,
|
||||
half blocks, and full blocks.
|
||||
|
||||
* 1.3.3 (2020-04-26)
|
||||
* The `ncdplot` type has been added for plots based on `double`s rather than
|
||||
|
12
USAGE.md
12
USAGE.md
@ -2437,8 +2437,8 @@ struct ncplane* ncvisual_plane(struct ncvisual* ncv);
|
||||
char* ncvisual_subtitle(const struct ncvisual* ncv);
|
||||
|
||||
// Rotate the visual π/2 radians clockwise or counterclockwise.
|
||||
int ncplane_rotate_cw(struct ncplane* n);
|
||||
int ncplane_rotate_ccw(struct ncplane* n);
|
||||
int ncvisual_rotate_cw(struct ncvisual* n);
|
||||
int ncvisual_rotate_ccw(struct ncvisual* n);
|
||||
```
|
||||
|
||||
It is also possible to seed an `ncvisual` directly from memory, without involving
|
||||
@ -2447,10 +2447,10 @@ a file. Both RGBA and BGRA 8bpc arrangements can be used.
|
||||
```c
|
||||
// Prepare an ncvisual, and its underlying plane, based off RGBA content in
|
||||
// memory at 'rgba'. 'rgba' must be a flat array of 32-bit 8bpc RGBA pixels.
|
||||
// These must be arranged in 'rowstride' * 4b lines, where the first 'cols'
|
||||
// * 4b are actual data. There must be 'rows' lines. The total size of 'rgba'
|
||||
// must thus be at least (rows * rowstride * 4) bytes, of which (rows * cols
|
||||
// * 4) bytes are actual data. The resulting plane will be 'rows' / 2 x 'cols'.
|
||||
// These must be arranged in 'rowstride' lines, where the first 'cols' * 4b
|
||||
// are actual data. There must be 'rows' lines. The total size of 'rgba'
|
||||
// must thus be at least (rows * rowstride) bytes, of which (rows * cols * 4)
|
||||
// bytes are actual data. The resulting plane will be ceil('rows'/2)x'cols'.
|
||||
ncvisual* ncvisual_from_rgba(notcurses* nc, const void* rgba, int rows, int rowstride, int cols);
|
||||
|
||||
// ncvisual_from_rgba(), but for BGRA.
|
||||
|
@ -134,6 +134,10 @@ notcurses_plane - operations on ncplanes
|
||||
|
||||
**bool ncplane_set_scrolling(struct ncplane* n, bool scrollp);**
|
||||
|
||||
**int ncplane_rotate_cw(struct ncplane* n);**
|
||||
|
||||
**int ncplane_rotate_ccw(struct ncplane* n);**
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Ncplanes are the fundamental drawing object of notcurses. All output functions
|
||||
|
@ -46,9 +46,9 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*);
|
||||
|
||||
**struct ncplane* ncvisual_plane(struct ncvisual* ncv);**
|
||||
|
||||
**int ncplane_rotate_cw(struct ncplane* n);**
|
||||
**int ncvisual_rotate_cw(struct ncvisual* n);**
|
||||
|
||||
**int ncplane_rotate_ccw(struct ncplane* n);**
|
||||
**int ncvisual_rotate_ccw(struct ncvisual* n);**
|
||||
|
||||
**char* ncvisual_subtitle(const struct ncvisual* ncv);**
|
||||
|
||||
@ -68,15 +68,15 @@ It is possible to create an **ncvisual** from memory, rather than from a
|
||||
disk, but only using raw RGBA/BGRA 8bpc content. For RGBA, use
|
||||
**ncvisual_from_rgba**. For BGRA, use **ncvisual_from_bgra**. Both require
|
||||
a number of **rows**, a number of image columns **cols**, and a virtual row
|
||||
length of **rowstride** columns. The input data must provide 32 bits per
|
||||
pixel, and thus must be at least **rowstride** * **rows** * 4 bytes, of
|
||||
length of **rowstride** / 4 columns. The input data must provide 32 bits per
|
||||
pixel, and thus must be at least **rowstride** * **rows** bytes, of
|
||||
which a **cols** * **rows** * 4-byte subset is used. It is not possible to
|
||||
**mmap(2)** an image file and use it directly--decompressed, decoded data
|
||||
is necessary. The resulting plane will be **rows** / 2 rows, and **cols**
|
||||
is necessary. The resulting plane will be ceil(**rows**/2) rows, and **cols**
|
||||
columns. It will not be necessary to call **ncvisual_decode**, but it is
|
||||
still necessary to call **ncvisual_render**.
|
||||
|
||||
Both **ncplane_rotate_cw** and **ncplane_rotate_ccw** execute a rotation of
|
||||
Both **ncvisual_rotate_cw** and **ncvisual_rotate_ccw** execute a rotation of
|
||||
π/2 radians, in the clockwise or counterclockwise direction respectively.
|
||||
|
||||
**ncvisual_subtitle** will return a UTF-8-encoded subtitle corresponding to
|
||||
|
@ -979,6 +979,16 @@ namespace ncpp
|
||||
return error_guard<bool, bool> (ncplane_translate_abs (plane, y, x), false);
|
||||
}
|
||||
|
||||
bool rotate_cw () const NOEXCEPT_MAYBE
|
||||
{
|
||||
return error_guard (ncplane_rotate_cw (plane), -1);
|
||||
}
|
||||
|
||||
bool rotate_ccw () const noexcept
|
||||
{
|
||||
return error_guard (ncplane_rotate_ccw (plane), -1);
|
||||
}
|
||||
|
||||
// Upstream call doesn't take ncplane* but we put it here for parity with has_no_background below
|
||||
bool has_no_foreground (Cell &cell) const noexcept
|
||||
{
|
||||
|
@ -1129,6 +1129,13 @@ ncplane_move_below(struct ncplane* n, struct ncplane* below){
|
||||
// Return the plane below this one, or NULL if this is at the bottom.
|
||||
API struct ncplane* ncplane_below(struct ncplane* n);
|
||||
|
||||
// Rotate the plane π/2 radians clockwise or counterclockwise. This cannot
|
||||
// be performed on arbitrary planes, because glyphs cannot be arbitrarily
|
||||
// rotated. The glyphs which can be rotated are limited: line-drawing
|
||||
// characters, spaces, half blocks, and full blocks.
|
||||
API int ncplane_rotate_cw(struct ncplane* n);
|
||||
API int ncplane_rotate_ccw(struct ncplane* n);
|
||||
|
||||
// Rotate the visual π/2 radians clockwise or counterclockwise. This cannot
|
||||
// be performed on arbitrary planes, because glyphs cannot be arbitrarily rotated.
|
||||
API int ncvisual_rotate_cw(struct ncvisual* n);
|
||||
@ -2065,10 +2072,10 @@ ncvisual_open_plane(struct notcurses* nc, const char* file, nc_err_e* ncerr,
|
||||
|
||||
// Prepare an ncvisual, and its underlying plane, based off RGBA content in
|
||||
// memory at 'rgba'. 'rgba' must be a flat array of 32-bit 8bpc RGBA pixels.
|
||||
// These must be arranged in 'rowstride' * 4b lines, where the first 'cols'
|
||||
// * 4b are actual data. There must be 'rows' lines. The total size of 'rgba'
|
||||
// must thus be at least (rows * rowstride * 4) bytes, of which (rows * cols
|
||||
// * 4) bytes are actual data. The resulting plane will be 'rows' / 2 x 'cols'.
|
||||
// These must be arranged in 'rowstride' lines, where the first 'cols' * 4b
|
||||
// are actual data. There must be 'rows' lines. The total size of 'rgba'
|
||||
// must thus be at least (rows * rowstride) bytes, of which (rows * cols * 4)
|
||||
// bytes are actual data. The resulting plane will be ceil('rows'/2)x'cols'.
|
||||
API struct ncvisual* ncvisual_from_rgba(struct notcurses* nc, const void* rgba,
|
||||
int rows, int rowstride, int cols);
|
||||
|
||||
@ -2709,7 +2716,9 @@ typedef struct ncfdplane_options {
|
||||
// ownership of the file descriptor, which will be closed in ncfdplane_destroy().
|
||||
API struct ncfdplane* ncfdplane_create(struct ncplane* n, const ncfdplane_options* opts,
|
||||
int fd, ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn);
|
||||
|
||||
API struct ncplane* ncfdplane_plane(struct ncfdplane* n);
|
||||
|
||||
API int ncfdplane_destroy(struct ncfdplane* n);
|
||||
|
||||
typedef struct ncsubproc_options {
|
||||
@ -2727,7 +2736,9 @@ API struct ncsubproc* ncsubproc_createvp(struct ncplane* n, const ncsubproc_opti
|
||||
API struct ncsubproc* ncsubproc_createvpe(struct ncplane* n, const ncsubproc_options* opts,
|
||||
const char* bin, char* const arg[], char* const env[],
|
||||
ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn);
|
||||
|
||||
API struct ncplane* ncsubproc_plane(struct ncsubproc* n);
|
||||
|
||||
API int ncsubproc_destroy(struct ncsubproc* n);
|
||||
|
||||
// Draw a QR code at the current position on the plane. If there is insufficient
|
||||
|
@ -427,6 +427,8 @@ int ncplane_putegc_stainable(struct ncplane* n, const char* gclust, int* sbytes)
|
||||
int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int* sbytes);
|
||||
int ncplane_format(struct ncplane* n, int ystop, int xstop, uint32_t attrword);
|
||||
int ncplane_stain(struct ncplane* n, int ystop, int xstop, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr);
|
||||
int ncplane_rotate_cw(struct ncplane* n);
|
||||
int ncplane_rotate_ccw(struct ncplane* n);
|
||||
int ncvisual_rotate_cw(struct ncvisual* n);
|
||||
int ncvisual_rotate_ccw(struct ncvisual* n);
|
||||
void ncplane_translate(const struct ncplane* src, const struct ncplane* dst, int* y, int* x);
|
||||
|
@ -373,21 +373,6 @@ int ncplane_format(struct ncplane* n, int ystop, int xstop, uint32_t attrword){
|
||||
return total;
|
||||
}
|
||||
|
||||
// generate a temporary plane that can hold the contents of n, rotated 90°
|
||||
static ncplane* rotate_plane(const ncplane* n){
|
||||
int absy, absx;
|
||||
ncplane_yx(n, &absy, &absx);
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
if(dimx % 2 != 0){
|
||||
return NULL;
|
||||
}
|
||||
const int newy = dimx / 2;
|
||||
const int newx = dimy * 2;
|
||||
ncplane* newp = ncplane_new(n->nc, newy, newx, absy, absx, n->userptr);
|
||||
return newp;
|
||||
}
|
||||
|
||||
// if we're a lower block, reverse the channels. if we're a space, set both to
|
||||
// the background. if we're a full block, set both to the foreground.
|
||||
static void
|
||||
@ -491,7 +476,8 @@ rotate_2x1_cw(ncplane* src, ncplane* dst, int srcy, int srcx, int dsty, int dstx
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rotate_2x1_ccw(ncplane* src, ncplane* dst, int srcy, int srcx, int dsty, int dstx){
|
||||
static int
|
||||
rotate_2x1_ccw(ncplane* src, ncplane* dst, int srcy, int srcx, int dsty, int dstx){
|
||||
cell c1 = CELL_TRIVIAL_INITIALIZER;
|
||||
cell c2 = CELL_TRIVIAL_INITIALIZER;
|
||||
if(ncplane_at_yx_cell(src, srcy, srcx, &c1) < 0){
|
||||
|
@ -12,12 +12,8 @@ extern "C" {
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/version.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/rational.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswscale/version.h>
|
||||
#include <libavformat/version.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#else
|
||||
#ifdef USE_OIIO
|
||||
const char* oiio_version(void);
|
||||
@ -638,6 +634,20 @@ enforce_utf8(void){
|
||||
|
||||
struct ncvisual* ncvisual_create(float timescale);
|
||||
|
||||
static inline void*
|
||||
memdup(const void* src, size_t len){
|
||||
void* ret = malloc(len);
|
||||
if(ret){
|
||||
memcpy(ret, src, len);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// generate a temporary plane that can hold the contents of n, rotated 90°
|
||||
ncplane* rotate_plane(const ncplane* n);
|
||||
|
||||
void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
627
src/lib/libav.c
627
src/lib/libav.c
@ -1,627 +0,0 @@
|
||||
#include <string.h>
|
||||
#include "version.h"
|
||||
#include "internal.h"
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVFrame;
|
||||
struct AVCodec;
|
||||
struct AVCodecParameters;
|
||||
struct AVPacket;
|
||||
|
||||
typedef struct ncvisual {
|
||||
int packet_outstanding;
|
||||
int dstwidth, dstheight;
|
||||
int stream_index; // match against this following av_read_frame()
|
||||
int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles
|
||||
float timescale; // scale frame duration by this value
|
||||
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
|
||||
// path sets ncobj. ncvisual_destroy() ought in that case kill the ncplane.
|
||||
int placex, placey;
|
||||
ncscale_e style; // none, scale, or stretch
|
||||
struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane
|
||||
#ifdef USE_FFMPEG
|
||||
struct AVFormatContext* fmtctx;
|
||||
struct AVCodecContext* codecctx; // video codec context
|
||||
struct AVCodecContext* subtcodecctx; // subtitle codec context
|
||||
struct AVFrame* frame;
|
||||
struct AVFrame* oframe;
|
||||
struct AVCodec* codec;
|
||||
struct AVCodecParameters* cparams;
|
||||
struct AVCodec* subtcodec;
|
||||
struct AVPacket* packet;
|
||||
struct SwsContext* swsctx;
|
||||
AVSubtitle subtitle;
|
||||
#endif
|
||||
} ncvisual;
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
ncplane* ncvisual_plane(ncvisual* ncv){
|
||||
return ncv->ncp;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_create(float timescale){
|
||||
ncvisual* ret = malloc(sizeof(*ret));
|
||||
if(ret == NULL){
|
||||
return NULL;
|
||||
}
|
||||
memset(ret, 0, sizeof(*ret));
|
||||
ret->timescale = timescale;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ncvisual_destroy(ncvisual* ncv){
|
||||
if(ncv){
|
||||
avcodec_close(ncv->codecctx);
|
||||
avcodec_free_context(&ncv->codecctx);
|
||||
av_frame_free(&ncv->frame);
|
||||
av_freep(&ncv->oframe);
|
||||
//avcodec_parameters_free(&ncv->cparams);
|
||||
sws_freeContext(ncv->swsctx);
|
||||
av_packet_free(&ncv->packet);
|
||||
avformat_close_input(&ncv->fmtctx);
|
||||
avsubtitle_free(&ncv->subtitle);
|
||||
if(ncv->ncobj && ncv->ncp){
|
||||
ncplane_destroy(ncv->ncp);
|
||||
}
|
||||
free(ncv);
|
||||
}
|
||||
}
|
||||
|
||||
bool notcurses_canopen(const notcurses* nc __attribute__ ((unused))){
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static void
|
||||
print_frame_summary(const AVCodecContext* cctx, const AVFrame* f){
|
||||
char pfmt[128];
|
||||
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,
|
||||
f->display_picture_number,
|
||||
f->width, f->height,
|
||||
f->format, pfmt);
|
||||
fprintf(stderr, " Data (%d):", AV_NUM_DATA_POINTERS);
|
||||
int i;
|
||||
for(i = 0 ; i < AV_NUM_DATA_POINTERS ; ++i){
|
||||
fprintf(stderr, " %p", f->data[i]);
|
||||
}
|
||||
fprintf(stderr, "\n Linesizes:");
|
||||
for(i = 0 ; i < AV_NUM_DATA_POINTERS ; ++i){
|
||||
fprintf(stderr, " %d", f->linesize[i]);
|
||||
}
|
||||
if(f->sample_aspect_ratio.num == 0 && f->sample_aspect_ratio.den == 1){
|
||||
fprintf(stderr, "\n Aspect ratio unknown");
|
||||
}else{
|
||||
fprintf(stderr, "\n Aspect ratio %d:%d", f->sample_aspect_ratio.num, f->sample_aspect_ratio.den);
|
||||
}
|
||||
if(f->interlaced_frame){
|
||||
fprintf(stderr, " [ILaced]");
|
||||
}
|
||||
if(f->palette_has_changed){
|
||||
fprintf(stderr, " [NewPal]");
|
||||
}
|
||||
fprintf(stderr, " PTS %ld Flags: 0x%04x\n", f->pts, f->flags);
|
||||
fprintf(stderr, " %lums@%lums (%skeyframe) qual: %d\n",
|
||||
f->pkt_duration, // FIXME in 'time_base' units
|
||||
f->best_effort_timestamp,
|
||||
f->key_frame ? "" : "non-",
|
||||
f->quality);
|
||||
}*/
|
||||
|
||||
static char*
|
||||
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 NULL;
|
||||
}
|
||||
const char* delim = strchr(ass, ',');
|
||||
int commas = 0; // we want 8
|
||||
while(delim && commas < 8){
|
||||
delim = strchr(delim + 1, ',');
|
||||
++commas;
|
||||
}
|
||||
if(!delim){
|
||||
return NULL;
|
||||
}
|
||||
// handle ASS syntax...\i0, \b0, etc.
|
||||
char* dup = strdup(delim + 1);
|
||||
char* c = dup;
|
||||
while(*c){
|
||||
if(*c == '\\'){
|
||||
*c = ' ';
|
||||
++c;
|
||||
if(*c){
|
||||
*c = ' ';;
|
||||
}
|
||||
}
|
||||
++c;
|
||||
}
|
||||
return dup;
|
||||
}
|
||||
|
||||
char* ncvisual_subtitle(const ncvisual* ncv){
|
||||
for(unsigned i = 0 ; i < ncv->subtitle.num_rects ; ++i){
|
||||
const AVSubtitleRect* rect = ncv->subtitle.rects[i];
|
||||
if(rect->type == SUBTITLE_ASS){
|
||||
return deass(rect->ass);
|
||||
}else if(rect->type == SUBTITLE_TEXT) {;
|
||||
return strdup(rect->text);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static nc_err_e
|
||||
averr2ncerr(int averr){
|
||||
// FIXME need to map averror codes to ncerrors
|
||||
//fprintf(stderr, "AVERR: %d/%x %d/%x\n", averr, averr, -averr, -averr);
|
||||
return -averr;
|
||||
}
|
||||
|
||||
nc_err_e ncvisual_decode(ncvisual* nc){
|
||||
bool have_frame = false;
|
||||
bool unref = false;
|
||||
av_freep(&nc->oframe->data[0]);
|
||||
do{
|
||||
do{
|
||||
if(nc->packet_outstanding){
|
||||
break;
|
||||
}
|
||||
if(unref){
|
||||
av_packet_unref(nc->packet);
|
||||
}
|
||||
int averr;
|
||||
if((averr = av_read_frame(nc->fmtctx, nc->packet)) < 0){
|
||||
/*if(averr != AVERROR_EOF){
|
||||
fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(*averr));
|
||||
}*/
|
||||
return averr2ncerr(averr);
|
||||
}
|
||||
unref = true;
|
||||
if(nc->packet->stream_index == nc->sub_stream_index){
|
||||
int result = 0, ret;
|
||||
ret = avcodec_decode_subtitle2(nc->subtcodecctx, &nc->subtitle, &result, nc->packet);
|
||||
if(ret >= 0 && result){
|
||||
// FIXME?
|
||||
}
|
||||
}
|
||||
}while(nc->packet->stream_index != nc->stream_index);
|
||||
++nc->packet_outstanding;
|
||||
if(avcodec_send_packet(nc->codecctx, nc->packet) < 0){
|
||||
//fprintf(stderr, "Error processing AVPacket (%s)\n", av_err2str(*ncerr));
|
||||
return ncvisual_decode(nc);
|
||||
}
|
||||
--nc->packet_outstanding;
|
||||
av_packet_unref(nc->packet);
|
||||
int averr = avcodec_receive_frame(nc->codecctx, nc->frame);
|
||||
if(averr >= 0){
|
||||
have_frame = true;
|
||||
}else if(averr == AVERROR(EAGAIN) || averr == AVERROR_EOF){
|
||||
have_frame = false;
|
||||
}else if(averr < 0){
|
||||
//fprintf(stderr, "Error decoding AVPacket (%s)\n", av_err2str(averr));
|
||||
return averr2ncerr(averr);
|
||||
}
|
||||
}while(!have_frame);
|
||||
//print_frame_summary(nc->codecctx, nc->frame);
|
||||
#define IMGALLOCALIGN 32
|
||||
int rows, cols;
|
||||
if(nc->ncp == NULL){ // create plane
|
||||
if(nc->style == NCSCALE_NONE){
|
||||
rows = nc->frame->height / 2;
|
||||
cols = nc->frame->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, NULL);
|
||||
nc->placey = 0;
|
||||
nc->placex = 0;
|
||||
if(nc->ncp == NULL){
|
||||
return NCERR_NOMEM;
|
||||
}
|
||||
}else{ // check for resize
|
||||
ncplane_dim_yx(nc->ncp, &rows, &cols);
|
||||
if(rows != nc->dstheight / 2 || cols != nc->dstwidth){
|
||||
sws_freeContext(nc->swsctx);
|
||||
nc->swsctx = NULL;
|
||||
nc->dstheight = rows * 2;
|
||||
nc->dstwidth = cols;
|
||||
}
|
||||
}
|
||||
const int targformat = AV_PIX_FMT_RGBA;
|
||||
nc->swsctx = sws_getCachedContext(nc->swsctx,
|
||||
nc->frame->width,
|
||||
nc->frame->height,
|
||||
nc->frame->format,
|
||||
nc->dstwidth,
|
||||
nc->dstheight,
|
||||
targformat,
|
||||
SWS_LANCZOS,
|
||||
NULL, NULL, NULL);
|
||||
if(nc->swsctx == NULL){
|
||||
//fprintf(stderr, "Error retrieving swsctx\n");
|
||||
return NCERR_DECODE;
|
||||
}
|
||||
memcpy(nc->oframe, nc->frame, sizeof(*nc->oframe));
|
||||
nc->oframe->format = targformat;
|
||||
nc->oframe->width = nc->dstwidth;
|
||||
nc->oframe->height = nc->dstheight;
|
||||
int size = av_image_alloc(nc->oframe->data, nc->oframe->linesize,
|
||||
nc->oframe->width, nc->oframe->height,
|
||||
nc->oframe->format, IMGALLOCALIGN);
|
||||
if(size < 0){
|
||||
//fprintf(stderr, "Error allocating visual data (%s)\n", av_err2str(size));
|
||||
return NCERR_NOMEM;
|
||||
}
|
||||
int height = sws_scale(nc->swsctx, (const uint8_t* const*)nc->frame->data,
|
||||
nc->frame->linesize, 0,
|
||||
nc->frame->height, nc->oframe->data, nc->oframe->linesize);
|
||||
if(height < 0){
|
||||
//fprintf(stderr, "Error applying scaling (%s)\n", av_err2str(height));
|
||||
return NCERR_NOMEM;
|
||||
}
|
||||
//print_frame_summary(nc->codecctx, nc->oframe);
|
||||
#undef IMGALLOCALIGN
|
||||
av_frame_unref(nc->frame);
|
||||
return NCERR_SUCCESS;
|
||||
}
|
||||
|
||||
static ncvisual*
|
||||
ncvisual_open(const char* filename, nc_err_e* ncerr){
|
||||
ncvisual* ncv = ncvisual_create(1);
|
||||
if(ncv == NULL){
|
||||
// fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
|
||||
*ncerr = NCERR_NOMEM;
|
||||
return NULL;
|
||||
}
|
||||
memset(ncv, 0, sizeof(*ncv));
|
||||
int averr = avformat_open_input(&ncv->fmtctx, filename, NULL, NULL);
|
||||
if(averr < 0){
|
||||
// fprintf(stderr, "Couldn't open %s (%s)\n", filename, av_err2str(*averr));
|
||||
*ncerr = averr2ncerr(averr);
|
||||
goto err;
|
||||
}
|
||||
averr = avformat_find_stream_info(ncv->fmtctx, NULL);
|
||||
if(averr < 0){
|
||||
/*fprintf(stderr, "Error extracting stream info from %s (%s)\n", filename,
|
||||
av_err2str(*averr));*/
|
||||
*ncerr = averr2ncerr(averr);
|
||||
goto err;
|
||||
}
|
||||
//av_dump_format(ncv->fmtctx, 0, filename, false);
|
||||
if((averr = av_find_best_stream(ncv->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->subtcodec, 0)) >= 0){
|
||||
ncv->sub_stream_index = averr;
|
||||
if((ncv->subtcodecctx = avcodec_alloc_context3(ncv->subtcodec)) == NULL){
|
||||
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
||||
*ncerr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
// FIXME do we need avcodec_parameters_to_context() here?
|
||||
if((averr = avcodec_open2(ncv->subtcodecctx, ncv->subtcodec, NULL)) < 0){
|
||||
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
|
||||
*ncerr = averr2ncerr(averr);
|
||||
goto err;
|
||||
}
|
||||
}else{
|
||||
ncv->sub_stream_index = -1;
|
||||
}
|
||||
if((ncv->packet = av_packet_alloc()) == NULL){
|
||||
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
|
||||
*ncerr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
if((averr = av_find_best_stream(ncv->fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->codec, 0)) < 0){
|
||||
// fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr));
|
||||
*ncerr = averr2ncerr(averr);
|
||||
goto err;
|
||||
}
|
||||
ncv->stream_index = averr;
|
||||
if(ncv->codec == NULL){
|
||||
//fprintf(stderr, "Couldn't find decoder for %s\n", filename);
|
||||
goto err;
|
||||
}
|
||||
AVStream* st = ncv->fmtctx->streams[ncv->stream_index];
|
||||
if((ncv->codecctx = avcodec_alloc_context3(ncv->codec)) == NULL){
|
||||
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
||||
*ncerr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
if(avcodec_parameters_to_context(ncv->codecctx, st->codecpar) < 0){
|
||||
goto err;
|
||||
}
|
||||
if((averr = avcodec_open2(ncv->codecctx, ncv->codec, NULL)) < 0){
|
||||
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
|
||||
*ncerr = averr2ncerr(averr);
|
||||
goto err;
|
||||
}
|
||||
/*if((ncv->cparams = avcodec_parameters_alloc()) == NULL){
|
||||
//fprintf(stderr, "Couldn't allocate codec params for %s\n", filename);
|
||||
*averr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->codecctx)) < 0){
|
||||
//fprintf(stderr, "Couldn't get codec params for %s (%s)\n", filename, av_err2str(*averr));
|
||||
goto err;
|
||||
}*/
|
||||
if((ncv->frame = av_frame_alloc()) == NULL){
|
||||
// fprintf(stderr, "Couldn't allocate frame for %s\n", filename);
|
||||
*ncerr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
if((ncv->oframe = av_frame_alloc()) == NULL){
|
||||
// fprintf(stderr, "Couldn't allocate output frame for %s\n", filename);
|
||||
*ncerr = NCERR_NOMEM;
|
||||
goto err;
|
||||
}
|
||||
return ncv;
|
||||
|
||||
err:
|
||||
ncvisual_destroy(ncv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncvisual* ncplane_visual_open(ncplane* nc, const char* filename, nc_err_e* ncerr){
|
||||
ncvisual* ncv = ncvisual_open(filename, ncerr);
|
||||
if(ncv == NULL){
|
||||
return NULL;
|
||||
}
|
||||
ncplane_dim_yx(nc, &ncv->dstheight, &ncv->dstwidth);
|
||||
ncv->dstheight *= 2;
|
||||
ncv->ncp = nc;
|
||||
ncv->style = NCSCALE_STRETCH;
|
||||
return ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_file(notcurses* nc, const char* filename,
|
||||
nc_err_e* ncerr, int y, int x, ncscale_e style){
|
||||
ncvisual* ncv = ncvisual_open(filename, ncerr);
|
||||
if(ncv == NULL){
|
||||
return NULL;
|
||||
}
|
||||
ncv->placey = y;
|
||||
ncv->placex = x;
|
||||
ncv->style = style;
|
||||
ncv->ncobj = nc;
|
||||
ncv->ncp = NULL;
|
||||
return ncv;
|
||||
}
|
||||
|
||||
int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx){
|
||||
//fprintf(stderr, "render %dx%d+%dx%d\n", begy, begx, leny, lenx);
|
||||
if(begy < 0 || begx < 0 || lenx < -1 || leny < -1){
|
||||
return -1;
|
||||
}
|
||||
const AVFrame* f = ncv->oframe;
|
||||
if(f == NULL){
|
||||
return -1;
|
||||
}
|
||||
//fprintf(stderr, "render %d/%d to %dx%d+%dx%d\n", f->height, f->width, begy, begx, leny, lenx);
|
||||
if(begx >= f->width || begy >= f->height){
|
||||
return -1;
|
||||
}
|
||||
if(lenx == -1){ // -1 means "to the end"; use all space available
|
||||
lenx = f->width - begx;
|
||||
}
|
||||
if(leny == -1){
|
||||
leny = f->height - begy;
|
||||
}
|
||||
if(lenx == 0 || leny == 0){ // no need to draw zero-size object, exit
|
||||
return 0;
|
||||
}
|
||||
if(begx + lenx > f->width || begy + leny > f->height){
|
||||
return -1;
|
||||
}
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(ncv->ncp, &dimy, &dimx);
|
||||
ncplane_cursor_move_yx(ncv->ncp, 0, 0);
|
||||
const int linesize = f->linesize[0];
|
||||
void* data = f->data[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);
|
||||
int bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(f->format));
|
||||
if(bpp != 32){
|
||||
return -1;
|
||||
}
|
||||
int ret = ncblit_rgba(ncv->ncp, ncv->placey, ncv->placex, linesize, data,
|
||||
begy, begx, leny, lenx);
|
||||
//av_frame_unref(ncv->oframe);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// iterative over the decoded frames, calling streamer() with curry for each.
|
||||
// frames carry a presentation time relative to the beginning, so we get an
|
||||
// initial timestamp, and check each frame against the elapsed time to sync
|
||||
// up playback.
|
||||
int ncvisual_stream(notcurses* nc, ncvisual* ncv, nc_err_e* ncerr,
|
||||
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;
|
||||
//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));
|
||||
sum_duration += (duration * ncv->timescale);
|
||||
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, NULL);
|
||||
}
|
||||
}
|
||||
if(*ncerr == NCERR_EOF){
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ncvisual_init(int loglevel){
|
||||
av_log_set_level(loglevel);
|
||||
// FIXME could also use av_log_set_callback() and capture the message...
|
||||
return 0;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_rgba(notcurses* nc, const void* rgba, int rows, int rowstride, int cols){
|
||||
ncvisual* ncv = ncvisual_create(1);
|
||||
ncv->placey = 0;
|
||||
ncv->placex = 0;
|
||||
ncv->ncobj = nc;
|
||||
const int targrows = rows / 2 + rows % 2;
|
||||
ncv->ncp = ncplane_new(nc, targrows, cols, 0, 0, NULL);
|
||||
if(ncv->ncp == NULL){
|
||||
ncvisual_destroy(ncv);
|
||||
return NULL;
|
||||
}
|
||||
// FIXME create frame
|
||||
return ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_bgra(notcurses* nc, const void* bgra, int rows, int rowstride, int cols){
|
||||
ncvisual* ncv = ncvisual_create(1);
|
||||
ncv->placey = 0;
|
||||
ncv->placex = 0;
|
||||
ncv->ncobj = nc;
|
||||
const int targrows = rows / 2 + rows % 2;
|
||||
ncv->ncp = ncplane_new(nc, targrows, cols, 0, 0, NULL);
|
||||
if(ncv->ncp == NULL){
|
||||
ncvisual_destroy(ncv);
|
||||
return NULL;
|
||||
}
|
||||
// FIXME create frame
|
||||
return ncv;
|
||||
}
|
||||
#else // built without ffmpeg
|
||||
#ifndef USE_OIIO // built without ffmpeg or oiio
|
||||
bool notcurses_canopen(const notcurses* nc __attribute__ ((unused))){
|
||||
return false;
|
||||
}
|
||||
|
||||
ncplane* ncvisual_plane(ncvisual* ncv){
|
||||
return ncv->ncp;
|
||||
}
|
||||
|
||||
nc_err_e ncvisual_decode(ncvisual* nc){
|
||||
(void)nc;
|
||||
return NCERR_UNIMPLEMENTED;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ncvisual* ncplane_visual_open(ncplane* nc, const char* filename, nc_err_e* ncerr){
|
||||
(void)nc;
|
||||
(void)filename;
|
||||
(void)ncerr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_file(notcurses* nc, const char* filename,
|
||||
nc_err_e* ncerr, int y, int x, ncscale_e style){
|
||||
(void)nc;
|
||||
(void)filename;
|
||||
(void)ncerr;
|
||||
(void)y;
|
||||
(void)x;
|
||||
(void)style;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* ncvisual_subtitle(const ncvisual* ncv){
|
||||
(void)ncv;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ncvisual_init(int loglevel){
|
||||
(void)loglevel;
|
||||
return 0; // allow success here
|
||||
}
|
||||
|
||||
void ncvisual_destroy(ncvisual* ncv){
|
||||
assert(!ncv);
|
||||
(void)ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_rgba(notcurses* nc, const void* rgba, int rows, int rowstride, int cols){
|
||||
(void)nc;
|
||||
(void)rgba;
|
||||
(void)rows;
|
||||
(void)rowstride;
|
||||
(void)cols;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_bgra(notcurses* nc, const void* bgra, int rows, int rowstride, int cols){
|
||||
(void)nc;
|
||||
(void)bgra;
|
||||
(void)rows;
|
||||
(void)rowstride;
|
||||
(void)cols;
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -1950,3 +1950,18 @@ int notcurses_lex_margins(const char* op, notcurses_options* opts){
|
||||
int notcurses_inputready_fd(notcurses* n){
|
||||
return fileno(n->ttyinfp);
|
||||
}
|
||||
|
||||
// generate a temporary plane that can hold the contents of n, rotated 90°
|
||||
ncplane* rotate_plane(const ncplane* n){
|
||||
int absy, absx;
|
||||
ncplane_yx(n, &absy, &absx);
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
if(dimx % 2 != 0){
|
||||
return NULL;
|
||||
}
|
||||
const int newy = dimx / 2;
|
||||
const int newx = dimy * 2;
|
||||
ncplane* newp = ncplane_new(n->nc, newy, newx, absy, absx, n->userptr);
|
||||
return newp;
|
||||
}
|
||||
|
344
src/lib/oiio.cpp
344
src/lib/oiio.cpp
@ -1,344 +0,0 @@
|
||||
#include "version.h"
|
||||
#ifdef USE_OIIO
|
||||
#include <OpenImageIO/filter.h>
|
||||
#include <OpenImageIO/version.h>
|
||||
#include <OpenImageIO/imageio.h>
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
#include <OpenImageIO/imagebufalgo.h>
|
||||
#include "internal.h"
|
||||
|
||||
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<OIIO::ImageBuf> raw;
|
||||
char* filename;
|
||||
bool did_scaling;
|
||||
uint64_t framenum;
|
||||
OIIO::ImageBuf scaled; // in use IFF did_scaling;
|
||||
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
|
||||
// path sets ncobj. ncvisual_destroy() ought in that case kill the ncplane.
|
||||
int placex, placey;
|
||||
ncscale_e style; // none, scale, or stretch
|
||||
struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane
|
||||
} ncvisual;
|
||||
|
||||
ncplane* ncvisual_plane(ncvisual* ncv){
|
||||
return ncv->ncp;
|
||||
}
|
||||
|
||||
bool notcurses_canopen(const notcurses* nc __attribute__ ((unused))){
|
||||
return true;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_create(float timescale){
|
||||
auto ret = new ncvisual;
|
||||
if(ret == nullptr){
|
||||
return nullptr;
|
||||
}
|
||||
ret->packet_outstanding = 0;
|
||||
ret->dstwidth = ret->dstheight = 0;
|
||||
ret->framenum = 0;
|
||||
ret->timescale = timescale;
|
||||
ret->image = nullptr;
|
||||
ret->ncp = nullptr;
|
||||
ret->placex = ret->placey = 0;
|
||||
ret->style = NCSCALE_NONE;
|
||||
ret->ncobj = nullptr;
|
||||
ret->frame = nullptr;
|
||||
ret->raw = nullptr;
|
||||
ret->filename = nullptr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ncvisual*
|
||||
ncvisual_open(const char* filename, nc_err_e* err){
|
||||
ncvisual* ncv = ncvisual_create(filename, 1);
|
||||
if(ncv == nullptr){
|
||||
*err = NCERR_NOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
if((ncv->filename = strdup(filename)) == nullptr){
|
||||
*err = NCERR_NOMEM;
|
||||
delete ncv;
|
||||
return nullptr;
|
||||
}
|
||||
ncv->image = OIIO::ImageInput::open(filename);
|
||||
if(!ncv->image){
|
||||
// fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
|
||||
*err = NCERR_DECODE;
|
||||
ncvisual_destroy(ncv);
|
||||
return nullptr;
|
||||
}
|
||||
/*const auto &spec = ncv->image->spec_dimensions();
|
||||
std::cout << "Opened " << filename << ": " << spec.height << "x" <<
|
||||
spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/
|
||||
return ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncplane_visual_open(ncplane* nc, const char* filename, nc_err_e* ncerr){
|
||||
ncvisual* ncv = ncvisual_open(filename, ncerr);
|
||||
if(ncv == nullptr){
|
||||
*ncerr = NCERR_NOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
ncplane_dim_yx(nc, &ncv->dstheight, &ncv->dstwidth);
|
||||
ncv->dstheight *= 2;
|
||||
ncv->ncp = nc;
|
||||
ncv->style = NCSCALE_STRETCH;
|
||||
ncv->ncobj = nullptr;
|
||||
return ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_file(notcurses* nc, const char* filename, nc_err_e* ncerr,
|
||||
int y, int x, ncscale_e style){
|
||||
ncvisual* ncv = ncvisual_open(filename, ncerr);
|
||||
if(ncv == nullptr){
|
||||
return nullptr;
|
||||
}
|
||||
ncv->placey = y;
|
||||
ncv->placex = x;
|
||||
ncv->style = style;
|
||||
ncv->ncobj = nc;
|
||||
ncv->ncp = nullptr;
|
||||
ncv->ncobj = nc;
|
||||
return ncv;
|
||||
}
|
||||
|
||||
nc_err_e ncvisual_decode(ncvisual* nc){
|
||||
//fprintf(stderr, "current subimage: %d frame: %p\n", nc->image->current_subimage(), nc->frame.get());
|
||||
const auto &spec = nc->image->spec_dimensions(nc->framenum);
|
||||
if(nc->frame){
|
||||
//fprintf(stderr, "seeking subimage: %d\n", nc->image->current_subimage() + 1);
|
||||
OIIO::ImageSpec newspec;
|
||||
if(!nc->image->seek_subimage(nc->image->current_subimage() + 1, 0, newspec)){
|
||||
return NCERR_EOF;
|
||||
}
|
||||
// FIXME check newspec vis-a-vis image->spec()?
|
||||
}
|
||||
//fprintf(stderr, "SUBIMAGE: %d\n", nc->image->current_subimage());
|
||||
nc->did_scaling = false;
|
||||
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){ // FIXME replace with channel shuffle
|
||||
std::fill(nc->frame.get(), nc->frame.get() + pixels, 0xfffffffful);
|
||||
}
|
||||
//fprintf(stderr, "READING: %d %ju\n", nc->image->current_subimage(), nc->framenum);
|
||||
if(!nc->image->read_image(nc->framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->frame.get(), 4)){
|
||||
return NCERR_DECODE;
|
||||
}
|
||||
//fprintf(stderr, "READ: %d %ju\n", nc->image->current_subimage(), nc->framenum);
|
||||
/*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
|
||||
);
|
||||
}*/
|
||||
OIIO::ImageSpec rgbaspec = spec;
|
||||
rgbaspec.nchannels = 4;
|
||||
nc->raw = std::make_unique<OIIO::ImageBuf>(rgbaspec, nc->frame.get());
|
||||
//fprintf(stderr, "SUBS: %d\n", nc->raw->nsubimages());
|
||||
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;
|
||||
}
|
||||
}
|
||||
if(nc->dstwidth != spec.width || nc->dstheight != spec.height){ // scale it
|
||||
OIIO::ROI roi(0, nc->dstwidth, 0, nc->dstheight, 0, 1, 0, 4);
|
||||
if(!OIIO::ImageBufAlgo::resize(nc->scaled, *nc->raw, "", 0, roi)){
|
||||
// FIXME
|
||||
}
|
||||
nc->did_scaling = true;
|
||||
}
|
||||
return NCERR_SUCCESS;
|
||||
}
|
||||
|
||||
int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx){
|
||||
//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->did_scaling ? ncv->scaled.spec() : ncv->raw->spec();
|
||||
const void* pixels = ncv->did_scaling ? ncv->scaled.localpixels() : ncv->raw->localpixels();
|
||||
//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,
|
||||
pixels, 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 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;
|
||||
}
|
||||
|
||||
char* ncvisual_subtitle(const ncvisual* ncv){ // no support in OIIO
|
||||
(void)ncv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ncvisual_init(int loglevel){
|
||||
// FIXME set OIIO global attribute "debug" based on loglevel
|
||||
(void)loglevel;
|
||||
// FIXME check OIIO_VERSION_STRING components against linked openimageio_version()
|
||||
return 0; // allow success here
|
||||
}
|
||||
|
||||
void ncvisual_destroy(ncvisual* ncv){
|
||||
if(ncv){
|
||||
if(ncv->image){
|
||||
ncv->image->close();
|
||||
}
|
||||
if(ncv->ncobj){
|
||||
ncplane_destroy(ncv->ncp);
|
||||
}
|
||||
free(ncv->filename);
|
||||
delete ncv;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME would be nice to have OIIO::attributes("libraries") in here
|
||||
const char* oiio_version(void){
|
||||
return OIIO_VERSION_STRING;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_rgba(notcurses* nc, const void* rgba, int rows, int rowstride, int cols){
|
||||
ncvisual* ncv = ncvisual_create(1);
|
||||
ncv->placey = 0;
|
||||
ncv->placex = 0;
|
||||
ncv->ncobj = nc;
|
||||
const int targrows = rows / 2 + rows % 2;
|
||||
ncv->ncp = ncplane_new(nc, targrows, cols, 0, 0, NULL);
|
||||
if(ncv->ncp == NULL){
|
||||
ncvisual_destroy(ncv);
|
||||
return NULL;
|
||||
}
|
||||
// FIXME create frame
|
||||
return ncv;
|
||||
}
|
||||
|
||||
ncvisual* ncvisual_from_bgra(notcurses* nc, const void* bgra, int rows, int rowstride, int cols){
|
||||
ncvisual* ncv = ncvisual_create(1);
|
||||
ncv->placey = 0;
|
||||
ncv->placex = 0;
|
||||
ncv->ncobj = nc;
|
||||
const int targrows = rows / 2 + rows % 2;
|
||||
ncv->ncp = ncplane_new(nc, targrows, cols, 0, 0, NULL);
|
||||
if(ncv->ncp == NULL){
|
||||
ncvisual_destroy(ncv);
|
||||
return NULL;
|
||||
}
|
||||
// FIXME create frame
|
||||
return ncv;
|
||||
}
|
||||
#endif
|
1067
src/lib/visual.cpp
1067
src/lib/visual.cpp
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
bool LockPiece(){ // returns true if game has ended by reaching level 16
|
||||
curpiece_->get_plane()->mergedown(*board_);
|
||||
curpiece_->mergedown(*board_);
|
||||
int bdimy, bdimx;
|
||||
board_->get_dim(&bdimy, &bdimx);
|
||||
int cleared; // how many contiguous lines were cleared
|
||||
|
@ -63,7 +63,7 @@ private:
|
||||
ncpp::NotCurses& nc_;
|
||||
uint64_t score_;
|
||||
std::mutex mtx_; // guards msdelay_
|
||||
std::unique_ptr<ncpp::Visual> curpiece_;
|
||||
std::unique_ptr<ncpp::Plane> curpiece_;
|
||||
std::unique_ptr<ncpp::Plane> board_;
|
||||
std::unique_ptr<ncpp::Visual> backg_;
|
||||
ncpp::Plane* stdplane_;
|
||||
@ -79,7 +79,7 @@ private:
|
||||
if(!curpiece_){
|
||||
return false;
|
||||
}
|
||||
curpiece_->get_plane()->get_yx(y, x);
|
||||
curpiece_->get_yx(y, x);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
bool MoveDown() { // returns true if the game has ended as a result of this move
|
||||
int y, x;
|
||||
if(PrepForMove(&y, &x)){
|
||||
if(!curpiece_->get_plane()->move(y + 1, x)){
|
||||
if(!curpiece_->move(y + 1, x)){
|
||||
throw TetrisNotcursesErr("move()");
|
||||
}
|
||||
if(InvalidMove()){
|
||||
if(!curpiece_->get_plane()->move(y, x)){
|
||||
if(!curpiece_->move(y, x)){
|
||||
throw TetrisNotcursesErr("move()");
|
||||
}
|
||||
if(y <= board_top_y_ - 1){
|
||||
|
@ -2,11 +2,11 @@ void MoveLateral(int direction) { // pass in -1 for left, 1 for right
|
||||
int shift = 2 * direction;
|
||||
int y, x;
|
||||
if(PrepForMove(&y, &x)){
|
||||
if(!curpiece_->get_plane()->move(y, x + shift)){
|
||||
if(!curpiece_->move(y, x + shift)){
|
||||
throw TetrisNotcursesErr("move()");
|
||||
}
|
||||
if(InvalidMove()){
|
||||
if(!curpiece_->get_plane()->move(y, x)){
|
||||
if(!curpiece_->move(y, x)){
|
||||
throw TetrisNotcursesErr("move()");
|
||||
}
|
||||
}else{
|
||||
|
@ -1,6 +1,6 @@
|
||||
// tidx is an index into tetriminos. yoff and xoff are relative to the
|
||||
// terminal's origin. returns colored north-facing tetrimino on a plane.
|
||||
std::unique_ptr<ncpp::Visual> NewPiece() {
|
||||
std::unique_ptr<ncpp::Plane> NewPiece() {
|
||||
// "North-facing" tetrimino forms (form in which they are released from the top) are expressed in terms of
|
||||
// two rows having between 2 and 4 columns. We map each game column to four columns and each game row to two
|
||||
// rows. Each byte of the texture maps to one 4x4 component block (and wastes 7 bits).
|
||||
@ -16,8 +16,7 @@ std::unique_ptr<ncpp::Visual> NewPiece() {
|
||||
int y, x;
|
||||
stdplane_->get_dim(&y, &x);
|
||||
const int xoff = x / 2 - BOARD_WIDTH + 2 * (random() % (BOARD_WIDTH / 2));
|
||||
/* FIXME
|
||||
std::unique_ptr<ncpp::Visual> n = std::make_unique<ncpp::Visual>(2, cols, board_top_y_ - 1, xoff, nullptr);
|
||||
std::unique_ptr<ncpp::Plane> n = std::make_unique<ncpp::Plane>(2, cols, board_top_y_ - 1, xoff, nullptr);
|
||||
if(n){
|
||||
uint64_t channels = 0;
|
||||
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
|
||||
@ -39,6 +38,4 @@ std::unique_ptr<ncpp::Visual> NewPiece() {
|
||||
throw TetrisNotcursesErr("render()");
|
||||
}
|
||||
return n;
|
||||
*/
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
bool InvalidMove() { // a bit wasteful, but piece are tiny
|
||||
int dy, dx;
|
||||
curpiece_->get_plane()->get_dim(&dy, &dx);
|
||||
curpiece_->get_dim(&dy, &dx);
|
||||
while(dy--){
|
||||
int x = dx;
|
||||
while(x--){
|
||||
ncpp::Cell c, b;
|
||||
if(curpiece_->get_plane()->get_at(dy, x, &c) < 0){
|
||||
if(curpiece_->get_at(dy, x, &c) < 0){
|
||||
throw TetrisNotcursesErr("get_at()");
|
||||
}
|
||||
if(c.is_simple()){
|
||||
continue;
|
||||
}
|
||||
curpiece_->get_plane()->release(c);
|
||||
curpiece_->release(c);
|
||||
int transy = dy, transx = x; // need game area coordinates via translation
|
||||
curpiece_->get_plane()->translate(*board_, &transy, &transx);
|
||||
curpiece_->translate(*board_, &transy, &transx);
|
||||
if(transy < 0 || transy >= board_->get_dim_y() || transx < 0 || transx >= board_->get_dim_x()){
|
||||
return true;
|
||||
}
|
||||
|
@ -1,26 +1,27 @@
|
||||
#include "main.h"
|
||||
#include <vector>
|
||||
|
||||
void RotateCW(struct notcurses* nc, struct ncvisual* n) {
|
||||
void RotateCW(struct notcurses* nc, struct ncplane* n) {
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_cw(n));
|
||||
CHECK(0 == ncplane_rotate_cw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_cw(n));
|
||||
CHECK(0 == ncplane_rotate_cw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_cw(n));
|
||||
CHECK(0 == ncplane_rotate_cw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_cw(n));
|
||||
CHECK(0 == ncplane_rotate_cw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
}
|
||||
|
||||
void RotateCCW(struct notcurses* nc, struct ncvisual* n) {
|
||||
void RotateCCW(struct notcurses* nc, struct ncplane* n) {
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_ccw(n));
|
||||
CHECK(0 == ncplane_rotate_ccw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_ccw(n));
|
||||
CHECK(0 == ncplane_rotate_ccw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_ccw(n));
|
||||
CHECK(0 == ncplane_rotate_ccw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
CHECK(0 == ncvisual_rotate_ccw(n));
|
||||
CHECK(0 == ncplane_rotate_ccw(n));
|
||||
CHECK(0 == notcurses_render(nc));
|
||||
}
|
||||
|
||||
@ -105,6 +106,51 @@ TEST_CASE("Rotate") {
|
||||
CHECK(0 == ncplane_destroy(testn));
|
||||
}
|
||||
|
||||
// use half of each dimension
|
||||
SUBCASE("RotateRGBACW") {
|
||||
std::vector<uint32_t> rgba((dimx / 2) * (dimy / 2), 0xffbbccff);
|
||||
auto ncv = ncvisual_from_rgba(nc_, rgba.data(), dimy / 2, dimx * 2, dimx / 2);
|
||||
REQUIRE(ncv);
|
||||
CHECK(dimx * dimy / 8 <= ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_cw(ncv));
|
||||
CHECK(dimx * dimy / 8 <= ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_cw(ncv));
|
||||
CHECK(dimx * dimy / 8 <= ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_cw(ncv));
|
||||
CHECK(dimx * dimy / 8 <= ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_cw(ncv));
|
||||
CHECK(dimx * dimy / 8 <= ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
ncvisual_destroy(ncv);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
SUBCASE("RotateRGBACCW") {
|
||||
std::vector<uint32_t> rgba(dimx * dimy, 0xffbbccff);
|
||||
auto ncv = ncvisual_from_rgba(nc_, rgba.data(), dimy, dimx * 2, dimx / 2);
|
||||
REQUIRE(ncv);
|
||||
CHECK(dimx * dimy / 4 == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_ccw(ncv));
|
||||
CHECK(dimx * dimy / 4 == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_ccw(ncv));
|
||||
CHECK(dimx * dimy / 4 == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_ccw(ncv));
|
||||
CHECK(dimx * dimy / 4 == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
CHECK(0 == ncvisual_rotate_ccw(ncv));
|
||||
CHECK(dimx * dimy / 4 == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
ncvisual_destroy(ncv);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
CHECK(0 == notcurses_stop(nc_));
|
||||
CHECK(0 == fclose(outfp_));
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "main.h"
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("Multimedia") {
|
||||
if(getenv("TERM") == nullptr){
|
||||
@ -123,6 +124,30 @@ TEST_CASE("Multimedia") {
|
||||
}
|
||||
#endif
|
||||
|
||||
SUBCASE("LoadRGBAFromMemory") {
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(ncp_, &dimy, &dimx);
|
||||
std::vector<uint32_t> rgba(dimx * dimy * 2, 0x88bbccff);
|
||||
auto ncv = ncvisual_from_rgba(nc_, rgba.data(), dimy * 2, dimx * 4, dimx);
|
||||
REQUIRE(ncv);
|
||||
CHECK(dimx * dimy == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
ncvisual_destroy(ncv);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
SUBCASE("LoadBGRAFromMemory") {
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(ncp_, &dimy, &dimx);
|
||||
std::vector<uint32_t> rgba(dimx * dimy * 2, 0x88bbccff);
|
||||
auto ncv = ncvisual_from_bgra(nc_, rgba.data(), dimy * 2, dimx * 4, dimx);
|
||||
REQUIRE(ncv);
|
||||
CHECK(dimx * dimy == ncvisual_render(ncv, 0, 0, -1, -1));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
ncvisual_destroy(ncv);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
CHECK(!notcurses_stop(nc_));
|
||||
CHECK(!fclose(outfp_));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user