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