mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 09:09:03 -04:00
implement sixel_as_rgba() #1724
This commit is contained in:
parent
61000ce2a4
commit
bb91c170dd
@ -408,80 +408,6 @@ target_link_directories(notcurses PRIVATE ${OIIO_LIBRARY_DIRS})
|
||||
target_link_directories(notcurses-static PRIVATE ${OIIO_STATIC_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
############################################################################
|
||||
# libncsixel (sixel library built atop notcurses)
|
||||
file(GLOB NCSIXSRCS CONFIGURE_DEPENDS src/sixel/*.c)
|
||||
add_library(ncsixel SHARED ${NCSIXSRCS} ${COMPATSRC})
|
||||
if(${USE_STATIC})
|
||||
add_library(ncsixel-static STATIC ${NCSIXSRCS})
|
||||
else()
|
||||
add_library(ncsixel-static STATIC EXCLUDE_FROM_ALL ${NCSIXSRCS})
|
||||
endif()
|
||||
set_target_properties(ncsixel PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
)
|
||||
set_target_properties(ncsixel-static PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
OUTPUT_NAME ncsixel
|
||||
)
|
||||
target_include_directories(ncsixel
|
||||
BEFORE
|
||||
PRIVATE
|
||||
include
|
||||
src
|
||||
"${CMAKE_REQUIRED_INCLUDES}"
|
||||
"${PROJECT_BINARY_DIR}/include"
|
||||
"${TERMINFO_INCLUDE_DIRS}"
|
||||
)
|
||||
target_include_directories(ncsixel-static
|
||||
BEFORE
|
||||
PRIVATE
|
||||
include
|
||||
src
|
||||
"${CMAKE_REQUIRED_INCLUDES}"
|
||||
"${PROJECT_BINARY_DIR}/include"
|
||||
"${TERMINFO_INCLUDE_DIRS}"
|
||||
)
|
||||
target_compile_definitions(ncsixel
|
||||
PRIVATE
|
||||
_GNU_SOURCE _DEFAULT_SOURCE
|
||||
)
|
||||
target_compile_definitions(ncsixel-static
|
||||
PRIVATE
|
||||
_GNU_SOURCE _DEFAULT_SOURCE
|
||||
)
|
||||
target_link_libraries(ncsixel
|
||||
PUBLIC
|
||||
notcurses
|
||||
)
|
||||
target_link_libraries(ncsixel-static
|
||||
PUBLIC
|
||||
notcurses-static
|
||||
)
|
||||
file(GLOB SIXELANALYZERSRC CONFIGURE DEPENDS src/sixelanalyzer/*.c)
|
||||
add_executable(sixelanalyzer ${SIXELANALYZERSRC})
|
||||
target_compile_definitions(sixelanalyzer
|
||||
PRIVATE
|
||||
_GNU_SOURCE
|
||||
)
|
||||
target_include_directories(sixelanalyzer
|
||||
BEFORE
|
||||
PRIVATE
|
||||
include
|
||||
src
|
||||
"${TERMINFO_INCLUDE_DIRS}"
|
||||
"${CMAKE_REQUIRED_INCLUDES}"
|
||||
"${PROJECT_BINARY_DIR}/include"
|
||||
)
|
||||
target_link_libraries(sixelanalyzer
|
||||
PRIVATE
|
||||
ncsixel
|
||||
${LIBM}
|
||||
${LIBRT}
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
############################################################################
|
||||
if(${USE_CXX})
|
||||
# libnotcurses++ (C++ wrappers)
|
||||
@ -1082,7 +1008,6 @@ if(BUILD_EXECUTABLES)
|
||||
install(TARGETS notcurses-demo DESTINATION bin)
|
||||
install(TARGETS notcurses-info DESTINATION bin)
|
||||
install(TARGETS ncneofetch DESTINATION bin)
|
||||
install(TARGETS sixelanalyzer DESTINATION bin)
|
||||
if(NOT WIN32)
|
||||
install(TARGETS tfman DESTINATION bin)
|
||||
endif()
|
||||
|
1
NEWS.md
1
NEWS.md
@ -8,6 +8,7 @@ rearrangements of Notcurses.
|
||||
* Added `NCOPTION_CLI_MODE`, an alias for the bitwise OR of
|
||||
`NCOPTION_SCROLLING`, `NCOPTION_NO_CLEAR_BITMAPS`,
|
||||
`NCOPTION_NO_ALTERNATE_SCREEN`, and `NCOPTION_PRESERVE_CURSOR`.
|
||||
* Added `ncsixel_as_rgba()`.
|
||||
|
||||
* 3.0.1 (2021-12-14)
|
||||
* Added the `NCPLANE_OPTION_VSCROLL` flag. Creating an `ncplane` with this
|
||||
|
4
USAGE.md
4
USAGE.md
@ -3632,10 +3632,12 @@ have only one frame), until it returns `NCERR_EOF`:
|
||||
// Open a visual at 'file', extracting a codec and parameters.
|
||||
struct ncvisual* ncvisual_from_file(const char* file);
|
||||
|
||||
|
||||
// extract the next frame from an ncvisual. returns NCERR_EOF on end of file,
|
||||
// and NCERR_SUCCESS on success, otherwise some other NCERR.
|
||||
int ncvisual_decode(struct ncvisual* nc);
|
||||
|
||||
// Convert a sixel escape into an RGBA vector.
|
||||
uint32_t* ncsixel_as_rgba(const char *s, unsigned leny, unsigned lenx);
|
||||
```
|
||||
|
||||
### Pixels
|
||||
|
@ -121,6 +121,8 @@ typedef struct ncvgeom {
|
||||
|
||||
**int ncplane_qrcode(struct ncplane* ***n***, unsigned* ***ymax***, unsigned* ***xmax***, const void* ***data***, size_t ***len***)**
|
||||
|
||||
**uint32_t* ncsixel_as_rgba(const char* ***s***, unsigned ***leny***, unsigned ***lenx***);**
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
An **ncvisual** is a virtual pixel framebuffer. They can be created from
|
||||
|
@ -1,25 +0,0 @@
|
||||
#ifndef NOTCURSES_LIBSIXEL
|
||||
#define NOTCURSES_LIBSIXEL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct sixel;
|
||||
struct sixelctx;
|
||||
|
||||
struct sixelctx* libncsixel_init(void);
|
||||
|
||||
struct sixel* libncsixel_encode(struct sixelctx* sctx, const char* file, unsigned colorregs);
|
||||
|
||||
uint32_t* libncsixel_explode(const struct sixel* s);
|
||||
|
||||
void libncsixel_stop(struct sixelctx* sctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -4492,6 +4492,10 @@ API ALLOC char* notcurses_hostname(void);
|
||||
// Returns a heap-allocated copy of human-readable OS name and version.
|
||||
API ALLOC char* notcurses_osversion(void);
|
||||
|
||||
// Convert a sixel escape into an RGBA vector.
|
||||
API ALLOC uint32_t* ncsixel_as_rgba(const char *s, unsigned leny, unsigned lenx)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Dump selected Notcurses state to the supplied 'debugfp'. Output is freeform,
|
||||
// newline-delimited, and subject to change. It includes geometry of all
|
||||
// planes, from all piles. No line has more than 80 columns' worth of output.
|
||||
|
@ -1,42 +0,0 @@
|
||||
#ifndef NOTCURSES_SIXEL_SIXEL
|
||||
#define NOTCURSES_SIXEL_SIXEL
|
||||
|
||||
// a library built atop Notcurses suitable for working with sixels outside
|
||||
// the Notcurses framework--a replacement for libsixel.
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#define RESTRICT
|
||||
#else
|
||||
#define RESTRICT restrict
|
||||
#endif
|
||||
|
||||
#ifndef __MINGW64__
|
||||
#define API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define API __declspec(dllexport)
|
||||
#endif
|
||||
#define ALLOC __attribute__((malloc)) __attribute__((warn_unused_result))
|
||||
|
||||
struct sixel;
|
||||
struct sixelctx;
|
||||
|
||||
API ALLOC struct sixelctx* libncsixel_init(void);
|
||||
|
||||
// load the file and encode the first frame as a sixel. if |colorregs| is 0,
|
||||
// use the number supported by the terminal, or 256 if the terminal does not
|
||||
// support sixel.
|
||||
API ALLOC __attribute__ ((nonnull (1, 2)))
|
||||
struct sixel* libncsixel_encode(struct sixelctx* sctx, const char* file,
|
||||
unsigned colorregs);
|
||||
|
||||
API void libncsixel_stop(struct sixelctx* sctx);
|
||||
|
||||
#undef API
|
||||
#undef ALLOC
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
23
src/lib/blit.h
Normal file
23
src/lib/blit.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef NOTCURSES_BLIT
|
||||
#define NOTCURSES_BLIT
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ncplane;
|
||||
struct blitterargs;
|
||||
|
||||
// scaledy and scaledx are output geometry from scaling; data is output data
|
||||
// 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 struct blitterargs* bargs);
|
||||
|
||||
void set_pixel_blitter(ncblitter blitfxn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -5,6 +5,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "blit.h"
|
||||
|
||||
// number of pixels that map to a single cell, height-wise
|
||||
static inline int
|
||||
encoding_y_scale(const tinfo* tcache, const struct blitset* bset) {
|
||||
@ -50,8 +52,6 @@ ncplot_defblitter(const notcurses* nc){
|
||||
return NCBLIT_1x1;
|
||||
}
|
||||
|
||||
void set_pixel_blitter(ncblitter blitfxn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -390,12 +390,6 @@ typedef struct blitterargs {
|
||||
} u;
|
||||
} blitterargs;
|
||||
|
||||
// scaledy and scaledx are output geometry from scaling; data is output data
|
||||
// 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);
|
||||
|
||||
// a system for rendering RGBA pixels as text glyphs or sixel/kitty bitmaps
|
||||
struct blitset {
|
||||
ncblitter_e geom;
|
||||
@ -693,7 +687,6 @@ void sprixel_hide(sprixel* s);
|
||||
// dimy and dimx are cell geometry, not pixel.
|
||||
sprixel* sprixel_alloc(ncplane* n, int dimy, int dimx);
|
||||
sprixel* sprixel_recycle(ncplane* n);
|
||||
int sprite_init(const tinfo* t, int fd);
|
||||
int sprite_clear_all(const tinfo* t, fbuf* f);
|
||||
// these three all use absolute coordinates
|
||||
void sprixel_invalidate(sprixel* s, int y, int x);
|
||||
@ -1077,7 +1070,7 @@ ALLOC char* ncplane_vprintf_prep(const char* format, va_list ap);
|
||||
|
||||
// Resize the provided ncvisual to the specified 'rows' x 'cols', but do not
|
||||
// change the internals of the ncvisual. Uses oframe.
|
||||
int ncvisual_blit_internal(struct ncvisual* ncv, int rows, int cols,
|
||||
int ncvisual_blit_internal(const struct ncvisual* ncv, int rows, int cols,
|
||||
ncplane* n, const struct blitset* bset,
|
||||
const blitterargs* bargs);
|
||||
|
||||
@ -1794,8 +1787,8 @@ create_polyfill_op(int y, int x, struct topolyfill** stck){
|
||||
typedef struct ncvisual_implementation {
|
||||
int (*visual_init)(int loglevel);
|
||||
void (*visual_printbanner)(fbuf* f);
|
||||
int (*visual_blit)(struct ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
|
||||
const struct blitset* bset, const blitterargs* barg);
|
||||
int (*visual_blit)(const struct ncvisual* ncv, unsigned rows, unsigned cols,
|
||||
ncplane* n, const struct blitset* bset, const blitterargs* barg);
|
||||
struct ncvisual* (*visual_create)(void);
|
||||
struct ncvisual* (*visual_from_file)(const char* fname);
|
||||
// ncv constructors other than ncvisual_from_file() need to set up the
|
||||
|
150
src/lib/sixel.c
150
src/lib/sixel.c
@ -1125,3 +1125,153 @@ uint8_t* sixel_trans_auxvec(const ncpile* p){
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
uint32_t* ncsixel_as_rgba(const char *sx, unsigned leny, unsigned lenx){
|
||||
#define MAXCOLORS 65535
|
||||
uint32_t* rgba = malloc(sizeof(*rgba) * leny * lenx);
|
||||
if(rgba == NULL){
|
||||
return NULL;
|
||||
}
|
||||
uint32_t* colors = malloc(sizeof(*colors) * MAXCOLORS);
|
||||
if(colors == NULL){
|
||||
free(rgba);
|
||||
return NULL;
|
||||
}
|
||||
// first we skip the header
|
||||
while(*sx != '#'){
|
||||
++sx;
|
||||
}
|
||||
// now we build the color table (form: #Pc;Pu;Px;Py;Pz). data starts with
|
||||
// a color spec lacking a semicolon.
|
||||
enum {
|
||||
STATE_WANT_HASH,
|
||||
STATE_WANT_COLOR,
|
||||
STATE_WANT_COLORSEMI,
|
||||
STATE_WANT_COLORSPACE,
|
||||
STATE_WANT_DATA,
|
||||
} state = STATE_WANT_HASH;
|
||||
unsigned color = 0;
|
||||
unsigned x = 0;
|
||||
unsigned y = 0;
|
||||
unsigned rle = 1;
|
||||
while(*sx){
|
||||
if(*sx == '\e'){
|
||||
break;
|
||||
}
|
||||
if(state == STATE_WANT_HASH){
|
||||
if('#' != *sx){
|
||||
goto err;
|
||||
}
|
||||
state = STATE_WANT_COLOR;
|
||||
}else if(state == STATE_WANT_COLOR){
|
||||
if(!isdigit(*sx)){
|
||||
goto err;
|
||||
}
|
||||
color = 0;
|
||||
do{
|
||||
color *= 10;
|
||||
color += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
//std::cerr << "Got color " << color << std::endl;
|
||||
--sx;
|
||||
state = STATE_WANT_COLORSEMI;
|
||||
}else if(state == STATE_WANT_COLORSEMI){
|
||||
// if we get a semicolon, we're a colorspec, otherwise data
|
||||
if(*sx == ';'){
|
||||
state = STATE_WANT_COLORSPACE;
|
||||
}else{
|
||||
state = STATE_WANT_DATA;
|
||||
rle = 1;
|
||||
}
|
||||
}else if(state == STATE_WANT_COLORSPACE){
|
||||
if('2' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int r = 0;
|
||||
do{
|
||||
r *= 10;
|
||||
r += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int g = 0;
|
||||
do{
|
||||
g *= 10;
|
||||
g += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int b = 0;
|
||||
do{
|
||||
b *= 10;
|
||||
b += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
uint32_t rgb = htole(0xff000000 + (r << 16u) * 255 / 100 + (g << 8u) * 255 / 100 + b * 255 / 100);
|
||||
//std::cerr << "Got color " << color << ": " << r << "/" << g << "/" << b << std::endl;
|
||||
if(color >= MAXCOLORS){
|
||||
goto err;
|
||||
}
|
||||
colors[color] = rgb;
|
||||
state = STATE_WANT_HASH;
|
||||
--sx;
|
||||
}
|
||||
// read until we hit next colorspec
|
||||
if(state == STATE_WANT_DATA){
|
||||
//std::cerr << "Character " << *sx << std::endl;
|
||||
if(*sx == '#'){
|
||||
state = STATE_WANT_HASH;
|
||||
--sx;
|
||||
}else if(*sx == '!'){ // RLE
|
||||
++sx;
|
||||
rle = 0;
|
||||
do{
|
||||
rle *= 10;
|
||||
rle += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
if(2 >= rle){
|
||||
goto err;
|
||||
}
|
||||
--sx;
|
||||
}else if(*sx == '$'){
|
||||
x = 0;
|
||||
state = STATE_WANT_HASH;
|
||||
}else if(*sx == '-'){
|
||||
x = 0;
|
||||
y += 6;
|
||||
state = STATE_WANT_HASH;
|
||||
}else{
|
||||
//std::cerr << "RLE: " << rle << " pos: " << y << "*" << x << std::endl;
|
||||
for(unsigned xpos = x ; xpos < x + rle ; ++xpos){
|
||||
for(unsigned ypos = y ; ypos < y + 6 ; ++ypos){
|
||||
if((*sx - 63) & (1u << (ypos - y))){
|
||||
// ought be an empty pixel
|
||||
//std::cerr << *s << " BMAP[" << ypos << "][" << xpos << "] = " << std::hex << colors[color] << std::dec << std::endl;
|
||||
rgba[ypos * lenx + xpos] = colors[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
x += rle;
|
||||
rle = 1;
|
||||
}
|
||||
}
|
||||
++sx;
|
||||
}
|
||||
free(colors);
|
||||
return rgba;
|
||||
|
||||
err:
|
||||
free(rgba);
|
||||
free(colors);
|
||||
return NULL;
|
||||
#undef MAXCOLORS
|
||||
}
|
||||
|
@ -167,6 +167,7 @@ create_tam(int rows, int cols){
|
||||
return tam;
|
||||
}
|
||||
|
||||
int sprite_init(const struct tinfo* t, int fd);
|
||||
int sixel_wipe(sprixel* s, int ycell, int xcell);
|
||||
// nulls out a cell from a kitty bitmap via changing the alpha value
|
||||
// throughout to 0. the same trick doesn't work on sixel, but there we
|
||||
@ -183,9 +184,9 @@ int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
|
||||
int fbcon_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
|
||||
int kitty_rebuild_animation(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
|
||||
int kitty_rebuild_selfref(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
|
||||
int sixel_draw(const tinfo* ti, const struct ncpile *p, sprixel* s,
|
||||
int sixel_draw(const struct tinfo* ti, const struct ncpile *p, sprixel* s,
|
||||
fbuf* f, int yoff, int xoff);
|
||||
int kitty_draw(const tinfo* ti, const struct ncpile *p, sprixel* s,
|
||||
int kitty_draw(const struct tinfo* ti, const struct ncpile *p, sprixel* s,
|
||||
fbuf* f, int yoff, int xoff);
|
||||
int kitty_move(sprixel* s, fbuf* f, unsigned noscroll, int yoff, int xoff);
|
||||
int sixel_scrub(const struct ncpile* p, sprixel* s);
|
||||
@ -208,8 +209,8 @@ int kitty_blit_selfref(struct ncplane* nc, int linesize, const void* data,
|
||||
int leny, int lenx, const struct blitterargs* bargs);
|
||||
int fbcon_blit(struct ncplane* nc, int linesize, const void* data,
|
||||
int leny, int lenx, const struct blitterargs* bargs);
|
||||
int fbcon_draw(const tinfo* ti, sprixel* s, int yoff, int xoff);
|
||||
void fbcon_scroll(const struct ncpile* p, tinfo* ti, int rows);
|
||||
int fbcon_draw(const struct tinfo* ti, sprixel* s, int yoff, int xoff);
|
||||
void fbcon_scroll(const struct ncpile* p, struct tinfo* ti, int rows);
|
||||
void sixel_refresh(const struct ncpile* p, sprixel* s);
|
||||
|
||||
// takes ownership of s on success.
|
||||
|
@ -13,6 +13,8 @@ extern "C" {
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <notcurses/notcurses.h>
|
||||
#include "sprite.h"
|
||||
#include "blit.h"
|
||||
#include "fbuf.h"
|
||||
#include "in.h"
|
||||
|
||||
|
@ -80,7 +80,7 @@ ncplane* ncvisual_subtitle_plane(ncplane* parent, const ncvisual* ncv){
|
||||
return visual_implementation->visual_subtitle(parent, ncv);
|
||||
}
|
||||
|
||||
int ncvisual_blit_internal(ncvisual* ncv, int rows, int cols, ncplane* n,
|
||||
int ncvisual_blit_internal(const ncvisual* ncv, int rows, int cols, ncplane* n,
|
||||
const struct blitset* bset, const blitterargs* barg){
|
||||
if(!(barg->flags & NCVISUAL_OPTION_NOINTERPOLATE)){
|
||||
if(visual_implementation->visual_blit){
|
||||
|
@ -641,7 +641,7 @@ ffmpeg_resize(ncvisual* n, unsigned rows, unsigned cols){
|
||||
|
||||
// rows/cols: scaled output geometry (pixels)
|
||||
static int
|
||||
ffmpeg_blit(ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
|
||||
ffmpeg_blit(const ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
|
||||
const struct blitset* bset, const blitterargs* bargs){
|
||||
void* data;
|
||||
int stride = 0;
|
||||
|
@ -11,7 +11,7 @@ int oiio_decode(ncvisual* nc);
|
||||
struct ncvisual_details* oiio_details_init(void);
|
||||
void oiio_printbanner(fbuf* f);
|
||||
void oiio_details_seed(struct ncvisual* ncv);
|
||||
int oiio_blit(ncvisual* ncv, unsigned rows, unsigned cols,
|
||||
int oiio_blit(const ncvisual* ncv, unsigned rows, unsigned cols,
|
||||
struct ncplane* n, const struct blitset* bset,
|
||||
const blitterargs* bargs);
|
||||
ncvisual* oiio_from_file(const char* filename);
|
||||
|
@ -1,200 +0,0 @@
|
||||
#include "sixel/sixel.h"
|
||||
#include <notcurses/notcurses.h>
|
||||
|
||||
// represents a sixel generated from some image
|
||||
typedef struct sixel {
|
||||
char* escape; // nul-terminated escape suitable for writing to the terminal
|
||||
// there is both the true pixel geometry, and the sprixel-padded pixel
|
||||
// geometry--the latter always has a height which is a multiple of six.
|
||||
unsigned pixy, pixx; // original pixel geometry
|
||||
unsigned sprixpixy, sprixpixx; // sprixel-padded pixel geometry
|
||||
// we might only occupy a portion of the final column and row of cells.
|
||||
unsigned celly, cellx; // cell geometry
|
||||
unsigned colorregs_avail; // color registers available
|
||||
unsigned colorregs_used; // color registers used
|
||||
} sixel;
|
||||
|
||||
typedef struct sixelctx {
|
||||
struct notcurses* nc;
|
||||
} sixelctx;
|
||||
|
||||
sixelctx* libncsixel_init(void){
|
||||
sixelctx* sctx = malloc(sizeof(*sctx));
|
||||
if(sctx == NULL){
|
||||
return NULL;
|
||||
}
|
||||
struct notcurses_options nopts = {
|
||||
.flags = NCOPTION_NO_ALTERNATE_SCREEN |
|
||||
NCOPTION_PRESERVE_CURSOR |
|
||||
NCOPTION_SUPPRESS_BANNERS |
|
||||
NCOPTION_DRAIN_INPUT,
|
||||
};
|
||||
if((sctx->nc = notcurses_init(&nopts, NULL)) == NULL){
|
||||
free(sctx);
|
||||
return NULL;
|
||||
}
|
||||
return sctx;
|
||||
}
|
||||
|
||||
sixel* libncsixel_encode(sixelctx* sctx, const char* file, unsigned colorregs){
|
||||
(void)sctx;
|
||||
(void)file;
|
||||
(void)colorregs;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t* libncsixel_explode(const sixel* s){
|
||||
uint32_t* rgba = malloc(sizeof(*rgba) * s->pixy * s->pixx);
|
||||
if(rgba == NULL){
|
||||
return NULL;
|
||||
}
|
||||
uint32_t* colors = malloc(sizeof(*colors) * s->colorregs_used);
|
||||
if(colors == NULL){
|
||||
free(rgba);
|
||||
return NULL;
|
||||
}
|
||||
const char* sx = s->escape;
|
||||
// first we skip the header
|
||||
while(*sx != '#'){
|
||||
++sx;
|
||||
}
|
||||
// now we build the color table (form: #Pc;Pu;Px;Py;Pz). data starts with
|
||||
// a color spec lacking a semicolon.
|
||||
enum {
|
||||
STATE_WANT_HASH,
|
||||
STATE_WANT_COLOR,
|
||||
STATE_WANT_COLORSEMI,
|
||||
STATE_WANT_COLORSPACE,
|
||||
STATE_WANT_DATA,
|
||||
} state = STATE_WANT_HASH;
|
||||
unsigned color = 0;
|
||||
unsigned x = 0;
|
||||
unsigned y = 0;
|
||||
unsigned rle = 1;
|
||||
while(*sx){
|
||||
if(*sx == '\e'){
|
||||
break;
|
||||
}
|
||||
if(state == STATE_WANT_HASH){
|
||||
if('#' != *sx){
|
||||
goto err;
|
||||
}
|
||||
state = STATE_WANT_COLOR;
|
||||
}else if(state == STATE_WANT_COLOR){
|
||||
if(!isdigit(*sx)){
|
||||
goto err;
|
||||
}
|
||||
color = 0;
|
||||
do{
|
||||
color *= 10;
|
||||
color += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
//std::cerr << "Got color " << color << std::endl;
|
||||
--sx;
|
||||
state = STATE_WANT_COLORSEMI;
|
||||
}else if(state == STATE_WANT_COLORSEMI){
|
||||
// if we get a semicolon, we're a colorspec, otherwise data
|
||||
if(*sx == ';'){
|
||||
state = STATE_WANT_COLORSPACE;
|
||||
}else{
|
||||
state = STATE_WANT_DATA;
|
||||
rle = 1;
|
||||
}
|
||||
}else if(state == STATE_WANT_COLORSPACE){
|
||||
if('2' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int r = 0;
|
||||
do{
|
||||
r *= 10;
|
||||
r += *sx - '0';
|
||||
++s;
|
||||
}while(isdigit(*sx));
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int g = 0;
|
||||
do{
|
||||
g *= 10;
|
||||
g += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
if(';' != *(sx++)){
|
||||
goto err;
|
||||
}
|
||||
int b = 0;
|
||||
do{
|
||||
b *= 10;
|
||||
b += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
uint32_t rgb = htole(0xff000000 + (r << 16u) * 255 / 100 + (g << 8u) * 255 / 100 + b * 255 / 100);
|
||||
//std::cerr << "Got color " << color << ": " << r << "/" << g << "/" << b << std::endl;
|
||||
if(color >= s->colorregs_used){
|
||||
goto err;
|
||||
}
|
||||
colors[color] = rgb;
|
||||
state = STATE_WANT_HASH;
|
||||
--sx;
|
||||
}
|
||||
// read until we hit next colorspec
|
||||
if(state == STATE_WANT_DATA){
|
||||
//std::cerr << "Character " << *sx << std::endl;
|
||||
if(*sx == '#'){
|
||||
state = STATE_WANT_HASH;
|
||||
--sx;
|
||||
}else if(*sx == '!'){ // RLE
|
||||
++sx;
|
||||
rle = 0;
|
||||
do{
|
||||
rle *= 10;
|
||||
rle += *sx - '0';
|
||||
++sx;
|
||||
}while(isdigit(*sx));
|
||||
if(2 >= rle){
|
||||
goto err;
|
||||
}
|
||||
--sx;
|
||||
}else if(*sx == '$'){
|
||||
x = 0;
|
||||
state = STATE_WANT_HASH;
|
||||
}else if(*sx == '-'){
|
||||
x = 0;
|
||||
y += 6;
|
||||
state = STATE_WANT_HASH;
|
||||
}else{
|
||||
//std::cerr << "RLE: " << rle << " pos: " << y << "*" << x << std::endl;
|
||||
for(unsigned xpos = x ; xpos < x + rle ; ++xpos){
|
||||
for(unsigned ypos = y ; ypos < y + 6 ; ++ypos){
|
||||
if((*sx - 63) & (1u << (ypos - y))){
|
||||
// ought be an empty pixel
|
||||
//std::cerr << *s << " BMAP[" << ypos << "][" << xpos << "] = " << std::hex << colors[color] << std::dec << std::endl;
|
||||
rgba[ypos * s->pixx + xpos] = colors[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
x += rle;
|
||||
rle = 1;
|
||||
}
|
||||
}
|
||||
++sx;
|
||||
}
|
||||
free(colors);
|
||||
return rgba;
|
||||
|
||||
err:
|
||||
free(rgba);
|
||||
free(colors);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void libncsixel_stop(sixelctx* sctx){
|
||||
if(sctx){
|
||||
notcurses_stop(sctx->nc);
|
||||
free(sctx);
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <notcurses/libsixel.h>
|
||||
|
||||
int main(void){
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -3,131 +3,6 @@
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
// convert the sprixel at s having pixel dimensions dimyXdimx to an rgb(a)
|
||||
// matrix for easier analysis. breaks on malformed sixels.
|
||||
std::vector<uint32_t> sixel_to_rgb(const char* s, size_t len, int dimy, int dimx) {
|
||||
std::vector<uint32_t> bmap(dimy * dimx, 0x00000000ull);
|
||||
std::vector<uint32_t> colors;
|
||||
// first we skip the header
|
||||
while(*s != '#'){
|
||||
++s;
|
||||
}
|
||||
// now we build the color table (form: #Pc;Pu;Px;Py;Pz). data starts with
|
||||
// a color spec lacking a semicolon.
|
||||
enum {
|
||||
STATE_WANT_HASH,
|
||||
STATE_WANT_COLOR,
|
||||
STATE_WANT_COLORSEMI,
|
||||
STATE_WANT_COLORSPACE,
|
||||
STATE_WANT_DATA,
|
||||
} state = STATE_WANT_HASH;
|
||||
unsigned color = 0;
|
||||
unsigned x = 0;
|
||||
unsigned y = 0;
|
||||
unsigned rle = 1;
|
||||
const char* begin = s;
|
||||
while((size_t)(s - begin) < len){
|
||||
if(*s == '\e'){
|
||||
break;
|
||||
}
|
||||
if(state == STATE_WANT_HASH){
|
||||
REQUIRE('#' == *s);
|
||||
state = STATE_WANT_COLOR;
|
||||
}else if(state == STATE_WANT_COLOR){
|
||||
CHECK(isdigit(*s));
|
||||
color = 0;
|
||||
do{
|
||||
color *= 10;
|
||||
color += *s - '0';
|
||||
++s;
|
||||
}while(isdigit(*s));
|
||||
//std::cerr << "Got color " << color << std::endl;
|
||||
--s;
|
||||
state = STATE_WANT_COLORSEMI;
|
||||
}else if(state == STATE_WANT_COLORSEMI){
|
||||
// if we get a semicolon, we're a colorspec, otherwise data
|
||||
if(*s == ';'){
|
||||
state = STATE_WANT_COLORSPACE;
|
||||
}else{
|
||||
state = STATE_WANT_DATA;
|
||||
rle = 1;
|
||||
}
|
||||
}else if(state == STATE_WANT_COLORSPACE){
|
||||
CHECK('2' == *(s++));
|
||||
CHECK(';' == *(s++));
|
||||
int r = 0;
|
||||
do{
|
||||
r *= 10;
|
||||
r += *s - '0';
|
||||
++s;
|
||||
}while(isdigit(*s));
|
||||
CHECK(';' == *(s++));
|
||||
int g = 0;
|
||||
do{
|
||||
g *= 10;
|
||||
g += *s - '0';
|
||||
++s;
|
||||
}while(isdigit(*s));
|
||||
CHECK(';' == *(s++));
|
||||
int b = 0;
|
||||
do{
|
||||
b *= 10;
|
||||
b += *s - '0';
|
||||
++s;
|
||||
}while(isdigit(*s));
|
||||
uint32_t rgb = htole(0xff000000 + (r << 16u) * 255 / 100 + (g << 8u) * 255 / 100 + b * 255 / 100);
|
||||
//std::cerr << "Got color " << color << ": " << r << "/" << g << "/" << b << std::endl;
|
||||
if(color >= colors.capacity()){
|
||||
colors.resize(color + 1);
|
||||
}
|
||||
colors[color] = rgb;
|
||||
state = STATE_WANT_HASH;
|
||||
--s;
|
||||
}
|
||||
// read until we hit next colorspec
|
||||
if(state == STATE_WANT_DATA){
|
||||
//std::cerr << "Character " << *s << std::endl;
|
||||
if(*s == '#'){
|
||||
state = STATE_WANT_HASH;
|
||||
--s;
|
||||
}else if(*s == '!'){ // RLE
|
||||
++s;
|
||||
rle = 0;
|
||||
do{
|
||||
rle *= 10;
|
||||
rle += *s - '0';
|
||||
++s;
|
||||
}while(isdigit(*s));
|
||||
CHECK(2 < rle);
|
||||
--s;
|
||||
}else if(*s == '$'){
|
||||
x = 0;
|
||||
state = STATE_WANT_HASH;
|
||||
}else if(*s == '-'){
|
||||
x = 0;
|
||||
y += 6;
|
||||
state = STATE_WANT_HASH;
|
||||
}else{
|
||||
//std::cerr << "RLE: " << rle << " pos: " << y << "*" << x << std::endl;
|
||||
for(unsigned xpos = x ; xpos < x + rle ; ++xpos){
|
||||
for(unsigned ypos = y ; ypos < y + 6 ; ++ypos){
|
||||
if((*s - 63) & (1u << (ypos - y))){
|
||||
// ought be an empty pixel
|
||||
CHECK(0x00000000ull == bmap[ypos * dimx + xpos]);
|
||||
//std::cerr << *s << " BMAP[" << ypos << "][" << xpos << "] = " << std::hex << colors[color] << std::dec << std::endl;
|
||||
bmap[ypos * dimx + xpos] = colors[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
x += rle;
|
||||
rle = 1;
|
||||
}
|
||||
}
|
||||
++s;
|
||||
}
|
||||
return bmap;
|
||||
}
|
||||
|
||||
/*
|
||||
void print_bmap(const std::vector<uint32_t> rgba, int pixy, int pixx){
|
||||
for(int y = 0 ; y < pixy ; ++y){
|
||||
@ -169,8 +44,8 @@ TEST_CASE("Sixels") {
|
||||
auto newn = ncvisual_blit(nc_, ncv, &vopts);
|
||||
REQUIRE(nullptr != newn);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
auto rgb = sixel_to_rgb(newn->sprite->glyph.buf, newn->sprite->glyph.used,
|
||||
newn->sprite->pixy, newn->sprite->pixx);
|
||||
auto rgb = ncsixel_as_rgba(newn->sprite->glyph.buf, newn->sprite->pixy, newn->sprite->pixx);
|
||||
REQUIRE(rgb);
|
||||
for(int y = 0 ; y < newn->sprite->pixy ; ++y){
|
||||
for(int x = 0 ; x < newn->sprite->pixx ; ++x){
|
||||
//fprintf(stderr, "%03d/%03d NCV: %08x RGB: %08x\n", y, x, ncv->data[y * newn->sprite->pixx + x], rgb[y * newn->sprite->pixx + x]);
|
||||
@ -191,8 +66,8 @@ TEST_CASE("Sixels") {
|
||||
vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
|
||||
auto newn = ncvisual_blit(nc_, ncv, &vopts);
|
||||
REQUIRE(nullptr != newn);
|
||||
auto rgbold = sixel_to_rgb(newn->sprite->glyph.buf, newn->sprite->glyph.used,
|
||||
newn->sprite->pixy, newn->sprite->pixx);
|
||||
auto rgbold = ncsixel_as_rgba(newn->sprite->glyph.buf, newn->sprite->pixy, newn->sprite->pixx);
|
||||
REQUIRE(rgbold);
|
||||
//print_bmap(rgbold, newn->sprite->pixy, newn->sprite->pixx);
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
struct ncplane_options nopts = {
|
||||
@ -214,8 +89,8 @@ TEST_CASE("Sixels") {
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
// FIXME at this point currently, we get a degraded back of the orca
|
||||
// test via conversion back to image? unsure
|
||||
auto rgbnew = sixel_to_rgb(newn->sprite->glyph.buf, newn->sprite->glyph.used,
|
||||
newn->sprite->pixy, newn->sprite->pixx);
|
||||
auto rgbnew = ncsixel_as_rgba(newn->sprite->glyph.buf, newn->sprite->pixy, newn->sprite->pixx);
|
||||
REQUIRE(rgbnew);
|
||||
//print_bmap(rgbnew, newn->sprite->pixy, newn->sprite->pixx);
|
||||
CHECK(0 == ncplane_destroy(newn));
|
||||
CHECK(0 == ncplane_destroy(blockerplane));
|
||||
|
Loading…
x
Reference in New Issue
Block a user