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