display images with iterm2 protocol (#1946)

Implement the basics of the iTerm2 graphics protocol, requiring PNG construction, zlib compression, and base64 encoding. We're not yet performing wipes nor restores, only display. This is still pretty experimental, but it worked with WezTerm. #1420
pull/1937/head
nick black 3 years ago committed by GitHub
parent a557b6d734
commit 757fb5811f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -333,10 +333,12 @@ tinfo_debug_bitmaps(struct ncplane* n, const tinfo* ti, const char* indent){
}else{
ncplane_printf(n, "%ssixel colorregs: %u\n", indent, ti->color_registers);
}
}else if(ti->pixel_move == NULL){
ncplane_printf(n, "%siTerm2 graphics support\n", indent);
}else if(ti->sixel_maxy_pristine){
ncplane_printf(n, "%srgba pixel graphics supported\n", indent);
ncplane_printf(n, "%srgba pixel graphics support\n", indent);
}else{
ncplane_printf(n, "%srgba pixel animation supported\n", indent);
ncplane_printf(n, "%srgba pixel animation support\n", indent);
}
char* path = prefix_data("notcurses.png");
if(path){

@ -0,0 +1,113 @@
#ifndef NOTCURSES_PNG
#define NOTCURSES_PNG
#ifdef __cplusplus
extern "C" {
#endif
// lookup table for base64
static unsigned const char b64subs[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// every 3 RGBA pixels (96 bits) become 16 base64-encoded bytes (128 bits). if
// there are only 2 pixels available, those 64 bits become 12 bytes. if there
// is only 1 pixel available, those 32 bits become 8 bytes. (pcount + 1) * 4
// bytes are used, plus a null terminator. we thus must receive 17.
static inline void
base64_rgba3(const uint32_t* pixels, size_t pcount, char* b64, bool wipe[static 3],
uint32_t transcolor){
uint32_t pixel = *pixels++;
unsigned r = ncpixel_r(pixel);
unsigned g = ncpixel_g(pixel);
unsigned b = ncpixel_b(pixel);
// we go ahead and take advantage of kitty's ability to reproduce 8-bit
// alphas by copying it in directly, rather than mapping to {0, 255}.
unsigned a = ncpixel_a(pixel);
if(wipe[0] || rgba_trans_p(pixel, transcolor)){
a = 0;
}
b64[0] = b64subs[(r & 0xfc) >> 2];
b64[1] = b64subs[(r & 0x3 << 4) | ((g & 0xf0) >> 4)];
b64[2] = b64subs[((g & 0xf) << 2) | ((b & 0xc0) >> 6)];
b64[3] = b64subs[b & 0x3f];
b64[4] = b64subs[(a & 0xfc) >> 2];
if(pcount == 1){
b64[5] = b64subs[(a & 0x3) << 4];
b64[6] = '=';
b64[7] = '=';
b64[8] = '\0';
return;
}
b64[5] = (a & 0x3) << 4;
pixel = *pixels++;
r = ncpixel_r(pixel);
g = ncpixel_g(pixel);
b = ncpixel_b(pixel);
a = wipe[1] ? 0 : rgba_trans_p(pixel, transcolor) ? 0 : 255;
b64[5] = b64subs[b64[5] | ((r & 0xf0) >> 4)];
b64[6] = b64subs[((r & 0xf) << 2) | ((g & 0xc0) >> 6u)];
b64[7] = b64subs[g & 0x3f];
b64[8] = b64subs[(b & 0xfc) >> 2];
b64[9] = b64subs[((b & 0x3) << 4) | ((a & 0xf0) >> 4)];
if(pcount == 2){
b64[10] = b64subs[(a & 0xf) << 2];
b64[11] = '=';
b64[12] = '\0';
return;
}
b64[10] = (a & 0xf) << 2;
pixel = *pixels;
r = ncpixel_r(pixel);
g = ncpixel_g(pixel);
b = ncpixel_b(pixel);
a = wipe[2] ? 0 : rgba_trans_p(pixel, transcolor) ? 0 : 255;
b64[10] = b64subs[b64[10] | ((r & 0xc0) >> 6)];
b64[11] = b64subs[r & 0x3f];
b64[12] = b64subs[(g & 0xfc) >> 2];
b64[13] = b64subs[((g & 0x3) << 4) | ((b & 0xf0) >> 4)];
b64[14] = b64subs[((b & 0xf) << 2) | ((a & 0xc0) >> 6)];
b64[15] = b64subs[a & 0x3f];
b64[16] = '\0';
}
// convert 3 8-bit bytes into 4 base64-encoded characters
static inline void
base64x3(const unsigned char* src, char* b64){
uint8_t a = src[0] >> 2u;
uint8_t b = ((src[0] & 0x3u) << 4u) + ((src[1] & 0xf0u) >> 4u);
uint8_t c = ((src[1] & 0x0fu) << 2u) + ((src[2] & 0xc0u) >> 6u);
uint8_t d = src[2] & 0x3f;
b64[0] = b64subs[a];
b64[1] = b64subs[b];
b64[2] = b64subs[c];
b64[3] = b64subs[d];
}
// finalize a base64 stream with 3 or fewer 8-bit bytes
static inline void
base64final(const unsigned char* src, char* b64, size_t b){
if(b == 3){
base64x3(src, b64);
}else if(b == 2){
uint8_t s0 = src[0] >> 2u;
uint8_t s1 = ((src[0] & 0x3u) << 4u) + ((src[1] & 0xf0u) >> 4u);
uint8_t s2 = ((src[1] & 0x0fu) << 2u);
b64[0] = b64subs[s0];
b64[1] = b64subs[s1];
b64[2] = b64subs[s2];
b64[3] = '=';
}else{ // b == 1
uint8_t s0 = src[0] >> 2u;
uint8_t s1 = (src[0] & 0x3u) << 4u;
b64[0] = b64subs[s0];
b64[1] = b64subs[s1];
b64[2] = '=';
b64[3] = '=';
}
}
#ifdef __cplusplus
}
#endif
#endif

@ -63,8 +63,7 @@ rgba_trans_q(const unsigned char* p, uint32_t transcolor){
// Retarded RGBA blitter (ASCII only).
static inline int
tria_blit_ascii(ncplane* nc, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs,
int bpp){
int leny, int lenx, const blitterargs* bargs){
//fprintf(stderr, "ASCII %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, bargs->begy, bargs->begx, data, bargs->u.cell.placey, bargs->u.cell.placex);
const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
int dimy, dimx, x, y;
@ -85,8 +84,8 @@ tria_blit_ascii(ncplane* nc, int linesize, const void* data,
if(x < 0){
continue;
}
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]);
const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * 4);
//fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, 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
@ -118,7 +117,7 @@ tria_blit_ascii(ncplane* nc, int linesize, const void* data,
// combined with 1:1 pixel aspect ratio.
static inline int
tria_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp){
const blitterargs* bargs){
const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
//fprintf(stderr, "HALF %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, bargs->begy, bargs->begx, data, bargs->u.cell.placey, bargs->u.cell.placex);
uint32_t transcolor = bargs->transcolor;
@ -140,12 +139,12 @@ tria_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
if(x < 0){
continue;
}
const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * bpp / CHAR_BIT);
const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * 4);
const unsigned char* rgbbase_down = zeroes;
if(visy < bargs->begy + leny - 1){
rgbbase_down = dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT);
rgbbase_down = dat + (linesize * (visy + 1)) + (visx * 4);
}
//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]);
//fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, 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
@ -450,7 +449,7 @@ qtrans_check(nccell* c, unsigned blendcolors,
// our disposal (foreground and background), we lose some fidelity.
static inline int
quadrant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp){
const blitterargs* bargs){
const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
int dimy, dimx, x, y;
@ -472,20 +471,20 @@ quadrant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
if(x < 0){
continue;
}
const unsigned char* rgbbase_tl = dat + (linesize * visy) + (visx * bpp / CHAR_BIT);
const unsigned char* rgbbase_tl = dat + (linesize * visy) + (visx * 4);
const unsigned char* rgbbase_tr = zeroes;
const unsigned char* rgbbase_bl = zeroes;
const unsigned char* rgbbase_br = zeroes;
if(visx < bargs->begx + lenx - 1){
rgbbase_tr = dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT);
rgbbase_tr = dat + (linesize * visy) + ((visx + 1) * 4);
if(visy < bargs->begy + leny - 1){
rgbbase_br = dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT);
rgbbase_br = dat + (linesize * (visy + 1)) + ((visx + 1) * 4);
}
}
if(visy < bargs->begy + leny - 1){
rgbbase_bl = dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT);
rgbbase_bl = dat + (linesize * (visy + 1)) + (visx * 4);
}
//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]);
//fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, 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;
@ -671,7 +670,7 @@ sex_trans_check(cell* c, const uint32_t rgbas[6], unsigned blendcolors,
// our disposal (foreground and background), we lose some fidelity.
static inline int
sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp){
const blitterargs* bargs){
const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
int dimy, dimx, x, y;
@ -693,20 +692,20 @@ sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
continue;
}
uint32_t rgbas[6] = { 0, 0, 0, 0, 0, 0 };
memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * 4)), sizeof(*rgbas));
if(visx < bargs->begx + lenx - 1){
memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * 4)), sizeof(*rgbas));
if(visy < bargs->begy + leny - 1){
memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * 4)), sizeof(*rgbas));
if(visy < bargs->begy + leny - 2){
memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * 4)), sizeof(*rgbas));
}
}
}
if(visy < bargs->begy + leny - 1){
memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * 4)), sizeof(*rgbas));
if(visy < bargs->begy + leny - 2){
memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * bpp / CHAR_BIT)), sizeof(*rgbas));
memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * 4)), sizeof(*rgbas));
}
}
nccell* c = ncplane_cell_ref_yx(nc, y, x);
@ -746,7 +745,7 @@ fold_rgb8(unsigned* restrict r, unsigned* restrict g, unsigned* restrict b,
// resolution. always transparent background.
static inline int
braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp){
const blitterargs* bargs){
const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
int dimy, dimx, x, y;
int total = 0; // number of cells written
@ -766,7 +765,7 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
if(x < 0){
continue;
}
const uint32_t* rgbbase_l0 = (const uint32_t*)(dat + (linesize * visy) + (visx * bpp / CHAR_BIT));
const uint32_t* rgbbase_l0 = (const uint32_t*)(dat + (linesize * visy) + (visx * 4));
const uint32_t* rgbbase_r0 = &zeroes32;
const uint32_t* rgbbase_l1 = &zeroes32;
const uint32_t* rgbbase_r1 = &zeroes32;
@ -778,23 +777,23 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
unsigned blends = 0;
unsigned egcidx = 0;
if(visx < bargs->begx + lenx - 1){
rgbbase_r0 = (const uint32_t*)(dat + (linesize * visy) + ((visx + 1) * bpp / CHAR_BIT));
rgbbase_r0 = (const uint32_t*)(dat + (linesize * visy) + ((visx + 1) * 4));
if(visy < bargs->begy + leny - 1){
rgbbase_r1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + ((visx + 1) * bpp / CHAR_BIT));
rgbbase_r1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + ((visx + 1) * 4));
if(visy < bargs->begy + leny - 2){
rgbbase_r2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + ((visx + 1) * bpp / CHAR_BIT));
rgbbase_r2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + ((visx + 1) * 4));
if(visy < bargs->begy + leny - 3){
rgbbase_r3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + ((visx + 1) * bpp / CHAR_BIT));
rgbbase_r3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + ((visx + 1) * 4));
}
}
}
}
if(visy < bargs->begy + leny - 1){
rgbbase_l1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + (visx * bpp / CHAR_BIT));
rgbbase_l1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + (visx * 4));
if(visy < bargs->begy + leny - 2){
rgbbase_l2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + (visx * bpp / CHAR_BIT));
rgbbase_l2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + (visx * 4));
if(visy < bargs->begy + leny - 3){
rgbbase_l3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + (visx * bpp / CHAR_BIT));
rgbbase_l3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + (visx * 4));
}
}
}
@ -836,7 +835,7 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
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]);
//fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, 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
@ -1076,7 +1075,7 @@ int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* v
},
},
};
return bset->blit(nc, linesize, data, leny, lenx, &bargs, 32);
return bset->blit(nc, linesize, data, leny, lenx, &bargs);
}
ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){

@ -404,8 +404,7 @@ typedef struct blitterargs {
// from scaling. we might actually need more pixels due to framing concerns,
// in which case just assume transparent input pixels where needed.
typedef int (*ncblitter)(struct ncplane* n, int linesize, const void* data,
int scaledy, int scaledx, const blitterargs* bargs,
int bpp);
int scaledy, int scaledx, const blitterargs* bargs);
// a system for rendering RGBA pixels as text glyphs or sixel/kitty bitmaps
struct blitset {
@ -1599,9 +1598,8 @@ const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, boo
static inline int
rgba_blit_dispatch(ncplane* nc, const struct blitset* bset,
int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs,
int bpp){
return bset->blit(nc, linesize, data, leny, lenx, bargs, bpp);
int leny, int lenx, const blitterargs* bargs){
return bset->blit(nc, linesize, data, leny, lenx, bargs);
}
static inline const struct blitset*

@ -0,0 +1,102 @@
// the iterm2 graphics protocol is based entirely around containerized formats
// https://iterm2.com/documentation-images.html
#include <stdio.h>
#include "internal.h"
#include "termdesc.h"
#include "sprite.h"
#include "png.h"
// yank a cell out of the PNG by setting all of its alphas to 0. the alphas
// will be preserved in the auxvec.
int iterm_wipe(sprixel* s, int ycell, int xcell){
return 0;
}
// build a cell of the PNG back up by copying auxvec alphas to it.
int iterm_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
return 0;
}
// spit out the control sequence and data.
int iterm_draw(const ncpile *p, sprixel* s, FILE* out, int y, int x){
if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){
return -1;
}
return s->glyphlen;
}
// damage any cells underneath the graphic, destroying it.
int iterm_scrub(const ncpile* p, sprixel* s){
return 0;
}
static int
write_iterm_graphic(sprixel* s, const void* data, int leny, int stride, int lenx){
s->glyph = NULL;
FILE* fp = open_memstream(&s->glyph, &s->glyphlen);
if(fp == NULL){
return -1;
}
if(ncfputs("\e]1337;File=inline=1:", fp) == EOF){
goto err;
}
// FIXME won't we need to pass TAM into write_png_b64()?
if(write_png_b64(data, leny, stride, lenx, fp)){
goto err;
}
if(fclose(fp) == EOF){
logerror("Error flushing %zuB (%s)\n", s->glyphlen, strerror(errno));
free(s->glyph);
return -1;
}
return 0;
err:
fclose(fp);
free(s->glyph);
s->glyph = NULL;
return -1;
}
// create an iterm2 control sequence complete with base64-encoded PNG.
int iterm_blit(ncplane* n, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs){
int cols = bargs->u.pixel.spx->dimx;
int rows = bargs->u.pixel.spx->dimy;
sprixel* s = bargs->u.pixel.spx;
tament* tam = NULL;
bool reuse = false;
void* png = NULL;
// if we have a sprixel attached to this plane, see if we can reuse it
// (we need the same dimensions) and thus immediately apply its T-A table.
if(n->tam){
if(n->leny == rows && n->lenx == cols){
tam = n->tam;
reuse = true;
}
}
int parse_start = 0;
if(!reuse){
tam = malloc(sizeof(*tam) * rows * cols);
if(tam == NULL){
goto error;
}
memset(tam, 0, sizeof(*tam) * rows * cols);
}
if(write_iterm_graphic(s, data, leny, linesize, lenx)){
goto error;
}
if(plane_blit_sixel(s, s->glyph, s->glyphlen, leny, lenx, parse_start, tam) < 0){
goto error;
}
return 1;
error:
if(!reuse){
free(tam);
}
free(png);
free(s->glyph);
return -1;
}

