mirror of
https://github.com/elima/gpu-playground.git
synced 2025-06-06 15:36:35 +00:00
Add an example program that loads a PNG or JPEG image into a GL texture
This commit is contained in:
parent
c8607b8797
commit
e07985e898
13
gl-image-loader/.dir-locals.el
Normal file
13
gl-image-loader/.dir-locals.el
Normal file
@ -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))
|
||||
)
|
30
gl-image-loader/Makefile
Normal file
30
gl-image-loader/Makefile
Normal file
@ -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
|
BIN
gl-image-loader/igalia-white-text.png
Normal file
BIN
gl-image-loader/igalia-white-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
101
gl-image-loader/image.c
Normal file
101
gl-image-loader/image.c
Normal file
@ -0,0 +1,101 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#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;
|
||||
}
|
||||
}
|
43
gl-image-loader/image.h
Normal file
43
gl-image-loader/image.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "jpeg.h"
|
||||
#include "png.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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);
|
139
gl-image-loader/jpeg.c
Normal file
139
gl-image-loader/jpeg.c
Normal file
@ -0,0 +1,139 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include "jpeg.h"
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
82
gl-image-loader/jpeg.h
Normal file
82
gl-image-loader/jpeg.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
258
gl-image-loader/main.c
Normal file
258
gl-image-loader/main.c
Normal file
@ -0,0 +1,258 @@
|
||||
#include <assert.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <path-to-PNG-or-JPEG-image>\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;
|
||||
}
|
148
gl-image-loader/png.c
Normal file
148
gl-image-loader/png.c
Normal file
@ -0,0 +1,148 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include "png.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
45
gl-image-loader/png.h
Normal file
45
gl-image-loader/png.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#define PNG_DEBUG 3
|
||||
#include <png.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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);
|
Loading…
x
Reference in New Issue
Block a user