diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dad24fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +stiv +stiv-jpeg +tiv +build/ \ No newline at end of file diff --git a/Build.Dockerfile b/Build.Dockerfile new file mode 100644 index 0000000..4f007e2 --- /dev/null +++ b/Build.Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 + +# Install dependencies +RUN apt update +RUN apt install -y libjpeg-dev cmake make clang + +VOLUME /app + +WORKDIR /app + +RUN echo "#!/bin/sh" > /var/lib/build.sh +RUN echo "mkdir -p /app/build" >> /var/lib/build.sh +RUN echo "cd /app/build" >> /var/lib/build.sh +RUN echo "cmake .." >> /var/lib/build.sh +RUN echo "make" >> /var/lib/build.sh + +CMD ["/bin/sh", "/var/lib/build.sh"] \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b6ca9c5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.0) +project(tiv C) + +add_subdirectory(libstiv) + +file(GLOB_RECURSE SOURCES app/*.c) +add_executable(stiv-jpeg ${SOURCES}) +target_include_directories(stiv-jpeg PRIVATE libstiv/src) +target_link_libraries(stiv-jpeg stiv_static jpeg) +target_compile_options(stiv-jpeg PRIVATE -Wall -Wextra -Werror -pedantic -std=c99 -O3 -g) +add_dependencies(stiv-jpeg stiv_static) diff --git a/Makefile b/Makefile index 2214d50..1d0d895 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VALAC=valac DESTDIR?=/ PREFIX?=usr #CFLAGS += -I/opt/local/include -I/usr/local/include -CFLAGS += $(shell pkg-config --cflags libjpeg) +CFLAGS += $(shell pkg-config --cflags libjpeg) -Wall -Wextra -pedantic -std=c99 -O3 -ffast-math #JPEGLIBS += -L/opt/local/lib -L/usr/local/lib -L/usr/local/Cellar -ljpeg JPEGLIBS += $(shell pkg-config --libs libjpeg) diff --git a/README.md b/README.md index 4904888..10f9bd7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,19 @@ $ tiv -d foo.img > .bitmap 2> .size $ stiv `cat .size` < .bitmap ``` +How to build? +--------------- + +First create the Dockerized build environment: +``` +docker build -t libstiv:latest -f Build.Dockerfile . +``` + +Then build the project with: +``` +docker run --rm -v $(pwd):/app -it libstiv:latest +``` + Author ------ diff --git a/TODO.md b/TODO.md index 222f05e..7125278 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ TODO ==== -* Refactor as a library * Canvas API to draw and write text * Interactive mode (zoom, pan, brightness, ..) * Enhace ansi mode dithering (blue is not always detected) diff --git a/app/stiv-jpeg.c b/app/stiv-jpeg.c new file mode 100644 index 0000000..7a03f94 --- /dev/null +++ b/app/stiv-jpeg.c @@ -0,0 +1,47 @@ +/* tiv - terminal image viewer - copyleft 2013-2015 - pancake */ + +#include +#include +#include + +int main(int argc, const char** argv) { + stivctx_t* ctx; + uint32_t width = 80; + stiv_mode mode = STIV_MODE_RGB; + + if (argc < 2) { + printf( + "stiv-jpeg . suckless terminal image viewer\n" + "Usage: stiv [image] [width] [mode]\n" + "Modes: [ascii,ansi,grey,256,rgb]\n"); + return 1; + } + + if (argc > 2) { + width = atoi(argv[2]); + } + + if (argc > 3) { + if (!strcmp(argv[3], "ascii")) + mode = STIV_MODE_ASCII; + else if (!strcmp(argv[3], "ansi")) + mode = STIV_MODE_ANSI; + else if (!strcmp(argv[3], "grey")) + mode = STIV_MODE_GREY; + else if (!strcmp(argv[3], "256")) + mode = STIV_MODE_256; + else if (!strcmp(argv[3], "rgb")) + mode = STIV_MODE_RGB; + } + + if (!(ctx = stiv_from_jpeg(argv[1], width, 0, mode))) { + printf("Failed to load image\n"); + return 1; + } + + stiv_display(ctx); + + stiv_free(ctx); + + return 0; +} \ No newline at end of file diff --git a/compile b/compile deleted file mode 100644 index ac08cdc..0000000 --- a/compile +++ /dev/null @@ -1 +0,0 @@ -make \ No newline at end of file diff --git a/libstiv/CMakeLists.txt b/libstiv/CMakeLists.txt new file mode 100644 index 0000000..6219ec8 --- /dev/null +++ b/libstiv/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.0) +project(libstiv C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +file(GLOB_RECURSE SOURCES src/*.c) + +add_library(stiv SHARED ${SOURCES}) +target_include_directories(stiv PRIVATE src) +target_compile_options(stiv PRIVATE -Wall -Wextra -Werror -pedantic -O3 -ffast-math -fPIC) +target_link_libraries(stiv m) + +add_library(stiv_static STATIC ${SOURCES}) +target_include_directories(stiv_static PRIVATE src) +target_compile_options(stiv_static PRIVATE -Wall -Wextra -Werror -pedantic -O3 -ffast-math -fPIC) +target_link_libraries(stiv_static m) diff --git a/libstiv/src/stiv.c b/libstiv/src/stiv.c new file mode 100644 index 0000000..fa75e4d --- /dev/null +++ b/libstiv/src/stiv.c @@ -0,0 +1,263 @@ +/* tiv - terminal image viewer - copyleft 2013-2015 - pancake */ + +#include "stiv.h" + +#include +#include +#include +#include +#include +#include + +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#define POND(x, y) (ABS((x)) * (y)) + +typedef void (*stiv_renderer_t)(const uint8_t*, const uint8_t*); + +typedef struct stivctx_t { + uint8_t* buffer; + stiv_renderer_t renderer; + size_t buffer_size; + uint32_t width; + uint32_t height; +} stivctx_t; + +static inline int reduce8(uint8_t r, uint8_t g, uint8_t b) { + int select = 0, odistance = -1, i = 0, distance = 0; + + const int colors[][3] = { + {0x00, 0x00, 0x00}, // black + {0xd0, 0x10, 0x10}, // red + {0x10, 0xe0, 0x10}, // green + {0xf7, 0xf5, 0x3a}, // yellow + {0x10, 0x10, 0xf0}, // blue // XXX + {0xfb, 0x3d, 0xf8}, // pink + {0x10, 0xf0, 0xf0}, // turqoise + {0xf0, 0xf0, 0xf0}, // white + }; + const int colors_len = sizeof(colors) / sizeof(colors[0]); + + // B&W + if (r < 30 && g < 30 && b < 30) return 0; + if (r > 200 && g > 200 && b > 200) return 7; + + for (i = 0; i < colors_len; i++) { + distance = POND(colors[i][0] - r, r) + POND(colors[i][1] - g, g) + + POND(colors[i][2] - b, b); + if (odistance == -1 || distance < odistance) { + odistance = distance; + select = i; + } + } + return select; +} + +void render_ansi(const uint8_t* c, const uint8_t* d) { + int fg = 0, color; + + (void)d; + + if ((color = reduce8(c[0], c[1], c[2])) == -1) return; + + printf("\x1b[%dm", color + (fg ? 30 : 40)); +} + +static inline int rgb(uint8_t r, uint8_t g, uint8_t b) { + r = (r / 50.6); + g = (g / 50.6); + b = (b / 50.6); + return 16 + (r * 36) + (g * 6) + b; +} + +void render_256(const uint8_t* c, const uint8_t* d) { + printf("\x1b[%d;5;%dm", 38, rgb(c[0], c[1], c[2])); + printf("\x1b[%d;5;%dm", 48, rgb(d[0], d[1], d[2])); +} + +void render_rgb(const uint8_t* c, const uint8_t* d) { + printf("\x1b[38;2;%d;%d;%dm", c[0], c[1], c[2]); + printf("\x1b[48;2;%d;%d;%dm", d[0], d[1], d[2]); +} + +void render_greyscale(const uint8_t* c, const uint8_t* d) { + int color1, color2, k; + + color1 = (c[0] + c[1] + c[2]) / 3; + color2 = (d[0] + d[1] + d[2]) / 3; + k = 231 + ((int)((float)color1 / 10.3)); + + if (k < 232) k = 232; + printf("\x1b[%d;5;%dm", 48, k); // bg + + k = 231 + ((int)((float)color2 / 10.3)); + + if (k < 232) k = 232; + printf("\x1b[%d;5;%dm", 38, k); // fg +} + +void render_ascii(const uint8_t* c, const uint8_t* d) { + const char pal[12] = " `.,-:+*%$##"; + int idx; + float p, q; + + p = (c[0] + c[1] + c[2]) / 3; + q = (d[0] + d[1] + d[2]) / 3; + idx = ((p + q) / 2) / (255 / (sizeof(pal) - 1)); + + printf("%c", pal[idx]); +} + +stivctx_t* stiv_create(const uint8_t* buffer, size_t buffer_size, + uint32_t width, uint32_t height, stiv_mode mode) { + stivctx_t* ctx; + + if (!(ctx = malloc(sizeof(*ctx)))) return NULL; + + switch (mode) { + case STIV_MODE_ASCII: + ctx->renderer = render_ascii; + break; + case STIV_MODE_ANSI: + ctx->renderer = render_ansi; + break; + case STIV_MODE_GREY: + ctx->renderer = render_greyscale; + break; + case STIV_MODE_256: + ctx->renderer = render_256; + break; + default: + ctx->renderer = render_rgb; + break; + } + + if (!(ctx->buffer = (uint8_t*)malloc(buffer_size))) { + free(ctx); + return NULL; + } + + memcpy(ctx->buffer, buffer, buffer_size); + ctx->buffer_size = buffer_size; + ctx->width = width; + ctx->height = height; + + return ctx; +} + +stivctx_t* stiv_from_jpeg(const char* jpeg_filepath, uint32_t width, + uint32_t height, stiv_mode mode) { + stivctx_t* ctx; + uint8_t *p1, *buf; + uint8_t** p2 = &p1; + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + int counter, stride; + FILE* fd; + + (void)height; + + // Open the JPEG file + if (!(fd = fopen(jpeg_filepath, "rb"))) { + fprintf(stderr, "Cannot open '%s'\n", jpeg_filepath); + return NULL; + } + + // Initialize the JPEG decompression object with default error handling. + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + + // jpeg_set_colorspace (&cinfo, JCS_RGB); + jpeg_stdio_src(&cinfo, fd); + jpeg_read_header(&cinfo, TRUE); + + // scale image works fine here + cinfo.scale_num = width; + cinfo.scale_denom = cinfo.image_width; + width = (width * cinfo.scale_num) / cinfo.scale_denom; + + jpeg_start_decompress(&cinfo); + + // Check if the image is RGB24 + stride = cinfo.output_width * cinfo.output_components; + if (cinfo.output_components != 3) { + printf("Not in rgb24\n"); + return NULL; + } + + // Allocate memory and decompress the image + counter = 0; + stride = cinfo.output_width * 3; + p1 = malloc(stride); + buf = malloc(stride * cinfo.output_height); + p2 = &p1; + while (cinfo.output_scanline < cinfo.output_height) { + *p2 = p1; + jpeg_read_scanlines(&cinfo, p2, 1); +#if STANDALONE + write(1, p1, cinfo.output_width * 3); +#else + memcpy(buf + counter, p1, cinfo.output_width * 3); +#endif + counter += stride; + } + + if (!(ctx = malloc(sizeof(*ctx)))) return NULL; + + switch (mode) { + case STIV_MODE_ASCII: + ctx->renderer = render_ascii; + break; + case STIV_MODE_ANSI: + ctx->renderer = render_ansi; + break; + case STIV_MODE_GREY: + ctx->renderer = render_greyscale; + break; + case STIV_MODE_256: + ctx->renderer = render_256; + break; + default: + ctx->renderer = render_rgb; + break; + } + + ctx->buffer = buf; + ctx->buffer_size = stride * cinfo.output_height; + ctx->width = cinfo.output_width; + ctx->height = cinfo.output_height; + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + free(p1); + fclose(fd); + + return ctx; +} + +void stiv_free(stivctx_t* ctx) { + if (!ctx) return; + + free(ctx->buffer); + memset(ctx, 0, sizeof(stivctx_t)); + free(ctx); +} + +void stiv_display(stivctx_t* ctx) { + if (!ctx) return; + + uint8_t *c, *d; + uint32_t x, y; + + for (y = 0; y < ctx->height; y += 2) { + for (x = 0; x < ctx->width; x++) { + c = (ctx->buffer + ((y) * (ctx->width * 3)) + (x * 3)); + d = (ctx->buffer + ((y + 1) * (ctx->width * 3)) + (x * 3)); + if (d > (ctx->buffer + ctx->buffer_size)) break; + ctx->renderer(c, d); + if (ctx->renderer != render_ascii) render_ascii(c, d); + } + printf((ctx->renderer == render_ascii) ? "\n" : "\x1b[0m\n"); + } +} diff --git a/libstiv/src/stiv.h b/libstiv/src/stiv.h new file mode 100644 index 0000000..283177c --- /dev/null +++ b/libstiv/src/stiv.h @@ -0,0 +1,64 @@ +/* tiv - terminal image viewer - copyleft 2013-2015 - pancake */ + +#ifndef __STIV_H__ +#define __STIV_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/// @brief The stiv context. +typedef struct stivctx_t stivctx_t; + +typedef enum { + STIV_MODE_ASCII, + STIV_MODE_ANSI, + STIV_MODE_GREY, + STIV_MODE_256, + STIV_MODE_RGB +} stiv_mode; + +/// @brief Create a stiv context from a raw RGB24 buffer +/// @param buffer The raw RGB24 buffer +/// @param buffer_size The size of the buffer in bytes +/// @param width The width of the image in pixels +/// @param height The height of the image in pixels +/// @param mode The mode to use for rendering +/// @return The stiv context +/// @note The buffer will be copied and managed internally. +/// @note The image will not be displayed until `stiv_display` +/// is called with the context. +/// @warning This function will return NULL on failure. +stivctx_t* stiv_create(const uint8_t* buffer, size_t buffer_size, + uint32_t width, uint32_t height, stiv_mode mode); + +/// @brief Create a stiv context from a JPEG file +/// @param jpeg_filepath The path to the JPEG file +/// @param width The width of the image in pixels +/// @param height The height of the image in pixels +/// @param mode The mode to use for rendering +/// @return The stiv context +/// @note The image will not be displayed until `stiv_display` +/// is called with the context. +/// @warning This function will return NULL on failure. +stivctx_t* stiv_from_jpeg(const char* jpeg_filepath, uint32_t width, + uint32_t height, stiv_mode mode); + +/// @brief Release the resources associated within a stiv context +/// @param ctx The stiv context +/// @note If `!ctx`, this is a no-op. +void stiv_free(stivctx_t* ctx); + +/// @brief Display the image associated with a stiv context +/// @param ctx The stiv context +/// @note If `!ctx`, this is a no-op. +void stiv_display(stivctx_t* ctx); + +#ifdef __cplusplus +} +#endif + +#endif // __STIV_H__ \ No newline at end of file diff --git a/stiv-jpeg.c b/stiv-jpeg.c deleted file mode 100644 index 757e36b..0000000 --- a/stiv-jpeg.c +++ /dev/null @@ -1,79 +0,0 @@ -/* tiv - terminal image viewer - copyleft 2013-2015 - pancake */ - -#include -#include -#include - -#define MAIN -#include "stiv.c" - -int -main(int argc, const char **argv) { - unsigned char *p1, *buf; - unsigned char **p2 = &p1; - struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr jerr; - int counter, width, stride; - JSAMPARRAY buffer; - int numlines; - FILE *fd; - if (argc<2) { - printf ("stiv-jpeg . suckless terminal image viewer\n" - "Usage: stiv [image] [width] [mode]\n" - "Modes: [ascii,ansi,grey,256,rgb]\n"); - return 1; - } - selectrenderer (argc>3?argv[3]:""); - fd = fopen (argv[1], "rb"); - if (!fd) { - fprintf (stderr, "Cannot open '%s'\n", argv[1]); - return 1; - } - width = (argc<3)? 78: atoi (argv[2]); - memset (&cinfo, 0, sizeof (cinfo)); - cinfo.err = jpeg_std_error (&jerr); - jpeg_create_decompress (&cinfo); - - //jpeg_set_colorspace (&cinfo, JCS_RGB); - jpeg_stdio_src (&cinfo, fd); - jpeg_read_header (&cinfo, TRUE); - - // scale image works fine here - cinfo.scale_num = width; - cinfo.scale_denom = cinfo.image_width; - width = ( width * cinfo.scale_num ) / cinfo.scale_denom; - - jpeg_start_decompress (&cinfo); - - stride = cinfo.output_width * cinfo.output_components; - if (cinfo.output_components != 3) { - printf ("Not in rgb24\n"); - return 1; - } - //fprintf (stderr, "%d %d\n", cinfo.output_width, cinfo.output_height); - - counter = 0; - stride = cinfo.output_width * 3; - p1 = malloc (stride); - buf = malloc (stride * cinfo.output_height); - p2 = &p1; - while (cinfo.output_scanline < cinfo.output_height) { - *p2 = p1; - numlines = jpeg_read_scanlines (&cinfo, p2, 1); -#if STANDALONE - write (1, p1, cinfo.output_width*3); -#else - memcpy (buf+counter, p1, cinfo.output_width* 3); -#endif - counter += stride; - } - dorender (buf, stride*cinfo.output_height, - cinfo.output_width, cinfo.output_height); - jpeg_finish_decompress (&cinfo); - jpeg_destroy_decompress (&cinfo); - - free (buf); - free (p1); - fclose (fd); - return 0; -} diff --git a/stiv.c b/stiv.c deleted file mode 100644 index c5e0558..0000000 --- a/stiv.c +++ /dev/null @@ -1,168 +0,0 @@ -/* tiv - terminal image viewer - copyleft 2013-2015 - pancake */ - -#include -#include -#include -#include - -#define XY(b,x,y) ( b+((y)*(w*3))+(x*3) ) -#define ABS(x) (((x)<0)?-(x):(x)) -#define POND(x,y) (ABS((x)) * (y)) - -void (*renderer)(const unsigned char*, const unsigned char *); - -static int reduce8 (int r, int g, int b) { - int colors_len = 8; - int select = 0; - int odistance = -1; - int i, k = 1; - int colors[][3] = { - { 0x00,0x00,0x00 }, // black - { 0xd0,0x10,0x10 }, // red - { 0x10,0xe0,0x10 }, // green - { 0xf7,0xf5,0x3a }, // yellow - { 0x10,0x10,0xf0 }, // blue // XXX - { 0xfb,0x3d,0xf8 }, // pink - { 0x10,0xf0,0xf0 }, // turqoise - { 0xf0,0xf0,0xf0 }, // white - }; - - r /= k; r *= k; - g /= k; g *= k; - b /= k; b *= k; - // B&W - if (r<30 && g<30 && b<30) return 0; - if (r>200&& g>200&& b>200) return 7; - odistance = -1; - for (i = 0; i255) r = 255; - if (g<0) g=0; if (g>255) g = 255; - if (b<0) b=0; if (b>255) b = 255; - r = (int)(r/50.6); - g = (int)(g/50.6); - b = (int)(b/50.6); - return 16 + (r*36) + (g*6) + b; -} - -void -render_256(const unsigned char *c, const unsigned char *d) { - printf ("\x1b[%d;5;%dm", 38, rgb (c[0], c[1], c[2])); - printf ("\x1b[%d;5;%dm", 48, rgb (d[0], d[1], d[2])); -} - -void -render_rgb(const unsigned char *c, const unsigned char *d) { - printf ("\x1b[38;2;%d;%d;%dm", c[0], c[1], c[2]); - printf ("\x1b[48;2;%d;%d;%dm", d[0], d[1], d[2]); -} - -void -render_greyscale(const unsigned char *c, const unsigned char *d) { - int color, color1, color2, k; - color1 = (c[0]+c[1]+c[2]) / 3; - color2 = (d[0]+d[1]+d[2]) / 3; - k = 231 + ((int)((float)color1/10.3)); - if (k<232) k = 232; - printf ("\x1b[%d;5;%dm", 48, k); // bg - k = 231 + ((int)((float)color2/10.3)); - if (k<232) k = 232; - printf ("\x1b[%d;5;%dm", 38, k); // fg -} - -void -render_ascii(const unsigned char *c, const unsigned char *d) { - const char *pal = " `.,-:+*%$#"; - int idx, pal_len = strlen (pal); - float p = (c[0]+c[1]+c[2])/3; - float q = (d[0]+d[1]+d[2])/3; - idx = ((p+q)/2) / (255/pal_len); - if (idx >= pal_len) idx = pal_len-1; - printf ("%c", pal[idx]); -} - -static void dorender (unsigned char *buf, int len, int w, int h) { - unsigned char *c, *d; - int x, y; - for (y=0; y (buf+len)) break; - renderer (c, d); - if (renderer != render_ascii) - render_ascii (c, d); - } - printf ((renderer==render_ascii)?"\n":"\x1b[0m\n"); - } -} - -static void selectrenderer(const char *arg) { - switch (arg[0]) { - case 'a': - renderer = (arg[1] == 'n')? - render_ansi: render_ascii; - break; - case 'g': renderer = render_greyscale; break; - case '2': renderer = render_256; break; - default: - renderer = render_rgb; - break; - } -} - -#ifndef MAIN -int -main(int argc, const char **argv) { - unsigned char *buf, *c, *d; - int n, x, y, w, h, imgsz, readsz; - if (argc<3) { - printf ("stiv . suckless terminal image viewer\n"); - printf ("Usage: stiv [width] [height] [ascii|ansi|grey|256|rgb] < rgb24\n"); - return 1; - } - w = atoi (argv[1]); - h = atoi (argv[2]); - if (argc>3) { - selectrenderer (argv[3]); - } else renderer = render_rgb; - if (w<1 || h<1) { - printf ("Invalid arguments\n"); - return 1; - } - imgsz = w * h * 3; - buf = malloc (imgsz); - readsz = 0; - do { - n = read(0, buf+readsz, imgsz); - if (n<1) break; - readsz += n; - } while (readsz < imgsz); - - dorender (buf, readsz, w, h); - - free (buf); - return 0; -} -#endif