Hermes2D  2.0
view.cpp
1 // This file is part of Hermes2D.
2 //
3 // Hermes2D is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 2 of the License, or
6 // (at your option) any later version.
7 //
8 // Hermes2D is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Hermes2D. If not, see <http://www.gnu.org/licenses/>.
15 
16 #ifndef NOGLUT
17 
18 #include <sstream>
19 #include <GL/glew.h>
20 #include <GL/freeglut.h>
21 #ifndef WIN32
22 # include <sys/time.h>
23 #endif
24 
25 #include "global.h"
26 #include "view_support.h"
27 #include "view.h"
28 #include "solution.h"
29 #include "view_data.cpp"
30 
31 namespace Hermes
32 {
33  namespace Hermes2D
34  {
35  namespace Views
36  {
37 #define HERMES_WAIT_CLOSE_MSG "close all views to continue"
38 #define HERMES_WAIT_KEYPRESS_MSG "press spacebar to continue"
39 
40  int View::screenshot_no = 1;
41 
42  void View::init()
43  {
44  jitter_x = jitter_y = 0.0;
45  dragging = scaling = false;
46  hq_frame = false;
47  frame_ready = false;
48  range_auto = true;
49  range_min = 0;
50  range_max = 1;
51  pal_type = H2DV_PT_HUESCALE;
52  pal_steps = 50;
53  pal_filter = GL_NEAREST;
54  margin = 15;
55  b_scale = true;
56  b_help = false;
57  scale_focused = scale_dragging = false;
58  pos_horz = pos_vert = 0;
59  scale_x = scale_y = labels_width = 0;
60  scale_width = 16;
61  scale_height = 320;
62  scale_numticks = 9;
63  strcpy(scale_fmt, "%.3g");
64  scale_fixed_width = -1;
65  want_screenshot = false;
66 
68  memset(rendering_frames, 0, FPS_FRAME_SIZE * sizeof(double));
69  }
70 
71  View::View(const char* title, WinGeom* wg) :
72  view_not_reset(true),
73  vertices_min_x(0),
74  vertices_max_x(0),
75  vertices_min_y(0),
76  vertices_max_y(0),
77  title(title),
78  output_id(-1),
79  gl_pallete_tex_id(0)
80  {
81  if(wg == NULL)
82  {
83  output_x = H2D_DEFAULT_X_POS;
84  output_y = H2D_DEFAULT_Y_POS;
85  output_width = H2D_DEFAULT_WIDTH;
86  output_height = H2D_DEFAULT_HEIGHT;
87  }
88  else
89  {
90  output_x = wg->x;
91  output_y = wg->y;
92  output_width = wg->width;
93  output_height = wg->height;
94  }
95 
96  init();
97  }
98 
99  View::View(char* title, WinGeom* wg) :
100  view_not_reset(true),
101  vertices_min_x(0),
102  vertices_max_x(0),
103  vertices_min_y(0),
104  vertices_max_y(0),
105  title(title),
106  output_id(-1),
107  gl_pallete_tex_id(0)
108  {
109  if(wg == NULL)
110  {
111  output_x = H2D_DEFAULT_X_POS;
112  output_y = H2D_DEFAULT_Y_POS;
113  output_width = H2D_DEFAULT_WIDTH;
114  output_height = H2D_DEFAULT_HEIGHT;
115  }
116  else
117  {
118  output_x = wg->x;
119  output_y = wg->y;
120  output_width = wg->width;
121  output_height = wg->height;
122  }
123 
124  init();
125  }
126 
127  View::~View()
128  {
129  if(output_id >= 0)
130  close();
131  }
132 
133  int View::create()
134  {
135  if(output_id < 0) //does not need thread protection because it is set up by a callback during call of add_view
136  return add_view(this, output_x, output_y, output_width, output_height, title.c_str());
137  else
138  return output_id;
139  }
140 
141  void View::close()
142  {
143  if(output_id >= 0) //does not need thread protection because it is set up by a callback during call of add_view
144  remove_view(output_id);
145  }
146 
147  void View::wait(const char* text)
148  {
149  wait(HERMES_WAIT_CLOSE, text);
150  }
151 
152  void View::wait(ViewWaitEvent wait_event, const char* text)
153  {
154  //prepare message
155  std::stringstream str;
156  str << " << ";
157  if(text != NULL)
158  str << text;
159  else
160  {
161  switch(wait_event)
162  {
163  case HERMES_WAIT_CLOSE: str << HERMES_WAIT_CLOSE_MSG; break;
164  case HERMES_WAIT_KEYPRESS: str << HERMES_WAIT_KEYPRESS_MSG; break;
165  default: throw Hermes::Exceptions::Exception("Unknown wait event"); break;
166  }
167  }
168  str << " >>" << std::endl;
169 
170  //do something
171  switch(wait_event)
172  {
173  case HERMES_WAIT_CLOSE: wait_for_all_views_close(str.str().c_str()); break;
174  case HERMES_WAIT_KEYPRESS: wait_for_any_key(str.str().c_str()); break;
175  default: throw Hermes::Exceptions::Exception("Unknown wait event"); break;
176  }
177  }
178 
180  {
181  bool do_refresh = true;
182  view_sync.enter();
183  if(output_id < 0)
184  do_refresh = false;
185  view_sync.leave();
186  if(do_refresh)
187  refresh_view(output_id);
188  }
189 
190  void View::reset_view(bool force_reset)
191  {
192  if(force_reset || view_not_reset)
193  {
194  double mesh_width = vertices_max_x - vertices_min_x;
195  double mesh_height = vertices_max_y - vertices_min_y;
196 
197  double usable_width = output_width - 2*margin - lspace - rspace;
198  double usable_height = output_height - 2*margin;
199 
200  // align in the proper direction
201  if(usable_width / usable_height < mesh_width / mesh_height)
202  scale = usable_width / mesh_width;
203  else
204  scale = usable_height / mesh_height;
205  log_scale = log(scale) / log(H2DV_SCALE_LOG_BASE);
206 
207  // center of the mesh
208  trans_x = -scale * (vertices_min_x + vertices_max_x) / 2;
209  trans_y = -scale * (vertices_min_y + vertices_max_y) / 2;
210 
211  view_not_reset = false;
212  }
213  }
214 
215  void View::on_create(int output_id)
216  {
217  this->output_id = output_id; //does not need thread protection because it is during execution of add_view
219  set_palette_filter(pal_filter == GL_LINEAR);
220  }
221 
222  void View::on_close()
223  {
224  view_sync.enter();
225  output_id = -1;
226  view_sync.leave();
227  }
228 
230  {
231  glClearColor(1.0, 1.0, 1.0, 0.0);
232  glClear(GL_COLOR_BUFFER_BIT);
233  }
234 
235  void View::pre_display()
236  {
237  //this->info("display: lock");
238  view_sync.enter();
239 
240  //begin time measuring
241  double time_start = get_tick_count();
242 
243  //antialising is supported through accumulation buffer (FIXME: use ARB_MULTISAMPLE if available)
244  if(!hq_frame)
245  {
247  this->on_display();
248  }
249  else
250  {
251  display_antialiased();
252  hq_frame = false;
253  }
254 
255  if(b_help) draw_help();
256  else if(b_scale) scale_dispatch();
257 
258  //wait to finish
259  glFinish();
260 
261  //calculate statistics
263  rendering_frames_top = (rendering_frames_top + 1) % FPS_FRAME_SIZE;
264 
265  if(want_screenshot)
266  {
267  glReadBuffer(GL_BACK_LEFT);
268  save_screenshot_internal(screenshot_filename.c_str());
269  want_screenshot = false;
270  }
271 
272  glutSwapBuffers();
273 
274  //frame synchronization
275  frame_ready = true;
277  view_sync.leave();
278  }
279 
280  static float jitter16[16][2] =
281  {
282  { 0.4375, 0.4375 }, { 0.1875, 0.5625 },
283  { 0.9375, 1.1875 }, { 0.4375, 0.9375-1 },
284  { 0.6875, 0.5625 }, { 0.1875, 0.0625 },
285  { 0.6875, 0.3125 }, { 0.1875, 0.3125 },
286  { 0.4375, 0.1875 }, { 0.9375-1, 0.4375 },
287  { 0.6875, 0.8125 }, { 0.4375, 0.6875 },
288  { 0.6875, 0.0625 }, { 0.9375, 0.9375 },
289  { 1.1875, 0.8125 }, { 0.9375, 0.6875 }
290  };
291 
292  void View::display_antialiased()
293  {
294  glClear(GL_ACCUM_BUFFER_BIT);
295  for (int i = 0; i < 16; i++)
296  {
297  jitter_x = jitter16[i][0];
298  jitter_y = jitter16[i][1];
299  set_ortho_projection();
301  on_display();
302  glAccum(GL_ACCUM, 1.0 / 16);
303  }
304  glAccum(GL_RETURN, 1.0);
305  jitter_x = jitter_y = 0.0;
306  }
307 
308  void View::set_ortho_projection(bool no_jitter)
309  {
310  double jx = no_jitter ? 0.0 : jitter_x;
311  double jy = no_jitter ? 0.0 : jitter_y;
312 
313  glMatrixMode(GL_PROJECTION);
314  glLoadIdentity();
315  glOrtho(jx, output_width + jx, output_height-1 + jy, -1 + jy, -10, 10);
316 
317  glMatrixMode(GL_MODELVIEW);
318  glLoadIdentity();
319  }
320 
321  void View::set_3d_projection(int fov, double znear, double zfar)
322  {
323  double top = znear * tan((double) fov / 2.0 / 180.0 * M_PI);
324  double right = (double) output_width / output_height * top;
325  double left = -right;
326  double bottom = -top;
327  double offsx = (right - left) / output_width * jitter_x;
328  double offsy = (top - bottom) / output_height * jitter_y;
329 
330  glMatrixMode(GL_PROJECTION);
331  glLoadIdentity();
332  glFrustum(left - offsx, right - offsx, bottom - offsy, top - offsy, znear, zfar);
333  }
334 
336  {
337  //calculate FPS
338  double frame_time_sum = 0;
339  for(int i = 0; i < FPS_FRAME_SIZE; i++)
340  frame_time_sum += rendering_frames[i];
341 
342  //prepare text
343  unsigned char buffer[128];
344  sprintf((char*)buffer, "avg. frame: %.1f ms", (float)(frame_time_sum / FPS_FRAME_SIZE));
345 
346  //prepare environment
347  void* font = GLUT_BITMAP_HELVETICA_10;
348  int width_px = glutBitmapLength(font, buffer);
349  int height_px = glutBitmapHeight(font);
350  int edge_thickness = 2;
351  set_ortho_projection(false);
352 
353  //render background
354  glEnable(GL_BLEND);
355  glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
356  glBegin(GL_QUADS);
357  glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
358  glVertex2i(output_width - (width_px + 2*edge_thickness), 0);
359  glVertex2i(output_width, 0);
360  glVertex2i(output_width, height_px + 2*edge_thickness);
361  glVertex2i(output_width - (width_px + 2*edge_thickness), height_px + 2*edge_thickness);
362  glEnd();
363 
364  // render text
365  glDisable(GL_BLEND);
366  glColor3f(1.0f, 0.0f, 0.0f);
367  glRasterPos2i(output_width - (width_px + edge_thickness), edge_thickness + height_px);
368  // iming information is printed into the image.
369  glutBitmapString(font, buffer);
370  }
371 
372  void View::on_reshape(int width, int height)
373  {
374  glViewport(0, 0, width, height);
375 
376  output_width = width;
377  output_height = height;
378  update_layout();
379  }
380 
381  void View::on_mouse_move(int x, int y)
382  {
383  if(dragging)
384  {
385  trans_x += (x - mouse_x);
386  trans_y += (mouse_y - y);
387  refresh();
388  }
389  else if(scaling)
390  {
391  log_scale += (mouse_y - y);
392  scale = pow(H2DV_SCALE_LOG_BASE, log_scale);
393  trans_x = scx - objx * scale - center_x;
394  trans_y = center_y - objy * scale - scy;
395  refresh();
396  }
397  else if(scale_dragging)
398  {
399  int oldv = pos_vert, oldh = pos_horz;
400  pos_horz = (x > output_width/2);
401  pos_vert = (y < output_height/2);
402  if(pos_horz != oldh || pos_vert != oldv)
403  {
404  update_layout();
405  refresh();
406  }
407  }
408  else
409  {
410  bool oldf = scale_focused;
411  scale_focused = (x >= scale_x && x <= scale_x + scale_width &&
412  y >= scale_y && y <= scale_y + scale_height);
413  if(oldf != scale_focused)
414  refresh();
415  }
416  mouse_x = x;
417  mouse_y = y;
418  }
419 
420  void View::on_left_mouse_down(int x, int y)
421  {
422  if(scale_focused)
423  scale_dragging = true;
424  else
425  dragging = true;
426  scaling = false;
427  mouse_x = x;
428  mouse_y = y;
429  }
430 
431  void View::on_left_mouse_up(int x, int y)
432  {
433  scaling = dragging = scale_dragging = false;
434  on_mouse_move(x, y);
435  }
436 
437  void View::on_right_mouse_down(int x, int y)
438  {
439  scaling = true;
440  dragging = false;
441  scx = x;
442  scy = y;
443  objx = (x - center_x - trans_x) / scale;
444  objy = (center_y - y - trans_y) / scale;
445  mouse_x = x;
446  mouse_y = y;
447  }
448 
449  void View::on_right_mouse_up(int x, int y)
450  {
451  scaling = dragging = false;
452  }
453 
454  void View::on_key_down(unsigned char key, int x, int y)
455  {
456  const char *file_name = NULL;
457 
458  switch (key)
459  {
460  case 'h':
461  {
462  hq_frame = true;
463  refresh();
464  break;
465  }
466 
467  case 27:
468  case 'q':
469  {
470  close();
471  break;
472  }
473 
474  case 's':
475  {
476  const char *file_name = get_screenshot_file_name();
477  glReadBuffer(GL_FRONT_LEFT);
478  save_screenshot_internal(file_name);
479  break;
480  }
481 
482  case 'p':
483  {
484  // There used to be a type called default, but it caused some weird behavior.
485  /*
486  switch(pal_type)
487  {
488  case H2DV_PT_DEFAULT: pal_type = H2DV_PT_HUESCALE; break;
489  case H2DV_PT_HUESCALE: pal_type = H2DV_PT_GRAYSCALE; break;
490  case H2DV_PT_GRAYSCALE: pal_type = H2DV_PT_INVGRAYSCALE; break;
491  case H2DV_PT_INVGRAYSCALE: pal_type = H2DV_PT_DEFAULT; break;
492  default: throw Hermes::Exceptions::Exception("Invalid palette type");
493  }
494  */
495  switch(pal_type)
496  {
497  case H2DV_PT_HUESCALE: pal_type = H2DV_PT_GRAYSCALE; break;
498  case H2DV_PT_GRAYSCALE: pal_type = H2DV_PT_INVGRAYSCALE; break;
499  case H2DV_PT_INVGRAYSCALE: pal_type = H2DV_PT_HUESCALE; break;
500  default: throw Hermes::Exceptions::Exception("Invalid palette type");
501  }
503  refresh();
504  break;
505  }
506 
507  default:
508  view_sync.enter();
510  view_sync.leave();
511  break;
512  }
513  }
514 
515  void View::on_special_key(int key, int x, int y)
516  {
517  switch (key)
518  {
519  case GLUT_KEY_F1:
520  b_help = !b_help;
521  refresh();
522  break;
523  }
524  }
525 
526  void View::wait_for_keypress(const char* text)
527  {
528  this->warn("Function View::wait_for_keypress deprecated: use View::wait instead");
530  }
531 
532  void View::wait_for_close()
533  {
534  view_sync.enter();
535  if(output_id >= 0)
537  view_sync.leave();
538  }
539 
540  void View::wait_for_draw()
541  {
542  // For some reason, this function removes the signal handlers. So we just
543  // remember them and restore them. Unfortunately, this doesn't work for some
544  // reason:
545  //sighandler_t old_segv, old_abrt;
546  //old_segv = signal(SIGSEGV, SIG_DFL);
547  //old_abrt = signal(SIGABRT, SIG_DFL);
548 
549  view_sync.enter();
550  if(output_id >= 0 && !frame_ready)
552  view_sync.leave();
553 
554  // Restore the old signal handlers -- doesn't work for some reason:
555  //signal(SIGSEGV, old_segv);
556  //signal(SIGABRT, old_abrt);
557  // So we just restore it by calling the original handler:
558  }
559 
561  {
562 #ifdef WIN32
563  LARGE_INTEGER freq, ticks;
564  QueryPerformanceFrequency(&freq);
565  QueryPerformanceCounter(&ticks);
566  return (1000.0 * (double)ticks.QuadPart) / (double)freq.QuadPart;
567 #else
568  struct timeval tv;
569  gettimeofday(&tv, NULL);
570  return (double) tv.tv_sec * 1000 + (double) tv.tv_usec / 1000;
571 #endif
572  }
573 
574  void View::set_title(const char* title)
575  {
576  bool do_set_title = true;
577 
578  // Always set the title property.
579  this->title = title;
580 
581  view_sync.enter();
582  if(output_id < 0)
583  // If the window does not exist, do nothing else and wait until it is created.
584  do_set_title = false;
585 
586  view_sync.leave();
587 
588  // If the window already exists, show the new title in its header.
589  if(do_set_title)
590  set_view_title(output_id, title);
591  }
592 
593  void View::get_palette_color(double x, float* gl_color)
594  {
595  if(pal_type == H2DV_PT_HUESCALE || pal_type == H2DV_PT_DEFAULT) { //default color
596  if(x < 0.0) x = 0.0;
597  else if(x > 1.0) x = 1.0;
598  x *= num_pal_entries;
599  int n = (int)x;
600  gl_color[0] = palette_data[n][0];
601  gl_color[1] = palette_data[n][1];
602  gl_color[2] = palette_data[n][2];
603  }
604  else if(pal_type == H2DV_PT_GRAYSCALE)
605  gl_color[0] = gl_color[1] = gl_color[2] = (float)x;
606  else if(pal_type == H2DV_PT_INVGRAYSCALE)
607  gl_color[0] = gl_color[1] = gl_color[2] = (float)(1.0 - x);
608  else
609  gl_color[0] = gl_color[1] = gl_color[2] = 1.0f;
610  }
611 
612  void View::set_num_palette_steps(int num)
613  {
614  if(num < 2) num = 2;
615  if(num > 256) num = 256;
616  pal_steps = num;
617  update_tex_adjust();
618 
619  view_sync.enter();
620  if(output_id >= 0)
622  view_sync.leave();
623 
624  refresh();
625  }
626 
628  {
629  int i;
630  unsigned char palette[256][3];
631  for (i = 0; i < pal_steps; i++)
632  {
633  float gl_color[3];
634  get_palette_color((double) i / pal_steps, gl_color);
635  palette[i][0] = (unsigned char) (gl_color[0] * 255);
636  palette[i][1] = (unsigned char) (gl_color[1] * 255);
637  palette[i][2] = (unsigned char) (gl_color[2] * 255);
638  }
639  for (i = pal_steps; i < 256; i++)
640  memcpy(palette[i], palette[pal_steps-1], 3);
641 
642  if(gl_pallete_tex_id == 0)
643  glGenTextures(1, &gl_pallete_tex_id);
644  glBindTexture(GL_TEXTURE_1D, gl_pallete_tex_id);
645  glTexImage1D(GL_TEXTURE_1D, 0, 3, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, palette);
646  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP);
647  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
648  }
649 
650  void View::set_palette_filter(bool linear)
651  {
652  view_sync.enter(); //lock to prevent simultaneuous rendering
653 
654  pal_filter = linear ? GL_LINEAR : GL_NEAREST;
655 
656  if(gl_pallete_tex_id == 0)
657  glGenTextures(1, &gl_pallete_tex_id);
658  glBindTexture(GL_TEXTURE_1D, gl_pallete_tex_id);
659  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, pal_filter);
660  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, pal_filter);
661  update_tex_adjust();
662 
663  view_sync.leave(); //unlock
664 
665  refresh();
666  }
667 
668  void View::set_palette(ViewPaletteType type)
669  {
670  view_sync.enter();
671  pal_type = type;
672  if(output_id >= 0)
674  view_sync.leave();
675 
676  //redisplay
677  refresh();
678  }
679 
680  void View::update_tex_adjust()
681  {
682  if(pal_filter == GL_LINEAR)
683  {
684  tex_scale = (double) (pal_steps-1) / 256.0;
685  tex_shift = 0.5 / 256.0;
686  }
687  else
688  {
689  tex_scale = (double) pal_steps / 256.0;
690  tex_shift = 0.0;
691  }
692  }
693 
694  void View::set_min_max_range(double min, double max)
695  {
696  if(max < min)
697  {
698  std::swap(min, max);
699  this->warn("Upper bound set below the lower bound: reversing to (%f, %f).", min, max);
700  }
701  view_sync.enter();
702  range_min = min;
703  range_max = max;
704  range_auto = false;
705  if(output_id >= 0)
706  update_layout();
707  view_sync.leave();
708  refresh();
709  }
710 
711  void View::auto_min_max_range()
712  {
713  view_sync.enter();
714  range_auto = true;
715  if(output_id >= 0)
716  update_layout();
717  view_sync.leave();
718 
719  refresh();
720  }
721 
722  void View::get_min_max_range(double& min, double& max)
723  {
724  view_sync.enter();
725  min = range_min;
726  max = range_max;
727  view_sync.leave();
728  }
729 
730  void View::draw_text(double x, double y, const char* text, int align)
731  {
732  void* font = GLUT_BITMAP_9_BY_15;
733  if(align > -1)
734  {
735  int width = glutBitmapLength(font, (const unsigned char*) text);
736  if(align == 1) x -= width; // align right
737  else x -= (double) width / 2; // center
738  }
739  y += 5; //(double) glutBitmapHeight(font) / 2 - 1;
740 
741  glDisable(GL_TEXTURE_1D);
742  glDisable(GL_LIGHTING);
743 
744  glRasterPos2d((int) (x + 0.5), (int) (y + 0.5));
745  glutBitmapString(font, (const unsigned char*) text);
746  }
747 
748  int View::get_text_width(const char* text)
749  {
750  void* font = GLUT_BITMAP_9_BY_15;
751  return glutBitmapLength(font, (const unsigned char*) text);
752  }
753 
754  void View::draw_help()
755  {
756  view_sync.enter();
757  set_ortho_projection(true);
758  glDisable(GL_DEPTH_TEST);
759  glDisable(GL_LIGHTING);
760  glDisable(GL_TEXTURE_1D);
761  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
762 
763  const char* text = get_help_text();
764 
765  int n = 1;
766  for (const char* p = text; *p; p++)
767  if(*p == '\n') n++;
768 
769  int width = get_text_width(text);
770  int height = n * glutBitmapHeight(GLUT_BITMAP_9_BY_15);
771  int x = 10, y = 10, b = 6;
772 
773  glEnable(GL_BLEND);
774  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
775  glColor4f(1.0f, 1.0f, 1.0f, 0.65f);
776  glBegin(GL_QUADS);
777  glVertex2d(x, y + height + 2*b);
778  glVertex2d(x + width + 2*b, y + height + 2*b);
779  glVertex2d(x + width + 2*b, y);
780  glVertex2d(x, y);
781  glEnd();
782 
783  glDisable(GL_BLEND);
784  glColor3f(0, 0, 0);
785  draw_text(x + b, y + b+7, text);
786  view_sync.leave();
787  refresh();
788  }
789 
790  char* View::get_screenshot_file_name()
791  {
792  static char file_name[1024] = { 0 };
793  bool got_file_name = false;
794  do
795  {
796  sprintf(file_name, "screen%03d.bmp", screenshot_no);
797  FILE *f = fopen(file_name, "r");
798  if(f == NULL)
799  got_file_name = true;
800  else
801  fclose(f);
802  screenshot_no++;
803  }
804  while (!got_file_name);
805  return file_name;
806  }
807 
808  typedef unsigned int dword;
809  typedef unsigned short word;
810 
811  const word BITMAP_ID = 0x4D42;
812 
813 #pragma pack(1)
814 
816  {
817  word type;
818  dword size;
819  word reserved1;
820  word reserved2;
821  dword off_bits;
822  };
823 
825  {
826  dword size;
827  dword width;
828  dword height;
829  word planes;
830  word bit_count;
831  dword compression;
832  dword size_image;
833  dword xdpi;
834  dword ydpi;
835  dword clr_used;
836  dword clr_important;
837  };
838 
839  void View::save_screenshot_internal(const char *file_name)
840  {
841  BitmapFileHeader file_header;
842  BitmapInfoHeader info_header;
843 
844  // alloc memory for pixel data (4 bytes per pixel)
845  char* pixels = NULL;
846  if((pixels = (char*) malloc(4 * output_width * output_height)) == NULL)
847  throw Hermes::Exceptions::Exception("Could not allocate memory for pixel data");
848 
849  // get pixels from framebuffer
850 #ifdef GL_BGRA_EXT
851  glReadPixels(0, 0, output_width, output_height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
852 #else
853  glReadPixels(0, 0, output_width, output_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // FIXME!!!
854  this->warn("BGRA format not supported. Saved image will have inverted colors");
855 #endif
856  // opening file for binary writing
857  FILE* file = fopen(file_name, "wb");
858  if(file == NULL)
859  throw Hermes::Exceptions::Exception("Could not open '%s' for writing", file_name);
860 
861  // fill in bitmap header
862  file_header.type = BITMAP_ID;
863  file_header.size = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) +
864  4 * output_width * output_height;
865  file_header.reserved1 = file_header.reserved2 = 0;
866  file_header.off_bits = 14 + 40; // length of both headers
867 
868  if(fwrite(&file_header, sizeof(file_header), 1, file) != 1)
869  {
870  fclose(file);
871  throw Hermes::Exceptions::Exception("Error writing bitmap header");
872  }
873 
874  // fill in bitmap info header
875  info_header.size = sizeof(BitmapInfoHeader);
876  info_header.width = output_width;
877  info_header.height = output_height;
878  info_header.planes = 1;
879  info_header.bit_count = 32; // 4 bytes per pixel = 32 bits
880  info_header.compression = 0;
881  info_header.size_image = output_width * output_height * 4;
882  info_header.xdpi = 2835; // 72 dpi
883  info_header.ydpi = 2835; // 72 dpi
884  info_header.clr_used = 0;
885  info_header.clr_important = 0;
886 
887  if(fwrite(&info_header, sizeof(info_header), 1, file) != 1)
888  {
889  fclose(file);
890  throw Hermes::Exceptions::Exception("Error writing bitmap header");
891  }
892 
893  // write image pixels
894  if(fwrite((GLubyte*) pixels, 1, info_header.size_image, file) != info_header.size_image)
895  {
896  fclose(file);
897  throw Hermes::Exceptions::Exception("Error writing pixel data");
898  }
899 
900  fclose(file);
901  free((void*) pixels);
902  printf("Image \"%s\" saved.\n", file_name);
903  }
904 
905  void View::save_screenshot(const char* bmpname, bool high_quality)
906  {
907  view_sync.enter();
908  if(output_id >= 0) { //set variable neccessary to create a screenshot
909  hq_frame = high_quality;
910  want_screenshot = true;
911  screenshot_filename = bmpname;
912  }
913  view_sync.leave();
914 
915  //request redraw
916  refresh();
917  }
918 
919  void View::save_numbered_screenshot(const char* format, int number, bool high_quality)
920  {
921  char buffer[1000];
922  sprintf(buffer, format, number);
923  save_screenshot(buffer, high_quality);
924  }
925 
926  int View::measure_scale_labels()
927  {
928  int result = 0;
929  for (int i = 0; i <= scale_numticks + 1; i++)
930  {
931  double value = range_min + (double) i * (range_max - range_min) / (scale_numticks + 1);
932  if(fabs(value) < 1e-8) value = 0.0;
933  char text[50];
934  sprintf(text, scale_fmt, value);
935  int w = get_text_width(text);
936  if(w > result) result = w;
937  }
938  return result;
939  }
940 
941  void View::draw_continuous_scale(char* title, bool righttext)
942  {
943  int i;
944  double y0 = scale_y + scale_height;
945 
946  set_ortho_projection(true);
947  glDisable(GL_DEPTH_TEST);
948  glDisable(GL_LIGHTING);
949  glDisable(GL_TEXTURE_1D);
950  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
951 
952  // background
953  const int b = 5;
954  glEnable(GL_BLEND);
955  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
956  glColor4f(1.0f, 1.0f, 1.0f, 0.65f);
957  int rt = righttext ? 0 : labels_width + 8;
958  glBegin(GL_QUADS);
959  glVertex2d(scale_x - b - rt, y0 + 5 + b);
960  glVertex2d(scale_x + scale_width + 8 + labels_width + b - rt, y0 + 5 + b);
961  glVertex2d(scale_x + scale_width + 8 + labels_width + b - rt, scale_y - 5 - b);
962  glVertex2d(scale_x - b - rt, scale_y - 5 - b);
963  glEnd();
964 
965  // palette
966  glDisable(GL_BLEND);
967  glColor3f(0.0f, 0.0f, 0.0f);
968  glBegin(GL_QUADS);
969  glVertex2d(scale_x, scale_y);
970  glVertex2d(scale_x, scale_y + scale_height + 1);
971  glVertex2d(scale_x + scale_width + 1, scale_y + scale_height + 1);
972  glVertex2d(scale_x + scale_width + 1, scale_y);
973  glEnd();
974 
975  glEnable(GL_TEXTURE_1D);
976  glBindTexture(GL_TEXTURE_1D, gl_pallete_tex_id);
977  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
978  glBegin(GL_QUADS);
979  glTexCoord1d(tex_scale + tex_shift);
980  glVertex2d(scale_x + 1, scale_y + 1);
981  glVertex2d(scale_x + scale_width, scale_y + 1);
982  glTexCoord1d(tex_shift);
983  glVertex2d(scale_x + scale_width, scale_y + scale_height);
984  glVertex2d(scale_x + 1, scale_y + scale_height);
985  glEnd();
986 
987  // focus
988  glDisable(GL_TEXTURE_1D);
989  if(scale_focused)
990  {
991  glEnable(GL_BLEND);
992  glColor4f(1.0f, 1.0f, 1.0f, 0.3f);
993  glBegin(GL_QUADS);
994  glVertex2d(scale_x + 1, scale_y + 1);
995  glVertex2d(scale_x + scale_width, scale_y + 1);
996  glVertex2d(scale_x + scale_width, scale_y + scale_height);
997  glVertex2d(scale_x + 1, scale_y + scale_height);
998  glEnd();
999  }
1000 
1001  // ticks
1002  glColor3f(0, 0, 0);
1003  glDisable(GL_BLEND);
1004  glDisable(GL_LINE_STIPPLE);
1005  glLineWidth(1.0);
1006  glBegin(GL_LINES);
1007  for (i = 0; i < scale_numticks; i++)
1008  {
1009  y0 = scale_y + scale_height - (double) (i + 1) * scale_height / (scale_numticks + 1);
1010  glVertex2d(scale_x, y0);
1011  glVertex2d(scale_x + 0.2 * scale_width + 1, y0);
1012  glVertex2d(scale_x + 0.8 * scale_width, y0);
1013  glVertex2d(scale_x + scale_width, y0);
1014  }
1015  glEnd();
1016 
1017  // labels
1018  for (i = 0; i <= scale_numticks + 1; i++)
1019  {
1020  double value = range_min + (double) i * (range_max - range_min) / (scale_numticks + 1);
1021  if(fabs(value) < 1e-8) value = 0.0;
1022  char text[50];
1023  sprintf(text, scale_fmt, value);
1024  y0 = scale_y + scale_height - (double) i * scale_height / (scale_numticks + 1);
1025  if(righttext)
1026  draw_text(scale_x + scale_width + 8, y0, text);
1027  else
1028  draw_text(scale_x - 8, y0, text, 1);
1029  }
1030  }
1031 
1032  void View::draw_discrete_scale(int numboxes, const char* boxnames[], const float boxcolors[][3])
1033  {
1034  set_ortho_projection(true);
1035  glDisable(GL_DEPTH_TEST);
1036  glDisable(GL_LIGHTING);
1037  glDisable(GL_TEXTURE_1D);
1038  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1039 
1040  // background
1041  const int b = 5;
1042  glEnable(GL_BLEND);
1043  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1044  glColor4f(1.0f, 1.0f, 1.0f, 0.65f);
1045  glBegin(GL_QUADS);
1046  glVertex2d(scale_x - b, scale_y - b);
1047  glVertex2d(scale_x - b, scale_y + scale_height + b + 1);
1048  glVertex2d(scale_x + scale_width + b + 1, scale_y + scale_height + b + 1);
1049  glVertex2d(scale_x + scale_width + b + 1, scale_y - b);
1050  glEnd();
1051 
1052  // boxes
1053  glDisable(GL_BLEND);
1054  int y = scale_y;
1055  for (int i = 0; i < numboxes; i++)
1056  {
1057  glColor3f(0.0, 0.0, 0.0);
1058  glBegin(GL_QUADS);
1059  glVertex2d(scale_x, y);
1060  glVertex2d(scale_x, y + scale_box_height + 1);
1061  glVertex2d(scale_x + scale_width + 1, y + scale_box_height + 1);
1062  glVertex2d(scale_x + scale_width + 1, y);
1063  glEnd();
1064 
1065  const float* color = boxcolors[numboxes-1-i];
1066  float bcolor[3] = { color[0], color[1], color[2] };
1067  if(scale_focused)
1068  {
1069  bcolor[0] = color[0]*0.7f + 1.0f*0.3f;
1070  bcolor[1] = color[1]*0.7f + 1.0f*0.3f;
1071  bcolor[2] = color[2]*0.7f + 1.0f*0.3f;
1072  }
1073 
1074  glColor3f(bcolor[0], bcolor[1], bcolor[2]);
1075  glBegin(GL_QUADS);
1076  glVertex2d(scale_x + 1, y + 1);
1077  glVertex2d(scale_x + 1, y + scale_box_height);
1078  glVertex2d(scale_x + scale_width, y + scale_box_height);
1079  glVertex2d(scale_x + scale_width, y + 1);
1080  glEnd();
1081 
1082  if((color[0] + color[1] + color[2]) / 3 > 0.5)
1083  glColor3f(0, 0, 0);
1084  else
1085  glColor3f(1, 1, 1);
1086 
1087  int a = scale_x + scale_width/2;
1088  int b = y + scale_box_height/2;
1089  draw_text(a, b, boxnames[numboxes-1-i], 0);
1090  draw_text(a + 1, b, boxnames[numboxes-1-i], 0);
1091 
1092  y += scale_box_height + scale_box_skip;
1093  }
1094  }
1095 
1096  void View::scale_dispatch()
1097  {
1098  draw_continuous_scale(NULL, !pos_horz);
1099  }
1100 
1102  {
1103  lspace = rspace = labels_width = 0;
1104  if(b_scale)
1105  {
1106  labels_width = scale_fixed_width;
1107  if(labels_width < 0) labels_width = measure_scale_labels();
1108  int space = scale_width + 8 + labels_width + margin;
1109  if(pos_horz == 0)
1110  { lspace = space; scale_x = margin; }
1111  else
1112  { rspace = space; scale_x = output_width - margin - scale_width; }
1113 
1114  if(pos_vert == 0)
1115  scale_y = output_height - margin - scale_height;
1116  else
1117  scale_y = margin;
1118  }
1119 
1120  center_x = ((double) output_width - 2*margin - lspace - rspace) / 2 + margin + lspace;
1121  center_y = (double) output_height / 2;
1122  }
1123 
1124  void View::show_scale(bool show)
1125  {
1126  view_sync.enter();
1127  b_scale = show;
1128  if(output_id >= 0)
1129  update_layout();
1130  view_sync.leave();
1131  refresh();
1132  }
1133 
1134  void View::set_scale_position(int horz, int vert)
1135  {
1136  view_sync.enter();
1137  pos_horz = horz;
1138  pos_vert = vert;
1139  if(output_id >= 0)
1140  update_layout();
1141  view_sync.leave();
1142  refresh();
1143  }
1144 
1145  void View::set_scale_size(int width, int height, int numticks)
1146  {
1147  view_sync.enter();
1148  scale_width = width;
1149  scale_height = height;
1150  scale_numticks = numticks;
1151  update_layout();
1152  view_sync.leave();
1153  refresh();
1154  }
1155 
1156  void View::set_scale_format(const char* fmt)
1157  {
1158  view_sync.enter();
1159  strncpy(scale_fmt, fmt, 19);
1160  update_layout();
1161  view_sync.leave();
1162  refresh();
1163  }
1164 
1165  void View::fix_scale_width(int width)
1166  {
1167  view_sync.enter();
1168  scale_fixed_width = width;
1169  update_layout();
1170  view_sync.leave();
1171  refresh();
1172  }
1173  }
1174  }
1175 }
1176 #endif