Make the PNG reader code re-useable
This commit is contained in:
parent
aeed20087e
commit
2b035739f8
@ -211,6 +211,7 @@ extern bool init_shaders(PyObject *module);
|
|||||||
extern bool init_mouse(PyObject *module);
|
extern bool init_mouse(PyObject *module);
|
||||||
extern bool init_kittens(PyObject *module);
|
extern bool init_kittens(PyObject *module);
|
||||||
extern bool init_logging(PyObject *module);
|
extern bool init_logging(PyObject *module);
|
||||||
|
extern bool init_png_reader(PyObject *module);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
extern int init_CoreText(PyObject *);
|
extern int init_CoreText(PyObject *);
|
||||||
extern bool init_cocoa(PyObject *module);
|
extern bool init_cocoa(PyObject *module);
|
||||||
@ -246,6 +247,7 @@ PyInit_fast_data_types(void) {
|
|||||||
if (!init_shaders(m)) return NULL;
|
if (!init_shaders(m)) return NULL;
|
||||||
if (!init_mouse(m)) return NULL;
|
if (!init_mouse(m)) return NULL;
|
||||||
if (!init_kittens(m)) return NULL;
|
if (!init_kittens(m)) return NULL;
|
||||||
|
if (!init_png_reader(m)) return NULL;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (!init_CoreText(m)) return NULL;
|
if (!init_CoreText(m)) return NULL;
|
||||||
if (!init_cocoa(m)) return NULL;
|
if (!init_cocoa(m)) return NULL;
|
||||||
|
|||||||
2
kitty/glfw-wrapper.h
generated
2
kitty/glfw-wrapper.h
generated
@ -1679,7 +1679,7 @@ typedef void (*glfwSetCursorPos_func)(GLFWwindow*, double, double);
|
|||||||
glfwSetCursorPos_func glfwSetCursorPos_impl;
|
glfwSetCursorPos_func glfwSetCursorPos_impl;
|
||||||
#define glfwSetCursorPos glfwSetCursorPos_impl
|
#define glfwSetCursorPos glfwSetCursorPos_impl
|
||||||
|
|
||||||
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int);
|
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int, int);
|
||||||
glfwCreateCursor_func glfwCreateCursor_impl;
|
glfwCreateCursor_func glfwCreateCursor_impl;
|
||||||
#define glfwCreateCursor glfwCreateCursor_impl
|
#define glfwCreateCursor glfwCreateCursor_impl
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <png.h>
|
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
|
#include "png-reader.h"
|
||||||
PyTypeObject GraphicsManager_Type;
|
PyTypeObject GraphicsManager_Type;
|
||||||
|
|
||||||
#define STORAGE_LIMIT (320 * (1024 * 1024))
|
#define STORAGE_LIMIT (320 * (1024 * 1024))
|
||||||
@ -227,87 +227,14 @@ err:
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct fake_file { uint8_t *buf; size_t sz, cur; };
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) {
|
png_error_handler(const char *code, const char *msg) {
|
||||||
struct fake_file *f = png_get_io_ptr(png);
|
set_add_response(code, "%s", msg);
|
||||||
if (f) {
|
|
||||||
size_t amt = MIN(length, f->sz - f->cur);
|
|
||||||
memcpy(out, f->buf + f->cur, amt);
|
|
||||||
f->cur += amt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
read_png_error_handler(png_structp png_ptr, png_const_charp msg) {
|
|
||||||
jmp_buf *jb;
|
|
||||||
set_add_response("EBADPNG", msg);
|
|
||||||
jb = png_get_error_ptr(png_ptr);
|
|
||||||
if (jb == NULL) fatal("read_png_error_handler: could not retrieve jmp_buf");
|
|
||||||
longjmp(*jb, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp UNUSED msg) {
|
|
||||||
// ignore warnings
|
|
||||||
}
|
|
||||||
|
|
||||||
struct png_jmp_data { uint8_t *decompressed; bool ok; png_bytep *row_pointers; int width, height; size_t sz; };
|
|
||||||
|
|
||||||
static void
|
|
||||||
inflate_png_inner(struct png_jmp_data *d, uint8_t *buf, size_t bufsz) {
|
|
||||||
struct fake_file f = {.buf = buf, .sz = bufsz};
|
|
||||||
png_structp png = NULL;
|
|
||||||
png_infop info = NULL;
|
|
||||||
jmp_buf jb;
|
|
||||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &jb, read_png_error_handler, read_png_warn_handler);
|
|
||||||
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
|
|
||||||
info = png_create_info_struct(png);
|
|
||||||
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
|
|
||||||
|
|
||||||
if (setjmp(jb)) goto err;
|
|
||||||
|
|
||||||
png_set_read_fn(png, &f, read_png_from_buffer);
|
|
||||||
png_read_info(png, info);
|
|
||||||
png_byte color_type, bit_depth;
|
|
||||||
d->width = png_get_image_width(png, info);
|
|
||||||
d->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);
|
|
||||||
d->sz = rowbytes * d->height * sizeof(png_byte);
|
|
||||||
d->decompressed = malloc(d->sz + 16);
|
|
||||||
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
|
|
||||||
d->row_pointers = malloc(d->height * sizeof(png_bytep));
|
|
||||||
if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG");
|
|
||||||
for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes;
|
|
||||||
png_read_image(png, d->row_pointers);
|
|
||||||
|
|
||||||
d->ok = true;
|
|
||||||
err:
|
|
||||||
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) {
|
inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) {
|
||||||
struct png_jmp_data d = {0};
|
png_read_data d = {.err_handler=png_error_handler};
|
||||||
inflate_png_inner(&d, buf, bufsz);
|
inflate_png_inner(&d, buf, bufsz);
|
||||||
if (d.ok) {
|
if (d.ok) {
|
||||||
free_load_data(&img->load_data);
|
free_load_data(&img->load_data);
|
||||||
|
|||||||
126
kitty/png-reader.c
Normal file
126
kitty/png-reader.c
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* png-reader.c
|
||||||
|
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "png-reader.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct fake_file { const 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct custom_error_handler {
|
||||||
|
jmp_buf jb;
|
||||||
|
png_error_handler_func err_handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_png_error_handler(png_structp png_ptr, png_const_charp msg) {
|
||||||
|
struct custom_error_handler *eh;
|
||||||
|
eh = png_get_error_ptr(png_ptr);
|
||||||
|
if (eh == NULL) fatal("read_png_error_handler: could not retrieve error handler");
|
||||||
|
if(eh->err_handler) eh->err_handler("EBADPNG", msg);
|
||||||
|
longjmp(eh->jb, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp UNUSED msg) {
|
||||||
|
// ignore warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ABRT(code, msg) { if(d->err_handler) d->err_handler(#code, msg); goto err; }
|
||||||
|
|
||||||
|
void
|
||||||
|
inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) {
|
||||||
|
struct fake_file f = {.buf = buf, .sz = bufsz};
|
||||||
|
png_structp png = NULL;
|
||||||
|
png_infop info = NULL;
|
||||||
|
struct custom_error_handler eh = {.err_handler = d->err_handler};
|
||||||
|
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &eh, read_png_error_handler, read_png_warn_handler);
|
||||||
|
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
|
||||||
|
info = png_create_info_struct(png);
|
||||||
|
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
|
||||||
|
|
||||||
|
if (setjmp(eh.jb)) goto err;
|
||||||
|
|
||||||
|
png_set_read_fn(png, &f, read_png_from_buffer);
|
||||||
|
png_read_info(png, info);
|
||||||
|
png_byte color_type, bit_depth;
|
||||||
|
d->width = png_get_image_width(png, info);
|
||||||
|
d->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);
|
||||||
|
d->sz = rowbytes * d->height * sizeof(png_byte);
|
||||||
|
d->decompressed = malloc(d->sz + 16);
|
||||||
|
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
|
||||||
|
d->row_pointers = malloc(d->height * sizeof(png_bytep));
|
||||||
|
if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG");
|
||||||
|
for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes;
|
||||||
|
png_read_image(png, d->row_pointers);
|
||||||
|
|
||||||
|
d->ok = true;
|
||||||
|
err:
|
||||||
|
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
png_error_handler(const char *code, const char *msg) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "[%s] %s", code, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
load_png_data(PyObject *self UNUSED, PyObject *args) {
|
||||||
|
Py_ssize_t sz;
|
||||||
|
const char *data;
|
||||||
|
if (!PyArg_ParseTuple(args, "s#", &data, &sz)) return NULL;
|
||||||
|
png_read_data d = {.err_handler=png_error_handler};
|
||||||
|
inflate_png_inner(&d, (const uint8_t*)data, sz);
|
||||||
|
PyObject *ans = NULL;
|
||||||
|
if (d.ok && !PyErr_Occurred()) {
|
||||||
|
ans = PyBytes_FromStringAndSize((const char*)d.decompressed, d.sz);
|
||||||
|
} else {
|
||||||
|
if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, "Unknown error while reading PNG data");
|
||||||
|
}
|
||||||
|
free(d.decompressed);
|
||||||
|
free(d.row_pointers);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef module_methods[] = {
|
||||||
|
METHODB(load_png_data, METH_VARARGS),
|
||||||
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
init_png_reader(PyObject *module) {
|
||||||
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
21
kitty/png-reader.h
Normal file
21
kitty/png-reader.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data-types.h"
|
||||||
|
#include <png.h>
|
||||||
|
typedef void(*png_error_handler_func)(const char*, const char*);
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *decompressed;
|
||||||
|
bool ok;
|
||||||
|
png_bytep *row_pointers;
|
||||||
|
int width, height;
|
||||||
|
size_t sz;
|
||||||
|
png_error_handler_func err_handler;
|
||||||
|
} png_read_data;
|
||||||
|
|
||||||
|
void inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz);
|
||||||
@ -10,7 +10,7 @@ from base64 import standard_b64decode, standard_b64encode
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
parse_bytes, set_send_to_gpu, shm_unlink, shm_write
|
load_png_data, parse_bytes, set_send_to_gpu, shm_unlink, shm_write
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import BaseTest
|
from . import BaseTest
|
||||||
@ -209,8 +209,12 @@ class TestGraphics(BaseTest):
|
|||||||
def test_load_png_simple(self):
|
def test_load_png_simple(self):
|
||||||
# 1x1 transparent PNG
|
# 1x1 transparent PNG
|
||||||
png_data = standard_b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==')
|
png_data = standard_b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==')
|
||||||
|
expected = b'\x00\xff\xff\x7f'
|
||||||
|
self.ae(load_png_data(png_data), expected)
|
||||||
s, g, l, sl = load_helpers(self)
|
s, g, l, sl = load_helpers(self)
|
||||||
sl(png_data, f=100, expecting_data=b'\x00\xff\xff\x7f')
|
sl(png_data, f=100, expecting_data=expected)
|
||||||
|
# test error handling for loading bad png data
|
||||||
|
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, b'dsfsdfsfsfd')
|
||||||
|
|
||||||
def test_image_put(self):
|
def test_image_put(self):
|
||||||
cw, ch = 10, 20
|
cw, ch = 10, 20
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user