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. #1420pull/1937/head
parent
a557b6d734
commit
757fb5811f
@ -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
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue