You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
884 lines
36 KiB
C
884 lines
36 KiB
C
#include <stddef.h>
|
|
#include "internal.h"
|
|
|
|
static const uint32_t zeroes32;
|
|
static const unsigned char zeroes[] = "\x00\x00\x00\x00";
|
|
|
|
// linearly interpolate a 24-bit RGB value along each 8-bit channel
|
|
static inline uint32_t
|
|
lerp(uint32_t c0, uint32_t c1){
|
|
uint32_t ret = 0;
|
|
unsigned r0, g0, b0, r1, g1, b1;
|
|
channel_rgb8(c0, &r0, &g0, &b0);
|
|
channel_rgb8(c1, &r1, &g1, &b1);
|
|
channel_set_rgb8(&ret, (r0 + r1 + 1) / 2,
|
|
(g0 + g1 + 1) / 2,
|
|
(b0 + b1 + 1) / 2);
|
|
return ret;
|
|
}
|
|
|
|
// linearly interpolate a 24-bit RGB value along each 8-bit channel
|
|
static inline uint32_t
|
|
trilerp(uint32_t c0, uint32_t c1, uint32_t c2){
|
|
uint32_t ret = 0;
|
|
unsigned r0, g0, b0, r1, g1, b1, r2, g2, b2;
|
|
channel_rgb8(c0, &r0, &g0, &b0);
|
|
channel_rgb8(c1, &r1, &g1, &b1);
|
|
channel_rgb8(c2, &r2, &g2, &b2);
|
|
channel_set_rgb8(&ret, (r0 + r1 + r2 + 2) / 3,
|
|
(g0 + g1 + g2 + 2) / 3,
|
|
(b0 + b1 + b2 + 2) / 3);
|
|
return ret;
|
|
}
|
|
|
|
// alpha comes to us 0--255, but we have only 3 alpha values to map them to.
|
|
// settled on experimentally.
|
|
static inline bool
|
|
ffmpeg_trans_p(unsigned char alpha){
|
|
if(alpha < 192){
|
|
//fprintf(stderr, "TRANSPARENT!\n");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Retarded RGBA blitter (ASCII only).
|
|
static inline int
|
|
tria_blit_ascii(ncplane* nc, int placey, int placex, int linesize,
|
|
const void* data, int begy, int begx,
|
|
int leny, int lenx, bool blendcolors){
|
|
//fprintf(stderr, "ASCII %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, begy, begx, data, placey, placex);
|
|
const int bpp = 32;
|
|
int dimy, dimx, x, y;
|
|
int total = 0; // number of cells written
|
|
ncplane_dim_yx(nc, &dimy, &dimx);
|
|
// FIXME not going to necessarily be safe on all architectures hrmmm
|
|
const unsigned char* dat = data;
|
|
int visy = begy;
|
|
for(y = placey ; visy < (begy + leny) && y < dimy ; ++y, ++visy){
|
|
if(ncplane_cursor_move_yx(nc, y, placex)){
|
|
return -1;
|
|
}
|
|
int visx = begx;
|
|
for(x = placex ; visx < (begx + lenx) && x < dimx ; ++x, ++visx){
|
|
const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * bpp / CHAR_BIT);
|
|
//fprintf(stderr, "[%04d/%04d] bpp: %d lsize: %d %02x %02x %02x %02x\n", y, x, bpp, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
|
|
nccell* c = ncplane_cell_ref_yx(nc, y, x);
|
|
// use the default for the background, as that's the only way it's
|
|
// effective in that case anyway
|
|
c->channels = 0;
|
|
c->stylemask = 0;
|
|
if(blendcolors){
|
|
cell_set_bg_alpha(c, CELL_ALPHA_BLEND);
|
|
cell_set_fg_alpha(c, CELL_ALPHA_BLEND);
|
|
}
|
|
if(ffmpeg_trans_p(rgbbase_up[3])){
|
|
cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
}else{
|
|
cell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
|
|
cell_set_bg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
|
|
if(pool_load_direct(&nc->pool, c, " ", 1, 1) <= 0){
|
|
return -1;
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// RGBA half-block blitter. Best for most images/videos. Full fidelity
|
|
// combined with 1:1 pixel aspect ratio.
|
|
static inline int
|
|
tria_blit(ncplane* nc, int placey, int placex, int linesize,
|
|
const void* data, int begy, int begx,
|
|
int leny, int lenx, bool blendcolors){
|
|
//fprintf(stderr, "HALF %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, begy, begx, data, placey, placex);
|
|
const int bpp = 32;
|
|
int dimy, dimx, x, y;
|
|
int total = 0; // number of cells written
|
|
ncplane_dim_yx(nc, &dimy, &dimx);
|
|
// FIXME not going to necessarily be safe on all architectures hrmmm
|
|
const unsigned char* dat = data;
|
|
int visy = begy;
|
|
for(y = placey ; visy < (begy + leny) && y < dimy ; ++y, visy += 2){
|
|
if(ncplane_cursor_move_yx(nc, y, placex)){
|
|
return -1;
|
|
}
|
|
int visx = begx;
|
|
for(x = placex ; visx < (begx + lenx) && x < dimx ; ++x, ++visx){
|
|
const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * bpp / CHAR_BIT);
|
|
const unsigned char* rgbbase_down = zeroes;
|
|
if(visy < begy + leny - 1){
|
|
rgbbase_down = dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT);
|
|
}
|
|
//fprintf(stderr, "[%04d/%04d] bpp: %d lsize: %d %02x %02x %02x %02x\n", y, x, bpp, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
|
|
nccell* c = ncplane_cell_ref_yx(nc, y, x);
|
|
// use the default for the background, as that's the only way it's
|
|
// effective in that case anyway
|
|
c->channels = 0;
|
|
c->stylemask = 0;
|
|
if(blendcolors){
|
|
cell_set_bg_alpha(c, CELL_ALPHA_BLEND);
|
|
cell_set_fg_alpha(c, CELL_ALPHA_BLEND);
|
|
}
|
|
if(ffmpeg_trans_p(rgbbase_up[3]) || ffmpeg_trans_p(rgbbase_down[3])){
|
|
cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
if(ffmpeg_trans_p(rgbbase_up[3]) && ffmpeg_trans_p(rgbbase_down[3])){
|
|
cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
}else if(ffmpeg_trans_p(rgbbase_up[3])){ // down has the color
|
|
if(cell_load(nc, c, "\u2584") <= 0){ // lower half block
|
|
return -1;
|
|
}
|
|
cell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
|
|
++total;
|
|
}else{ // up has the color
|
|
if(cell_load(nc, c, "\u2580") <= 0){ // upper half block
|
|
return -1;
|
|
}
|
|
cell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
|
|
++total;
|
|
}
|
|
}else{
|
|
if(memcmp(rgbbase_up, rgbbase_down, 3) == 0){
|
|
cell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
|
|
cell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
|
|
if(cell_load(nc, c, " ") <= 0){ // only need the background
|
|
return -1;
|
|
}
|
|
}else{
|
|
cell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
|
|
cell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
|
|
if(cell_load(nc, c, "\u2580") <= 0){ // upper half block
|
|
return -1;
|
|
}
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// get a non-negative "distance" between two rgb values
|
|
static inline uint32_t
|
|
rgb_diff(unsigned r1, unsigned g1, unsigned b1, unsigned r2, unsigned g2, unsigned b2){
|
|
uint32_t distance = 0;
|
|
distance += r1 > r2 ? r1 - r2 : r2 - r1;
|
|
distance += g1 > g2 ? g1 - g2 : g2 - g1;
|
|
distance += b1 > b2 ? b1 - b2 : b2 - b1;
|
|
return distance;
|
|
}
|
|
|
|
// once we find the closest pair of colors, we need look at the other two
|
|
// colors, and determine whether either belongs with us rather with them.
|
|
// if so, take the closer, and trilerp it in with us. otherwise, lerp the
|
|
// two excluded pixels (and retain our original lerp).
|
|
static const struct qdriver {
|
|
int pair[2]; // indices of contributing pair
|
|
int others[2]; // indices of excluded pair
|
|
const char* egc; // EGC corresponding to contributing pair
|
|
const char* oth0egc; // EGC upon absorbing others[0]
|
|
const char* oth1egc; // EGC upon absorbing others[1]
|
|
} quadrant_drivers[6] = {
|
|
{ .pair = { 0, 1 }, .others = { 2, 3 }, .egc = "▀", .oth0egc = "▛", .oth1egc = "▜", },
|
|
{ .pair = { 0, 2 }, .others = { 1, 3 }, .egc = "▌", .oth0egc = "▛", .oth1egc = "▙", },
|
|
{ .pair = { 0, 3 }, .others = { 1, 2 }, .egc = "▚", .oth0egc = "▜", .oth1egc = "▙", },
|
|
{ .pair = { 1, 2 }, .others = { 0, 3 }, .egc = "▞", .oth0egc = "▛", .oth1egc = "▟", },
|
|
{ .pair = { 1, 3 }, .others = { 0, 2 }, .egc = "▐", .oth0egc = "▜", .oth1egc = "▟", },
|
|
{ .pair = { 2, 3 }, .others = { 0, 1 }, .egc = "▄", .oth0egc = "▙", .oth1egc = "▟", },
|
|
};
|
|
|
|
// get the six distances between four colors. diffs must be an array of
|
|
// at least 6 uint32_t values.
|
|
static void
|
|
rgb_4diff(uint32_t* diffs, uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br){
|
|
struct rgb {
|
|
unsigned r, g, b;
|
|
} colors[4];
|
|
channel_rgb8(tl, &colors[0].r, &colors[0].g, &colors[0].b);
|
|
channel_rgb8(tr, &colors[1].r, &colors[1].g, &colors[1].b);
|
|
channel_rgb8(bl, &colors[2].r, &colors[2].g, &colors[2].b);
|
|
channel_rgb8(br, &colors[3].r, &colors[3].g, &colors[3].b);
|
|
for(size_t idx = 0 ; idx < sizeof(quadrant_drivers) / sizeof(*quadrant_drivers) ; ++idx){
|
|
const struct qdriver* qd = quadrant_drivers + idx;
|
|
const struct rgb* rgb0 = colors + qd->pair[0];
|
|
const struct rgb* rgb1 = colors + qd->pair[1];
|
|
diffs[idx] = rgb_diff(rgb0->r, rgb0->g, rgb0->b,
|
|
rgb1->r, rgb1->g, rgb1->b);
|
|
}
|
|
}
|
|
|
|
// solve for the EGC and two colors to best represent four colors at top
|
|
// left, top right, bot left, bot right
|
|
static inline const char*
|
|
quadrant_solver(uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br,
|
|
uint32_t* fore, uint32_t* back){
|
|
const uint32_t colors[4] = { tl, tr, bl, br };
|
|
//fprintf(stderr, "%08x/%08x/%08x/%08x\n", tl, tr, bl, br);
|
|
uint32_t diffs[sizeof(quadrant_drivers) / sizeof(*quadrant_drivers)];
|
|
rgb_4diff(diffs, tl, tr, bl, br);
|
|
// compiler can't verify that we'll always be less than 769 somewhere,
|
|
// so fuck it, just go ahead and initialize to 0 / diffs[0]
|
|
size_t mindiffidx = 0;
|
|
unsigned mindiff = diffs[0]; // 3 * 256 + 1; // max distance is 256 * 3
|
|
// if all diffs are 0, emit a space
|
|
bool allzerodiffs = (mindiff == 0);
|
|
for(size_t idx = 1 ; idx < sizeof(diffs) / sizeof(*diffs) ; ++idx){
|
|
if(diffs[idx] < mindiff){
|
|
mindiffidx = idx;
|
|
mindiff = diffs[idx];
|
|
}
|
|
if(diffs[idx]){
|
|
allzerodiffs = false;
|
|
}
|
|
}
|
|
if(allzerodiffs){
|
|
*fore = *back = tl;
|
|
return " ";
|
|
}
|
|
// at this point, 0 <= mindiffidx <= 5. foreground color will be the
|
|
// lerp of this nearest pair. we then check the other two. if they are
|
|
// closer to one another than either is to our lerp, lerp between them.
|
|
// otherwise, bring the closer one into our lerped fold.
|
|
const struct qdriver* qd = &quadrant_drivers[mindiffidx];
|
|
// the diff of the excluded pair is conveniently located at the inverse
|
|
// location within diffs[] viz mindiffidx.
|
|
// const uint32_t otherdiff = diffs[5 - mindiffidx];
|
|
*fore = lerp(colors[qd->pair[0]], colors[qd->pair[1]]);
|
|
*back = lerp(colors[qd->others[0]], colors[qd->others[1]]);
|
|
//fprintf(stderr, "mindiff: %u[%zu] fore: %08x back: %08x %d+%d/%d+%d\n", mindiff, mindiffidx, *fore, *back, qd->pair[0], qd->pair[1], qd->others[0], qd->others[1]);
|
|
const char* egc = qd->egc;
|
|
// break down the excluded pair and lerp
|
|
unsigned r0, r1, g0, g1, b0, b1;
|
|
unsigned roth, goth, both, rlerp, glerp, blerp;
|
|
channel_rgb8(colors[qd->others[0]], &r0, &g0, &b0);
|
|
channel_rgb8(colors[qd->others[1]], &r1, &g1, &b1);
|
|
channel_rgb8(*fore, &rlerp, &glerp, &blerp);
|
|
channel_rgb8(*back, &roth, &goth, &both);
|
|
//fprintf(stderr, "rgbs: %02x %02x %02x / %02x %02x %02x\n", r0, g0, b0, r1, g1, b1);
|
|
diffs[0] = rgb_diff(r0, g0, b0, rlerp, glerp, blerp);
|
|
diffs[1] = rgb_diff(r0, g0, b0, roth, goth, both);
|
|
diffs[2] = rgb_diff(r1, g1, b1, rlerp, glerp, blerp);
|
|
diffs[3] = rgb_diff(r1, g1, b1, roth, goth, both);
|
|
//fprintf(stderr, "diffs: %08x %08x %08x %08x\n", diffs[0], diffs[1], diffs[2], diffs[3]);
|
|
if(diffs[0] < diffs[1] && diffs[0] < diffs[2]){
|
|
egc = qd->oth0egc;
|
|
*back = colors[qd->others[1]];
|
|
*fore = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[0]]);
|
|
//fprintf(stderr, "swap 1 %08x %08x\n", *fore, *back);
|
|
}else if(diffs[2] < diffs[3]){
|
|
egc = qd->oth1egc;
|
|
*back = colors[qd->others[0]];
|
|
*fore = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[1]]);
|
|
//fprintf(stderr, "swap 2 %08x %08x\n", *fore, *back);
|
|
}
|
|
return egc;
|
|
}
|
|
|
|
// quadrant check for transparency. returns an EGC if we found transparent
|
|
// quads and have solved for colors (this EGC ought then be loaded into the
|
|
// cell). returns NULL otherwise. transparency trumps everything else in terms
|
|
// of priority -- if even one quadrant is transparent, we will have a
|
|
// transparent background, and lerp the rest together for foreground. we thus
|
|
// have a 16-way conditional tree in which each EGC must show up exactly once.
|
|
// FIXME we ought be able to just build up a bitstring and use it as an index!
|
|
// FIXME pass in rgbas as array of uint32_t ala sexblitter
|
|
static inline const char*
|
|
qtrans_check(nccell* c, bool blendcolors,
|
|
const unsigned char* rgbbase_tl, const unsigned char* rgbbase_tr,
|
|
const unsigned char* rgbbase_bl, const unsigned char* rgbbase_br){
|
|
uint32_t tl = 0, tr = 0, bl = 0, br = 0;
|
|
channel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
|
|
channel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
|
|
channel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
|
|
channel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
|
|
const char* egc = NULL;
|
|
if(ffmpeg_trans_p(rgbbase_tl[3])){
|
|
// top left is transparent
|
|
if(ffmpeg_trans_p(rgbbase_tr[3])){
|
|
// all of top is transparent
|
|
if(ffmpeg_trans_p(rgbbase_bl[3])){
|
|
// top and left are transparent
|
|
if(ffmpeg_trans_p(rgbbase_br[3])){
|
|
// entirety is transparent, load with nul (but not NULL)
|
|
cell_set_fg_default(c);
|
|
egc = "";
|
|
}else{
|
|
cell_set_fg_rgb8(c, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
|
|
egc = "▗";
|
|
}
|
|
}else{
|
|
if(ffmpeg_trans_p(rgbbase_br[3])){
|
|
cell_set_fg_rgb8(c, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
|
|
egc = "▖";
|
|
}else{
|
|
cell_set_fchannel(c, lerp(bl, br));
|
|
egc = "▄";
|
|
}
|
|
}
|
|
}else{ // top right is foreground, top left is transparent
|
|
if(ffmpeg_trans_p(rgbbase_bl[3])){
|
|
if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent
|
|
cell_set_fg_rgb8(c, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
|
|
egc = "▝";
|
|
}else{
|
|
cell_set_fchannel(c, lerp(tr, br));
|
|
egc = "▐";
|
|
}
|
|
}else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent
|
|
cell_set_fchannel(c, lerp(tr, bl));
|
|
egc = "▞";
|
|
}else{
|
|
cell_set_fchannel(c, trilerp(tr, bl, br));
|
|
egc = "▟";
|
|
}
|
|
}
|
|
}else{ // topleft is foreground for all here
|
|
if(ffmpeg_trans_p(rgbbase_tr[3])){
|
|
if(ffmpeg_trans_p(rgbbase_bl[3])){
|
|
if(ffmpeg_trans_p(rgbbase_br[3])){
|
|
cell_set_fg_rgb8(c, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
|
|
egc = "▘";
|
|
}else{
|
|
cell_set_fchannel(c, lerp(tl, br));
|
|
egc = "▚";
|
|
}
|
|
}else if(ffmpeg_trans_p(rgbbase_br[3])){
|
|
cell_set_fchannel(c, lerp(tl, bl));
|
|
egc = "▌";
|
|
}else{
|
|
cell_set_fchannel(c, trilerp(tl, bl, br));
|
|
egc = "▙";
|
|
}
|
|
}else if(ffmpeg_trans_p(rgbbase_bl[3])){
|
|
if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent
|
|
cell_set_fchannel(c, lerp(tl, tr));
|
|
egc = "▀";
|
|
}else{ // only bl is transparent
|
|
cell_set_fchannel(c, trilerp(tl, tr, br));
|
|
egc = "▜";
|
|
}
|
|
}else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent
|
|
cell_set_fchannel(c, trilerp(tl, tr, bl));
|
|
egc = "▛";
|
|
}else{
|
|
return NULL; // no transparency
|
|
}
|
|
}
|
|
assert(egc);
|
|
cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
if(*egc == '\0'){
|
|
cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
}else if(blendcolors){
|
|
cell_set_fg_alpha(c, CELL_ALPHA_BLEND);
|
|
}
|
|
return egc;
|
|
}
|
|
|
|
// quadrant blitter. maps 2x2 to each cell. since we only have two colors at
|
|
// our disposal (foreground and background), we lose some fidelity.
|
|
static inline int
|
|
quadrant_blit(ncplane* nc, int placey, int placex, int linesize,
|
|
const void* data, int begy, int begx,
|
|
int leny, int lenx, bool blendcolors){
|
|
const int bpp = 32;
|
|
int dimy, dimx, x, y;
|
|
int total = 0; // number of cells written
|
|
ncplane_dim_yx(nc, &dimy, &dimx);
|
|
//fprintf(stderr, "quadblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, placey, placex);
|
|
// FIXME not going to necessarily be safe on all architectures hrmmm
|
|
const unsigned char* dat = data;
|
|
int visy = begy;
|
|
for(y = placey ; visy < (begy + leny) && y < dimy ; ++y, visy += 2){
|
|
if(ncplane_cursor_move_yx(nc, y, placex)){
|
|
return -1;
|
|
}
|
|
int visx = begx;
|
|
for(x = placex ; visx < (begx + lenx) && x < dimx ; ++x, visx += 2){
|
|
const unsigned char* rgbbase_tl = dat + (linesize * visy) + (visx * bpp / CHAR_BIT);
|
|
const unsigned char* rgbbase_tr = zeroes;
|
|
const unsigned char* rgbbase_bl = zeroes;
|
|
const unsigned char* rgbbase_br = zeroes;
|
|
if(visx < begx + lenx - 1){
|
|
rgbbase_tr = dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT);
|
|
if(visy < begy + leny - 1){
|
|
rgbbase_br = dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT);
|
|
}
|
|
}
|
|
if(visy < begy + leny - 1){
|
|
rgbbase_bl = dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT);
|
|
}
|
|
//fprintf(stderr, "[%04d/%04d] bpp: %d lsize: %d %02x %02x %02x %02x\n", y, x, bpp, linesize, rgbbase_tl[0], rgbbase_tr[1], rgbbase_bl[2], rgbbase_br[3]);
|
|
nccell* c = ncplane_cell_ref_yx(nc, y, x);
|
|
c->channels = 0;
|
|
c->stylemask = 0;
|
|
const char* egc = qtrans_check(c, blendcolors, rgbbase_tl, rgbbase_tr, rgbbase_bl, rgbbase_br);
|
|
if(egc == NULL){
|
|
uint32_t tl = 0, tr = 0, bl = 0, br = 0;
|
|
channel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
|
|
channel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
|
|
channel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
|
|
channel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
|
|
uint32_t bg, fg;
|
|
//fprintf(stderr, "qtrans check: %d/%d\n%08x %08x\n%08x %08x\n", y, x, *(const uint32_t*)rgbbase_tl, *(const uint32_t*)rgbbase_tr, *(const uint32_t*)rgbbase_bl, *(const uint32_t*)rgbbase_br);
|
|
egc = quadrant_solver(tl, tr, bl, br, &fg, &bg);
|
|
assert(egc);
|
|
//fprintf(stderr, "%d/%d %08x/%08x\n", y, x, fg, bg);
|
|
cell_set_fchannel(c, fg);
|
|
cell_set_bchannel(c, bg);
|
|
if(blendcolors){
|
|
cell_set_bg_alpha(c, CELL_ALPHA_BLEND);
|
|
cell_set_fg_alpha(c, CELL_ALPHA_BLEND);
|
|
}
|
|
}
|
|
if(*egc){
|
|
if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
|
|
return -1;
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// take a sum over channels, and the sample count, write back lerped channel
|
|
static inline uint32_t
|
|
generalerp(unsigned rsum, unsigned gsum, unsigned bsum, int count){
|
|
if(count == 0){
|
|
assert(0 == rsum);
|
|
assert(0 == gsum);
|
|
assert(0 == bsum);
|
|
return 0;
|
|
}
|
|
return CHANNEL_RGB_INITIALIZER((rsum + (count - 1)) / count,
|
|
(gsum + (count - 1)) / count,
|
|
(bsum + (count - 1)) / count);
|
|
}
|
|
|
|
// Solve for the cell rendered by this 3x2 sample. None of the input pixels may
|
|
// be transparent (that ought already have been handled). We use exhaustive
|
|
// search, which might be quite computationally intensive for the worst case
|
|
// (all six pixels are different colors). We want to solve for the 2-partition
|
|
// of pixels that minimizes total source distance from the resulting lerps.
|
|
static const char*
|
|
sex_solver(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){
|
|
static const char* sex[64] = {
|
|
" ", "🬀", "🬁", "🬃", "🬇", "🬏", "🬞", "🬂",
|
|
"🬄", "🬈", "🬐", "🬟", "🬅", "🬉", "🬑", "🬠",
|
|
"🬋", "🬓", "🬢", "🬖", "🬦", "🬭", "🬆", "🬊",
|
|
"🬒", "🬡", "🬌", "▌", "🬣", "🬗", "🬧", "🬍",
|
|
"█", "🬻", "🬺", "🬸", "🬴", "🬬", "🬝", "🬹",
|
|
"🬷", "🬳", "🬫", "🬜", "🬶", "🬲", "🬪", "🬛",
|
|
"🬰", "🬨", "🬙", "🬥", "🬕", "🬎", "🬵", "🬱",
|
|
"🬩", "🬚", "🬯", "▐", "🬘", "🬤", "🬔", "🬮",
|
|
};
|
|
// each element within the set of 64 has an inverse element within the set,
|
|
// for which we will calculate the same total differences, so just handle the
|
|
// first 32, and then assign fg to whichever cluster is larger.
|
|
static const unsigned partitions[32] = {
|
|
0, // 1 way to arrange 0
|
|
1, 2, 4, 8, 16, 32, // 6 ways to arrange 1
|
|
3, 5, 9, 17, 33, 6, 10, 18, 34, 12, 20, 36, 24, 40, 48, // 15 ways for 2
|
|
// 16 ways to arrange 3, *but* six of them are inverses, so 10
|
|
7, 11, 19, 35, 13, 21, 37, 25, 41, 14 // 10 + 15 + 6 + 1 == 32
|
|
};
|
|
// we loop over the bitstrings, dividing the pixels into two sets, and then
|
|
// taking a general lerp over each set. we then compute the sum of absolute
|
|
// differences, and see if it's the new minimum.
|
|
int best = -1;
|
|
uint32_t mindiff = UINT_MAX;
|
|
//fprintf(stderr, "%06x %06x\n%06x %06x\n%06x %06x\n", rgbas[0], rgbas[1], rgbas[2], rgbas[3], rgbas[4], rgbas[5]);
|
|
for(size_t glyph = 0 ; glyph < sizeof(partitions) / sizeof(*partitions) ; ++glyph){
|
|
unsigned rsum0 = 0, rsum1 = 0;
|
|
unsigned gsum0 = 0, gsum1 = 0;
|
|
unsigned bsum0 = 0, bsum1 = 0;
|
|
int insum = 0;
|
|
for(unsigned mask = 0 ; mask < 6 ; ++mask){
|
|
if(partitions[glyph] & (1u << mask)){
|
|
rsum0 += ncpixel_r(rgbas[mask]);
|
|
gsum0 += ncpixel_g(rgbas[mask]);
|
|
bsum0 += ncpixel_b(rgbas[mask]);
|
|
++insum;
|
|
}else{
|
|
rsum1 += ncpixel_r(rgbas[mask]);
|
|
gsum1 += ncpixel_g(rgbas[mask]);
|
|
bsum1 += ncpixel_b(rgbas[mask]);
|
|
}
|
|
}
|
|
//fprintf(stderr, "sum0: %u/%u/%u sum1: %u/%u/%u insum: %d\n", rsum0, gsum0, bsum0, rsum1, gsum1, bsum1, insum);
|
|
uint32_t l0 = generalerp(rsum0, gsum0, bsum0, insum);
|
|
uint32_t l1 = generalerp(rsum1, gsum1, bsum1, 6 - insum);
|
|
uint32_t totaldiff = 0;
|
|
for(unsigned mask = 0 ; mask < 6 ; ++mask){
|
|
unsigned r, g, b;
|
|
if(partitions[glyph] & (1u << mask)){
|
|
channel_rgb8(l0, &r, &g, &b);
|
|
}else{
|
|
channel_rgb8(l1, &r, &g, &b);
|
|
}
|
|
totaldiff += rgb_diff(ncpixel_r(rgbas[mask]), ncpixel_g(rgbas[mask]),
|
|
ncpixel_b(rgbas[mask]), r, g, b);
|
|
//fprintf(stderr, "mask: %u totaldiff: %u insum: %d (%08x / %08x)\n", mask, totaldiff, insum, l0, l1);
|
|
}
|
|
//fprintf(stderr, "bits: %u %zu totaldiff: %u best: %u (%d)\n", partitions[glyph], glyph, totaldiff, mindiff, best);
|
|
if(totaldiff < mindiff){
|
|
mindiff = totaldiff;
|
|
best = glyph;
|
|
channels_set_fchannel(channels, l0);
|
|
channels_set_bchannel(channels, l1);
|
|
}
|
|
if(totaldiff == 0){ // can't beat that!
|
|
break;
|
|
}
|
|
}
|
|
//fprintf(stderr, "solved for best: %d (%u)\n", best, mindiff);
|
|
assert(best >= 0 && best < 64);
|
|
if(blendcolors){
|
|
channels_set_fg_alpha(channels, CELL_ALPHA_BLEND);
|
|
channels_set_bg_alpha(channels, CELL_ALPHA_BLEND);
|
|
}
|
|
return sex[best];
|
|
}
|
|
|
|
static const char*
|
|
sex_trans_check(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){
|
|
static const char* sex[64] = {
|
|
"█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵", "🬴", "🬳", "🬲", // 10
|
|
"🬱", "🬰", "🬯", "🬮", "🬭", "🬬", "🬫", "🬪", "🬩", "🬨", "▐", // 21
|
|
"🬧", "🬦", "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", // 30
|
|
"🬞", "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖", "🬕", // 40
|
|
"🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏", "🬎", "🬍", "🬌", // 50
|
|
"🬋", "🬊", "🬉", "🬈", "🬇", "🬆", "🬅", "🬄", "🬃", "🬂", // 60
|
|
"🬁", "🬀", " ",
|
|
};
|
|
unsigned transstring = 0;
|
|
unsigned r = 0, g = 0, b = 0;
|
|
unsigned div = 0;
|
|
for(unsigned mask = 0 ; mask < 6 ; ++mask){
|
|
if(ffmpeg_trans_p(ncpixel_a(rgbas[mask]))){
|
|
transstring |= (1u << mask);
|
|
}else{
|
|
r += ncpixel_r(rgbas[mask]);
|
|
g += ncpixel_g(rgbas[mask]);
|
|
b += ncpixel_b(rgbas[mask]);
|
|
++div;
|
|
}
|
|
}
|
|
if(transstring == 0){
|
|
return NULL;
|
|
}
|
|
channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT);
|
|
// there were some transparent pixels. since they get priority, the foreground
|
|
// is just a general lerp across non-transparent pixels.
|
|
const char* egc = sex[transstring];
|
|
channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT);
|
|
//fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc);
|
|
if(*egc == ' '){ // entirely transparent
|
|
channels_set_fg_alpha(channels, CELL_ALPHA_TRANSPARENT);
|
|
return "";
|
|
}else{ // partially transparent, thus div >= 1
|
|
//fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b);
|
|
channels_set_fchannel(channels, generalerp(r, g, b, div));
|
|
if(blendcolors){
|
|
channels_set_fg_alpha(channels, CELL_ALPHA_BLEND);
|
|
}
|
|
}
|
|
return egc;
|
|
}
|
|
|
|
// sextant blitter. maps 3x2 to each cell. since we only have two colors at
|
|
// our disposal (foreground and background), we lose some fidelity.
|
|
static inline int
|
|
sextant_blit(ncplane* nc, int placey, int placex, int linesize,
|
|
const void* data, int begy, int begx,
|
|
int leny, int lenx, bool blendcolors){
|
|
const int bpp = 32;
|
|
int dimy, dimx, x, y;
|
|
int total = 0; // number of cells written
|
|
ncplane_dim_yx(nc, &dimy, &dimx);
|
|
//fprintf(stderr, "sexblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, placey, placex);
|
|
const unsigned char* dat = data;
|
|
int visy = begy;
|
|
for(y = placey ; visy < (begy + leny) && y < dimy ; ++y, visy += 3){
|
|
if(ncplane_cursor_move_yx(nc, y, placex)){
|
|
return -1;
|
|
}
|
|
int visx = begx;
|
|
for(x = placex ; visx < (begx + lenx) && x < dimx ; ++x, visx += 2){
|
|
uint32_t rgbas[6] = { 0, 0, 0, 0, 0, 0 };
|
|
memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
if(visx < begx + lenx - 1){
|
|
memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
if(visy < begy + leny - 1){
|
|
memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
if(visy < begy + leny - 2){
|
|
memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
}
|
|
}
|
|
}
|
|
if(visy < begy + leny - 1){
|
|
memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
if(visy < begy + leny - 2){
|
|
memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
|
|
}
|
|
}
|
|
nccell* c = ncplane_cell_ref_yx(nc, y, x);
|
|
c->channels = 0;
|
|
c->stylemask = 0;
|
|
const char* egc = sex_trans_check(rgbas, &c->channels, blendcolors);
|
|
if(egc == NULL){
|
|
egc = sex_solver(rgbas, &c->channels, blendcolors);
|
|
}
|
|
//fprintf(stderr, "sex EGC: %s channels: %016lx\n", egc, c->channels);
|
|
if(*egc){
|
|
if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
|
|
return -1;
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// fold the r, g, and b components of the pixel into *r, *g, and *b, and
|
|
// increment *foldcount
|
|
static inline void
|
|
fold_rgb8(unsigned* restrict r, unsigned* restrict g, unsigned* restrict b,
|
|
const uint32_t* pixel, unsigned* foldcount){
|
|
*r += ncpixel_r(*pixel);
|
|
*g += ncpixel_g(*pixel);
|
|
*b += ncpixel_b(*pixel);
|
|
++*foldcount;
|
|
}
|
|
|
|
// Braille blitter. maps 4x2 to each cell. since we only have one color at
|
|
// our disposal (foreground), we lose some fidelity. this is optimal for
|
|
// visuals with only two colors in a given area, as it packs lots of
|
|
// resolution. always transparent background.
|
|
static inline int
|
|
braille_blit(ncplane* nc, int placey, int placex, int linesize,
|
|
const void* data, int begy, int begx,
|
|
int leny, int lenx, bool blendcolors){
|
|
const int bpp = 32;
|
|
int dimy, dimx, x, y;
|
|
int total = 0; // number of cells written
|
|
ncplane_dim_yx(nc, &dimy, &dimx);
|
|
// FIXME not going to necessarily be safe on all architectures hrmmm
|
|
const unsigned char* dat = data;
|
|
int visy = begy;
|
|
for(y = placey ; visy < (begy + leny) && y < dimy ; ++y, visy += 4){
|
|
if(ncplane_cursor_move_yx(nc, y, placex)){
|
|
return -1;
|
|
}
|
|
int visx = begx;
|
|
for(x = placex ; visx < (begx + lenx) && x < dimx ; ++x, visx += 2){
|
|
const uint32_t* rgbbase_l0 = (const uint32_t*)(dat + (linesize * visy) + (visx * bpp / CHAR_BIT));
|
|
const uint32_t* rgbbase_r0 = &zeroes32;
|
|
const uint32_t* rgbbase_l1 = &zeroes32;
|
|
const uint32_t* rgbbase_r1 = &zeroes32;
|
|
const uint32_t* rgbbase_l2 = &zeroes32;
|
|
const uint32_t* rgbbase_r2 = &zeroes32;
|
|
const uint32_t* rgbbase_l3 = &zeroes32;
|
|
const uint32_t* rgbbase_r3 = &zeroes32;
|
|
unsigned r = 0, g = 0, b = 0;
|
|
unsigned blends = 0;
|
|
unsigned egcidx = 0;
|
|
if(visx < begx + lenx - 1){
|
|
rgbbase_r0 = (const uint32_t*)(dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT));
|
|
if(visy < begy + leny - 1){
|
|
rgbbase_r1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT));
|
|
if(visy < begy + leny - 2){
|
|
rgbbase_r2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + ((visx + 1) * bpp / CHAR_BIT));
|
|
if(visy < begy + leny - 3){
|
|
rgbbase_r3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + ((visx + 1) * bpp / CHAR_BIT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(visy < begy + leny - 1){
|
|
rgbbase_l1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT));
|
|
if(visy < begy + leny - 2){
|
|
rgbbase_l2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + (visx * bpp / CHAR_BIT));
|
|
if(visy < begy + leny - 3){
|
|
rgbbase_l3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + (visx * bpp / CHAR_BIT));
|
|
}
|
|
}
|
|
}
|
|
// FIXME fold this into the above?
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_l0))){
|
|
egcidx |= 1u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_l0, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_l1))){
|
|
egcidx |= 2u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_l1, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_l2))){
|
|
egcidx |= 4u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_l2, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_r0))){
|
|
egcidx |= 8u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_r0, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_r1))){
|
|
egcidx |= 16u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_r1, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_r2))){
|
|
egcidx |= 32u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_r2, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_l3))){
|
|
egcidx |= 64u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_l3, &blends);
|
|
}
|
|
if(!ffmpeg_trans_p(ncpixel_a(*rgbbase_r3))){
|
|
egcidx |= 128u;
|
|
fold_rgb8(&r, &g, &b, rgbbase_r3, &blends);
|
|
}
|
|
//fprintf(stderr, "[%04d/%04d] bpp: %d lsize: %d %02x %02x %02x %02x\n", y, x, bpp, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
|
|
nccell* c = ncplane_cell_ref_yx(nc, y, x);
|
|
// use the default for the background, as that's the only way it's
|
|
// effective in that case anyway
|
|
c->channels = 0;
|
|
c->stylemask = 0;
|
|
if(blendcolors){
|
|
cell_set_fg_alpha(c, CELL_ALPHA_BLEND);
|
|
}
|
|
// FIXME for now, we just sample, color-wise, and always draw crap.
|
|
// more complicated to do optimally than quadrants, for sure. ideally,
|
|
// we only get one color in an area.
|
|
cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
if(!egcidx){
|
|
cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT);
|
|
// FIXME else look for pairs of transparency!
|
|
}else{
|
|
if(blends){
|
|
cell_set_fg_rgb8(c, r / blends, g / blends, b / blends);
|
|
}
|
|
// UTF-8 encodings of the Braille Patterns are always 0xe2 0xaX 0xCC,
|
|
// where 0 <= X <= 3 and 0x80 <= CC <= 0xbf (4 groups of 64).
|
|
char egc[4] = { 0xe2, 0xa0, 0x80, 0x00 };
|
|
egc[2] += egcidx % 64;
|
|
egc[1] += egcidx / 64;
|
|
if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
|
|
return -1;
|
|
}
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// NCBLIT_DEFAULT is not included, as it has no defined properties. It ought
|
|
// be replaced with some real blitter implementation by the calling widget.
|
|
const struct blitset notcurses_blitters[] = {
|
|
{ .geom = NCBLIT_8x1, .width = 1, .height = 8, .egcs = L" ▁▂▃▄▅▆▇█",
|
|
.blit = tria_blit, .name = "eightstep", .fill = false, },
|
|
{ .geom = NCBLIT_1x1, .width = 1, .height = 1, .egcs = L" █",
|
|
.blit = tria_blit_ascii,.name = "ascii", .fill = false, },
|
|
{ .geom = NCBLIT_2x1, .width = 1, .height = 2, .egcs = L" ▄█",
|
|
.blit = tria_blit, .name = "halfblock", .fill = false, },
|
|
{ .geom = NCBLIT_2x2, .width = 2, .height = 2, .egcs = L" ▗▐▖▄▟▌▙█",
|
|
.blit = quadrant_blit, .name = "quadblitter", .fill = false, },
|
|
{ .geom = NCBLIT_3x2, .width = 2, .height = 3, .egcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█",
|
|
.blit = sextant_blit, .name = "sexblitter", .fill = false, },
|
|
{ .geom = NCBLIT_4x1, .width = 1, .height = 4, .egcs = L" ▂▄▆█",
|
|
.blit = tria_blit, .name = "fourstep", .fill = false, },
|
|
{ .geom = NCBLIT_BRAILLE, .width = 2, .height = 4, .egcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿",
|
|
.blit = braille_blit, .name = "braille", .fill = true, },
|
|
{ .geom = NCBLIT_SIXEL, .width = 1, .height = 6, .egcs = L"",
|
|
.blit = NULL, .name = "sixel", .fill = true, }, // FIXME
|
|
{ .geom = 0, .width = 0, .height = 0, .egcs = NULL,
|
|
.blit = NULL, .name = NULL, .fill = false, },
|
|
};
|
|
|
|
int notcurses_lex_blitter(const char* op, ncblitter_e* blitter){
|
|
const struct blitset* bset = notcurses_blitters;
|
|
while(bset->name){
|
|
if(strcasecmp(bset->name, op) == 0){
|
|
*blitter = bset->geom;
|
|
return 0;
|
|
}
|
|
++bset;
|
|
}
|
|
if(strcasecmp("default", op) == 0){
|
|
*blitter = NCBLIT_DEFAULT;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
const char* notcurses_str_blitter(ncblitter_e blitter){
|
|
if(blitter == NCBLIT_DEFAULT){
|
|
return "default";
|
|
}
|
|
const struct blitset* bset = notcurses_blitters;
|
|
while(bset->name){
|
|
if(bset->geom == blitter){
|
|
return bset->name;
|
|
}
|
|
++bset;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int ncblit_bgrx(const void* data, int linesize, const struct ncvisual_options* vopts){
|
|
if(vopts->leny <= 0 || vopts->lenx <= 0){
|
|
return -1;
|
|
}
|
|
void* rdata = bgra_to_rgba(data, vopts->leny, linesize, vopts->lenx);
|
|
if(rdata == NULL){
|
|
return -1;
|
|
}
|
|
int r = ncblit_rgba(rdata, linesize, vopts);
|
|
free(rdata);
|
|
return r;
|
|
}
|
|
|
|
int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* vopts){
|
|
if(vopts->flags > NCVISUAL_OPTION_BLEND){
|
|
fprintf(stderr, "Warning: unknown ncvisual options %016jx\n", (uintmax_t)vopts->flags);
|
|
}
|
|
if(linesize <= 0 || (size_t)linesize < vopts->lenx * sizeof(uint32_t)){
|
|
return -1;
|
|
}
|
|
struct ncplane* nc = vopts->n;
|
|
if(nc == NULL){
|
|
return -1;
|
|
}
|
|
int lenx = vopts->lenx;
|
|
int leny = vopts->leny;
|
|
int begy = vopts->begy;
|
|
int begx = vopts->begx;
|
|
//fprintf(stderr, "render %dx%d+%dx%d %p\n", begy, begx, leny, lenx, ncv->data);
|
|
if(begy < 0 || begx < 0 || lenx < -1 || leny < -1){
|
|
return -1;
|
|
}
|
|
ncblitter_e blitter;
|
|
if(!vopts || vopts->blitter == NCBLIT_DEFAULT){
|
|
blitter = ncvisual_media_defblitter(ncplane_notcurses(nc), NCSCALE_NONE);
|
|
}else{
|
|
blitter = vopts->blitter;
|
|
}
|
|
const bool degrade = !(vopts->flags & NCVISUAL_OPTION_NODEGRADE);
|
|
const struct blitset* bset = lookup_blitset(notcurses_canutf8(ncplane_notcurses(nc)),
|
|
blitter, degrade);
|
|
if(bset == NULL){
|
|
return -1;
|
|
}
|
|
const bool blend = (vopts->flags & NCVISUAL_OPTION_BLEND);
|
|
return bset->blit(nc, vopts->y, vopts->x, linesize, data, begy, begx,
|
|
leny, lenx, blend);
|
|
}
|
|
|
|
ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){
|
|
return rgba_blitter_default(nc->utf8, scale, nc->tcache.sextants);
|
|
}
|