diff --git a/README.asciidoc b/README.asciidoc index 1d6d96d3c..a0a439439 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -81,6 +81,8 @@ the following dependencies are installed first. * python >= 3.5 * glfw >= 3.2 * libunistring +* zlib +* libpng * glew >= 2.0 (not needed on macOS) * fontconfig (not needed on macOS) * xrdb and xsel (only on X11 based systems) diff --git a/kitty/graphics.c b/kitty/graphics.c index 3c73531a6..c60ddd1cf 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #define REPORT_ERROR(fmt, ...) { fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n"); } @@ -58,9 +60,7 @@ free_load_data(LoadData *ld) { static inline void free_image(Image *img) { - img->data_loaded = false; // TODO: free the texture if texture_id is not zero - img->texture_id = 0; free_load_data(&(img->load_data)); } @@ -127,6 +127,117 @@ img_by_internal_id(GraphicsManager *self, size_t id) { return NULL; } +static inline bool +inflate_zlib(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) { + bool ok = false; + z_stream z; + uint8_t *decompressed = malloc(img->load_data.data_sz); + if (decompressed == NULL) fatal("Out of memory allocating decompression buffer"); + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + z.avail_in = bufsz; + z.next_in = (Bytef*)buf; + z.avail_out = img->load_data.data_sz; + z.next_out = decompressed; + int ret; + if ((ret = inflateInit(&z)) != Z_OK) { + REPORT_ERROR("Failed to initialize inflate with error code: %d", ret); + goto err; + } + if ((ret = inflate(&z, Z_FINISH)) != Z_STREAM_END) { + REPORT_ERROR("Failed to inflate image data with error code: %d", ret); + goto err; + } + if (z.avail_out) { + REPORT_ERROR("Failed to inflate image data with error code: %d", ret); + goto err; + } + free_load_data(&img->load_data); + img->load_data.buf_capacity = img->load_data.data_sz; + img->load_data.buf = decompressed; + img->load_data.buf_used = img->load_data.data_sz - z.avail_out; + ok = true; +err: + inflateEnd(&z); + if (!ok) free(decompressed); + return ok; +} + +struct fake_file { uint8_t *buf; size_t sz, cur; }; + +static void +read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) { + struct fake_file *f = png_get_io_ptr(png); + if (f) { + size_t amt = MIN(length, f->sz - f->cur); + memcpy(out, f->buf + f->cur, amt); + f->cur += amt; + } +} + +static inline bool +inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) { +#define RE(...) { REPORT_ERROR(__VA_ARGS__); goto err; } + bool ok = false; + uint8_t *decompressed = NULL; + png_structp png = NULL; + png_infop info = NULL; + png_bytep * row_pointers = NULL; + struct fake_file f = {.buf = buf, .sz = bufsz}; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) RE("%s", "Failed to create PNG read structure"); + info = png_create_info_struct(png); + if (!info) RE("%s", "Failed to create PNG info structure"); + + if(setjmp(png_jmpbuf(png))) RE("%s", "Invalid PNG data"); + + png_set_read_fn(png, &f, read_png_from_buffer); + png_read_info(png, info); + int width, height; + png_byte color_type, bit_depth; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + + // Ensure we get RGBA data out of libpng + if(bit_depth == 16) png_set_strip_16(png); + if(color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. + if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + + if(png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + + // These color_type don't have an alpha channel then fill it with 0xff. + if(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + + if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); + png_read_update_info(png, info); + + int rowbytes = png_get_rowbytes(png, info); + size_t sz = rowbytes * height * sizeof(png_byte); + decompressed = malloc(sz + 16); + if (decompressed == NULL) RE("%s", "Out of memory allocating decompression buffer for PNG"); + row_pointers = malloc(height * sizeof(png_bytep)); + if (row_pointers == NULL) RE("%s", "Out of memory allocating row_pointers buffer for PNG"); + for (int i = 0; i < height; i++) row_pointers[height - 1 - i] = decompressed + i * rowbytes; + png_read_image(png, row_pointers); + + free_load_data(&img->load_data); + img->load_data.buf_capacity = sz; + img->load_data.buf = decompressed; + img->load_data.buf_used = sz; + img->width = width; img->height = height; + ok = true; +err: + if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL); + if (!ok) free(decompressed); + free(row_pointers); + return ok; +#undef RE +} static bool add_trim_predicate(Image *img) { @@ -156,11 +267,16 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ switch(g->format) { case PNG: sz *= 4; + img->load_data.is_4byte_aligned = true; break; case RGB: case RGBA: sz *= g->format / 8; + img->load_data.is_4byte_aligned = g->format == RGBA || (img->width % 4 == 0); break; + default: + REPORT_ERROR("Unknown image format: %u", g->format); + return; } img->load_data.data_sz = sz; if (tt == 'd') { @@ -207,7 +323,52 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ REPORT_ERROR("Unknown transmission type: %c", g->transmission_type); return; } - + if (!img->data_loaded) return; + bool needs_processing = g->compressed || g->format == PNG; + if (needs_processing) { + uint8_t *buf; size_t bufsz; +#define IB { if (img->load_data.buf) { buf = img->load_data.buf; bufsz = img->load_data.buf_used; } else { buf = img->load_data.mapped_file; bufsz = img->load_data.mapped_file_sz; } } + switch(g->compressed) { + case 'z': + IB; + if (!inflate_zlib(self, img, buf, bufsz)) { + img->data_loaded = false; return; + } + break; + case 0: + break; + default: + REPORT_ERROR("Unknown image compression: %c", g->compressed); + img->data_loaded = false; return; + } + switch(g->format) { + case PNG: + IB; + if (!inflate_png(self, img, buf, bufsz)) { + img->data_loaded = false; return; + } + break; + default: break; + } +#undef IB + img->load_data.data = img->load_data.buf; + if (img->load_data.buf_used < img->load_data.data_sz) { + REPORT_ERROR("Insufficient image data: %zu < %zu", img->load_data.buf_used, img->load_data.data_sz); + img->data_loaded = false; + } + } else { + if (tt == 'd') { + if (img->load_data.buf_used < img->load_data.data_sz) { + REPORT_ERROR("Insufficient image data: %zu < %zu", img->load_data.buf_used, img->load_data.data_sz); + img->data_loaded = false; + } else img->load_data.data = img->load_data.buf; + } else { + if (img->load_data.mapped_file_sz < img->load_data.data_sz) { + REPORT_ERROR("Insufficient image data: %zu < %zu", img->load_data.mapped_file_sz, img->load_data.data_sz); + img->data_loaded = false; + } else img->load_data.data = img->load_data.mapped_file; + } + } } void diff --git a/kitty/graphics.h b/kitty/graphics.h index 64446c1be..6062ad00f 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -24,6 +24,8 @@ typedef struct { size_t mapped_file_sz; size_t data_sz; + uint8_t *data; + bool is_4byte_aligned; } LoadData; typedef struct { diff --git a/setup.py b/setup.py index 61f28ba5f..b8adafff7 100755 --- a/setup.py +++ b/setup.py @@ -153,6 +153,7 @@ def init_env(debug=False, sanitize=False, native_optimizations=True, profile=Fal raise SystemExit( 'glew >= 2.0.0 is required, found version: ' + ver ) + cflags.extend(pkg_config('libpng', '--cflags-only-I')) if not isosx: cflags.extend(pkg_config('glew', '--cflags-only-I')) if isosx: @@ -170,7 +171,8 @@ def init_env(debug=False, sanitize=False, native_optimizations=True, profile=Fal else: glfw_ldflags = pkg_config('glfw3', '--libs') glew_libs = pkg_config('glew', '--libs') - ldpaths = pylib + glew_libs + font_libs + glfw_ldflags + ['-lunistring'] + libpng = pkg_config('libpng', '--libs') + ldpaths = pylib + glew_libs + font_libs + glfw_ldflags + libpng + ['-lunistring'] if not isosx: ldpaths += ['-lrt']