diff --git a/gl-image-loader/.dir-locals.el b/gl-image-loader/.dir-locals.el new file mode 100644 index 0000000..d9a374c --- /dev/null +++ b/gl-image-loader/.dir-locals.el @@ -0,0 +1,13 @@ +((prog-mode + (indent-tabs-mode . nil) + (tab-width . 8) + (c-basic-offset . 3) + (c-file-style . "stroustrup") + (fill-column . 78) + (eval . (progn + (c-set-offset 'case-label '0) + (c-set-offset 'innamespace '0) + (c-set-offset 'inline-open '0))) + ) + (makefile-mode (indent-tabs-mode . t)) + ) diff --git a/gl-image-loader/Makefile b/gl-image-loader/Makefile new file mode 100644 index 0000000..049d406 --- /dev/null +++ b/gl-image-loader/Makefile @@ -0,0 +1,30 @@ +.PHONY: all clean + +CFLAGS = -std=c99 -g -ggdb -O0 -Wall + +LDFLAGS = -lm + +PKG_CONFIG_LIBS = \ + glfw3 \ + glesv2 \ + libpng \ + libjpeg \ + $(NULL) + +CFLAGS += $(shell pkg-config --cflags $(PKG_CONFIG_LIBS)) +LDFLAGS += $(shell pkg-config --libs $(PKG_CONFIG_LIBS)) + +OBJS = png.o jpeg.o image.o + +all: gl-image-loader + +png.o: png.c png.h +jpeg.o: jpeg.c jpeg.h +image.o: image.c image.h + +gl-image-loader: main.c $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +clean: + rm -f ./*.o + rm -f gl-image-loader diff --git a/gl-image-loader/igalia-white-text.png b/gl-image-loader/igalia-white-text.png new file mode 100644 index 0000000..5c42ecb Binary files /dev/null and b/gl-image-loader/igalia-white-text.png differ diff --git a/gl-image-loader/image.c b/gl-image-loader/image.c new file mode 100644 index 0000000..543a065 --- /dev/null +++ b/gl-image-loader/image.c @@ -0,0 +1,101 @@ +#include +#include +#include "image.h" + +bool +o_image_init_from_filename (struct o_image *self, + const char *filename) +{ + assert (self != NULL); + assert (filename != NULL); + + /* Try PNG. */ + bool ok = png_decoder_init_from_filename (&self->png, filename); + if (ok) { + self->type = O_IMAGE_TYPE_PNG; + self->width = self->png.width; + self->height = self->png.height; + + switch (self->png.format) { + case PNG_COLOR_TYPE_RGB: + self->format = O_IMAGE_FORMAT_RGB; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + self->format = O_IMAGE_FORMAT_RGBA; + break; + default: + assert (!"PNG image format not handled\n"); + } + } else { + png_clear (&self->png); + + /* Try JPEG. */ + bool ok = jpeg_decoder_init_from_filename (&self->jpeg, filename); + if (ok) { + assert (self->jpeg.status == JPEG_STATUS_DECODE_READY); + + self->type = O_IMAGE_TYPE_JPEG; + self->width = self->jpeg.width; + self->height = self->jpeg.height; + + switch (self->jpeg.format) { + case JPEG_FORMAT_RGB: + case JPEG_FORMAT_EXT_RGB: + self->format = O_IMAGE_FORMAT_RGB; + break; + case JPEG_FORMAT_EXT_RGBA: + self->format = O_IMAGE_FORMAT_RGBA; + break; + default: + printf ("JPEG format: %d\n", self->jpeg.format); + assert (!"JPEG image format not handled\n"); + } + } else { + printf ("Unknown or unhandled image format.\n"); + return false; + } + } + + return true; +} + +void +o_image_clear (struct o_image *self) +{ + assert (self != NULL); + + if (self->type == O_IMAGE_TYPE_PNG) + png_clear (&self->png); + else if (self->type == O_IMAGE_TYPE_JPEG) + jpeg_clear (&self->jpeg); +} + +ssize_t +o_image_read (struct o_image *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows) +{ + assert (self != NULL); + + switch (self->type) { + case O_IMAGE_TYPE_PNG: + return png_read (&self->png, + buffer, + size, + first_row, + num_rows); + + case O_IMAGE_TYPE_JPEG: + return jpeg_read (&self->jpeg, + buffer, + size, + first_row, + num_rows); + + default: + errno = ENXIO; + return -1; + } +} diff --git a/gl-image-loader/image.h b/gl-image-loader/image.h new file mode 100644 index 0000000..afb5c82 --- /dev/null +++ b/gl-image-loader/image.h @@ -0,0 +1,43 @@ +#pragma once + +#include "jpeg.h" +#include "png.h" +#include +#include + +enum o_image_format { + O_IMAGE_FORMAT_INVALID, + O_IMAGE_FORMAT_RGB, + O_IMAGE_FORMAT_RGBA, +}; + +enum o_image_type { + O_IMAGE_TYPE_INVALID, + O_IMAGE_TYPE_PNG, + O_IMAGE_TYPE_JPEG, +}; + +struct o_image { + uint32_t width; + uint32_t height; + + uint8_t type; + uint32_t format; + + struct png_ctx png; + struct jpeg_ctx jpeg; +}; + +bool +o_image_init_from_filename (struct o_image *self, + const char *filename); + +void +o_image_clear (struct o_image *self); + +ssize_t +o_image_read (struct o_image *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows); diff --git a/gl-image-loader/jpeg.c b/gl-image-loader/jpeg.c new file mode 100644 index 0000000..3e7c85e --- /dev/null +++ b/gl-image-loader/jpeg.c @@ -0,0 +1,139 @@ +#include +#include +#include "jpeg.h" +#include + +static void +handle_error_exit (j_common_ptr cinfo) +{ + struct jpeg_error_handler *err_handler = + (struct jpeg_error_handler *) cinfo->err; + + /* Display the message. */ + (*cinfo->err->output_message) (cinfo); + + err_handler->ctx->status = JPEG_STATUS_ERROR; + + longjmp (err_handler->setjmp_buffer, 1); +} + +/* public API */ + +bool +jpeg_decoder_init_from_filename (struct jpeg_ctx *self, + const char *filename) +{ + assert (self != NULL); + assert (filename != NULL); + + memset (self, 0x00, sizeof (struct jpeg_ctx)); + + self->file_obj = fopen (filename, "rb"); + if (self->file_obj == NULL) + return false; + + /* Set an error manager. */ + self->cinfo.err = + jpeg_std_error (&self->err_handler.jpeg_error_mgr); + self->err_handler.jpeg_error_mgr.error_exit = + handle_error_exit; + + self->err_handler.ctx = self; + + if (setjmp (self->err_handler.setjmp_buffer) != 0) { + /* @FIXME: check the error code and fill errno accordingly */ + jpeg_clear (self); + return false; + } + + /* Create and set up the decompression object. */ + jpeg_create_decompress (&self->cinfo); + jpeg_stdio_src (&self->cinfo, self->file_obj); + + /* Read JPEG header. */ + int result = jpeg_read_header (&self->cinfo, true); + if (result != JPEG_HEADER_OK) { + jpeg_clear (self); + return false; + } + + jpeg_start_decompress (&self->cinfo); + + self->row_stride = self->cinfo.output_width * self->cinfo.output_components; + self->width = self->cinfo.output_width; + self->height = self->cinfo.output_height; + self->format = self->cinfo.out_color_space; + + self->status = JPEG_STATUS_DECODE_READY; + + return true; +} + +void +jpeg_clear (struct jpeg_ctx *self) +{ + if (self->file_obj != NULL) { + fclose (self->file_obj); + self->file_obj = NULL; + } + + if (self->status != JPEG_STATUS_NONE) + jpeg_destroy_decompress (&self->cinfo); + + self->status = JPEG_STATUS_NONE; +} + +ssize_t +jpeg_read (struct jpeg_ctx *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows) +{ + assert (self != NULL); + assert (self->status == JPEG_STATUS_DECODE_READY || + self->status == JPEG_STATUS_DONE); + assert (size == 0 || buffer != NULL); + assert (self->row_stride > 0 && size >= self->row_stride); + + size_t _num_rows = 0; + size_t _first_row = 0; + size_t result = 0; + + if (self->status == JPEG_STATUS_DONE) + goto out; + + _first_row = self->cinfo.output_scanline; + + uint32_t lines = size / self->row_stride; + for (int32_t i = 0; i < lines; i++) { + uint8_t *rowptr[1]; + rowptr[0] = buffer + self->row_stride * i; + + jpeg_read_scanlines (&self->cinfo, rowptr, 1); + if (self->status == JPEG_STATUS_ERROR) { + /* @FIXME: handle exit errors here */ + return -1; + } + + _num_rows++; + } + + _num_rows = self->cinfo.output_scanline - _first_row; + + if (self->cinfo.output_scanline == self->cinfo.output_height) { + jpeg_finish_decompress (&self->cinfo); + self->status = JPEG_STATUS_DONE; + } + + result = _num_rows * self->row_stride; + + out: + if (first_row != NULL) + *first_row = _first_row; + + if (num_rows != NULL) + *num_rows = _num_rows; + + return result; +} diff --git a/gl-image-loader/jpeg.h b/gl-image-loader/jpeg.h new file mode 100644 index 0000000..20d24cf --- /dev/null +++ b/gl-image-loader/jpeg.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +enum jpeg_status { + JPEG_STATUS_NONE = 0, + JPEG_STATUS_DECODE_READY, + JPEG_STATUS_ENCODE_READY, + JPEG_STATUS_ERROR, + JPEG_STATUS_DONE, +}; + +enum jpeg_format { + JPEG_FORMAT_UNKNOWN, /* error/unspecified */ + JPEG_FORMAT_GRAYSCALE, /* monochrome */ + JPEG_FORMAT_RGB, /* red/green/blue as specified by the RGB_RED, + RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros */ + JPEG_FORMAT_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JPEG_FORMAT_CMYK, /* C/M/Y/K */ + JPEG_FORMAT_YCCK, /* Y/Cb/Cr/K */ + JPEG_FORMAT_EXT_RGB, /* red/green/blue */ + JPEG_FORMAT_EXT_RGBX, /* red/green/blue/x */ + JPEG_FORMAT_EXT_BGR, /* blue/green/red */ + JPEG_FORMAT_EXT_BGRX, /* blue/green/red/x */ + JPEG_FORMAT_EXT_XBGR, /* x/blue/green/red */ + JPEG_FORMAT_EXT_XRGB, /* x/red/green/blue */ + /* When out_color_space is set to JCS_EXT_RGBX, JCS_EXT_BGRX, JCS_EXT_XBGR, + or JCS_EXT_XRGB during decompression, the X byte is undefined, and in + order to ensure the best performance, libjpeg-turbo can set that byte to + whatever value it wishes. Use the following colorspace constants to + ensure that the X byte is set to 0xFF, so that it can be interpreted as an + opaque alpha channel. */ + JPEG_FORMAT_EXT_RGBA, /* red/green/blue/alpha */ + JPEG_FORMAT_EXT_BGRA, /* blue/green/red/alpha */ + JPEG_FORMAT_EXT_ABGR, /* alpha/blue/green/red */ + JPEG_FORMAT_EXT_ARGB, /* alpha/red/green/blue */ + JPEG_FORMAT_RGB565 /* 5-bit red/6-bit green/5-bit blue */ +}; + +struct jpeg_ctx; + +struct jpeg_error_handler { + struct jpeg_error_mgr jpeg_error_mgr; + + struct jpeg_ctx *ctx; + jmp_buf setjmp_buffer; +}; + +struct jpeg_ctx { + FILE *file_obj; + struct jpeg_decompress_struct cinfo; + + struct jpeg_error_mgr err_manager; + + enum jpeg_status status; + + uint32_t width; + uint32_t height; + uint32_t row_stride; + enum jpeg_format format; + + struct jpeg_error_handler err_handler; +}; + +bool +jpeg_decoder_init_from_filename (struct jpeg_ctx *self, + const char *filename); + +void +jpeg_clear (struct jpeg_ctx *self); + +ssize_t +jpeg_read (struct jpeg_ctx *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows); diff --git a/gl-image-loader/main.c b/gl-image-loader/main.c new file mode 100644 index 0000000..4ef1589 --- /dev/null +++ b/gl-image-loader/main.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "image.h" + +#define IMAGE_FILENAME_DEFAULT "./igalia-white-text.png" + +static bool +gl_utils_print_shader_log (GLuint shader) +{ + GLint length; + char buffer[4096] = {0}; + GLint success; + + glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &length); + if (length == 0) + return true; + + glGetShaderInfoLog (shader, 4096, NULL, buffer); + if (strlen (buffer) > 0) + printf ("Shader compilation log: %s\n", buffer); + + glGetShaderiv (shader, GL_COMPILE_STATUS, &success); + + return success == GL_TRUE; +} + +static GLuint +gl_utils_load_shader (const char *shader_source, GLenum type) +{ + GLuint shader = glCreateShader (type); + + glShaderSource (shader, 1, &shader_source, NULL); + assert (glGetError () == GL_NO_ERROR); + glCompileShader (shader); + assert (glGetError () == GL_NO_ERROR); + + gl_utils_print_shader_log (shader); + + return shader; +} + +static GLuint +create_shader_program (void) +{ + const char *VERTEX_SOURCE = + "attribute vec2 pos;\n" + "attribute vec2 texture;\n" + "varying vec2 v_texture;\n" + "void main() {\n" + " v_texture = texture;\n" + " gl_Position = vec4(pos, 0, 1);\n" + "}\n"; + + const char *FRAGMENT_SOURCE = + "precision mediump float;\n" + "uniform sampler2D u_tex;\n" + "varying vec2 v_texture;\n" + "void main() {\n" + " gl_FragColor = texture2D(u_tex, v_texture);\n" + "}\n"; + + GLuint vertex_shader = gl_utils_load_shader (VERTEX_SOURCE, + GL_VERTEX_SHADER); + assert (vertex_shader >= 0); + assert (glGetError () == GL_NO_ERROR); + + GLuint fragment_shader = gl_utils_load_shader (FRAGMENT_SOURCE, + GL_FRAGMENT_SHADER); + assert (fragment_shader >= 0); + assert (glGetError () == GL_NO_ERROR); + + GLuint program = glCreateProgram (); + assert (glGetError () == GL_NO_ERROR); + glAttachShader (program, vertex_shader); + assert (glGetError () == GL_NO_ERROR); + glAttachShader (program, fragment_shader); + assert (glGetError () == GL_NO_ERROR); + + glBindAttribLocation (program, 0, "pos"); + glBindAttribLocation (program, 1, "texture"); + + glLinkProgram (program); + assert (glGetError () == GL_NO_ERROR); + + glDeleteShader (vertex_shader); + glDeleteShader (fragment_shader); + + return program; +} + +int32_t +main (int32_t argc, char *argv[]) +{ + printf ("Usage: %s \n", argv[0]); + + /* Load an decode an image. */ + static struct o_image image; + + const char *image_url; + if (argc > 1) + image_url = argv[1]; + else + image_url = IMAGE_FILENAME_DEFAULT; + + /* This loads the image header (metadata), but doesn't load any pixel + * data or do any decoding. + */ + if (! o_image_init_from_filename (&image, image_url)) + return -1; + + GLFWwindow* window; + + /* Initialize GLFW. */ + if (! glfwInit ()) + return -1; + + /* Select an OpenGL-ES 2.0 profile. */ + glfwWindowHint (GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 0); + + /* Create a windowed mode window and its OpenGL context */ + window = glfwCreateWindow (image.width, + image.height, + "GL Image Loader", + NULL, + NULL); + if (window == NULL) { + glfwTerminate (); + return -1; + } + + /* Make the window's context current */ + glfwMakeContextCurrent (window); + + /* Dump some GL capabilities. */ + const GLubyte *gles_version = glGetString (GL_VERSION); + printf ("%s\n", (char *) gles_version); + + /* Create a texture for the image. */ + GLuint tex; + glGenTextures (1, &tex); + assert (glGetError () == GL_NO_ERROR); + assert (tex > 0); + glBindTexture (GL_TEXTURE_2D, tex); + assert (glGetError () == GL_NO_ERROR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + /* Load the image into the texture, progressively in chunks of max + * BLOCK_SIZE bytes. */ +#define BLOCK_SIZE (8192 * 1) + uint8_t buf[BLOCK_SIZE] = {0, }; + size_t first_row; + size_t num_rows; + + GLuint format = image.format == O_IMAGE_FORMAT_RGB ? + GL_RGB : GL_RGBA; + + /* Allocate the texture size. */ + glTexImage2D (GL_TEXTURE_2D, + 0, + format, + image.width, image.height, + 0, + format, + GL_UNSIGNED_BYTE, + NULL); + assert (glGetError () == GL_NO_ERROR); + + ssize_t size_read; + do { + size_read = o_image_read (&image, + buf, + BLOCK_SIZE, + &first_row, + &num_rows); + assert (size_read >= 0); + if (size_read > 0) { + glTexSubImage2D (GL_TEXTURE_2D, + 0, + 0, first_row, + image.width, num_rows, + format, + GL_UNSIGNED_BYTE, + buf); + assert (glGetError () == GL_NO_ERROR); + } + } while (size_read > 0); + + glBindTexture (GL_TEXTURE_2D, 0); +#undef BLOCK_SIZE + + /* Create shader program to sample the texture. */ + GLuint program = create_shader_program (); + glUseProgram (program); + assert (glGetError () == GL_NO_ERROR); + + /* Loop until the user closes the window */ + while (! glfwWindowShouldClose (window)) { + /* Render here */ + glClearColor (0.25, 0.25, 0.25, 0.5); + glClear (GL_COLOR_BUFFER_BIT); + + /* Bind the texture. */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, tex); + assert (glGetError () == GL_NO_ERROR); + + /* Enable blending for transparent PNGs. */ + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* Draw a quad. */ + static const GLfloat s_vertices[4][2] = { + { -1.0, 1.0 }, + { 1.0, 1.0 }, + { -1.0, -1.0 }, + { 1.0, -1.0 }, + }; + + static const GLfloat s_texturePos[4][2] = { + { 0, 0 }, + { 1, 0 }, + { 0, 1 }, + { 1, 1 }, + }; + + glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, s_vertices); + glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 0, s_texturePos); + + glEnableVertexAttribArray (0); + glEnableVertexAttribArray (1); + + glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray (0); + glDisableVertexAttribArray (1); + + /* Swap front and back buffers */ + glfwSwapBuffers (window); + + /* Poll and process events */ + glfwPollEvents (); + } + + glfwTerminate (); + + return 0; +} diff --git a/gl-image-loader/png.c b/gl-image-loader/png.c new file mode 100644 index 0000000..bd901f7 --- /dev/null +++ b/gl-image-loader/png.c @@ -0,0 +1,148 @@ +#include +#include +#include "png.h" +#include +#include + +bool +png_decoder_init_from_filename (struct png_ctx *self, + const char *filename) +{ + assert (self != NULL); + assert (filename != NULL); + + memset (self, 0x00, sizeof (struct png_ctx)); + + self->file_obj = fopen (filename, "rb"); + if (self->file_obj == NULL) + return false; + + /* Check PNG signature. */ + uint8_t header[8]; + ssize_t read_size = fread (header, 1, 8, self->file_obj); + assert (read_size > 0); + + if (png_sig_cmp ((png_const_bytep) header, 0, 8) != 0) { + png_clear (self); + errno = EINVAL; + return false; + } + + /* Create the PNG decoder object. */ + self->png_ptr = + png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (self->png_ptr == NULL) { + png_clear (self); + errno = ENOMEM; + return false; + } + + /* Create the PNG info object. */ + self->info_ptr = png_create_info_struct (self->png_ptr); + if (self->info_ptr == NULL) { + png_clear (self); + errno = ENOMEM; + return false; + } + + /* Initialize error handling. */ + if (setjmp (png_jmpbuf (self->png_ptr)) != 0) { + png_clear (self); + errno = ENOMEM; + return false; + } + + png_init_io (self->png_ptr, self->file_obj); + png_set_sig_bytes (self->png_ptr, 8); + + /* @FIXME: does this generates errors? */ + png_read_info (self->png_ptr, self->info_ptr); + + self->width = png_get_image_width (self->png_ptr, self->info_ptr); + self->height = png_get_image_height (self->png_ptr, self->info_ptr); + assert (self->width > 0 && self->height > 0); + + self->format = png_get_color_type (self->png_ptr, self->info_ptr); + self->row_stride = png_get_rowbytes (self->png_ptr, self->info_ptr); + + self->status = PNG_STATUS_DECODE_READY; + + return true; +} + +void +png_clear (struct png_ctx *self) +{ + assert (self != NULL); + + if (self->png_ptr != NULL) { + if (self->info_ptr != NULL) + png_destroy_info_struct (self->png_ptr, &self->info_ptr); + + png_destroy_read_struct (&self->png_ptr, &self->info_ptr, NULL); + } + + if (self->file_obj != NULL) { + fclose (self->file_obj); + self->file_obj = NULL; + } + + self->status = PNG_STATUS_NONE; +} + +ssize_t +png_read (struct png_ctx *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows) +{ + assert (self != NULL); + assert (self->status == PNG_STATUS_DECODE_READY || + self->status == PNG_STATUS_DONE); + assert (size == 0 || buffer != NULL); + assert (self->row_stride > 0 && size >= self->row_stride); + + size_t _num_rows = 0; + size_t _first_row = 0; + size_t result = 0; + + if (self->status == PNG_STATUS_DONE) + goto out; + + _first_row = self->last_decoded_row; + uint32_t max_read_rows = size / self->row_stride; + +#define MIN(a,b) (a > b ? b : a) + _num_rows = MIN (self->height - self->last_decoded_row, + max_read_rows); +#undef MIN + + png_bytepp rows = calloc (_num_rows, sizeof (png_bytep)); + for (int32_t i = 0; i < _num_rows; i++) + rows[i] = buffer + (i * self->row_stride); + + png_read_rows (self->png_ptr, + rows, + NULL, + _num_rows); + free (rows); + + self->last_decoded_row += _num_rows; + result = _num_rows * self->row_stride; + + if (self->last_decoded_row == self->height) { + png_read_end (self->png_ptr, self->info_ptr); + + self->status = PNG_STATUS_DONE; + } + + out: + if (first_row != NULL) + *first_row = _first_row; + + if (num_rows != NULL) + *num_rows = _num_rows; + + return result; +} diff --git a/gl-image-loader/png.h b/gl-image-loader/png.h new file mode 100644 index 0000000..3d46c60 --- /dev/null +++ b/gl-image-loader/png.h @@ -0,0 +1,45 @@ +#pragma once + +#define PNG_DEBUG 3 +#include +#include +#include +#include + +enum png_status { + PNG_STATUS_NONE = 0, + PNG_STATUS_DECODE_READY, + PNG_STATUS_ENCODE_READY, + PNG_STATUS_ERROR, + PNG_STATUS_DONE, +}; + +struct png_ctx { + FILE *file_obj; + + png_structp png_ptr; + png_infop info_ptr; + + enum png_status status; + + uint32_t width; + uint32_t height; + size_t row_stride; + uint8_t format; + + uint32_t last_decoded_row; +}; + +bool +png_decoder_init_from_filename (struct png_ctx *self, + const char *filename); + +void +png_clear (struct png_ctx *self); + +ssize_t +png_read (struct png_ctx *self, + void *buffer, + size_t size, + size_t *first_row, + size_t *num_rows);