gKit2 light
Loading...
Searching...
No Matches
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
20struct 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(unsigned y= 0; y < image.height(); y++)
34 for(unsigned 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(unsigned y= 0; y < image.height(); y++)
43 for(unsigned 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
517protected:
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 unsigned 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
546int 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}
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
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:413
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 clear_drop_events()
desactive drag/drop.
Definition window.cpp:93
const std::vector< std::string > & drop_events()
drag/drop. recupere tous les fichiers.
Definition window.cpp:75
int window_height()
renvoie la hauteur de la fenetre de l'application.
Definition window.cpp:27
void clear_key_state(const SDL_Keycode key)
desactive une touche du clavier.
Definition window.cpp:46
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
bool select(Widgets &w, const char *text, const int option, int &status)
Definition widgets.cpp:173
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:40
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:110
int window_width()
renvoie la largeur de la fenetre de l'application.
Definition window.cpp:23
float global_time()
renvoie le temps ecoule depuis le lancement de l'application, en millisecondes.
Definition window.cpp:126
Image read_image(const char *filename, const bool flipY)
Definition image_io.cpp:171
bool write_image(const Image &image, const char *filename, const bool flipY)
enregistre une image au format .png
Definition image_io.cpp:225
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:10
int screenshot(const char *filename)
enregistre le contenu de la fenetre dans un fichier. doit etre de type .png / .bmp
Definition texture.cpp:178
GLuint read_program(const char *filename, const char *definitions)
Definition program.cpp:218
int program_print_errors(const GLuint program)
affiche les erreurs de compilation.
Definition program.cpp:446
int release_program(const GLuint program)
detruit les shaders et le program.
Definition program.cpp:225
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
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:152