Repository: dankamongmen/notcurses (language: python)

Issue:
smooth bitmap movement with kitty graphics
Currently, bitmaps can move only a cell at a time. That leads to jerky movement. With Kitty, we can do subcell placement. With sixel, we can fake it by adding transparent rows/columns. Consider doing so.

Actually, this could apply to all blitters. Quadblitter could move in half-cell increments by adding a transparent row or column *of source pixels*. hrmmmmmmmmmmmM!
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking


Discussion / hints:
note that this isn't just relevant to movement of planes, but also for centering. `notcurses-info` currently looks like this (see below). i can't get the logo centered, because it's 60 pixels high, on 4x20 pixel high cells. we'd thus want to be halfway into the first row, but that's unavailable, and we're stuck with either the first or second row, 10 pixels away from where we want to be.

![2021-06-30-180436_800x1420_scrot](https://user-images.githubusercontent.com/143473/124037474-a3872c00-d9cd-11eb-9447-14dcc08f2690.png)

yeah we ought definitely do this. i think the API is basically "expose pixel coordinates for `ncvisual_render()`, and get as close to it as possible". with kitty bitmaps, we can get directly there with no further help. with sixel, we'd need insert the transparencies. with cells, we'd always have to do it manually.
i think it might be better, as mentioned in #2230, to add two new fields to `ncvisual_options`, `pixoffy` and `pixoffx`.
or maybe we'd like a new function, `ncvisual_render_bitmap()` which is restricted to `NCBLIT_PIXEL` and uses absolute x,y pixel coordinates...
hrmmm. let's go ahead and add the fields to `ncvisual_options` (not implemented for now) for #1777's sake. we'll only use it with cells when the offsets are 0 or perhaps half. if we want an absolute positioning function later, we can add one.
so ought we deprecate `notcurses_render()`?

Fix patch:
diff --git a/USAGE.md b/USAGE.md
index 37e8464150..04bd641510 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -3378,6 +3378,13 @@ struct ncvisual_options {
   ncblitter_e blitter; // glyph set to use (maps input to output cells)
   uint64_t flags; // bitmask over NCVISUAL_OPTION_*
   uint32_t transcolor; // used only if NCVISUAL_OPTION_ADDALPHA is set
+  // pixel offsets within the cell. if NCBLIT_PIXEL is used, the bitmap will
+  // be drawn offset from the upper-left cell's origin by these amounts. it is
+  // an error if either number exceeds the cell-pixel geometry in its
+  // dimension. if NCBLIT_PIXEL is not used, these fields are ignored.
+  // this functionality can be used for smooth bitmap movement.
+  // FIXME not yet implemented.
+  unsigned pxoffy, pxoffx;
 };
 
 typedef enum {
diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md
index e5f6aabc3b..a36cba34d4 100644
--- a/doc/man/man3/notcurses_visual.3.md
+++ b/doc/man/man3/notcurses_visual.3.md
@@ -47,6 +47,7 @@ struct ncvisual_options {
   ncblitter_e blitter; // glyph set to use
   uint64_t flags; // bitmask over NCVISUAL_OPTION_*
   uint32_t transcolor; // use this color for ADDALPHA
+  unsigned pxoffy, pxoffx; // pixel offset from origin
 };
 
 typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*);
@@ -286,6 +287,12 @@ Only one bitmap can be blitted onto a plane at a time (but multiple planes
 with bitmaps may be visible); blitting a second to the same plane will delete
 the original.
 
+**pxoffy** and **pxoffx** can specify an offset from the origin of the upper
+left cell. This can be used for absolute positioning of a bitmap, or for
+smooth movement of same. It is an error if **pxoffy** exceeds the cell height
+in pixels, or **pxoffx** exceeds the cell width in pixels. If
+**NCBLIT_PIXEL** is not used, these fields are ignored.
+
 # RETURN VALUES
 
 **ncvisual_from_file** returns an **ncvisual** object on success, or **NULL**
@@ -371,6 +378,8 @@ Multiple threads may not currently call **ncvisual_render** concurrently
 using the same **ncvisual**, even if targeting distinct **ncplane**s. This
 will likely change in the future.
 
+**pxoffy** and **pxoffx** are not yet implemented.
+
 # SEE ALSO
 
 **notcurses(3)**,
diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h
index 5b2cc1c006..e23ac32667 100644
--- a/include/notcurses/notcurses.h
+++ b/include/notcurses/notcurses.h
@@ -2804,7 +2804,6 @@ API ALLOC struct ncvisual* ncvisual_from_plane(const struct ncplane* n,
 #define NCVISUAL_OPTION_ADDALPHA      0x0010ull // transcolor is in effect
 #define NCVISUAL_OPTION_CHILDPLANE    0x0020ull // interpret n as parent
 #define NCVISUAL_OPTION_NOINTERPOLATE 0x0040ull // non-interpolative scaling
-                                                // 0x0080 is used internally
 
 struct ncvisual_options {
   // if no ncplane is provided, one will be created using the exact size
@@ -2834,6 +2833,13 @@ struct ncvisual_options {
   ncblitter_e blitter; // glyph set to use (maps input to output cells)
   uint64_t flags; // bitmask over NCVISUAL_OPTION_*
   uint32_t transcolor; // treat this color as transparent under NCVISUAL_OPTION_ADDALPHA
+  // pixel offsets within the cell. if NCBLIT_PIXEL is used, the bitmap will
+  // be drawn offset from the upper-left cell's origin by these amounts. it is
+  // an error if either number exceeds the cell-pixel geometry in its
+  // dimension. if NCBLIT_PIXEL is not used, these fields are ignored.
+  // this functionality can be used for smooth bitmap movement.
+  // FIXME not yet implemented.
+  unsigned pxoffy, pxoffx;
 };
 
 // Create an RGBA flat array from the selected region of the ncplane 'nc'.
diff --git a/src/demo/unicodeblocks.c b/src/demo/unicodeblocks.c
index e454765c92..38e3d365a1 100644
--- a/src/demo/unicodeblocks.c
+++ b/src/demo/unicodeblocks.c
@@ -103,33 +103,14 @@ draw_block(struct ncplane* nn, uint32_t blockstart){
   for(chunk = 0 ; chunk < BLOCKSIZE / CHUNKSIZE ; ++chunk){
     int z;
     for(z = 0 ; z < CHUNKSIZE ; ++z){
-      wchar_t w = blockstart + chunk * CHUNKSIZE + z;
-      char utf8arr[MB_CUR_MAX * 3 + 5];
+      uint32_t w = blockstart + chunk * CHUNKSIZE + z;
       // problematic characters FIXME (see TERMINALS.md)
-      if(w == 0x070f || w == 0x08e2 || w == 0x06dd){
-        strcpy(utf8arr, "  ");
-      }else if(wcwidth(w) >= 1 && iswgraph(w)){
-        mbstate_t ps;
-        memset(&ps, 0, sizeof(ps));
-        int bwc = wcrtomb(utf8arr, w, &ps);
-        if(bwc < 0){
-          fprintf(stderr, "Couldn't convert %u (%x) (%lc) (%s)\n",
-                  blockstart + chunk * CHUNKSIZE + z,
-                  blockstart + chunk * CHUNKSIZE + z,
-                  (wint_t)w, strerror(errno));
+      if(w != 0x070f && w != 0x08e2 && w != 0x06dd){
+        ncplane_set_fg_rgb8(nn, 0xad + z * 2, 0xff, 0x2f - z * 2);
+        ncplane_set_bg_rgb8(nn, 8 * chunk, 8 * chunk, 8 * chunk);
+        if(ncplane_putwc_yx(nn, chunk + 1, z * 2 + 1, w) < 0){
           return -1;
         }
-        if(wcwidth(w) < 2){
-          utf8arr[bwc++] = ' ';
-        }
-        utf8arr[bwc++] = '\0';
-      }else{ // don't dump non-printing codepoints
-        strcpy(utf8arr, "  ");
-      }
-      ncplane_set_fg_rgb8(nn, 0xad + z * 2, 0xff, 0x2f - z * 2);
-      ncplane_set_bg_rgb8(nn, 8 * chunk, 8 * chunk, 8 * chunk);
-      if(ncplane_putstr_yx(nn, chunk + 1, z * 2 + 1, utf8arr) < 0){
-        return -1;
       }
     }
   }


Test patch:
diff --git a/src/tests/bitmap.cpp b/src/tests/bitmap.cpp
index 5bf27cd5e6..ca0758b8dd 100644
--- a/src/tests/bitmap.cpp
+++ b/src/tests/bitmap.cpp
@@ -27,16 +27,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xe61c28ff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     CHECK(0 == ncvisual_resize(ncv, 1, 1));
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
@@ -54,16 +47,10 @@ TEST_CASE("Bitmaps") {
     REQUIRE(nullptr != ncv);
     auto nn = ncplane_dup(n_, nullptr);
     REQUIRE(nullptr != nn);
-    struct ncvisual_options vopts = {
-      .n = nn,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.n = nn;
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     int maxy, maxx;
     ncplane_pixelgeom(n_, nullptr, nullptr, nullptr, nullptr, &maxy, &maxx);
     CHECK(0 == ncvisual_resize(ncv, maxy, maxx));
@@ -90,17 +77,10 @@ TEST_CASE("Bitmaps") {
       .flags = 0, .margin_b = 0, .margin_r = 0,
     };
     auto n = ncplane_create(n_, &nopts);
-    struct ncvisual_options vopts = {
-      .n = n,
-      .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.n = n;
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     CHECK(nullptr == ncvisual_render(nc_, ncv, &vopts));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
@@ -120,17 +100,10 @@ TEST_CASE("Bitmaps") {
       .flags = 0, .margin_b = 0, .margin_r = 0,
     };
     auto n = ncplane_create(n_, &nopts);
-    struct ncvisual_options vopts = {
-      .n = n,
-      .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.n = n;
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     CHECK(nullptr == ncvisual_render(nc_, ncv, &vopts));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
@@ -152,17 +125,9 @@ TEST_CASE("Bitmaps") {
       .flags = 0, .margin_b = 0, .margin_r = 0,
     };
     auto n = ncplane_create(n_, &nopts);
-    struct ncvisual_options vopts = {
-      .n = n,
-      .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     CHECK(nullptr != ncvisual_render(nc_, ncv, &vopts));
     CHECK(0 > ncplane_putchar_yx(n, ' ', 0, 0));
     CHECK(0 == notcurses_render(nc_));
@@ -177,16 +142,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xe61c28ff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr, .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto botn = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != botn);
     // should just have a red plane
@@ -240,16 +198,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xffffffff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
@@ -320,16 +271,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xff7799dd));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     struct ncplane_options nopts = {
@@ -388,6 +332,7 @@ TEST_CASE("Bitmaps") {
                NCVISUAL_OPTION_HORALIGNED |
                NCVISUAL_OPTION_VERALIGNED,
       .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto y = nc_->tcache.cellpixy * 6;
     auto x = nc_->tcache.cellpixx;
@@ -430,6 +375,7 @@ TEST_CASE("Bitmaps") {
                NCVISUAL_OPTION_HORALIGNED |
                NCVISUAL_OPTION_VERALIGNED,
       .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto y = nc_->tcache.cellpixy * 6;
     auto x = nc_->tcache.cellpixx;
@@ -454,16 +400,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xffffffff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = y, .lenx = x,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
@@ -492,16 +431,9 @@ TEST_CASE("Bitmaps") {
     }
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = y, .lenx = x,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
@@ -530,16 +462,9 @@ TEST_CASE("Bitmaps") {
     }
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = y, .lenx = x,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
@@ -576,16 +501,9 @@ TEST_CASE("Bitmaps") {
     }
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 2, .x = 2,
-      .begy = 0, .begx = 0,
-      .leny = y, .lenx = x,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     ncvisual_destroy(ncv);
@@ -673,16 +591,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y * 4, htole(0xffccccff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     for(int i = 0 ; i <= ncplane_dim_x(n_) ; ++i){
       CHECK(0 == ncplane_move_yx(n, 0, i));
@@ -698,16 +609,10 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y * 4, htole(0xffccccff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = ncplane_dim_x(n_) - 3,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
+    vopts.x = ncplane_dim_x(n_) - 3;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     for(int i = ncplane_dim_x(n_) - 3 ; i >= 0 ; --i){
       CHECK(0 == ncplane_move_yx(n, 0, i));
@@ -723,16 +628,9 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y * 4, htole(0xffccccff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     for(int i = 0 ; i <= ncplane_dim_y(n_) ; ++i){
       CHECK(0 == ncplane_move_yx(n, i, 0));
@@ -748,16 +646,10 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y * 4, htole(0xffccccff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = ncplane_dim_y(n_) - 3, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_PIXEL,
-      .flags = NCVISUAL_OPTION_NODEGRADE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_PIXEL;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
+    vopts.y = ncplane_dim_y(n_) - 3;
     auto n = ncvisual_render(nc_, ncv, &vopts);
     for(int i = ncplane_dim_y(n_) - 3 ; i >= 0 ; --i){
       CHECK(0 == ncplane_move_yx(n, i, 0));
diff --git a/src/tests/blit.cpp b/src/tests/blit.cpp
index 8a2179f401..5d4f9aa801 100644
--- a/src/tests/blit.cpp
+++ b/src/tests/blit.cpp
@@ -27,19 +27,11 @@ TEST_CASE("Blit") {
     };
     auto ncp = ncplane_create(n_, &nopts);
     REQUIRE(nullptr != ncp);
-    struct ncvisual_options vopts = {
-      .n = ncp,
-      .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0,
-      .begx = 0,
-      .leny = 2,
-      .lenx = 4,
-      .blitter = NCBLIT_1x1,
-      .flags = 0,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.n = ncp;
+    vopts.leny = 2;
+    vopts.lenx = 4;
+    vopts.blitter = NCBLIT_1x1;
     ncblit_bgrx(data, 16, &vopts);
     for(int y = 0 ; y < 2 ; ++y){
       for(int x = 0 ; x < 4 ; ++x){
@@ -77,19 +69,11 @@ TEST_CASE("Blit") {
     };
     auto ncp = ncplane_create(n_, &nopts);
     REQUIRE(nullptr != ncp);
-    struct ncvisual_options vopts = {
-      .n = ncp,
-      .scaling = NCSCALE_NONE,
-      .y = 0,
-      .x = 0,
-      .begy = 0,
-      .begx = 0,
-      .leny = 2,
-      .lenx = 4,
-      .blitter = NCBLIT_1x1,
-      .flags = 0,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.n = ncp;
+    vopts.leny = 2;
+    vopts.lenx = 4;
+    vopts.blitter = NCBLIT_1x1;
     ncblit_bgrx(data, 20, &vopts);
     for(int y = 0 ; y < 2 ; ++y){
       for(int x = 0 ; x < 4 ; ++x){
@@ -137,12 +121,8 @@ TEST_CASE("Blit") {
         ncpixel_set_b(pbr, 0x95);
         auto ncv = ncvisual_from_rgba(p2x2, 2, 8, 2);
         REQUIRE(nullptr != ncv);
-        struct ncvisual_options vopts = {
-          .n = nullptr, .scaling = NCSCALE_NONE,
-          .y = 0, .x = 0, .begy = 0, .begx = 0, .leny = 0, .lenx = 0,
-          .blitter = NCBLIT_2x2, .flags = 0,
-          .transcolor = 0,
-        };
+        struct ncvisual_options vopts{};
+        vopts.blitter = NCBLIT_2x2;
         auto ncp = ncvisual_render(nc_, ncv, &vopts);
         ncvisual_destroy(ncv);
         REQUIRE(nullptr != ncp);
@@ -167,16 +147,8 @@ TEST_CASE("Blit") {
     };
     auto ncv = ncvisual_from_rgba(data, 1, 8, 2);
     REQUIRE(nullptr != ncv);
-    struct ncvisual_options vopts = {
-      .n = nullptr,
-      .scaling = NCSCALE_NONE,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_1x1,
-      .flags = 0,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.blitter = NCBLIT_1x1;
     auto p = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
@@ -201,16 +173,8 @@ TEST_CASE("Blit") {
       };
       auto ncv = ncvisual_from_rgba(data, 2, 16, 4);
       REQUIRE(nullptr != ncv);
-      struct ncvisual_options vopts = {
-        .n = nullptr,
-        .scaling = NCSCALE_NONE,
-        .y = 0, .x = 0,
-        .begy = 0, .begx = 0,
-        .leny = 0, .lenx = 0,
-        .blitter = NCBLIT_2x1,
-        .flags = 0,
-        .transcolor = 0,
-      };
+      struct ncvisual_options vopts{};
+      vopts.blitter = NCBLIT_2x1;
       auto p = ncvisual_render(nc_, ncv, &vopts);
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
@@ -252,16 +216,8 @@ TEST_CASE("Blit") {
       };
       auto ncv = ncvisual_from_rgba(data, 2, 128, 32);
       REQUIRE(nullptr != ncv);
-      struct ncvisual_options vopts = {
-        .n = nullptr,
-        .scaling = NCSCALE_NONE,
-        .y = 0, .x = 0,
-        .begy = 0, .begx = 0,
-        .leny = 0, .lenx = 0,
-        .blitter = NCBLIT_2x2,
-        .flags = 0,
-        .transcolor = 0,
-      };
+      struct ncvisual_options vopts{};
+      vopts.blitter = NCBLIT_2x2;
       auto p = ncvisual_render(nc_, ncv, &vopts);
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
diff --git a/src/tests/media.cpp b/src/tests/media.cpp
index cff8bb3e3d..a31424c87f 100644
--- a/src/tests/media.cpp
+++ b/src/tests/media.cpp
@@ -356,16 +356,11 @@ TEST_CASE("Media") {
     };
     auto parent = ncplane_create(n_, &opts);
     REQUIRE(parent);
-    struct ncvisual_options vopts = {
-      .n = parent,
-      .scaling = NCSCALE_STRETCH,
-      .y = 0, .x = 0,
-      .begy = 0, .begx = 0,
-      .leny = 0, .lenx = 0,
-      .blitter = NCBLIT_1x1,
-      .flags = NCVISUAL_OPTION_CHILDPLANE,
-      .transcolor = 0,
-    };
+    struct ncvisual_options vopts{};
+    vopts.scaling = NCSCALE_STRETCH;
+    vopts.n = parent;
+    vopts.blitter = NCBLIT_1x1;
+    vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
     auto ncv = ncvisual_from_file(find_data("onedot.png").get());
     REQUIRE(ncv);
     auto child = ncvisual_render(nc_, ncv, &vopts);
diff --git a/src/tests/stacking.cpp b/src/tests/stacking.cpp
index e976ad360d..1d4ec4ec95 100644
--- a/src/tests/stacking.cpp
+++ b/src/tests/stacking.cpp
@@ -43,7 +43,7 @@ TEST_CASE("Stacking") {
     struct ncvisual_options vopts = {
       .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0,
       .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0,
-      .transcolor = 0,
+      .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
     };
     CHECK(top == ncvisual_render(nc_, ncv, &vopts));
     ncvisual_destroy(ncv);
@@ -81,7 +81,7 @@ TEST_CASE("Stacking") {
     struct ncvisual_options vopts = {
       .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0,
       .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0,
-      .transcolor = 0,
+      .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
     };
     CHECK(top == ncvisual_render(nc_, ncv, &vopts));
     ncvisual_destroy(ncv);
@@ -120,7 +120,7 @@ TEST_CASE("Stacking") {
       struct ncvisual_options vopts = {
         .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0,
         .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0,
-        .transcolor = 0,
+        .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
       };
       CHECK(top == ncvisual_render(nc_, ncv, &vopts));
       ncvisual_destroy(ncv);
@@ -162,7 +162,7 @@ TEST_CASE("Stacking") {
       struct ncvisual_options vopts = {
         .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0,
         .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0,
-        .transcolor = 0,
+        .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
       };
       CHECK(top == ncvisual_render(nc_, ncv, &vopts));
       ncvisual_destroy(ncv);
diff --git a/src/tests/visual.cpp b/src/tests/visual.cpp
index 516e180efd..9c452d0754 100644
--- a/src/tests/visual.cpp
+++ b/src/tests/visual.cpp
@@ -107,6 +107,7 @@ TEST_CASE("Visual") {
       .blitter = NCBLIT_1x1,
       .flags = NCVISUAL_OPTION_HORALIGNED,
       .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto ncv = ncvisual_from_rgba(pixels, 2, 2 * sizeof(*pixels), 2);
     REQUIRE(nullptr != ncv);
@@ -132,6 +133,7 @@ TEST_CASE("Visual") {
       .leny = 5, .lenx = 8,
       .blitter = NCBLIT_1x1,
       .flags = 0, .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
@@ -157,6 +159,7 @@ TEST_CASE("Visual") {
       .leny = 0, .lenx = 0,
       .blitter = NCBLIT_1x1,
       .flags = 0, .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto n = ncvisual_render(nc_, ncv, &vopts);
     CHECK(0 == notcurses_render(nc_));
@@ -193,6 +196,7 @@ TEST_CASE("Visual") {
       .leny = 5, .lenx = 8,
       .blitter = NCBLIT_1x1,
       .flags = 0, .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto n = ncvisual_render(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
@@ -216,6 +220,7 @@ TEST_CASE("Visual") {
       .leny = 0, .lenx = 0,
       .blitter = NCBLIT_1x1,
       .flags = 0, .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     auto newn = ncvisual_render(nc_, ncv, &vopts);
     CHECK(0 == notcurses_render(nc_));
@@ -477,6 +482,7 @@ TEST_CASE("Visual") {
         .blitter = NCBLIT_2x2,
         .flags = 0,
         .transcolor = 0,
+        .pxoffy = 0, .pxoffx = 0,
       };
       auto ncvp = ncvisual_render(nc_, ncv, &vopts);
       REQUIRE(nullptr != ncvp);
@@ -521,6 +527,7 @@ TEST_CASE("Visual") {
           .blitter = NCBLIT_2x2,
           .flags = 0,
           .transcolor = 0,
+          .pxoffy = 0, .pxoffx = 0,
         };
         auto ncvp = ncvisual_render(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
@@ -568,6 +575,7 @@ TEST_CASE("Visual") {
           .blitter = NCBLIT_2x2,
           .flags = 0,
           .transcolor = 0,
+          .pxoffy = 0, .pxoffx = 0,
         };
         auto ncvp = ncvisual_render(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
@@ -620,6 +628,7 @@ TEST_CASE("Visual") {
           .blitter = NCBLIT_2x2,
           .flags = 0,
           .transcolor = 0,
+          .pxoffy = 0, .pxoffx = 0,
         };
         auto ncvp = ncvisual_render(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
@@ -671,6 +680,7 @@ TEST_CASE("Visual") {
           .blitter = NCBLIT_2x2,
           .flags = 0,
           .transcolor = 0,
+          .pxoffy = 0, .pxoffx = 0,
         };
         auto ncvp = ncvisual_render(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
@@ -716,6 +726,7 @@ TEST_CASE("Visual") {
       .blitter = NCBLIT_1x1,
       .flags = 0,
       .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     const uint32_t pixels[16] = {
       htole(0xffffffff), htole(0xffffffff), htole(0xffc0ffff), htole(0xffffc0ff),
@@ -771,6 +782,7 @@ TEST_CASE("Visual") {
                NCVISUAL_OPTION_HORALIGNED |
                NCVISUAL_OPTION_VERALIGNED,
       .transcolor = 0,
+      .pxoffy = 0, .pxoffx = 0,
     };
     const uint32_t pixels[1] = { htole(0xffffffff) };
     auto ncv = ncvisual_from_rgba(pixels, 1, 4, 1);