gKit2 light
image_viewer.cpp
Go to the documentation of this file.
1 
3 
4 #include <cfloat>
5 #include <algorithm>
6 
7 #include "app.h"
8 #include "widgets.h"
9 
10 #include "files.h"
11 #include "image.h"
12 #include "image_io.h"
13 #include "image_hdr.h"
14 
15 #include "program.h"
16 #include "uniforms.h"
17 #include "texture.h"
18 
19 
20 struct ImageViewer : public App
21 {
22  ImageViewer( std::vector<const char *>& filenames ) : App(1024, 640), m_filenames()
23  {
24  for(unsigned i= 0; i < filenames.size(); i++)
25  m_filenames.push_back(filenames[i]);
26  }
27 
28  void range( const Image& image )
29  {
30  int bins[100] = {};
31  float ymin= FLT_MAX;
32  float ymax= 0.f;
33  for(int y= 0; y < image.height(); y++)
34  for(int x= 0; x < image.width(); x++)
35  {
36  Color color= image(x, y);
37  float y= color.r + color.g + color.b;
38  if(y < ymin) ymin= y;
39  if(y > ymax) ymax= y;
40  }
41 
42  for(int y= 0; y < image.height(); y++)
43  for(int x= 0; x < image.width(); x++)
44  {
45  Color color= image(x, y);
46  float y= color.r + color.g + color.b;
47  int b= (y - ymin) * 100.f / (ymax - ymin);
48  if(b >= 99) b= 99;
49  if(b < 0) b= 0;
50  bins[b]++;
51  }
52 
53  printf("range [%f..%f]\n", ymin, ymax);
54  //~ for(int i= 0; i < 100; i++)
55  //~ printf("%f ", ((float) bins[i] * 100.f / (m_width * m_height)));
56  //~ printf("\n");
57 
58  float qbins= 0;
59  for(int i= 0; i < 100; i++)
60  {
61  if(qbins > .75f)
62  {
63  m_saturation= ymin + (float) i / 100.f * (ymax - ymin);
64  m_saturation_step= m_saturation / 40.f;
65  m_saturation_max= ymax;
66  break;
67  }
68 
69  qbins= qbins + (float) bins[i] / (m_width * m_height);
70  }
71  m_compression= 2.2f;
72  }
73 
74  Image tone( const Image& image, const float saturation, const float gamma )
75  {
76  Image tmp(image.width(), image.height());
77 
78  float invg= 1 / gamma;
79  float k= 1 / std::pow(saturation, invg);
80  for(unsigned i= 0; i < image.size(); i++)
81  {
82  Color color= image(i);
83  if(std::isnan(color.r) || std::isnan(color.g) || std::isnan(color.b))
84  // marque les pixels pourris avec une couleur improbable...
85  color= Color(1, 0, 1);
86  else
87  // sinon transformation gamma rgb -> srgb
88  color= Color(k * std::pow(color.r, invg), k * std::pow(color.g, invg), k * std::pow(color.b, invg));
89 
90  tmp(i)= Color(color, 1);
91  }
92 
93  return tmp;
94  }
95 
96  Image gray( const Image& image )
97  {
98  Image tmp(image.width(), image.height());
99 
100  for(unsigned i= 0; i < image.size(); i++)
101  {
102  Color color= image(i);
103  float g= (color.r + color.b + color.b) / 3;
104  //~ float g= std::max(color.r, std::max(color.g, std::max(color.b, float(0))));
105  tmp(i)= Color(g);
106  }
107 
108  return tmp;
109  }
110 
111 
112  void title( const int index )
113  {
114  char tmp[1024];
115  sprintf(tmp, "buffer %02d: %s", index, m_filenames[index].c_str());
116  SDL_SetWindowTitle(m_window, tmp);
117  }
118 
119  Image read( const char *filename )
120  {
121  Image image;
122  if(is_pfm_image(filename))
123  image= read_image_pfm(filename);
124  else if(is_hdr_image(filename))
125  image= read_image_hdr(filename);
126  else
127  image= read_image(filename);
128 
129  return image;
130  }
131 
132  int init( )
133  {
134  m_width= 0;
135  m_height= 0;
136 
137  for(unsigned i= 0; i < m_filenames.size(); i++)
138  {
139  printf("loading buffer %u...\n", i);
140 
141  Image image= read(m_filenames[i].c_str());
142  if(image.size() == 0)
143  continue;
144 
145  m_images.push_back(image);
146  m_times.push_back(timestamp(m_filenames[i].c_str()));
147 
148  m_textures.push_back(make_texture(0, image));
149 
150  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
151  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
152 
153  m_width= std::max(m_width, image.width());
154  m_height= std::max(m_height, image.height());
155  }
156 
157  if(m_images.empty())
158  {
159  printf("no image...\n");
160  return -1;
161  }
162 
163  // change le titre de la fenetre
164  title(0);
165 
166  // redminsionne la fenetre
167  SDL_SetWindowSize(m_window, m_width, m_height);
168 
169  glGenVertexArrays(1, &m_vao);
170  glBindVertexArray(m_vao);
171 
172  m_program= read_program( smart_path("data/shaders/tonemap.glsl") );
173  program_print_errors(m_program);
174 
175  //
176  m_red= 1;
177  m_green= 1;
178  m_blue= 1;
179  m_alpha= 1;
180  m_gray= 0;
181  m_smooth= 1;
182  m_difference= 0;
183  m_compression= 2.2f;
184  m_saturation= 1;
185  m_saturation_step= 1;
186  m_saturation_max= 1000;
187  m_index= 0;
188  m_reference_index= -1;
189  m_zoom= 4;
190  m_graph= 0;
191 
192  // parametres d'exposition / compression
193  range(m_images.front());
194 
195  //
196  m_widgets= create_widgets();
197 
198  //
199  glGenSamplers(1, &m_sampler_nearest);
200  glSamplerParameteri(m_sampler_nearest, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
201  glSamplerParameteri(m_sampler_nearest, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
202  glSamplerParameteri(m_sampler_nearest, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
203  glSamplerParameteri(m_sampler_nearest, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
204 
205  // etat openGL par defaut
206  glUseProgram(0);
207  glBindVertexArray(0);
208  glBindTexture(GL_TEXTURE_2D, 0);
209 
210  glDisable(GL_DEPTH_TEST);
211  glDisable(GL_CULL_FACE);
212  return 0;
213  }
214 
215  int quit( )
216  {
217  glDeleteVertexArrays(1, &m_vao);
218  glDeleteTextures(m_textures.size(), m_textures.data());
219 
220  release_program(m_program);
221  release_widgets(m_widgets);
222  return 0;
223  }
224 
225  int render( )
226  {
227  // effacer l'image
228  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
229 
230  if(key_state('r'))
231  {
232  clear_key_state('r');
233  reload_program(m_program, smart_path("data/shaders/tonemap.glsl") );
234  program_print_errors(m_program);
235  }
236 
237  if(key_state(SDLK_LEFT))
238  {
239  clear_key_state(SDLK_LEFT);
240  m_index= (m_index -1 + m_textures.size()) % m_textures.size();
241  // change aussi le titre de la fenetre
242  title(m_index);
243  }
244 
245  if(key_state(SDLK_RIGHT))
246  {
247  clear_key_state(SDLK_RIGHT);
248  m_index= (m_index +1 + m_textures.size()) % m_textures.size();
249  // change aussi le titre de la fenetre
250  title(m_index);
251  }
252 
253  // verification de la date de l'image
254  static float last_time= 0;
255  // quelques fois par seconde, ca suffit, pas tres malin de le faire 60 fois par seconde...
256  if(global_time() > last_time + 400)
257  {
258  size_t time= timestamp(m_filenames[m_index].c_str());
259  if(time != m_times[m_index])
260  {
261  // date modifiee, recharger l'image
262  printf("reload image '%s'...\n", m_filenames[m_index].c_str());
263 
264  Image image= read(m_filenames[m_index].c_str());
265  if(image.size())
266  {
267  m_times[m_index]= time;
268  m_images[m_index]= image;
269 
270  // transfere la nouvelle version
271  glBindTexture(GL_TEXTURE_2D, m_textures[m_index]);
272  glTexImage2D(GL_TEXTURE_2D, 0,
273  GL_RGBA32F, image.width(), image.height(), 0,
274  GL_RGBA, GL_FLOAT, image.data());
275 
276  glGenerateMipmap(GL_TEXTURE_2D);
277  }
278  }
279 
280  last_time= global_time();
281  }
282 
283  if(drop_events().size())
284  {
285  for(unsigned i= 0; i < drop_events().size(); i++)
286  {
287  const char *filename= drop_events()[i].c_str();
288  if(filename && filename[0])
289  {
290  //~ printf("drop file [%d] '%s'...\n", int(m_filenames.size()), filename);
291 
292  Image image= read(filename);
293  if(image.size())
294  {
295  m_images.push_back( image );
296  m_filenames.push_back( filename );
297  m_times.push_back( timestamp(filename) );
298  m_textures.push_back( make_texture(0, image) );
299 
300  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
301  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
302  }
303  }
304 
305  printf("index %d\n", m_index);
306  for(unsigned i= 0; i < m_filenames.size(); i++)
307  printf("images[%d] '%s'\n", i, m_filenames[i].c_str());
308  }
309 
311  assert(drop_events().size() == 0);
312  }
313 
314 
315  int xmouse, ymouse;
316  unsigned int bmouse= SDL_GetMouseState(&xmouse, &ymouse);
317 
318  glBindVertexArray(m_vao);
319  glUseProgram(m_program);
320 
321  // selection des buffers + filtrage
322  GLuint sampler= 0;
323  if(!m_smooth)
324  sampler= m_sampler_nearest;
325 
326  program_use_texture(m_program, "image", 0, m_textures[m_index], sampler);
327  if(m_reference_index == -1)
328  program_use_texture(m_program, "image_next", 1, m_textures[(m_index +1) % m_textures.size()], sampler);
329  else
330  program_use_texture(m_program, "image_next", 1, m_textures[m_reference_index], sampler);
331 
332  // activer le split de l'ecran
333  if(bmouse & SDL_BUTTON(1))
334  program_uniform(m_program, "split", (int) xmouse);
335  else
336  program_uniform(m_program, "split", (int) window_width() +2);
337 
338  // parametres
339  program_uniform(m_program, "channels", Color(m_red, m_green, m_blue, m_alpha));
340  program_uniform(m_program, "gray", float(m_gray));
341  program_uniform(m_program, "difference", float(m_difference));
342  program_uniform(m_program, "compression", m_compression);
343  program_uniform(m_program, "saturation", m_saturation);
344 
345  // zoom
346  if(bmouse & SDL_BUTTON(3))
347  {
348  SDL_MouseWheelEvent wheel= wheel_event();
349  if(wheel.y != 0)
350  {
351  m_zoom= m_zoom + float(wheel.y) / 4.f;
352  if(m_zoom < .1f) m_zoom= .1f;
353  if(m_zoom > 10.f) m_zoom= 10.f;
354  }
355  }
356 
357  program_uniform(m_program, "center", vec2( float(xmouse) / float(window_width()), float(window_height() - ymouse -1) / float(window_height())));
358  if(bmouse & SDL_BUTTON(3))
359  program_uniform(m_program, "zoom", m_zoom);
360  else
361  program_uniform(m_program, "zoom", 1.f);
362 
363  // graphes / courbes
364  if(key_state('g'))
365  {
366  clear_key_state('g');
367  m_graph= (m_graph +1) % 2;
368  }
369 
370  program_uniform(m_program, "graph", int(m_graph));
371  program_uniform(m_program, "line", vec2(float(window_height() - ymouse -1) / float(window_height()), float(window_height() - ymouse -1)));
372 
373  // dessine 1 triangle plein ecran
374  glDrawArrays(GL_TRIANGLES, 0, 3);
375 
376  // actions
377  if(key_state('c'))
378  {
379  clear_key_state('c');
380 
381  // change l'extension
382  std::string file= m_filenames[m_index];
383  size_t ext= file.rfind(".");
384  if(ext != std::string::npos)
385  file= file.substr(0, ext) + "-tone.png";
386 
387  printf("writing '%s'...\n", file.c_str());
388  screenshot(file.c_str());
389  }
390 
391  begin(m_widgets);
392  value(m_widgets, "saturation", m_saturation, 0.f, m_saturation_max*10, m_saturation_step);
393  value(m_widgets, "compression", m_compression, .1f, 10.f, .1f);
394 
395  int reset= 0;
396  button(m_widgets, "reset", reset);
397  if(reset) range(m_images[m_index]);
398 
399  int reload= 0;
400  button(m_widgets, "reload", reload);
401  if(reload)
402  {
403  Image image= read(m_filenames[m_index].c_str());
404  {
405  m_images[m_index]= image;
406  m_times[m_index]= timestamp(m_filenames[m_index].c_str());
407 
408  // transfere la nouvelle version
409  glBindTexture(GL_TEXTURE_2D, m_textures[m_index]);
410  glTexImage2D(GL_TEXTURE_2D, 0,
411  GL_RGBA32F, image.width(), image.height(), 0,
412  GL_RGBA, GL_FLOAT, image.data());
413 
414  glGenerateMipmap(GL_TEXTURE_2D);
415  }
416  }
417 
418  int reference= (m_index == m_reference_index) ? 1 : 0;
419  if(button(m_widgets, "reference", reference))
420  {
421  if(reference) m_reference_index= m_index; // change de reference
422  else m_reference_index= -1; // deselectionne la reference
423  }
424 
425  int export_all= 0;
426  button(m_widgets, "export all", export_all);
427  if(export_all)
428  {
429  #pragma omp parallel for
430  for(unsigned i= 0; i < m_images.size(); i++)
431  {
432  Image image;
433  if(m_gray)
434  image= tone(gray(m_images[i]), m_saturation, m_compression);
435  else
436  image= tone(m_images[i], m_saturation, m_compression);
437 
438  char filename[1024];
439  sprintf(filename, "%s-tone.png", m_filenames[i].c_str());
440  printf("exporting '%s'...\n", filename);
441  write_image(image, filename);
442  }
443  }
444 
445  begin_line(m_widgets);
446  {
447  int px= xmouse;
448  int py= window_height() - ymouse -1;
449  float x= px / float(window_width()) * m_images[m_index].width();
450  float y= py / float(window_height()) * m_images[m_index].height();
451  Color pixel= m_images[m_index](x, y);
452  label(m_widgets, "pixel %d %d: %f %f %f", int(x), int(y), pixel.r, pixel.g, pixel.b);
453  }
454 
455  begin_line(m_widgets);
456  button(m_widgets, "R", m_red);
457  button(m_widgets, "G", m_green);
458  button(m_widgets, "B", m_blue);
459  button(m_widgets, "A", m_alpha);
460  button(m_widgets, "gray", m_gray);
461  button(m_widgets, "smooth", m_smooth);
462 
463  if(m_reference_index != -1)
464  button(m_widgets, "diff to reference", m_difference);
465 
466  begin_line(m_widgets);
467  {
468  static int list= 0;
469  button(m_widgets, "select image...", list);
470  if(list)
471  {
472  char tmp[1024];
473  for(unsigned i= 0; i < m_filenames.size(); i++)
474  {
475  begin_line(m_widgets);
476  sprintf(tmp, "[%u] %s", i, m_filenames[i].c_str());
477  select(m_widgets, tmp, i, m_index);
478  }
479  }
480  }
481 
482  end(m_widgets);
483 
484  draw(m_widgets, window_width(), window_height());
485 
486  if(key_state('s'))
487  {
488  clear_key_state('s');
489 
490  static int calls= 0;
491  screenshot("screenshot", ++calls);
492  printf("screenshot %d...\n", calls);
493  }
494 
495  if(key_state(SDLK_LCTRL) && key_state('w'))
496  {
497  clear_key_state('w');
498 
499  m_filenames.erase(m_filenames.begin() + m_index);
500  m_times.erase(m_times.begin() + m_index);
501  m_images.erase(m_images.begin() + m_index);
502  m_textures.erase(m_textures.begin() + m_index);
503  if(m_reference_index == m_index)
504  m_reference_index= -1;
505 
506  if(m_textures.empty())
507  return 0;
508 
509  m_index= m_index % int(m_textures.size());
510  // change aussi le titre de la fenetre
511  title(m_index);
512  }
513 
514  return 1;
515  }
516 
517 protected:
518  Widgets m_widgets;
519 
520  std::vector<std::string> m_filenames;
521  std::vector<size_t> m_times;
522  std::vector<Image> m_images;
523  std::vector<GLuint> m_textures;
524  int m_width, m_height;
525 
526  GLuint m_program;
527  GLuint m_vao;
528  GLuint m_sampler_nearest;
529 
530  int m_red, m_green, m_blue, m_alpha, m_gray;
531  int m_smooth;
532  int m_difference;
533 
534  float m_compression;
535  float m_saturation;
536  float m_saturation_step;
537  float m_saturation_max;
538 
539  float m_zoom;
540  int m_index;
541  int m_reference_index;
542  int m_graph;
543 };
544 
545 
546 int main( int argc, char **argv )
547 {
548  if(argc == 1)
549  {
550  printf("usage: %s image.[bmp|png|jpg|tga|hdr]\n", argv[0]);
551  return 0;
552  }
553 
554  std::vector<const char *> options(argv +1, argv + argc);
555  ImageViewer app(options);
556  app.run();
557 
558  return 0;
559 }
classe application.
Definition: app.h:20
App(const int width, const int height, const int major=3, const int minor=3, const int samples=0)
constructeur, dimensions de la fenetre et version d'openGL.
Definition: app.cpp:11
representation d'une image.
Definition: image.h:21
int height() const
renvoie la hauteur de l'image.
Definition: image.h:100
const void * data() const
renvoie un pointeur sur le stockage des couleurs des pixels.
Definition: image.h:84
unsigned size() const
renvoie le nombre de pixels de l'image.
Definition: image.h:102
int width() const
renvoie la largeur de l'image.
Definition: image.h:98
void begin(Widgets &w)
debut de la description des elements de l'interface graphique.
Definition: widgets.cpp:29
Widgets create_widgets()
cree une interface graphique. a detruire avec release_widgets( ).
Definition: widgets.cpp:12
void release_widgets(Widgets &w)
detruit l'interface graphique.
Definition: widgets.cpp:23
bool button(Widgets &w, const char *text, int &status)
Definition: widgets.cpp:155
void label(Widgets &w, const char *format,...)
cree un texte. meme fonctionnement que printf().
Definition: widgets.cpp:142
void clear_drop_events()
desactive drag/drop.
Definition: window.cpp:95
int window_height()
renvoie la hauteur de la fenetre de l'application.
Definition: window.cpp:29
bool value(Widgets &w, const char *label, int &value, const int value_min, const int value_max, const int value_step)
valeur editable par increment.
Definition: widgets.cpp:191
void clear_key_state(const SDL_Keycode key)
desactive une touche du clavier.
Definition: window.cpp:48
void printf(Text &text, const int px, const int py, const char *format,...)
affiche un texte a la position x, y. meme utilisation que printf().
Definition: text.cpp:140
const std::vector< std::string > & drop_events()
drag/drop. recupere tous les fichiers.
Definition: window.cpp:77
bool select(Widgets &w, const char *text, const int option, int &status)
Definition: widgets.cpp:173
void end(Widgets &w)
termine la description des elements de l'interface graphique.
Definition: widgets.cpp:404
int key_state(const SDL_Keycode key)
renvoie l'etat d'une touche du clavier. cf la doc SDL2 pour les codes.
Definition: window.cpp:42
void begin_line(Widgets &w)
place les prochains elements sur une nouvelle ligne.
Definition: widgets.cpp:129
SDL_MouseWheelEvent wheel_event()
renvoie le dernier evenement. etat de la molette de la souris / glisser sur le pad.
Definition: window.cpp:112
int window_width()
renvoie la largeur de la fenetre de l'application.
Definition: window.cpp:25
const char * smart_path(const char *filename)
renvoie le chemin(path) vers le fichier 'filename' apres l'avoir cherche dans un repertoire standard....
Definition: window.cpp:431
float global_time()
renvoie le temps ecoule depuis le lancement de l'application, en millisecondes.
Definition: window.cpp:128
int write_image(const Image &image, const char *filename)
enregistre une image dans un fichier png.
Definition: image_io.cpp:85
Image read_image(const char *filename)
Definition: image_io.cpp:18
Point max(const Point &a, const Point &b)
renvoie la plus grande composante de chaque point. x, y, z= max(a.x, b.x), max(a.y,...
Definition: vec.cpp:35
GLuint make_texture(const int unit, const int width, const int height, const GLenum texel_type, const GLenum data_format, const GLenum data_type)
creation de textures filtrables / mipmaps
Definition: texture.cpp:25
int screenshot(const char *filename)
enregistre le contenu de la fenetre dans un fichier. doit etre de type .png / .bmp
Definition: texture.cpp:188
GLuint read_program(const char *filename, const char *definitions)
Definition: program.cpp:204
void program_uniform(const GLuint program, const char *uniform, const std::vector< unsigned > &v)
affecte un tableau de valeurs a un uniform du shader program.
Definition: uniforms.cpp:94
int program_print_errors(const GLuint program)
affiche les erreurs de compilation.
Definition: program.cpp:432
int release_program(const GLuint program)
detruit les shaders et le program.
Definition: program.cpp:211
void program_use_texture(const GLuint program, const char *uniform, const int unit, const GLuint texture, const GLuint sampler)
configure le pipeline et le shader program pour utiliser une texture, et des parametres de filtrage,...
Definition: uniforms.cpp:198
Image read_image_pfm(const char *filename)
charge une image a partir d'un fichier .pfm.
Definition: image_hdr.cpp:103
bool is_pfm_image(const char *filename)
renvoie vrai si le nom de fichier se termine par .pfm.
Definition: image_hdr.cpp:171
bool is_hdr_image(const char *filename)
renvoie vrai si le nom de fichier se termine par .hdr.
Definition: image_hdr.cpp:10
Image read_image_hdr(const char *filename)
Definition: image_hdr.cpp:16
representation d'une couleur (rgba) transparente ou opaque.
Definition: color.h:14
int init()
a deriver pour creer les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
int quit()
a deriver pour detruire les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
int render()
a deriver pour afficher les objets. renvoie 1 pour continuer, 0 pour fermer l'application.
vecteur generique, utilitaire.
Definition: vec.h:131