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:
nick black 2020-05-04 11:05:52 -04:00 committed by Nick Black
parent f7e002d913
commit f63325db60
21 changed files with 1243 additions and 1039 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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

View File

@ -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);

View File

@ -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){

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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;
}

View File

@ -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){

View File

@ -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{

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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_));

View File

@ -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_));
}