@ -1,4 +1,5 @@
#include "internal.h"
#include "base64.h"
// Kitty has its own bitmap graphics protocol, rather superior to DEC Sixel.
// A header is written with various directives, followed by a number of
@ -36,10 +37,6 @@
//
// https://sw.kovidgoyal.net/kitty/graphics-protocol.html
// lookup table for base64
static unsigned const char b64subs[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// convert a base64 character into its equivalent integer 0..63
static inline int
b64idx(char b64){
@ -56,67 +53,6 @@ b64idx(char b64){
}
}
// every 3 RGBA pixels (96 bits) become 16 base64-encoded bytes (128 bits). if
// there are only 2 pixels available, those 64 bits become 12 bytes. if there
// is only 1 pixel available, those 32 bits become 8 bytes. (pcount + 1) * 4
// bytes are used, plus a null terminator. we thus must receive 17.
static inline void
base64_rgba3(const uint32_t* pixels, size_t pcount, char* b64, bool wipe[static 3],
uint32_t transcolor){
uint32_t pixel = *pixels++;
unsigned r = ncpixel_r(pixel);
unsigned g = ncpixel_g(pixel);
unsigned b = ncpixel_b(pixel);
// we go ahead and take advantage of kitty's ability to reproduce 8-bit
// alphas by copying it in directly, rather than mapping to {0, 255}.
unsigned a = ncpixel_a(pixel);
if(wipe[0] || rgba_trans_p(pixel, transcolor)){
a = 0;
}
b64[0] = b64subs[(r & 0xfc) >> 2];
b64[1] = b64subs[(r & 0x3 << 4) | ((g & 0xf0) >> 4)];
b64[2] = b64subs[((g & 0xf) << 2) | ((b & 0xc0) >> 6)];
b64[3] = b64subs[b & 0x3f];
b64[4] = b64subs[(a & 0xfc) >> 2];
if(pcount == 1){
b64[5] = b64subs[(a & 0x3) << 4];
b64[6] = '=';
b64[7] = '=';
b64[8] = '\0';
return;
}
b64[5] = (a & 0x3) << 4;
pixel = *pixels++;
r = ncpixel_r(pixel);
g = ncpixel_g(pixel);
b = ncpixel_b(pixel);
a = wipe[1] ? 0 : rgba_trans_p(pixel, transcolor) ? 0 : 255;
b64[5] = b64subs[b64[5] | ((r & 0xf0) >> 4)];
b64[6] = b64subs[((r & 0xf) << 2) | ((g & 0xc0) >> 6u)];
b64[7] = b64subs[g & 0x3f];
b64[8] = b64subs[(b & 0xfc) >> 2];
b64[9] = b64subs[((b & 0x3) << 4) | ((a & 0xf0) >> 4)];
if(pcount == 2){
b64[10] = b64subs[(a & 0xf) << 2];
b64[11] = '=';
b64[12] = '\0';
return;
}
b64[10] = (a & 0xf) << 2;
pixel = *pixels;
r = ncpixel_r(pixel);
g = ncpixel_g(pixel);
b = ncpixel_b(pixel);
a = wipe[2] ? 0 : rgba_trans_p(pixel, transcolor) ? 0 : 255;
b64[10] = b64subs[b64[10] | ((r & 0xc0) >> 6)];
b64[11] = b64subs[r & 0x3f];
b64[12] = b64subs[(g & 0xfc) >> 2];
b64[13] = b64subs[((g & 0x3) << 4) | ((b & 0xf0) >> 4)];
b64[14] = b64subs[((b & 0xf) << 2) | ((a & 0xc0) >> 6)];
b64[15] = b64subs[a & 0x3f];
b64[16] = '\0';
}
// null out part of a triplet (a triplet is 3 pixels, which map to 12 bytes, which map to
// 16 bytes when base64 encoded). skip the initial |skip| pixels, and null out a maximum
// of |max| pixels after that. returns the number of pixels nulled out. |max| must be
@ -782,13 +718,12 @@ error:
}
int kitty_blit(ncplane* n, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp __attribute__ ((unused))){
const blitterargs* bargs){
return kitty_blit_core(n, linesize, data, leny, lenx, bargs, false);
}
int kitty_blit_animated(ncplane* n, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs,
int bpp __attribute__ ((unused))){
int leny, int lenx, const blitterargs* bargs){
return kitty_blit_core(n, linesize, data, leny, lenx, bargs, true);
}
@ -800,8 +735,7 @@ int kitty_remove(int id, FILE* out){
return 0;
}
// removes the kitty bitmap graphic identified by s->id, and damages those
// cells which weren't SPRIXCELL_OPAQUE
// damages cells underneath the graphic which weren't SPRIXCELL_OPAQUE
int kitty_scrub(const ncpile* p, sprixel* s){
//fprintf(stderr, "FROM: %d/%d state: %d s->n: %p\n", s->movedfromy, s->movedfromx, s->invalidated, s->n);
for(int yy = s->movedfromy ; yy < s->movedfromy + s->dimy && yy < p->dimy ; ++yy){

@ -1,9 +1,11 @@
#include <zlib.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <arpa/inet.h>
#include "visual-details.h"
#include "internal.h"
#include "base64.h"
#include "png.h"
// http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
@ -16,6 +18,7 @@
// [loser] languages which have difficulty dealing with unsigned values."
#define CHUNK_MAX_DATA 0x80000000llu
static const unsigned char PNGHEADER[] = "\x89PNG\x0d\x0a\x1a\x0a";
static const unsigned char IEND[] = "\x00\x00\x00\x00IEND\xae\x42\x60\x82";
// FIXME replace with PCLMULQDQ method (and ARM CRC32 instruction)
// this is taken from the PNG reference
@ -61,15 +64,15 @@ crc(const unsigned char *buf, int len){
// compress the ncvisual data suitably for PNG. this requires adding a byte
// of filter type (currently always 0) before each scanline =[.
static void*
compress_image(const ncvisual* ncv, size_t* dlen){
z_stream zctx;
compress_image(const void* data, int rows, int rowstride, int cols, size_t* dlen){
z_stream zctx = { };
int zret;
if((zret = deflateInit(&zctx, Z_DEFAULT_COMPRESSION)) != Z_OK){
logerror("Couldn't get a deflate context (%d)\n", zret);
return NULL;
}
// one byte per scanline for adaptive filtering type (always 0 for now)
uint64_t databytes = ncv->pixx * ncv->pixy * 4 + ncv->pixy;
uint64_t databytes = cols * rows * 4 + rows;
unsigned long bound = deflateBound(&zctx, databytes);
unsigned char* buf = malloc(bound);
if(buf == NULL){
@ -80,22 +83,23 @@ compress_image(const ncvisual* ncv, size_t* dlen){
zctx.next_out = buf;
zctx.avail_out = bound;
// enough space for a single scanline + filter byte
unsigned char* sbuf = malloc(1 + ncv->pixx * 4);
unsigned char* sbuf = malloc(1 + cols * 4);
if(sbuf == NULL){
logerror("Couldn't allocate %zuB\n", 1 + ncv->pixx * 4);
deflateEnd(&zctx);
logerror("Couldn't allocate %zuB\n", 1 + cols * 4);
free(buf);
deflateEnd(&zctx);
return NULL;
}
for(int i = 0 ; i < ncv->pixy ; ++i){
for(int i = 0 ; i < rows ; ++i){
if(zctx.avail_out == 0){
free(buf);
free(sbuf);
deflateEnd(&zctx);
return NULL;
}
zctx.avail_in = ncv->pixx * 4 + 1;
zctx.avail_in = cols * 4 + 1;
sbuf[0] = 0;
memcpy(sbuf + 1, ncv->data + ncv->rowstride * i, ncv->pixx * 4);
memcpy(sbuf + 1, data + rowstride * i, cols * 4);
zctx.next_in = sbuf;
if((zret = deflate(&zctx, Z_NO_FLUSH)) != Z_OK){
logerror("Error deflating %dB to %dB (%d)\n", zctx.avail_in, zctx.avail_out, zret);
@ -123,8 +127,9 @@ compress_image(const ncvisual* ncv, size_t* dlen){
// number of bytes necessary to encode (uncompressed) the visual specified by
// |ncv|. on error, *|deflated| will be NULL.
size_t compute_png_size(const ncvisual* ncv, void** deflated, size_t* dlen){
if((*deflated = compress_image(ncv, dlen)) == NULL){
size_t compute_png_size(const void* data, int rows, int rowstride, int cols,
void** deflated, size_t* dlen){
if((*deflated = compress_image(data, rows, rowstride, cols, dlen)) == NULL){
return 0;
}
//fprintf(stderr, "ACTUAL: %zu (0x%02x) (0x%02x)\n", *dlen, (*(char **)deflated)[*dlen - 1], (*(char**)deflated)[20]);
@ -150,21 +155,21 @@ chunk_crc(const unsigned char* buf){
logerror("Chunk length too large (%lu)\n", length);
return 0;
}
length += 4; // don't use length or crc fields
length += 4; // don't use length or crc fields (but type *is* covered)
uint32_t crc32 = htonl(crc(buf + 4, length));
return crc32;
}
// write the ihdr at |buf|, which is guaranteed to be large enough (25B).
static size_t
write_ihdr(const ncvisual* ncv, unsigned char* buf){
write_ihdr(int rows, int cols, unsigned char buf[static 25]){
uint32_t length = htonl(IHDR_DATA_BYTES);
memcpy(buf, &length, 4);
static const char ctype[] = "IHDR";
memcpy(buf + 4, ctype, 4);
uint32_t width = htonl(ncv->pixx);
uint32_t width = htonl(cols);
memcpy(buf + 8, &width, 4);
uint32_t height = htonl(ncv->pixy);
uint32_t height = htonl(rows);
memcpy(buf + 12, &height, 4);
uint8_t depth = 8; // 8 bits per channel
memcpy(buf + 16, &depth, 1);
@ -208,8 +213,7 @@ write_idats(unsigned char* buf, const unsigned char* data, size_t dlen){
// write the constant 12B IEND chunk at |buf|. it contains no data.
static size_t
write_iend(unsigned char* buf){
static const char iend[] = "\x00\x00\x00\x00IEND\xae\x42\x60\x82";
memcpy(buf, iend, CHUNK_DESC_BYTES);
memcpy(buf, IEND, CHUNK_DESC_BYTES);
return CHUNK_DESC_BYTES;
}
@ -217,11 +221,11 @@ write_iend(unsigned char* buf){
// deflated data |deflated| of |dlen| bytes. |buf| must be large enough to
// write all necessary data; it ought have been sized with compute_png_size().
static size_t
create_png(const ncvisual* ncv, void* buf, const unsigned char* deflated,
create_png(int rows, int cols, void* buf, const unsigned char* deflated,
size_t dlen){
size_t written = sizeof(PNGHEADER) - 1;
memcpy(buf, PNGHEADER, written);
size_t r = write_ihdr(ncv, (unsigned char*)buf + written);
size_t r = write_ihdr(rows, cols, (unsigned char*)buf + written);
written += r;
r = write_idats((unsigned char*)buf + written, deflated, dlen);
written += r;
@ -239,12 +243,14 @@ mmap_round_size(size_t s){
// write a PNG, creating the buffer ourselves. it must be munmapped. the
// resulting length is written to *bsize on success (the file/map might be
// larger than this, but the end is immaterial padding). returns MMAP_FAILED
// on a failure. if |fd| is negative, an anonymous map will be made.
void* create_png_mmap(const ncvisual* ncv, size_t* bsize, int fd){
// on a failure. if |fd| is negative, an anonymous map will be made. |rows|
// and |cols| are in pixels; |rowstride| is in bytes.
void* create_png_mmap(const void* data, int rows, int rowstride, int cols,
size_t* bsize, int fd){
void* deflated;
size_t dlen;
size_t mlen;
*bsize = compute_png_size(ncv, &deflated, &dlen);
*bsize = compute_png_size(data, rows, rowstride, cols, &deflated, &dlen);
if(deflated == NULL){
logerror("Couldn't compress to %d\n", fd);
return MAP_FAILED;
@ -262,19 +268,14 @@ void* create_png_mmap(const ncvisual* ncv, size_t* bsize, int fd){
loginfo("Set size of %d to %zuB\n", fd, mlen);
}
// FIXME hugetlb?
void* map = mmap(NULL, mlen, PROT_WRITE | PROT_READ,
#ifdef MAP_SHARED_VALIDATE
MAP_SHARED_VALIDATE |
#else
MAP_SHARED |
#endif
void* map = mmap(NULL, mlen, PROT_WRITE | PROT_READ, MAP_SHARED |
(fd >= 0 ? 0 : MAP_ANONYMOUS), fd, 0);
if(map == MAP_FAILED){
logerror("Couldn't get %zuB map for %d\n", mlen, fd);
logerror("Couldn't get %zuB map for %d (%s)\n", mlen, fd, strerror(errno));
free(deflated);
return MAP_FAILED;
}
size_t w = create_png(ncv, map, deflated, dlen);
size_t w = create_png(rows, cols, map, deflated, dlen);
free(deflated);
loginfo("Wrote %zuB PNG to %d\n", w, fd);
if(fd >= 0){
@ -286,3 +287,115 @@ void* create_png_mmap(const ncvisual* ncv, size_t* bsize, int fd){
}
return map;
}
struct b64ctx {
unsigned char src[3]; // try to convert three at a time
size_t srcidx; // how many src bytes we have
};
static int
fwrite64(const void* src, size_t osize, FILE* fp, struct b64ctx* bctx){
size_t w = 0;
char b64[4];
if(bctx->srcidx){
size_t copy = sizeof(bctx->src) - bctx->srcidx;
// the unlikely event that we don't fill the bctx with our entire chunk...
if(copy > osize){
memcpy(bctx->src + bctx->srcidx, src, osize);
bctx->srcidx += osize;
return 0;
}
memcpy(bctx->src + bctx->srcidx, src, copy);
base64x3(bctx->src, b64);
bctx->srcidx = 0;
if(fwrite(b64, 4, 1, fp) != 1){
return -1;
}
w = copy;
}
// the bctx is now guaranteed to be empty
while(w + 3 <= osize){
base64x3((const unsigned char*)src + w, b64);
if(fwrite(b64, 4, 1, fp) != 1){
return -1;
}
w += 3;
}
// less than 3 remain; copy them into the bctx for further use
if(w < osize){
bctx->srcidx = osize - w;
memcpy(bctx->src, src + w, bctx->srcidx);
}
return 1;
}
static size_t
fwrite_idats(FILE* fp, const unsigned char* data, size_t dlen,
struct b64ctx* bctx){
static const char ctype[] = "IDAT";
uint32_t written = 0;
uint32_t dwritten = 0;
while(dlen){
uint32_t thischunk = dlen;
if(thischunk > CHUNK_MAX_DATA){
thischunk = CHUNK_MAX_DATA;
}
uint32_t nclen = htonl(thischunk);
if(fwrite64(&nclen, 4, fp, bctx) != 1 ||
fwrite64(ctype, 4, fp, bctx) != 1 ||
fwrite64(data + dwritten, thischunk, fp, bctx) != 1){
return 0;
}
// FIXME horrible; PoC; do not retain!
unsigned char* crcbuf = malloc(thischunk + 8);
memcpy(crcbuf, &nclen, 4);
memcpy(crcbuf + 4, ctype, 4);
memcpy(crcbuf + 8, data + dwritten, thischunk);
// END horribleness
uint32_t crc = chunk_crc(crcbuf);
free(crcbuf); // FIXME well a bit more
if(fwrite64(&crc, 4, fp, bctx) != 1){
return 0;
}
dlen -= thischunk;
dwritten += thischunk;
written += CHUNK_DESC_BYTES + thischunk;
}
return written;
}
int write_png_b64(const void* data, int rows, int rowstride, int cols, FILE* fp){
void* deflated;
size_t dlen;
compute_png_size(data, rows, rowstride, cols, &deflated, &dlen);
if(deflated == NULL){
return -1;
}
struct b64ctx bctx = { };
if(fwrite64(PNGHEADER, sizeof(PNGHEADER) - 1, fp, &bctx) != 1){
free(deflated);
return -1;
}
unsigned char ihdr[25];
write_ihdr(rows, cols, ihdr);
if(fwrite64(ihdr, sizeof(ihdr), fp, &bctx) != 1){
free(deflated);
return -1;
}
if(fwrite_idats(fp, deflated, dlen, &bctx) == 0){
free(deflated);
return -1;
}
free(deflated);
if(fwrite64(IEND, sizeof(IEND) - 1, fp, &bctx) != 1){
return -1;
}
if(bctx.srcidx){
char b64[4];
base64final(bctx.src, b64, bctx.srcidx);
if(fwrite(b64, 4, 1, fp) != 1){
return -1;
}
}
return 0;
}

@ -5,11 +5,17 @@
extern "C" {
#endif
#include <stdio.h>
#include <sys/mman.h>
struct ncvisual;
void* create_png_mmap(const struct ncvisual* ncv, size_t* bsize, int fd);
void* create_png_mmap(const void* data, int rows, int rowstride, int cols,
size_t* bsize, int fd);
// create the PNG, encode it using base64, and write it to |fp|
int write_png_b64(const void* data, int rows, int rowstride, int cols,
FILE* fp);
#ifdef __cplusplus
}

@ -728,7 +728,7 @@ sixel_blit_inner(int leny, int lenx, sixeltable* stab,
// |leny| and |lenx| are the scaled output geometry. we take |leny| up to the
// nearest multiple of six greater than or equal to |leny|.
int sixel_blit(ncplane* n, int linesize, const void* data, int leny, int lenx,
const blitterargs* bargs, int bpp __attribute__ ((unused))){
const blitterargs* bargs){
int colorregs = bargs->u.pixel.colorregs;
if(colorregs > 256){
colorregs = 256;

@ -164,14 +164,19 @@ int kitty_wipe(sprixel* s, int ycell, int xcell);
// wipes out a cell by animating an all-transparent cell, and integrating
// it with the original image using the animation protocol of 0.20.0+.
int kitty_wipe_animation(sprixel* s, int ycell, int xcell);
// wipes out a cell by changing the alpha value throughout the PNG cell to 0.
int iterm_wipe(sprixel* s, int ycell, int xcell);
int sixel_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
int iterm_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
int kitty_rebuild_animation(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
int sixel_draw(const struct ncpile *p, sprixel* s, FILE* out, int y, int x);
int kitty_draw(const struct ncpile *p, sprixel* s, FILE* out, int y, int x);
int iterm_draw(const struct ncpile *p, sprixel* s, FILE* out, int y, int x);
int kitty_move(sprixel* s, FILE* out, unsigned noscroll);
int sixel_draw(const struct ncpile *p, sprixel* s, FILE* out, int y, int x);
int sixel_scrub(const struct ncpile* p, sprixel* s);
int kitty_scrub(const struct ncpile* p, sprixel* s);
int iterm_scrub(const struct ncpile* p, sprixel* s);
int kitty_remove(int id, FILE* out);
int kitty_clear_all(FILE* fp);
int sixel_init(const tinfo* t, int fd);
@ -182,12 +187,13 @@ uint8_t* sixel_trans_auxvec(const struct tinfo* ti);
uint8_t* kitty_trans_auxvec(const struct tinfo* ti);
int kitty_commit(FILE* fp, sprixel* s, unsigned noscroll);
int sixel_blit(struct ncplane* nc, int linesize, const void* data,
int leny, int lenx, const struct blitterargs* bargs, int bpp);
int leny, int lenx, const struct blitterargs* bargs);
int kitty_blit(struct ncplane* nc, int linesize, const void* data,
int leny, int lenx, const struct blitterargs* bargs, int bpp);
int leny, int lenx, const struct blitterargs* bargs);
int iterm_blit(struct ncplane* nc, int linesize, const void* data,
int leny, int lenx, const struct blitterargs* bargs);
int kitty_blit_animated(struct ncplane* n, int linesize, const void* data,
int leny, int lenx, const struct blitterargs* bargs,
int bpp);
int leny, int lenx, const struct blitterargs* bargs);
#ifdef __cplusplus
}

@ -55,6 +55,8 @@ setup_sixel_bitmaps(tinfo* ti, int fd, bool invert80){
ti->pixel_draw = sixel_draw;
ti->pixel_scrub = sixel_scrub;
ti->pixel_wipe = sixel_wipe;
ti->pixel_remove = NULL;
ti->pixel_move = NULL;
ti->pixel_shutdown = sixel_shutdown;
ti->pixel_rebuild = sixel_rebuild;
ti->pixel_trans_auxvec = sixel_trans_auxvec;
@ -63,6 +65,26 @@ setup_sixel_bitmaps(tinfo* ti, int fd, bool invert80){
sprite_init(ti, fd);
}
// iterm2 has a container-based protocol
static inline void
setup_iterm_bitmaps(tinfo* ti, int fd){
ti->pixel_init = NULL;
ti->pixel_shutdown = NULL;
ti->sprixel_scale_height = 1;
ti->pixel_remove = NULL;
// be awarre: absence of pixel_move plus absence of sixel details is used by
// notcurses-info to determine iTerm2 support.
ti->pixel_move = NULL;
ti->color_registers = 0;
ti->pixel_draw = iterm_draw;
ti->pixel_scrub = iterm_scrub;
ti->pixel_wipe = iterm_wipe;
ti->pixel_rebuild = iterm_rebuild;
ti->pixel_trans_auxvec = kitty_trans_auxvec;
set_pixel_blitter(iterm_blit);
sprite_init(ti, fd);
}
// kitty 0.19.3 didn't have C=1, and thus needs sixel_maxy_pristine. it also
// lacked animation, and thus requires the older interface.
static inline void
@ -495,9 +517,9 @@ apply_term_heuristics(tinfo* ti, const char* termname, int fd,
if(add_smulx_escapes(ti, tablelen, tableused)){
return -1;
}
}else{
termname = "XTerm";
}
// we don't yet want to use the iterm2 protocol in place of sixel
//setup_iterm_bitmaps(ti, fd);
}else if(qterm == TERMINAL_XTERM){
termname = "XTerm";
// xterm 357 added color palette escapes XT{PUSH,POP,REPORT}COLORS
@ -511,10 +533,11 @@ apply_term_heuristics(tinfo* ti, const char* termname, int fd,
ti->caps.quadrants = true;
ti->caps.rgb = true;
}else if(qterm == TERMINAL_ITERM){
// iTerm implements DCS ASU, but no detection for it
// iTerm implements DCS ASU, but has no detection for it
if(add_appsync_escapes_dcs(ti, tablelen, tableused)){
return -1;
}
setup_iterm_bitmaps(ti, fd);
}else if(qterm == TERMINAL_LINUX){
struct utsname un;
if(uname(&un) == 0){

@ -8,8 +8,11 @@ extern "C" {
// internal header, not installed
#include "input.h"
#include <stdint.h>
#include <pthread.h>
#include <stdbool.h>
#include <termios.h>
#include <notcurses/notcurses.h>
struct ncpile;
struct sprixel;

@ -88,7 +88,7 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
return -1;
}
int ret = -1;
if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, barg, 32) >= 0){
if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, barg) >= 0){
ret = 0;
}
if(data != ncv->data){

@ -542,7 +542,7 @@ int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
}
//fprintf(stderr, "WHN NCV: %d/%d bargslen: %d/%d targ: %d/%d\n", inframe->width, inframe->height, bargs->leny, bargs->lenx, rows, cols);
int ret = 0;
if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, bargs, 32) < 0){
if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, bargs) < 0){
//fprintf(stderr, "rgba dispatch failed!\n");
ret = -1;
}

@ -6,9 +6,8 @@
int oiio_blit_dispatch(struct ncplane* nc, const struct blitset* bset,
int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs,
int bpp){
if(rgba_blit_dispatch(nc, bset, linesize, data, leny, lenx, bargs, bpp) < 0){
int leny, int lenx, const blitterargs* bargs){
if(rgba_blit_dispatch(nc, bset, linesize, data, leny, lenx, bargs) < 0){
return -1;
}
return 0;

@ -150,7 +150,6 @@ int oiio_blit(struct ncvisual* ncv, int rows, int cols,
//fprintf(stderr, "%d/%d -> %d/%d on the resize\n", ncv->pixy, ncv->pixx, rows, cols);
void* data = nullptr;
int stride;
int pstride;
auto ibuf = std::make_unique<OIIO::ImageBuf>();
if(ncv->details->ibuf && (ncv->pixx != cols || ncv->pixy != rows)){ // scale it
// FIXME need to honor leny/lenx and begy/begx
@ -158,17 +157,15 @@ int oiio_blit(struct ncvisual* ncv, int rows, int cols,
if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details->ibuf, "", 0, roi)){
return -1;
}
pstride = ibuf->pixel_stride();
stride = ibuf->scanline_stride();
data = ibuf->localpixels();
//fprintf(stderr, "HAVE SOME NEW DATA: %p\n", ibuf->localpixels());
}else{
data = ncv->data;
stride = ncv->rowstride;
pstride = 4; // FIXME need pixel_stride() if loaded from oiio...
}
//std::cerr << "output: " << ibuf->roi() << " stride: " << stride << " pstride: " << pstride << std::endl;
return oiio_blit_dispatch(n, bset, stride, data, rows, cols, bargs, pstride * CHAR_BIT);
return oiio_blit_dispatch(n, bset, stride, data, rows, cols, bargs);
}
// FIXME before we can enable this, we need build an OIIO::APPBUFFER-style

@ -21,8 +21,7 @@ ncvisual* oiio_create(void);
void oiio_destroy(ncvisual* ncv);
int oiio_blit_dispatch(struct ncplane* nc, const struct blitset* bset,
int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs,
int bpp);
int leny, int lenx, const blitterargs* bargs);
#ifdef __cplusplus
}

Loading…
Cancel
Save