1/** 2 * Convolution with GLSL. 3 * Note: uses GL_ARB_shader_objects, GL_ARB_vertex_shader, GL_ARB_fragment_shader, 4 * not the OpenGL 2.0 shader API. 5 * Author: Zack Rusin 6 */ 7 8#include <GL/glew.h> 9 10#define GL_GLEXT_PROTOTYPES 11#include "readtex.h" 12 13#include "glut_wrap.h" 14#include <stdio.h> 15#include <stdlib.h> 16#include <assert.h> 17#include <math.h> 18 19enum Filter { 20 GAUSSIAN_BLUR, 21 SHARPEN, 22 MEAN_REMOVAL, 23 EMBOSS, 24 EDGE_DETECT, 25 NO_FILTER, 26 LAST 27}; 28#define QUIT LAST 29 30struct BoundingBox { 31 float minx, miny, minz; 32 float maxx, maxy, maxz; 33}; 34struct Texture { 35 GLuint id; 36 GLfloat x; 37 GLfloat y; 38 GLint width; 39 GLint height; 40 GLenum format; 41}; 42 43static const char *textureLocation = DEMOS_DATA_DIR "girl2.rgb"; 44 45static GLfloat viewRotx = 0.0, viewRoty = 0.0, viewRotz = 0.0; 46static struct BoundingBox box; 47static struct Texture texture; 48static GLuint program; 49static GLint menuId; 50static enum Filter filter = GAUSSIAN_BLUR; 51 52 53static void checkError(int line) 54{ 55 GLenum err = glGetError(); 56 if (err) { 57 printf("GL Error %s (0x%x) at line %d\n", 58 gluErrorString(err), (int) err, line); 59 } 60} 61 62static void loadAndCompileShader(GLuint shader, const char *text) 63{ 64 GLint stat; 65 66 glShaderSource(shader, 1, (const GLchar **) &text, NULL); 67 68 glCompileShader(shader); 69 70 glGetShaderiv(shader, GL_COMPILE_STATUS, &stat); 71 if (!stat) { 72 GLchar log[1000]; 73 GLsizei len; 74 glGetShaderInfoLog(shader, 1000, &len, log); 75 fprintf(stderr, "Problem compiling shader: %s\n", log); 76 exit(1); 77 } 78 else { 79 printf("Shader compiled OK\n"); 80 } 81} 82 83static void readShader(GLuint shader, const char *filename) 84{ 85 const int max = 100*1000; 86 int n; 87 char *buffer = (char*) malloc(max); 88 FILE *f = fopen(filename, "r"); 89 if (!f) { 90 fprintf(stderr, "Unable to open shader file %s\n", filename); 91 exit(1); 92 } 93 94 n = fread(buffer, 1, max, f); 95 printf("Read %d bytes from shader file %s\n", n, filename); 96 if (n > 0) { 97 buffer[n] = 0; 98 loadAndCompileShader(shader, buffer); 99 } 100 101 fclose(f); 102 free(buffer); 103} 104 105 106static void 107checkLink(GLuint prog) 108{ 109 GLint stat; 110 glGetProgramiv(prog, GL_LINK_STATUS, &stat); 111 if (!stat) { 112 GLchar log[1000]; 113 GLsizei len; 114 glGetProgramInfoLog(prog, 1000, &len, log); 115 fprintf(stderr, "Linker error:\n%s\n", log); 116 } 117 else { 118 fprintf(stderr, "Link success!\n"); 119 } 120} 121 122static void fillConvolution(GLint *k, 123 GLfloat *scale, 124 GLfloat *color) 125{ 126 switch(filter) { 127 case GAUSSIAN_BLUR: 128 k[0] = 1; k[1] = 2; k[2] = 1; 129 k[3] = 2; k[4] = 4; k[5] = 2; 130 k[6] = 1; k[7] = 2; k[8] = 1; 131 132 *scale = 1./16.; 133 break; 134 case SHARPEN: 135 k[0] = 0; k[1] = -2; k[2] = 0; 136 k[3] = -2; k[4] = 11; k[5] = -2; 137 k[6] = 0; k[7] = -2; k[8] = 0; 138 139 *scale = 1./3.; 140 break; 141 case MEAN_REMOVAL: 142 k[0] = -1; k[1] = -1; k[2] = -1; 143 k[3] = -1; k[4] = 9; k[5] = -1; 144 k[6] = -1; k[7] = -1; k[8] = -1; 145 146 *scale = 1./1.; 147 break; 148 case EMBOSS: 149 k[0] = -1; k[1] = 0; k[2] = -1; 150 k[3] = 0; k[4] = 4; k[5] = 0; 151 k[6] = -1; k[7] = 0; k[8] = -1; 152 153 *scale = 1./1.; 154 color[0] = 0.5; 155 color[1] = 0.5; 156 color[2] = 0.5; 157 color[3] = 0.5; 158 break; 159 case EDGE_DETECT: 160 k[0] = 1; k[1] = 1; k[2] = 1; 161 k[3] = 0; k[4] = 0; k[5] = 0; 162 k[6] = -1; k[7] = -1; k[8] = -1; 163 164 *scale = 1.; 165 color[0] = 0.5; 166 color[1] = 0.5; 167 color[2] = 0.5; 168 color[3] = 0.5; 169 break; 170 case NO_FILTER: 171 k[0] = 0; k[1] = 0; k[2] = 0; 172 k[3] = 0; k[4] = 1; k[5] = 0; 173 k[6] = 0; k[7] = 0; k[8] = 0; 174 175 *scale = 1.; 176 break; 177 default: 178 assert(!"Unhandled switch value"); 179 } 180} 181 182static void setupConvolution(void) 183{ 184 GLint *kernel = (GLint*)malloc(sizeof(GLint) * 9); 185 GLfloat scale = 0.0; 186 GLfloat *vecKer = (GLfloat*)malloc(sizeof(GLfloat) * 9 * 4); 187 GLuint loc; 188 GLuint i; 189 GLfloat baseColor[4]; 190 baseColor[0] = 0; 191 baseColor[1] = 0; 192 baseColor[2] = 0; 193 baseColor[3] = 0; 194 195 fillConvolution(kernel, &scale, baseColor); 196 /*vector of 4*/ 197 for (i = 0; i < 9; ++i) { 198 vecKer[i*4 + 0] = kernel[i]; 199 vecKer[i*4 + 1] = kernel[i]; 200 vecKer[i*4 + 2] = kernel[i]; 201 vecKer[i*4 + 3] = kernel[i]; 202 } 203 204 loc = glGetUniformLocationARB(program, "KernelValue"); 205 glUniform4fv(loc, 9, vecKer); 206 loc = glGetUniformLocationARB(program, "ScaleFactor"); 207 glUniform4f(loc, scale, scale, scale, scale); 208 loc = glGetUniformLocationARB(program, "BaseColor"); 209 glUniform4f(loc, baseColor[0], baseColor[1], 210 baseColor[2], baseColor[3]); 211 212 free(vecKer); 213 free(kernel); 214} 215 216static void createProgram(const char *vertProgFile, 217 const char *fragProgFile) 218{ 219 GLuint fragShader = 0, vertShader = 0; 220 221 program = glCreateProgram(); 222 if (vertProgFile) { 223 vertShader = glCreateShader(GL_VERTEX_SHADER); 224 readShader(vertShader, vertProgFile); 225 glAttachShader(program, vertShader); 226 } 227 228 if (fragProgFile) { 229 fragShader = glCreateShader(GL_FRAGMENT_SHADER); 230 readShader(fragShader, fragProgFile); 231 glAttachShader(program, fragShader); 232 } 233 234 glLinkProgram(program); 235 checkLink(program); 236 237 glUseProgram(program); 238 239 /* 240 assert(glIsProgram(program)); 241 assert(glIsShader(fragShader)); 242 assert(glIsShader(vertShader)); 243 */ 244 245 checkError(__LINE__); 246 {/*texture*/ 247 GLuint texLoc = glGetUniformLocationARB(program, "srcTex"); 248 glUniform1iARB(texLoc, 0); 249 } 250 {/*setup offsets */ 251 float offsets[] = { 1.0 / texture.width, 1.0 / texture.height, 252 0.0 , 1.0 / texture.height, 253 -1.0 / texture.width, 1.0 / texture.height, 254 1.0 / texture.width, 0.0, 255 0.0 , 0.0, 256 -1.0 / texture.width, 0.0, 257 1.0 / texture.width, -1.0 / texture.height, 258 0.0 , -1.0 / texture.height, 259 -1.0 / texture.width, -1.0 / texture.height }; 260 GLuint offsetLoc = glGetUniformLocationARB(program, "Offset"); 261 glUniform2fv(offsetLoc, 9, offsets); 262 } 263 setupConvolution(); 264 265 checkError(__LINE__); 266} 267 268 269static void readTexture(const char *filename) 270{ 271 GLubyte *data; 272 273 texture.x = 0; 274 texture.y = 0; 275 276 glGenTextures(1, &texture.id); 277 glBindTexture(GL_TEXTURE_2D, texture.id); 278 glTexParameteri(GL_TEXTURE_2D, 279 GL_TEXTURE_MIN_FILTER, GL_NEAREST); 280 glTexParameteri(GL_TEXTURE_2D, 281 GL_TEXTURE_MAG_FILTER, GL_NEAREST); 282 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 283 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 284 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 285 data = LoadRGBImage(filename, &texture.width, &texture.height, 286 &texture.format); 287 if (!data) { 288 printf("Error: couldn't load texture image '%s'\n", filename); 289 exit(1); 290 } 291 printf("Texture %s (%d x %d)\n", 292 filename, texture.width, texture.height); 293 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 294 texture.width, texture.height, 0, texture.format, 295 GL_UNSIGNED_BYTE, data); 296} 297 298static void menuSelected(int entry) 299{ 300 switch (entry) { 301 case QUIT: 302 exit(0); 303 break; 304 default: 305 filter = (enum Filter)entry; 306 } 307 setupConvolution(); 308 309 glutPostRedisplay(); 310} 311 312static void menuInit(void) 313{ 314 menuId = glutCreateMenu(menuSelected); 315 316 glutAddMenuEntry("Gaussian blur", GAUSSIAN_BLUR); 317 glutAddMenuEntry("Sharpen", SHARPEN); 318 glutAddMenuEntry("Mean removal", MEAN_REMOVAL); 319 glutAddMenuEntry("Emboss", EMBOSS); 320 glutAddMenuEntry("Edge detect", EDGE_DETECT); 321 glutAddMenuEntry("None", NO_FILTER); 322 323 glutAddMenuEntry("Quit", QUIT); 324 325 glutAttachMenu(GLUT_RIGHT_BUTTON); 326} 327 328static void init(void) 329{ 330 if (!glutExtensionSupported("GL_ARB_shader_objects") || 331 !glutExtensionSupported("GL_ARB_vertex_shader") || 332 !glutExtensionSupported("GL_ARB_fragment_shader")) { 333 fprintf(stderr, "Sorry, this program requires GL_ARB_shader_objects, GL_ARB_vertex_shader, and GL_ARB_fragment_shader\n"); 334 exit(1); 335 } 336 337 fprintf(stderr, "GL_RENDERER = %s\n", (char *) glGetString(GL_RENDERER)); 338 fprintf(stderr, "GL_VERSION = %s\n", (char *) glGetString(GL_VERSION)); 339 fprintf(stderr, "GL_VENDOR = %s\n", (char *) glGetString(GL_VENDOR)); 340 341 menuInit(); 342 readTexture(textureLocation); 343 createProgram("convolution.vert", "convolution.frag"); 344 345 glEnable(GL_TEXTURE_2D); 346 glClearColor(1.0, 1.0, 1.0, 1.0); 347 /*glShadeModel(GL_SMOOTH);*/ 348 glShadeModel(GL_FLAT); 349} 350 351static void reshape(int width, int height) 352{ 353 glViewport(0, 0, width, height); 354 glMatrixMode(GL_PROJECTION); 355 glLoadIdentity(); 356 box.minx = 0; 357 box.maxx = width; 358 box.miny = 0; 359 box.maxy = height; 360 box.minz = 0; 361 box.maxz = 1; 362 glOrtho(box.minx, box.maxx, box.miny, box.maxy, -999999, 999999); 363 glMatrixMode(GL_MODELVIEW); 364} 365 366static void keyPress(unsigned char key, int x, int y) 367{ 368 switch(key) { 369 case 27: 370 exit(0); 371 default: 372 break; 373 } 374 glutPostRedisplay(); 375} 376 377static void 378special(int k, int x, int y) 379{ 380 switch (k) { 381 case GLUT_KEY_UP: 382 viewRotx += 2.0; 383 break; 384 case GLUT_KEY_DOWN: 385 viewRotx -= 2.0; 386 break; 387 case GLUT_KEY_LEFT: 388 viewRoty += 2.0; 389 break; 390 case GLUT_KEY_RIGHT: 391 viewRoty -= 2.0; 392 break; 393 default: 394 return; 395 } 396 glutPostRedisplay(); 397} 398 399 400static void draw(void) 401{ 402 GLfloat center[2]; 403 GLfloat anchor[2]; 404 405 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 406 407 glLoadIdentity(); 408 glPushMatrix(); 409 410 center[0] = box.maxx/2; 411 center[1] = box.maxy/2; 412 anchor[0] = center[0] - texture.width/2; 413 anchor[1] = center[1] - texture.height/2; 414 415 glTranslatef(center[0], center[1], 0); 416 glRotatef(viewRotx, 1.0, 0.0, 0.0); 417 glRotatef(viewRoty, 0.0, 1.0, 0.0); 418 glRotatef(viewRotz, 0.0, 0.0, 1.0); 419 glTranslatef(-center[0], -center[1], 0); 420 421 glTranslatef(anchor[0], anchor[1], 0); 422 glBegin(GL_TRIANGLE_STRIP); 423 { 424 glColor3f(1., 0., 0.); 425 glTexCoord2f(0, 0); 426 glVertex3f(0, 0, 0); 427 428 glColor3f(0., 1., 0.); 429 glTexCoord2f(0, 1.0); 430 glVertex3f(0, texture.height, 0); 431 432 glColor3f(1., 0., 0.); 433 glTexCoord2f(1.0, 0); 434 glVertex3f(texture.width, 0, 0); 435 436 glColor3f(0., 1., 0.); 437 glTexCoord2f(1, 1); 438 glVertex3f(texture.width, texture.height, 0); 439 } 440 glEnd(); 441 442 glPopMatrix(); 443 444 glutSwapBuffers(); 445} 446 447int main(int argc, char **argv) 448{ 449 glutInit(&argc, argv); 450 451 glutInitWindowSize(400, 400); 452 glutInitDisplayMode(GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE); 453 454 if (!glutCreateWindow("Image Convolutions")) { 455 fprintf(stderr, "Couldn't create window!\n"); 456 exit(1); 457 } 458 459 glewInit(); 460 init(); 461 462 glutReshapeFunc(reshape); 463 glutKeyboardFunc(keyPress); 464 glutSpecialFunc(special); 465 glutDisplayFunc(draw); 466 467 468 glutMainLoop(); 469 return 0; 470} 471