Code to read compressed and PNG encoded image data

This commit is contained in:
Kovid Goyal 2017-09-27 17:31:11 +05:30
parent df1e06ec2c
commit 01fb9436dd
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 171 additions and 4 deletions

View File

@ -81,6 +81,8 @@ the following dependencies are installed first.
* python >= 3.5 * python >= 3.5
* glfw >= 3.2 * glfw >= 3.2
* libunistring * libunistring
* zlib
* libpng
* glew >= 2.0 (not needed on macOS) * glew >= 2.0 (not needed on macOS)
* fontconfig (not needed on macOS) * fontconfig (not needed on macOS)
* xrdb and xsel (only on X11 based systems) * xrdb and xsel (only on X11 based systems)

View File

@ -12,6 +12,8 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <zlib.h>
#include <png.h>
#define REPORT_ERROR(fmt, ...) { fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n"); } #define REPORT_ERROR(fmt, ...) { fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n"); }
@ -58,9 +60,7 @@ free_load_data(LoadData *ld) {
static inline void static inline void
free_image(Image *img) { free_image(Image *img) {
img->data_loaded = false;
// TODO: free the texture if texture_id is not zero // TODO: free the texture if texture_id is not zero
img->texture_id = 0;
free_load_data(&(img->load_data)); free_load_data(&(img->load_data));
} }
@ -127,6 +127,117 @@ img_by_internal_id(GraphicsManager *self, size_t id) {
return NULL; 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 static bool
add_trim_predicate(Image *img) { add_trim_predicate(Image *img) {
@ -156,11 +267,16 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
switch(g->format) { switch(g->format) {
case PNG: case PNG:
sz *= 4; sz *= 4;
img->load_data.is_4byte_aligned = true;
break; break;
case RGB: case RGB:
case RGBA: case RGBA:
sz *= g->format / 8; sz *= g->format / 8;
img->load_data.is_4byte_aligned = g->format == RGBA || (img->width % 4 == 0);
break; break;
default:
REPORT_ERROR("Unknown image format: %u", g->format);
return;
} }
img->load_data.data_sz = sz; img->load_data.data_sz = sz;
if (tt == 'd') { 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); REPORT_ERROR("Unknown transmission type: %c", g->transmission_type);
return; 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 void

View File

@ -24,6 +24,8 @@ typedef struct {
size_t mapped_file_sz; size_t mapped_file_sz;
size_t data_sz; size_t data_sz;
uint8_t *data;
bool is_4byte_aligned;
} LoadData; } LoadData;
typedef struct { typedef struct {

View File

@ -153,6 +153,7 @@ def init_env(debug=False, sanitize=False, native_optimizations=True, profile=Fal
raise SystemExit( raise SystemExit(
'glew >= 2.0.0 is required, found version: ' + ver 'glew >= 2.0.0 is required, found version: ' + ver
) )
cflags.extend(pkg_config('libpng', '--cflags-only-I'))
if not isosx: if not isosx:
cflags.extend(pkg_config('glew', '--cflags-only-I')) cflags.extend(pkg_config('glew', '--cflags-only-I'))
if isosx: if isosx:
@ -170,7 +171,8 @@ def init_env(debug=False, sanitize=False, native_optimizations=True, profile=Fal
else: else:
glfw_ldflags = pkg_config('glfw3', '--libs') glfw_ldflags = pkg_config('glfw3', '--libs')
glew_libs = pkg_config('glew', '--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: if not isosx:
ldpaths += ['-lrt'] ldpaths += ['-lrt']