mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
absorb first ncvisual_decode into ncvisual_from_file() #655
This commit is contained in:
parent
10b134b596
commit
33318254b2
4
USAGE.md
4
USAGE.md
@ -2594,9 +2594,9 @@ When compiled against a suitable engine (FFmpeg and OpenImageIO are both
|
|||||||
currently supported), Notcurses can populate a visual with pixels decoded
|
currently supported), Notcurses can populate a visual with pixels decoded
|
||||||
from an image or video using `ncvisual_from_file()`. Once opened,
|
from an image or video using `ncvisual_from_file()`. Once opened,
|
||||||
`ncvisual_decode()` should be used to extract each frame (an image will
|
`ncvisual_decode()` should be used to extract each frame (an image will
|
||||||
have only one frame):
|
have only one frame), until it returns `NCERR_EOF`:
|
||||||
|
|
||||||
```
|
```c
|
||||||
// Open a visual at 'file', extracting a codec and parameters.
|
// Open a visual at 'file', extracting a codec and parameters.
|
||||||
struct ncvisual* ncvisual_from_file(const char* file, nc_err_e* ncerr);
|
struct ncvisual* ncvisual_from_file(const char* file, nc_err_e* ncerr);
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ If Notcurses was built against a multimedia engine (FFMpeg or OpenImageIO),
|
|||||||
image and video files can be loaded into visuals using
|
image and video files can be loaded into visuals using
|
||||||
**ncvisual_from_file**. **ncvisual_from_file** discovers the container
|
**ncvisual_from_file**. **ncvisual_from_file** discovers the container
|
||||||
and codecs, but does not verify that the entire file is well-formed.
|
and codecs, but does not verify that the entire file is well-formed.
|
||||||
**ncvisual_decode** ought be invoked to recover the actual frames, once
|
**ncvisual_decode** ought be invoked to recover subsequent frames, once
|
||||||
per frame.
|
per frame.
|
||||||
|
|
||||||
Once the visual is loaded, it can be transformed using **ncvisual_rotate**
|
Once the visual is loaded, it can be transformed using **ncvisual_rotate**
|
||||||
|
@ -24,9 +24,6 @@ chunli_draw(struct notcurses* nc, const char* ext, int count, const cell* b){
|
|||||||
if(chuns[i].ncv == NULL){
|
if(chuns[i].ncv == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if((err = ncvisual_decode(chuns[i].ncv)) != NCERR_SUCCESS){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if((chuns[i].n = ncvisual_render(nc, chuns[i].ncv, NULL)) == NULL){
|
if((chuns[i].n = ncvisual_render(nc, chuns[i].ncv, NULL)) == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -72,9 +69,6 @@ int chunli_demo(struct notcurses* nc){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
free(path);
|
free(path);
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct ncplane* ncp;
|
struct ncplane* ncp;
|
||||||
if((ncp = ncvisual_render(nc, ncv, NULL)) == NULL){
|
if((ncp = ncvisual_render(nc, ncv, NULL)) == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -38,8 +38,7 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){
|
|||||||
}
|
}
|
||||||
int vheight, yscale;
|
int vheight, yscale;
|
||||||
int vwidth, xscale;
|
int vwidth, xscale;
|
||||||
if((ncerr = ncvisual_decode(ncv)) != NCERR_SUCCESS ||
|
if(ncvisual_geom(nc, ncv, NCBLIT_DEFAULT, &vheight, &vwidth, &yscale, &xscale)){
|
||||||
ncvisual_geom(nc, ncv, NCBLIT_DEFAULT, &vheight, &vwidth, &yscale, &xscale)){
|
|
||||||
ncvisual_destroy(ncv);
|
ncvisual_destroy(ncv);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -183,10 +183,6 @@ int fallin_demo(struct notcurses* nc){
|
|||||||
if(ncv == NULL){
|
if(ncv == NULL){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(ncv);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.n = stdn,
|
.n = stdn,
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
|
@ -154,9 +154,6 @@ int luigi_demo(struct notcurses* nc){
|
|||||||
if(nv == NULL){
|
if(nv == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if((ncerr = ncvisual_decode(nv)) != NCERR_SUCCESS){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.n = notcurses_stddim_yx(nc, &rows, &cols),
|
.n = notcurses_stddim_yx(nc, &rows, &cols),
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
@ -195,10 +192,6 @@ int luigi_demo(struct notcurses* nc){
|
|||||||
if(wmncv == NULL){
|
if(wmncv == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if((ncerr = ncvisual_decode(wmncv)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(wmncv);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
uint64_t channels = 0;
|
uint64_t channels = 0;
|
||||||
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
|
channels_set_fg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
|
||||||
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
|
channels_set_bg_alpha(&channels, CELL_ALPHA_TRANSPARENT);
|
||||||
|
@ -40,10 +40,6 @@ fadethread(void* vnc){
|
|||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
};
|
};
|
||||||
int three = 3;
|
int three = 3;
|
||||||
if(NCERR_SUCCESS != ncvisual_decode(ncv)){
|
|
||||||
ncvisual_destroy(ncv);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
struct ncplane* globeplane;
|
struct ncplane* globeplane;
|
||||||
if((globeplane = ncvisual_render(nc, ncv, &vopts)) == NULL){
|
if((globeplane = ncvisual_render(nc, ncv, &vopts)) == NULL){
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -151,10 +147,6 @@ int outro(struct notcurses* nc){
|
|||||||
if(chncv == NULL){
|
if(chncv == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if((err = ncvisual_decode(chncv)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(chncv);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.n = ncp,
|
.n = ncp,
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
|
@ -76,11 +76,6 @@ view_images(struct notcurses* nc, struct ncplane* nstd, int dimy, int dimx){
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
free(pic);
|
free(pic);
|
||||||
if((err = ncvisual_decode(ncv2)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(ncv2);
|
|
||||||
ncplane_destroy(dsplane);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.n = dsplane,
|
.n = dsplane,
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
@ -107,11 +102,6 @@ view_images(struct notcurses* nc, struct ncplane* nstd, int dimy, int dimx){
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
free(pic);
|
free(pic);
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(ncv);
|
|
||||||
ncplane_destroy(dsplane);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
vopts.n = notcurses_stdplane(nc);
|
vopts.n = notcurses_stdplane(nc);
|
||||||
if(ncvisual_render(nc, ncv, &vopts) == NULL){
|
if(ncvisual_render(nc, ncv, &vopts) == NULL){
|
||||||
ncvisual_destroy(ncv);
|
ncvisual_destroy(ncv);
|
||||||
|
@ -226,6 +226,7 @@ nc_err_e ncvisual_resize(ncvisual* nc, int rows, int cols) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
||||||
|
AVStream* st;
|
||||||
*ncerr = NCERR_SUCCESS;
|
*ncerr = NCERR_SUCCESS;
|
||||||
ncvisual* ncv = ncvisual_create();
|
ncvisual* ncv = ncvisual_create();
|
||||||
if(ncv == nullptr){
|
if(ncv == nullptr){
|
||||||
@ -238,15 +239,13 @@ ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
|||||||
if(averr < 0){
|
if(averr < 0){
|
||||||
//fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr);
|
//fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr);
|
||||||
*ncerr = averr2ncerr(averr);
|
*ncerr = averr2ncerr(averr);
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
averr = avformat_find_stream_info(ncv->details.fmtctx, nullptr);
|
averr = avformat_find_stream_info(ncv->details.fmtctx, nullptr);
|
||||||
if(averr < 0){
|
if(averr < 0){
|
||||||
//fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr);
|
//fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr);
|
||||||
*ncerr = averr2ncerr(averr);
|
*ncerr = averr2ncerr(averr);
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
//av_dump_format(ncv->details.fmtctx, 0, filename, false);
|
//av_dump_format(ncv->details.fmtctx, 0, filename, false);
|
||||||
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details.subtcodec, 0)) >= 0){
|
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details.subtcodec, 0)) >= 0){
|
||||||
@ -254,15 +253,13 @@ ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
|||||||
if((ncv->details.subtcodecctx = avcodec_alloc_context3(ncv->details.subtcodec)) == nullptr){
|
if((ncv->details.subtcodecctx = avcodec_alloc_context3(ncv->details.subtcodec)) == nullptr){
|
||||||
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
||||||
*ncerr = NCERR_NOMEM;
|
*ncerr = NCERR_NOMEM;
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
// FIXME do we need avcodec_parameters_to_context() here?
|
// FIXME do we need avcodec_parameters_to_context() here?
|
||||||
if((averr = avcodec_open2(ncv->details.subtcodecctx, ncv->details.subtcodec, nullptr)) < 0){
|
if((averr = avcodec_open2(ncv->details.subtcodecctx, ncv->details.subtcodec, nullptr)) < 0){
|
||||||
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
|
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
|
||||||
*ncerr = averr2ncerr(averr);
|
*ncerr = averr2ncerr(averr);
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ncv->details.sub_stream_index = -1;
|
ncv->details.sub_stream_index = -1;
|
||||||
@ -271,22 +268,20 @@ ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
|||||||
if((ncv->details.packet = av_packet_alloc()) == nullptr){
|
if((ncv->details.packet = av_packet_alloc()) == nullptr){
|
||||||
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
|
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
|
||||||
*ncerr = NCERR_NOMEM;
|
*ncerr = NCERR_NOMEM;
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details.codec, 0)) < 0){
|
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details.codec, 0)) < 0){
|
||||||
// fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr));
|
// fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr));
|
||||||
*ncerr = averr2ncerr(averr);
|
*ncerr = averr2ncerr(averr);
|
||||||
ncvisual_destroy(ncv);
|
goto err;
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
ncv->details.stream_index = averr;
|
ncv->details.stream_index = averr;
|
||||||
if(ncv->details.codec == nullptr){
|
if(ncv->details.codec == nullptr){
|
||||||
//fprintf(stderr, "Couldn't find decoder for %s\n", filename);
|
//fprintf(stderr, "Couldn't find decoder for %s\n", filename);
|
||||||
ncvisual_destroy(ncv);
|
*ncerr = NCERR_DECODE;
|
||||||
return nullptr;
|
goto err;
|
||||||
}
|
}
|
||||||
AVStream* st = ncv->details.fmtctx->streams[ncv->details.stream_index];
|
st = ncv->details.fmtctx->streams[ncv->details.stream_index];
|
||||||
if((ncv->details.codecctx = avcodec_alloc_context3(ncv->details.codec)) == nullptr){
|
if((ncv->details.codecctx = avcodec_alloc_context3(ncv->details.codec)) == nullptr){
|
||||||
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
|
||||||
*ncerr = NCERR_NOMEM;
|
*ncerr = NCERR_NOMEM;
|
||||||
@ -313,6 +308,9 @@ ncvisual* ncvisual_from_file(const char* filename, nc_err_e* ncerr) {
|
|||||||
// frame is set up in prep_details(), so that format can be set there, as
|
// frame is set up in prep_details(), so that format can be set there, as
|
||||||
// is necessary when it is prepared from inputs other than files. oframe
|
// is necessary when it is prepared from inputs other than files. oframe
|
||||||
// is set up whenever we convert to RGBA.
|
// is set up whenever we convert to RGBA.
|
||||||
|
if((*ncerr = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
return ncv;
|
return ncv;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
@ -41,10 +41,6 @@ int main(int argc, char** argv){
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
notcurses_render(nc);
|
notcurses_render(nc);
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
ncvisual_destroy(ncv);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.n = std,
|
.n = std,
|
||||||
.scaling = scaling,
|
.scaling = scaling,
|
||||||
|
@ -89,9 +89,6 @@ int main(void){
|
|||||||
if(!ncv){
|
if(!ncv){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
.n = n,
|
.n = n,
|
||||||
|
@ -31,9 +31,6 @@ int main(int argc, char** argv){
|
|||||||
if(!ncv){
|
if(!ncv){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
if((ncerr = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
int scaley, scalex;
|
int scaley, scalex;
|
||||||
ncvisual_geom(nc, ncv, NCBLIT_DEFAULT, nullptr, nullptr, &scaley, &scalex);
|
ncvisual_geom(nc, ncv, NCBLIT_DEFAULT, nullptr, nullptr, &scaley, &scalex);
|
||||||
//ncvisual_resize(ncv, dimy * scaley, dimx * scalex);
|
//ncvisual_resize(ncv, dimy * scaley, dimx * scalex);
|
||||||
@ -45,7 +42,6 @@ int main(int argc, char** argv){
|
|||||||
if(notcurses_render(nc)){
|
if(notcurses_render(nc)){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
ncvisual_destroy(ncv);
|
|
||||||
return notcurses_stop(nc) || failed ? EXIT_FAILURE : EXIT_SUCCESS;
|
return notcurses_stop(nc) || failed ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
@ -90,9 +90,6 @@ int main(void){
|
|||||||
if(!ncv){
|
if(!ncv){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
if((err = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
struct ncvisual_options vopts = {
|
struct ncvisual_options vopts = {
|
||||||
.scaling = NCSCALE_STRETCH,
|
.scaling = NCSCALE_STRETCH,
|
||||||
.n = n,
|
.n = n,
|
||||||
|
@ -35,9 +35,6 @@ int main(int argc, char** argv){
|
|||||||
if(!ncv){
|
if(!ncv){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
if((ncerr = ncvisual_decode(ncv)) != NCERR_SUCCESS){
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
int scaley, scalex;
|
int scaley, scalex;
|
||||||
vopts.n = n;
|
vopts.n = n;
|
||||||
if(ncvisual_render(nc, ncv, &vopts) == nullptr){
|
if(ncvisual_render(nc, ncv, &vopts) == nullptr){
|
||||||
|
@ -28,8 +28,6 @@ TEST_CASE("Visual") {
|
|||||||
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
||||||
REQUIRE(ncv);
|
REQUIRE(ncv);
|
||||||
REQUIRE(NCERR_SUCCESS == ncerr);
|
REQUIRE(NCERR_SUCCESS == ncerr);
|
||||||
ncerr = ncvisual_decode(ncv);
|
|
||||||
REQUIRE(NCERR_SUCCESS == ncerr);
|
|
||||||
/*CHECK(dimy * 2 == frame->height);
|
/*CHECK(dimy * 2 == frame->height);
|
||||||
CHECK(dimx == frame->width); FIXME */
|
CHECK(dimx == frame->width); FIXME */
|
||||||
struct ncvisual_options opts{};
|
struct ncvisual_options opts{};
|
||||||
@ -49,8 +47,6 @@ TEST_CASE("Visual") {
|
|||||||
ncplane_dim_yx(ncp_, &dimy, &dimx);
|
ncplane_dim_yx(ncp_, &dimy, &dimx);
|
||||||
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
||||||
REQUIRE(ncv);
|
REQUIRE(ncv);
|
||||||
REQUIRE(0 == ncerr);
|
|
||||||
ncerr = ncvisual_decode(ncv);
|
|
||||||
REQUIRE(NCERR_SUCCESS == ncerr);
|
REQUIRE(NCERR_SUCCESS == ncerr);
|
||||||
/*CHECK(dimy * 2 == frame->height);
|
/*CHECK(dimy * 2 == frame->height);
|
||||||
CHECK(dimx == frame->width); FIXME */
|
CHECK(dimx == frame->width); FIXME */
|
||||||
@ -71,8 +67,6 @@ TEST_CASE("Visual") {
|
|||||||
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
auto ncv = ncvisual_from_file(find_data("changes.jpg"), &ncerr);
|
||||||
REQUIRE(ncv);
|
REQUIRE(ncv);
|
||||||
REQUIRE(NCERR_SUCCESS == ncerr);
|
REQUIRE(NCERR_SUCCESS == ncerr);
|
||||||
ncerr = ncvisual_decode(ncv);
|
|
||||||
REQUIRE(NCERR_SUCCESS == ncerr);
|
|
||||||
/*CHECK(dimy * 2 == frame->height);
|
/*CHECK(dimy * 2 == frame->height);
|
||||||
CHECK(dimx == frame->width); FIXME */
|
CHECK(dimx == frame->width); FIXME */
|
||||||
struct ncvisual_options opts{};
|
struct ncvisual_options opts{};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user