1
0
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:
Eduardo Lima Mitev 2018-10-29 11:24:58 +01:00
parent c8607b8797
commit e07985e898
10 changed files with 859 additions and 0 deletions

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

101
gl-image-loader/image.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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);