471 lines
11 KiB
C
471 lines
11 KiB
C
/**
|
|
* Convolution with GLSL.
|
|
* Note: uses GL_ARB_shader_objects, GL_ARB_vertex_shader, GL_ARB_fragment_shader,
|
|
* not the OpenGL 2.0 shader API.
|
|
* Author: Zack Rusin
|
|
*/
|
|
|
|
#include <GL/glew.h>
|
|
|
|
#define GL_GLEXT_PROTOTYPES
|
|
#include "readtex.h"
|
|
|
|
#include <GL/glut.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
enum Filter {
|
|
GAUSSIAN_BLUR,
|
|
SHARPEN,
|
|
MEAN_REMOVAL,
|
|
EMBOSS,
|
|
EDGE_DETECT,
|
|
NO_FILTER,
|
|
LAST
|
|
};
|
|
#define QUIT LAST
|
|
|
|
struct BoundingBox {
|
|
float minx, miny, minz;
|
|
float maxx, maxy, maxz;
|
|
};
|
|
struct Texture {
|
|
GLuint id;
|
|
GLfloat x;
|
|
GLfloat y;
|
|
GLint width;
|
|
GLint height;
|
|
GLenum format;
|
|
};
|
|
|
|
static const char *textureLocation = "../images/girl2.rgb";
|
|
|
|
static GLfloat viewRotx = 0.0, viewRoty = 0.0, viewRotz = 0.0;
|
|
static struct BoundingBox box;
|
|
static struct Texture texture;
|
|
static GLuint program;
|
|
static GLint menuId;
|
|
static enum Filter filter = GAUSSIAN_BLUR;
|
|
|
|
|
|
static void checkError(int line)
|
|
{
|
|
GLenum err = glGetError();
|
|
if (err) {
|
|
printf("GL Error %s (0x%x) at line %d\n",
|
|
gluErrorString(err), (int) err, line);
|
|
}
|
|
}
|
|
|
|
static void loadAndCompileShader(GLuint shader, const char *text)
|
|
{
|
|
GLint stat;
|
|
|
|
glShaderSource(shader, 1, (const GLchar **) &text, NULL);
|
|
|
|
glCompileShader(shader);
|
|
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &stat);
|
|
if (!stat) {
|
|
GLchar log[1000];
|
|
GLsizei len;
|
|
glGetShaderInfoLog(shader, 1000, &len, log);
|
|
fprintf(stderr, "Problem compiling shader: %s\n", log);
|
|
exit(1);
|
|
}
|
|
else {
|
|
printf("Shader compiled OK\n");
|
|
}
|
|
}
|
|
|
|
static void readShader(GLuint shader, const char *filename)
|
|
{
|
|
const int max = 100*1000;
|
|
int n;
|
|
char *buffer = (char*) malloc(max);
|
|
FILE *f = fopen(filename, "r");
|
|
if (!f) {
|
|
fprintf(stderr, "Unable to open shader file %s\n", filename);
|
|
exit(1);
|
|
}
|
|
|
|
n = fread(buffer, 1, max, f);
|
|
printf("Read %d bytes from shader file %s\n", n, filename);
|
|
if (n > 0) {
|
|
buffer[n] = 0;
|
|
loadAndCompileShader(shader, buffer);
|
|
}
|
|
|
|
fclose(f);
|
|
free(buffer);
|
|
}
|
|
|
|
|
|
static void
|
|
checkLink(GLuint prog)
|
|
{
|
|
GLint stat;
|
|
glGetProgramiv(prog, GL_LINK_STATUS, &stat);
|
|
if (!stat) {
|
|
GLchar log[1000];
|
|
GLsizei len;
|
|
glGetProgramInfoLog(prog, 1000, &len, log);
|
|
fprintf(stderr, "Linker error:\n%s\n", log);
|
|
}
|
|
else {
|
|
fprintf(stderr, "Link success!\n");
|
|
}
|
|
}
|
|
|
|
static void fillConvolution(GLint *k,
|
|
GLfloat *scale,
|
|
GLfloat *color)
|
|
{
|
|
switch(filter) {
|
|
case GAUSSIAN_BLUR:
|
|
k[0] = 1; k[1] = 2; k[2] = 1;
|
|
k[3] = 2; k[4] = 4; k[5] = 2;
|
|
k[6] = 1; k[7] = 2; k[8] = 1;
|
|
|
|
*scale = 1./16.;
|
|
break;
|
|
case SHARPEN:
|
|
k[0] = 0; k[1] = -2; k[2] = 0;
|
|
k[3] = -2; k[4] = 11; k[5] = -2;
|
|
k[6] = 0; k[7] = -2; k[8] = 0;
|
|
|
|
*scale = 1./3.;
|
|
break;
|
|
case MEAN_REMOVAL:
|
|
k[0] = -1; k[1] = -1; k[2] = -1;
|
|
k[3] = -1; k[4] = 9; k[5] = -1;
|
|
k[6] = -1; k[7] = -1; k[8] = -1;
|
|
|
|
*scale = 1./1.;
|
|
break;
|
|
case EMBOSS:
|
|
k[0] = -1; k[1] = 0; k[2] = -1;
|
|
k[3] = 0; k[4] = 4; k[5] = 0;
|
|
k[6] = -1; k[7] = 0; k[8] = -1;
|
|
|
|
*scale = 1./1.;
|
|
color[0] = 0.5;
|
|
color[1] = 0.5;
|
|
color[2] = 0.5;
|
|
color[3] = 0.5;
|
|
break;
|
|
case EDGE_DETECT:
|
|
k[0] = 1; k[1] = 1; k[2] = 1;
|
|
k[3] = 0; k[4] = 0; k[5] = 0;
|
|
k[6] = -1; k[7] = -1; k[8] = -1;
|
|
|
|
*scale = 1.;
|
|
color[0] = 0.5;
|
|
color[1] = 0.5;
|
|
color[2] = 0.5;
|
|
color[3] = 0.5;
|
|
break;
|
|
case NO_FILTER:
|
|
k[0] = 0; k[1] = 0; k[2] = 0;
|
|
k[3] = 0; k[4] = 1; k[5] = 0;
|
|
k[6] = 0; k[7] = 0; k[8] = 0;
|
|
|
|
*scale = 1.;
|
|
break;
|
|
default:
|
|
assert(!"Unhandled switch value");
|
|
}
|
|
}
|
|
|
|
static void setupConvolution()
|
|
{
|
|
GLint *kernel = (GLint*)malloc(sizeof(GLint) * 9);
|
|
GLfloat scale = 0.0;
|
|
GLfloat *vecKer = (GLfloat*)malloc(sizeof(GLfloat) * 9 * 4);
|
|
GLuint loc;
|
|
GLuint i;
|
|
GLfloat baseColor[4];
|
|
baseColor[0] = 0;
|
|
baseColor[1] = 0;
|
|
baseColor[2] = 0;
|
|
baseColor[3] = 0;
|
|
|
|
fillConvolution(kernel, &scale, baseColor);
|
|
/*vector of 4*/
|
|
for (i = 0; i < 9; ++i) {
|
|
vecKer[i*4 + 0] = kernel[i];
|
|
vecKer[i*4 + 1] = kernel[i];
|
|
vecKer[i*4 + 2] = kernel[i];
|
|
vecKer[i*4 + 3] = kernel[i];
|
|
}
|
|
|
|
loc = glGetUniformLocationARB(program, "KernelValue");
|
|
glUniform4fv(loc, 9, vecKer);
|
|
loc = glGetUniformLocationARB(program, "ScaleFactor");
|
|
glUniform4f(loc, scale, scale, scale, scale);
|
|
loc = glGetUniformLocationARB(program, "BaseColor");
|
|
glUniform4f(loc, baseColor[0], baseColor[1],
|
|
baseColor[2], baseColor[3]);
|
|
|
|
free(vecKer);
|
|
free(kernel);
|
|
}
|
|
|
|
static void createProgram(const char *vertProgFile,
|
|
const char *fragProgFile)
|
|
{
|
|
GLuint fragShader = 0, vertShader = 0;
|
|
|
|
program = glCreateProgram();
|
|
if (vertProgFile) {
|
|
vertShader = glCreateShader(GL_VERTEX_SHADER);
|
|
readShader(vertShader, vertProgFile);
|
|
glAttachShader(program, vertShader);
|
|
}
|
|
|
|
if (fragProgFile) {
|
|
fragShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
readShader(fragShader, fragProgFile);
|
|
glAttachShader(program, fragShader);
|
|
}
|
|
|
|
glLinkProgram(program);
|
|
checkLink(program);
|
|
|
|
glUseProgram(program);
|
|
|
|
/*
|
|
assert(glIsProgram(program));
|
|
assert(glIsShader(fragShader));
|
|
assert(glIsShader(vertShader));
|
|
*/
|
|
|
|
checkError(__LINE__);
|
|
{/*texture*/
|
|
GLuint texLoc = glGetUniformLocationARB(program, "srcTex");
|
|
glUniform1iARB(texLoc, 0);
|
|
}
|
|
{/*setup offsets */
|
|
float offsets[] = { 1.0 / texture.width, 1.0 / texture.height,
|
|
0.0 , 1.0 / texture.height,
|
|
-1.0 / texture.width, 1.0 / texture.height,
|
|
1.0 / texture.width, 0.0,
|
|
0.0 , 0.0,
|
|
-1.0 / texture.width, 0.0,
|
|
1.0 / texture.width, -1.0 / texture.height,
|
|
0.0 , -1.0 / texture.height,
|
|
-1.0 / texture.width, -1.0 / texture.height };
|
|
GLuint offsetLoc = glGetUniformLocationARB(program, "Offset");
|
|
glUniform2fv(offsetLoc, 9, offsets);
|
|
}
|
|
setupConvolution();
|
|
|
|
checkError(__LINE__);
|
|
}
|
|
|
|
|
|
static void readTexture(const char *filename)
|
|
{
|
|
GLubyte *data;
|
|
|
|
texture.x = 0;
|
|
texture.y = 0;
|
|
|
|
glGenTextures(1, &texture.id);
|
|
glBindTexture(GL_TEXTURE_2D, texture.id);
|
|
glTexParameteri(GL_TEXTURE_2D,
|
|
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D,
|
|
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
data = LoadRGBImage(filename, &texture.width, &texture.height,
|
|
&texture.format);
|
|
if (!data) {
|
|
printf("Error: couldn't load texture image '%s'\n", filename);
|
|
exit(1);
|
|
}
|
|
printf("Texture %s (%d x %d)\n",
|
|
filename, texture.width, texture.height);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
|
|
texture.width, texture.height, 0, texture.format,
|
|
GL_UNSIGNED_BYTE, data);
|
|
}
|
|
|
|
static void menuSelected(int entry)
|
|
{
|
|
switch (entry) {
|
|
case QUIT:
|
|
exit(0);
|
|
break;
|
|
default:
|
|
filter = (enum Filter)entry;
|
|
}
|
|
setupConvolution();
|
|
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
static void menuInit()
|
|
{
|
|
menuId = glutCreateMenu(menuSelected);
|
|
|
|
glutAddMenuEntry("Gaussian blur", GAUSSIAN_BLUR);
|
|
glutAddMenuEntry("Sharpen", SHARPEN);
|
|
glutAddMenuEntry("Mean removal", MEAN_REMOVAL);
|
|
glutAddMenuEntry("Emboss", EMBOSS);
|
|
glutAddMenuEntry("Edge detect", EDGE_DETECT);
|
|
glutAddMenuEntry("None", NO_FILTER);
|
|
|
|
glutAddMenuEntry("Quit", QUIT);
|
|
|
|
glutAttachMenu(GLUT_RIGHT_BUTTON);
|
|
}
|
|
|
|
static void init()
|
|
{
|
|
if (!glutExtensionSupported("GL_ARB_shader_objects") ||
|
|
!glutExtensionSupported("GL_ARB_vertex_shader") ||
|
|
!glutExtensionSupported("GL_ARB_fragment_shader")) {
|
|
fprintf(stderr, "Sorry, this program requires GL_ARB_shader_objects, GL_ARB_vertex_shader, and GL_ARB_fragment_shader\n");
|
|
exit(1);
|
|
}
|
|
|
|
fprintf(stderr, "GL_RENDERER = %s\n", (char *) glGetString(GL_RENDERER));
|
|
fprintf(stderr, "GL_VERSION = %s\n", (char *) glGetString(GL_VERSION));
|
|
fprintf(stderr, "GL_VENDOR = %s\n", (char *) glGetString(GL_VENDOR));
|
|
|
|
menuInit();
|
|
readTexture(textureLocation);
|
|
createProgram("convolution.vert", "convolution.frag");
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glClearColor(1.0, 1.0, 1.0, 1.0);
|
|
/*glShadeModel(GL_SMOOTH);*/
|
|
glShadeModel(GL_FLAT);
|
|
}
|
|
|
|
static void reshape(int width, int height)
|
|
{
|
|
glViewport(0, 0, width, height);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
box.minx = 0;
|
|
box.maxx = width;
|
|
box.miny = 0;
|
|
box.maxy = height;
|
|
box.minz = 0;
|
|
box.maxz = 1;
|
|
glOrtho(box.minx, box.maxx, box.miny, box.maxy, -999999, 999999);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
static void keyPress(unsigned char key, int x, int y)
|
|
{
|
|
switch(key) {
|
|
case 27:
|
|
exit(0);
|
|
default:
|
|
break;
|
|
}
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
static void
|
|
special(int k, int x, int y)
|
|
{
|
|
switch (k) {
|
|
case GLUT_KEY_UP:
|
|
viewRotx += 2.0;
|
|
break;
|
|
case GLUT_KEY_DOWN:
|
|
viewRotx -= 2.0;
|
|
break;
|
|
case GLUT_KEY_LEFT:
|
|
viewRoty += 2.0;
|
|
break;
|
|
case GLUT_KEY_RIGHT:
|
|
viewRoty -= 2.0;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
|
|
static void draw()
|
|
{
|
|
GLfloat center[2];
|
|
GLfloat anchor[2];
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glLoadIdentity();
|
|
glPushMatrix();
|
|
|
|
center[0] = box.maxx/2;
|
|
center[1] = box.maxy/2;
|
|
anchor[0] = center[0] - texture.width/2;
|
|
anchor[1] = center[1] - texture.height/2;
|
|
|
|
glTranslatef(center[0], center[1], 0);
|
|
glRotatef(viewRotx, 1.0, 0.0, 0.0);
|
|
glRotatef(viewRoty, 0.0, 1.0, 0.0);
|
|
glRotatef(viewRotz, 0.0, 0.0, 1.0);
|
|
glTranslatef(-center[0], -center[1], 0);
|
|
|
|
glTranslatef(anchor[0], anchor[1], 0);
|
|
glBegin(GL_TRIANGLE_STRIP);
|
|
{
|
|
glColor3f(1., 0., 0.);
|
|
glTexCoord2f(0, 0);
|
|
glVertex3f(0, 0, 0);
|
|
|
|
glColor3f(0., 1., 0.);
|
|
glTexCoord2f(0, 1.0);
|
|
glVertex3f(0, texture.height, 0);
|
|
|
|
glColor3f(1., 0., 0.);
|
|
glTexCoord2f(1.0, 0);
|
|
glVertex3f(texture.width, 0, 0);
|
|
|
|
glColor3f(0., 1., 0.);
|
|
glTexCoord2f(1, 1);
|
|
glVertex3f(texture.width, texture.height, 0);
|
|
}
|
|
glEnd();
|
|
|
|
glPopMatrix();
|
|
|
|
glutSwapBuffers();
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
glutInit(&argc, argv);
|
|
|
|
glutInitWindowSize(400, 400);
|
|
glutInitDisplayMode(GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE);
|
|
|
|
if (!glutCreateWindow("Image Convolutions")) {
|
|
fprintf(stderr, "Couldn't create window!\n");
|
|
exit(1);
|
|
}
|
|
|
|
glewInit();
|
|
init();
|
|
|
|
glutReshapeFunc(reshape);
|
|
glutKeyboardFunc(keyPress);
|
|
glutSpecialFunc(special);
|
|
glutDisplayFunc(draw);
|
|
|
|
|
|
glutMainLoop();
|
|
return 0;
|
|
}
|