NCBI C++ ToolKit
cn3d_png.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: cn3d_png.cpp 98571 2022-12-06 17:34:25Z dzhang $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Authors: Paul Thiessen
27 *
28 * File Description:
29 * save structure window to PNG file
30 *
31 * ===========================================================================
32 */
33 
34 #include <ncbi_pch.hpp>
35 #include <corelib/ncbistd.hpp>
36 #include <corelib/ncbi_limits.h>
37 
38 // need GL headers for off-screen rendering
39 #if defined(__WXMSW__)
40 #include <windows.h>
41 #include <GL/gl.h>
42 #include <GL/glu.h>
43 
44 #elif defined(__WXGTK__)
45 #include <GL/gl.h>
46 #include <GL/glu.h>
47 #include <GL/glx.h>
48 
49 #elif defined(__WXMAC__)
50 #include <AGL/agl.h>
51 #include <OpenGL/gl.h>
52 #include <OpenGL/glu.h>
53 #endif
54 
55 #ifdef CN3D_PNG_OSMESA
56 #include <GL/osmesa.h>
57 #endif
58 
59 #include <png.h>
60 #include <zlib.h>
61 
63 
64 #include <wx/platform.h>
65 
66 #include "cn3d_png.hpp"
67 #include "cn3d_glcanvas.hpp"
68 #include "opengl_renderer.hpp"
69 #include "progress_meter.hpp"
70 #include "cn3d_tools.hpp"
71 #include "messenger.hpp"
72 
73 
74 ////////////////////////////////////////////////////////////////////////////////////////////////
75 // The following is taken unmodified from wxDesigner's C++ code from png_dialog.wdr
76 ////////////////////////////////////////////////////////////////////////////////////////////////
77 
78 #include <wx/image.h>
79 #include <wx/statline.h>
80 #include <wx/spinbutt.h>
81 #include <wx/spinctrl.h>
82 #include <wx/splitter.h>
83 #include <wx/listctrl.h>
84 #include <wx/treectrl.h>
85 #include <wx/notebook.h>
86 #include <wx/grid.h>
87 
88 // Declare window functions
89 
90 #define ID_TEXT 10000
91 #define ID_B_BROWSE 10001
92 #define ID_T_NAME 10002
93 #define ID_T_WIDTH 10003
94 #define ID_T_HEIGHT 10004
95 #define ID_C_ASPECT 10005
96 #define ID_C_INTERLACE 10006
97 #define ID_B_OK 10007
98 #define ID_B_CANCEL 10008
99 wxSizer *SetupPNGOptionsDialog( wxPanel *parent, bool call_fit = TRUE, bool set_sizer = TRUE );
100 
101 ////////////////////////////////////////////////////////////////////////////////////////////////
102 
103 // fix jmpbuf symbol problem on aix
104 #if defined(_AIX43) && defined(jmpbuf)
105 #undef jmpbuf
106 #endif
107 
109 
110 
111 BEGIN_SCOPE(Cn3D)
112 
116 
117 //class PNGOptionsDialog : private wxDialog
118 class PNGOptionsDialog : public wxDialog
119 {
120 public:
121  PNGOptionsDialog(wxWindow *parent);
122  bool Activate(int initialWidth, int initialHeight, bool initialInterlaced);
123  bool GetValues(wxString *outputFilename, int *width, int *height, bool *interlaced);
124 
125 private:
128 
129  // event callbacks
130  void OnButton(wxCommandEvent& event);
131  void OnChangeSize(wxCommandEvent& event);
132  void OnCheckbox(wxCommandEvent& event);
133  void OnCloseWindow(wxCloseEvent& event);
134 
135  DECLARE_EVENT_TABLE()
136 };
137 
138 #define DECLARE_AND_FIND_WINDOW_RETURN_ON_ERR(var, id, type) \
139  type *var; \
140  var = wxDynamicCast(FindWindow(id), type); \
141  if (!var) { \
142  ERRORMSG("Can't find window with id " << id); \
143  return; \
144  }
145 
146 #define DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(var, id, type, errResult) \
147  type *var; \
148  var = wxDynamicCast(FindWindow(id), type); \
149  if (!var) { \
150  ERRORMSG("Can't find window with id " << id); \
151  return errResult; \
152  }
153 
154 // to make sure values are legitimate integers; use wxString::ToDouble to avoid parsing
155 // strings as octal or hexadecimal...
156 #define GET_AND_IS_VALID_SIZE(textctrl, var) \
157  (textctrl->GetValue().ToDouble(&var) && var >= 1 && fmod(var, 1.0) == 0.0 && var <= kMax_Int)
158 
159 const wxWindowID ButtonID = wxID_HIGHEST + 1;
160 
161 BEGIN_EVENT_TABLE(PNGOptionsDialog, wxDialog)
162  EVT_BUTTON (-1, PNGOptionsDialog::OnButton)
163  EVT_TEXT (-1, PNGOptionsDialog::OnChangeSize)
167 
168 PNGOptionsDialog::PNGOptionsDialog(wxWindow *parent) :
169  wxDialog(parent, -1, "Export Options", wxPoint(50, 50), wxDefaultSize, wxDEFAULT_DIALOG_STYLE),
170  dontProcessChange(false)
171 {
172  // construct the panel
173  wxPanel *panel = new wxPanel(this, -1);
174  wxSizer *topSizer = SetupPNGOptionsDialog(panel, false);
175 
176  // call sizer stuff
177  topSizer->Fit(panel);
178  SetClientSize(topSizer->GetMinSize());
179 }
180 
181 void PNGOptionsDialog::OnButton(wxCommandEvent& event)
182 {
183  switch (event.GetId()) {
184  case ID_B_BROWSE: {
186  wxString filename = wxFileSelector(
187  "Choose a filename for output", GetUserDir().c_str(), "",
188  ".png", "All Files|*.*|PNG files (*.png)|*.png",
189  wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
190  if (filename.size() > 0) tName->SetValue(filename);
191  break;
192  }
193  case ID_B_OK: {
194  wxString str;
195  int w, h;
196  bool i;
197  if (GetValues(&str, &w, &h, &i))
198  EndModal(wxOK);
199  else
200  wxBell();
201  break;
202  }
203  case ID_B_CANCEL:
204  EndModal(wxCANCEL);
205  break;
206  }
207 }
208 
209 void PNGOptionsDialog::OnChangeSize(wxCommandEvent& event)
210 {
211  // avoid recursive calls to this, since SetValue() triggers this event; only process size fields
212  if (dontProcessChange || !(event.GetId() == ID_T_WIDTH || event.GetId() == ID_T_HEIGHT)) return;
213 
218 
219  // post refreshes for both, in case background color changes
220  tWidth->Refresh(true);
221  tHeight->Refresh(true);
222 
223  double w, h;
224  if (!GET_AND_IS_VALID_SIZE(tWidth, w)) {
225  tWidth->SetBackgroundColour(*wxRED);
226  return;
227  }
228  tWidth->SetBackgroundColour(*wxWHITE);
229  if (!GET_AND_IS_VALID_SIZE(tHeight, h)) {
230  tHeight->SetBackgroundColour(*wxRED);
231  return;
232  }
233  tHeight->SetBackgroundColour(*wxWHITE);
234 
235  // adjust to aspect ratio if indicated
236  dontProcessChange = true;
237  if (cAspect->GetValue()) {
238  wxString num;
239  if (event.GetId() == ID_T_WIDTH) {
240  h = floor(w / initialAspect + 0.5);
241  num.Printf("%i", (int) h);
242  tHeight->SetValue(num);
243  } else {
244  w = floor(h * initialAspect + 0.5);
245  num.Printf("%i", (int) w);
246  tWidth->SetValue(num);
247  }
248  }
249  dontProcessChange = false;
250 
251  // only allow interlacing if image is small enough
252  if (w * h > MAX_BUFFER_PIXELS) {
253  if (cInterlace->IsEnabled()) {
254  cInterlace->Enable(false);
255  cInterlace->SetValue(false);
256  }
257  } else {
258  if (!cInterlace->IsEnabled()) {
259  cInterlace->Enable(true);
260  cInterlace->SetValue(true);
261  }
262  }
263 }
264 
265 void PNGOptionsDialog::OnCheckbox(wxCommandEvent& event)
266 {
267  if (event.GetId() == ID_C_ASPECT) {
268  // adjust height when aspect checkbox is turned on
270  if (cAspect->GetValue()) {
271 
274  double w, h;
275 
276  if (GET_AND_IS_VALID_SIZE(tWidth, w)) {
277  wxString num;
278  h = floor(w / initialAspect + 0.5);
279  num.Printf("%i", (int) h);
280  tHeight->SetValue(num);
281  }
282  }
283  }
284 }
285 
286 void PNGOptionsDialog::OnCloseWindow(wxCloseEvent& event)
287 {
288  EndModal(wxCANCEL);
289 }
290 
291 bool PNGOptionsDialog::Activate(int initialWidth, int initialHeight, bool initialInterlaced)
292 {
294  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(tWidth, ID_T_WIDTH, wxTextCtrl, false)
295  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(tHeight, ID_T_HEIGHT, wxTextCtrl, false)
296  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(cAspect, ID_C_ASPECT, wxCheckBox, false)
297  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(cInterlace, ID_C_INTERLACE, wxCheckBox, false)
298 
299  dontProcessChange = true;
300  wxString num;
301  num.Printf("%i", initialWidth);
302  tWidth->SetValue(num);
303  num.Printf("%i", initialHeight);
304  tHeight->SetValue(num);
305  initialAspect = ((double) initialWidth) / initialHeight;
306  cAspect->SetValue(true);
307  dontProcessChange = false;
308 
309  if (initialWidth*initialHeight > MAX_BUFFER_PIXELS) {
310  cInterlace->Enable(false);
311  cInterlace->SetValue(false);
312  } else
313  cInterlace->SetValue(initialInterlaced);
314 
315  // bring up file selector first
316  wxCommandEvent browse(wxEVT_COMMAND_BUTTON_CLICKED, ID_B_BROWSE);
317  OnButton(browse);
318  if (tName->GetValue().size() == 0) return false; // cancelled
319 
320  // return dialog result
321  return (ShowModal() == wxOK);
322 }
323 
324 bool PNGOptionsDialog::GetValues(wxString *outputFilename, int *width, int *height, bool *interlaced)
325 {
327  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(tWidth, ID_T_WIDTH, wxTextCtrl, false)
328  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(tHeight, ID_T_HEIGHT, wxTextCtrl, false)
329  DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(cInterlace, ID_C_INTERLACE, wxCheckBox, false)
330 
331  *outputFilename = tName->GetValue();
332  double val;
333  if (!GET_AND_IS_VALID_SIZE(tWidth, val)) return false;
334  *width = (int) val;
335  if (!GET_AND_IS_VALID_SIZE(tHeight, val)) return false;
336  *height = (int) val;
337  *interlaced = (cInterlace->IsEnabled() && cInterlace->GetValue());
338  return true;
339 }
340 
341 
342 static bool GetOutputParameters(wxString *outputFilename, int *width, int *height, bool *interlaced)
343 {
344  PNGOptionsDialog dialog(NULL);
345  bool ok = dialog.Activate(*width, *height, *interlaced);
346  if (ok) dialog.GetValues(outputFilename, width, height, interlaced);
347  return ok;
348 }
349 
350 // callback used by PNG library to report errors
351 static void writepng_error_handler(png_structp png_ptr, png_const_charp msg)
352 {
353  ERRORMSG("PNG library error: " << msg);
354 }
355 
356 #ifdef __WXGTK__
357 static bool gotAnXError;
358 int X_error_handler(Display *dpy, XErrorEvent *error)
359 {
360  gotAnXError = true;
361  return 0;
362 }
363 #endif
364 
365 // called after each row is written to the file
366 static void write_row_callback(png_structp png_ptr, png_uint_32 row, int pass)
367 {
368  if (!progressMeter) return;
369  int progress = 0;
370 
371  if (nRows < 0) { /* if negative, then we're doing interlacing */
372  double start, end;
373  switch (pass + 1) {
374  case 1: start = 0; end = 1; break;
375  case 2: start = 1; end = 2; break;
376  case 3: start = 2; end = 3; break;
377  case 4: start = 3; end = 5; break;
378  case 5: start = 5; end = 7; break;
379  case 6: start = 7; end = 11; break;
380  case 7: start = 11; end = 15; break;
381  default: return; // png lib gives final pass=7,row=0 to signal completion
382  }
383  progress = (int) (1.0 * PROGRESS_RESOLUTION *
384  ((start / 15) + (((double) row) / (-nRows)) * ((end - start) / 15)));
385  } else { /* not interlaced */
386  progress = (int) (1.0 * PROGRESS_RESOLUTION * row / nRows);
387  }
388 
389  progressMeter->SetValue(progress);
390 }
391 
392 bool ExportPNG(Cn3DGLCanvas *glCanvas,
393  // the following parameters should only be used in non-windowed mode (and glCanvas=NULL)
394  OpenGLRenderer *renderer,
395  const string& outputFilename,
396  int outputWidth,
397  int outputHeight,
398  bool interlaced)
399 {
400  // sanity checks
401  if (IsWindowedMode()) {
402 #if !defined(__WXMSW__) && !defined(__WXGTK__) && !defined(__WXMAC__)
403  ERRORMSG("PNG export not (yet) implemented on this platform");
404  return false;
405 #endif
406  if (!glCanvas || !glCanvas->renderer) {
407  ERRORMSG("ExportPNG() - bad glCanvas parameter");
408  return false;
409  }
410  renderer = glCanvas->renderer;
411  }
412 
413  // non-windowed mode uses Mesa
414  else {
415 #ifndef CN3D_PNG_OSMESA
416  ERRORMSG("cn3d_png_nowin must be compiled with CN3D_PNG_OSMESA");
417  return false;
418 #endif
419 #if !defined(OSMESA_MAJOR_VERSION) || !defined(OSMESA_MINOR_VERSION) || !defined(OSMESA_RGB)
420  ERRORMSG("Non-windowed rendering currently requires Mesa");
421  return false;
422 #endif
423  if (glCanvas || !renderer) {
424  ERRORMSG("ExportPNG() - bad glCanvas/renderer parameters");
425  return false;
426  }
427  }
428 
429  bool success = false, shareDisplayLists = true;
430  int bufferHeight, bytesPerPixel = 3, nChunks = 1;
431  wxString filename;
432  FILE *out = NULL;
433  unsigned char *rowStorage = NULL;
434  png_structp png_ptr = NULL;
435  png_infop info_ptr = NULL;
436 
437  if (IsWindowedMode()) {
438  outputWidth = glCanvas->GetClientSize().GetWidth(); // initial size
439  outputHeight = glCanvas->GetClientSize().GetHeight();
440  if (!GetOutputParameters(&filename, &outputWidth, &outputHeight, &interlaced))
441  return true; // cancelled
442  } else {
443  filename = outputFilename.c_str();
444  }
445 
446  try {
447  INFOMSG("saving PNG file '" << filename.c_str() << "'");
448 
449  if (IsWindowedMode()) {
450  // need to avoid any GL calls in glCanvas while off-screen rendering is happening; so
451  // temporarily prevent glCanvas from responding to window resize/exposure, etc.
452  glCanvas->SuspendRendering(true);
453  }
454 
455  // decide whether the in-memory image buffer can fit the whole drawing,
456  // or whether we need to split it up into separate horizontal chunks
457  bufferHeight = outputHeight;
458  if (outputWidth * outputHeight > MAX_BUFFER_PIXELS) {
459  bufferHeight = MAX_BUFFER_PIXELS / outputWidth;
460  nChunks = outputHeight / bufferHeight; // whole chunks +
461  if (outputHeight % bufferHeight != 0) ++nChunks; // partially occupied chunk
462  interlaced = false;
463  }
464 
465  INFOMSG("output size: " << outputWidth << 'x' << outputHeight << ", interlaced=" << (interlaced ? "yes" : "no"));
466 
467  if (IsWindowedMode()) {
468  // create and show progress meter
469  wxString message;
470  message.Printf("Writing PNG file %s (%ix%i)",
471  (wxString(wxFileNameFromPath(filename.c_str()))).c_str(),
472  outputWidth, outputHeight);
473  progressMeter = new ProgressMeter(NULL, message, "Saving...", PROGRESS_RESOLUTION);
474  }
475 
476  // open the output file for writing
477  out = fopen(filename.c_str(), "wb");
478  if (!out) throw "can't open file for writing";
479 
480  // for non-windowed mode, use Mesa off-screen rendering API
481 #ifdef CN3D_PNG_OSMESA
482  OSMesaContext mesaContext = 0;
483  unsigned char *mesaBuffer = NULL;
484 
485  if ((mesaContext = OSMesaCreateContext(OSMESA_RGBA, NULL)) == 0)
486  throw "OSMesaCreateContext failed";
487  mesaBuffer = new unsigned char[outputWidth * bufferHeight * 4 * sizeof(GLubyte)];
488  if (OSMesaMakeCurrent(mesaContext, mesaBuffer, GL_UNSIGNED_BYTE, outputWidth, bufferHeight) != GL_TRUE)
489  throw "OSMesaMakeCurrent failed";
490  shareDisplayLists = false;
491  TRACEMSG("Created OSMesa context and made it current");
492 
493 #else
494 
495  // set up off-screen contexts for windowed mode, possibly sharing display lists with main window context
496 
497 #if defined(__WXMSW__)
498  HDC hdc = NULL, current_hdc = NULL;
499  HGLRC hglrc = NULL, current_hglrc = NULL;
500  HGDIOBJ current_hgdiobj = NULL;
501  HBITMAP hbm = NULL;
502  PIXELFORMATDESCRIPTOR pfd;
503  int nPixelFormat;
504 
505  current_hglrc = wglGetCurrentContext(); // save to restore later
506  current_hdc = wglGetCurrentDC();
507 
508  // create bitmap of same color type as current rendering context
509  hbm = CreateCompatibleBitmap(current_hdc, outputWidth, bufferHeight);
510  if (!hbm) throw "failed to create rendering BITMAP";
511  hdc = CreateCompatibleDC(current_hdc);
512  if (!hdc) throw "CreateCompatibleDC failed";
513  current_hgdiobj = SelectObject(hdc, hbm);
514  if (!current_hgdiobj) throw "SelectObject failed";
515 
516  memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
517  pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
518  pfd.nVersion = 1;
519  pfd.dwFlags = PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL; // NOT doublebuffered, to save memory
520  pfd.iPixelType = PFD_TYPE_RGBA;
521  pfd.cColorBits = GetDeviceCaps(current_hdc, BITSPIXEL);
522  pfd.iLayerType = PFD_MAIN_PLANE;
523  nPixelFormat = ChoosePixelFormat(hdc, &pfd);
524  if (!nPixelFormat) {
525  ERRORMSG("ChoosePixelFormat failed");
526  throw GetLastError();
527  }
528  if (!SetPixelFormat(hdc, nPixelFormat, &pfd)) {
529  ERRORMSG("SetPixelFormat failed");
530  throw GetLastError();
531  }
532  hglrc = wglCreateContext(hdc);
533  if (!hglrc) {
534  ERRORMSG("wglCreateContext failed");
535  throw GetLastError();
536  }
537  // try to share display lists with regular window, to save memory and draw time
538  if (!wglShareLists(current_hglrc, hglrc)) {
539  WARNINGMSG("wglShareLists failed: " << GetLastError());
540  shareDisplayLists = false;
541  }
542  if (!wglMakeCurrent(hdc, hglrc)) {
543  ERRORMSG("wglMakeCurrent failed");
544  throw GetLastError();
545  }
546 
547 #elif defined(__WXGTK__)
548  GLint glSize;
549  int nAttribs, attribs[20];
550  XVisualInfo *visinfo = NULL;
551  bool localVI = false;
552  Pixmap xPixmap = 0;
553  GLXContext currentCtx = NULL, glCtx = NULL;
554  GLXPixmap glxPixmap = 0;
555  GLXDrawable currentXdrw = 0;
556  Display *display = NULL;
557  int (*currentXErrHandler)(Display *, XErrorEvent *) = NULL;
558 
559  currentCtx = glXGetCurrentContext(); // save current context info
560  currentXdrw = glXGetCurrentDrawable();
561  display = (Display *) wxGetDisplay();
562 
563  currentXErrHandler = XSetErrorHandler(X_error_handler);
564  gotAnXError = false;
565 
566  // first, try to get a non-doublebuffered visual, to economize on memory
567  nAttribs = 0;
568  attribs[nAttribs++] = GLX_USE_GL;
569  attribs[nAttribs++] = GLX_RGBA;
570  attribs[nAttribs++] = GLX_RED_SIZE;
571  glGetIntegerv(GL_RED_BITS, &glSize);
572  attribs[nAttribs++] = glSize;
573  attribs[nAttribs++] = GLX_GREEN_SIZE;
574  attribs[nAttribs++] = glSize;
575  attribs[nAttribs++] = GLX_BLUE_SIZE;
576  attribs[nAttribs++] = glSize;
577  attribs[nAttribs++] = GLX_DEPTH_SIZE;
578  glGetIntegerv(GL_DEPTH_BITS, &glSize);
579  attribs[nAttribs++] = glSize;
580  attribs[nAttribs++] = None;
581  visinfo = glXChooseVisual(display, DefaultScreen(display), attribs);
582 
583  // if that fails, just revert to the one used for the regular window
584  if (visinfo)
585  localVI = true;
586  else
587 #if wxCHECK_VERSION(2,9,0)
588  visinfo = (XVisualInfo *) glCanvas->GetXVisualInfo();
589 #else
590  visinfo = (XVisualInfo *) (glCanvas->m_vi);
591 #endif
592 
593  // create pixmap
594  xPixmap = XCreatePixmap(display,
595  RootWindow(display, DefaultScreen(display)),
596  outputWidth, bufferHeight, visinfo->depth);
597  if (!xPixmap) throw "failed to create Pixmap";
598  glxPixmap = glXCreateGLXPixmap(display, visinfo, xPixmap);
599  if (!glxPixmap) throw "failed to create GLXPixmap";
600  if (gotAnXError) throw "Got an X error creating GLXPixmap";
601 
602  // try to share display lists with "regular" context
603  // ... seems to be too flaky - fails on Linux/Mesa, Solaris
604 // glCtx = glXCreateContext(display, visinfo, currentCtx, False);
605 // if (!glCtx || !glXMakeCurrent(display, glxPixmap, glCtx)) {
606 // WARNINGMSG("failed to make GLXPixmap rendering context with shared display lists");
607 // if (glCtx) glXDestroyContext(display, glCtx);
608  shareDisplayLists = false;
609 
610  // try to create context without shared lists
611  glCtx = glXCreateContext(display, visinfo, NULL, False);
612  if (!glCtx || !glXMakeCurrent(display, glxPixmap, glCtx))
613  throw "failed to make GLXPixmap rendering context without shared display lists";
614 // }
615  if (gotAnXError) throw "Got an X error setting GLX context";
616 
617 #elif defined(__WXMAC__)
618  unsigned char *base = NULL;
619  GLint attrib[20], glSize;
620  int na = 0;
621  AGLPixelFormat fmt = NULL;
622  AGLContext ctx = NULL, currentCtx;
623 
624  currentCtx = aglGetCurrentContext();
625 
626  // Mac pixels seem to always be 32-bit
627  bytesPerPixel = 4;
628 
629  base = new unsigned char[outputWidth * bufferHeight * bytesPerPixel];
630  if (!base) throw "failed to allocate image buffer";
631 
632  // create an off-screen rendering context (NOT doublebuffered)
633  attrib[na++] = AGL_OFFSCREEN;
634  attrib[na++] = AGL_RGBA;
635  glGetIntegerv(GL_RED_BITS, &glSize);
636  attrib[na++] = AGL_RED_SIZE;
637  attrib[na++] = glSize;
638  attrib[na++] = AGL_GREEN_SIZE;
639  attrib[na++] = glSize;
640  attrib[na++] = AGL_BLUE_SIZE;
641  attrib[na++] = glSize;
642  glGetIntegerv(GL_DEPTH_BITS, &glSize);
643  attrib[na++] = AGL_DEPTH_SIZE;
644  attrib[na++] = glSize;
645  attrib[na++] = AGL_NONE;
646 
647  if ((fmt=aglChoosePixelFormat(NULL, 0, attrib)) == NULL)
648  throw "aglChoosePixelFormat failed";
649  // try to share display lists with current "regular" context
650 // if ((ctx=aglCreateContext(fmt, currentCtx)) == NULL) {
651 // WARNINGMSG("aglCreateContext with shared lists failed");
652  shareDisplayLists = false;
653  if ((ctx=aglCreateContext(fmt, NULL)) == NULL)
654  throw "aglCreateContext without shared lists failed";
655 // }
656 
657  // attach off-screen buffer to this context
658  if (!aglSetOffScreen(ctx, outputWidth, bufferHeight, bytesPerPixel * outputWidth, base))
659  throw "aglSetOffScreen failed";
660  if (!aglSetCurrentContext(ctx))
661  throw "aglSetCurrentContext failed";
662 
663  renderer->RecreateQuadric(); // Macs have context-sensitive quadrics...
664 
665 #endif // WX*
666 #endif // OSMESA
667 
668  TRACEMSG("interlaced: " << interlaced << ", nChunks: " << nChunks
669  << ", buffer height: " << bufferHeight << ", shared: " << shareDisplayLists);
670 
671  // allocate a row of pixel storage
672  rowStorage = new unsigned char[outputWidth * bytesPerPixel];
673  if (!rowStorage) throw "failed to allocate pixel row buffer";
674 
675  /* set up the PNG writing (see libpng.txt) */
676  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, writepng_error_handler, NULL);
677  if (!png_ptr) throw "can't create PNG write structure";
678  info_ptr = png_create_info_struct(png_ptr);
679  if (!info_ptr) throw "can't create PNG info structure";
680  if (setjmp(png_jmpbuf(png_ptr))) {
681  png_destroy_write_struct(&png_ptr, &info_ptr);
682  throw "png error encountered, jump point activated";
683  }
684  png_init_io(png_ptr, out);
685 
686  // sets callback that's called by PNG after each written row
687  png_set_write_status_fn(png_ptr, write_row_callback);
688 
689  // set various PNG parameters
690  png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
691  png_set_IHDR(png_ptr, info_ptr, outputWidth, outputHeight,
692  8, PNG_COLOR_TYPE_RGB,
693  interlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
694  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
695  png_write_info(png_ptr, info_ptr);
696 
697 
698  // Redraw the model into the new off-screen context, then use glReadPixels
699  // to retrieve pixel data. It's much easier to use glReadPixels rather than
700  // trying to read directly from the off-screen buffer, since GL can do all
701  // the potentially tricky work of translating from whatever pixel format
702  // the buffer uses into "regular" RGB byte triples. If fonts need scaling,
703  // have to reconstruct lists regardless of whether display lists are shared.
704  renderer->Init();
705  if (IsWindowedMode()) {
706  if (!shareDisplayLists || outputHeight != glCanvas->GetClientSize().GetHeight()) {
707  glCanvas->SetGLFontFromRegistry(((double) outputHeight) / glCanvas->GetClientSize().GetHeight());
708  renderer->Construct();
709  }
710  } else {
711  renderer->Construct();
712  }
713 
714  // set up camera so that it's the same view as it is in the window (or in file for non-win mode)
715  glViewport(0, 0, outputWidth, outputHeight);
716  if (IsWindowedMode()) {
717  renderer->NewView();
718  } else {
719  if (!renderer->HasASNViewSettings())
720  renderer->ComputeBestView();
721  renderer->RestoreSavedView();
722  }
723  glPixelStorei(GL_PACK_ALIGNMENT, 1);
724 
725  // Write image row by row to avoid having to allocate another full image's
726  // worth of memory. Do multiple passes for interlacing, if necessary. Note
727  // that PNG's rows are stored top down, while GL's are bottom up.
728  if (interlaced) {
729 
730  // need to draw only once
731  renderer->Display();
732 
733  int pass, r;
734  nRows = -outputHeight; // signal to monitor that we're interlacing
735  if (png_set_interlace_handling(png_ptr) != 7) throw "confused by unkown PNG interlace scheme";
736  for (pass = 1; pass <= 7; ++pass) {
737  for (int i = outputHeight - 1; i >= 0; --i) {
738  r = outputHeight - i - 1;
739  // when interlacing, only certain rows are actually read
740  // during certain passes - avoid unncessary reads
741  if (
742  ((pass == 1 || pass == 2) && (r % 8 == 0)) ||
743  ((pass == 3) && ((r - 4) % 8 == 0)) ||
744  ((pass == 4) && (r % 4 == 0)) ||
745  ((pass == 5) && ((r - 2) % 4 == 0)) ||
746  ((pass == 6) && (r % 2 == 0)) ||
747  ((pass == 7) && ((r - 1) % 2 == 0))
748  )
749  glReadPixels(0, i, outputWidth, 1, GL_RGB, GL_UNSIGNED_BYTE, rowStorage);
750  // ... but still have to call this for each row in each pass */
751  png_write_row(png_ptr, rowStorage);
752  }
753  }
754  }
755 
756  // not interlaced, but possibly chunked
757  else {
758  int bufferRow, bufferRowStart;
759  nRows = outputHeight;
760  for (int chunk = nChunks - 1; chunk >= 0; --chunk) {
761 
762  // set viewport for this chunk and redraw
763  if (nChunks > 1) {
764  TRACEMSG("drawing chunk #" << (chunk + 1));
765  glViewport(0, -chunk*bufferHeight, outputWidth, outputHeight);
766  }
767  renderer->Display();
768 
769  // only draw "visible" part of top chunk
770  if (chunk == nChunks - 1)
771  bufferRowStart = outputHeight - 1 - bufferHeight * (nChunks - 1);
772  else
773  bufferRowStart = bufferHeight - 1;
774 
775  // dump chunk to PNG file
776  for (bufferRow = bufferRowStart; bufferRow >= 0; --bufferRow) {
777  glReadPixels(0, bufferRow, outputWidth, 1, GL_RGB, GL_UNSIGNED_BYTE, rowStorage);
778  png_write_row(png_ptr, rowStorage);
779  }
780  }
781  }
782 
783  // finish up PNG write
784  png_write_end(png_ptr, info_ptr);
785  success = true;
786 
787  // general cleanup
788  if (out) fclose(out);
789  if (rowStorage) delete rowStorage;
790  if (png_ptr) {
791  if (info_ptr)
792  png_destroy_write_struct(&png_ptr, &info_ptr);
793  else
794  png_destroy_write_struct(&png_ptr, NULL);
795  }
796 
797 #ifdef CN3D_PNG_OSMESA
798  if (mesaBuffer) delete[] mesaBuffer;
799  OSMesaDestroyContext(mesaContext);
800 #else
801 
802  if (progressMeter) {
803  progressMeter->Close(true);
804  progressMeter->Destroy();
806  }
807 
808  // platform-specific cleanup, restore context
809 #if defined(__WXMSW__)
810  if (current_hdc && current_hglrc) wglMakeCurrent(current_hdc, current_hglrc);
811  if (hglrc) wglDeleteContext(hglrc);
812  if (hbm) DeleteObject(hbm);
813  if (hdc) DeleteDC(hdc);
814 
815 #elif defined(__WXGTK__)
816  gotAnXError = false;
817  if (glCtx) {
818  glXMakeCurrent(display, currentXdrw, currentCtx);
819  glXDestroyContext(display, glCtx);
820  }
821  if (glxPixmap) glXDestroyGLXPixmap(display, glxPixmap);
822  if (xPixmap) XFreePixmap(display, xPixmap);
823  if (localVI && visinfo) XFree(visinfo);
824  if (gotAnXError) WARNINGMSG("Got an X error destroying GLXPixmap context");
825  XSetErrorHandler(currentXErrHandler);
826 
827 #elif defined(__WXMAC__)
828  aglSetCurrentContext(currentCtx);
829  if (ctx) aglDestroyContext(ctx);
830  if (fmt) aglDestroyPixelFormat(fmt);
831  if (base) delete[] base;
832  renderer->RecreateQuadric(); // Macs have context-sensitive quadrics...
833 
834 #endif // WX*
835 #endif // OSMESA
836 
837  } catch (const char *err) {
838  ERRORMSG("Error creating PNG: " << err);
839  } catch (exception& e) {
840  ERRORMSG("Uncaught exception while creating PNG: " << e.what());
841  }
842 
843  if (IsWindowedMode()) {
844  // reset font after "regular" context restore
845  glCanvas->SuspendRendering(false);
846  if (outputHeight != glCanvas->GetClientSize().GetHeight()) {
847  glCanvas->SetCurrent();
848  glCanvas->SetGLFontFromRegistry(1.0);
849  if (shareDisplayLists)
851  }
852  glCanvas->Refresh(false);
853  }
854 
855  return success;
856 }
857 
858 END_SCOPE(Cn3D)
859 
860 
861 ////////////////////////////////////////////////////////////////////////////////////////////////
862 // The following is taken unmodified from wxDesigner's C++ code from png_dialog.wdr
863 ////////////////////////////////////////////////////////////////////////////////////////////////
864 
865 wxSizer *SetupPNGOptionsDialog( wxPanel *parent, bool call_fit, bool set_sizer )
866 {
867  wxBoxSizer *item0 = new wxBoxSizer( wxVERTICAL );
868 
869  wxStaticBox *item2 = new wxStaticBox( parent, -1, "Output Settings" );
870  wxStaticBoxSizer *item1 = new wxStaticBoxSizer( item2, wxVERTICAL );
871 
872  wxFlexGridSizer *item3 = new wxFlexGridSizer( 1, 0, 0, 0 );
873  item3->AddGrowableCol( 1 );
874 
875  wxStaticText *item4 = new wxStaticText( parent, ID_TEXT, "File name:", wxDefaultPosition, wxDefaultSize, 0 );
876  item3->Add( item4, 0, wxALIGN_CENTRE|wxALL, 5 );
877 
878  item3->Add( 20, 20, 0, wxALIGN_CENTRE|wxALL, 5 );
879 
880  wxButton *item5 = new wxButton( parent, ID_B_BROWSE, "Browse", wxDefaultPosition, wxDefaultSize, 0 );
881  item3->Add( item5, 0, wxALIGN_CENTRE|wxLEFT|wxRIGHT|wxTOP, 5 );
882 
883  item1->Add( item3, 0, wxGROW|wxALIGN_CENTER_VERTICAL, 5 );
884 
885  wxTextCtrl *item6 = new wxTextCtrl( parent, ID_T_NAME, "", wxDefaultPosition, wxDefaultSize, 0 );
886  item1->Add( item6, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
887 
888  wxFlexGridSizer *item7 = new wxFlexGridSizer( 2, 0, 0, 0 );
889  item7->AddGrowableCol( 1 );
890  item7->AddGrowableRow( 1 );
891 
892  wxStaticText *item8 = new wxStaticText( parent, ID_TEXT, "Width:", wxDefaultPosition, wxDefaultSize, 0 );
893  item7->Add( item8, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP, 5 );
894 
895  item7->Add( 20, 20, 0, wxALIGN_CENTRE|wxALL, 5 );
896 
897  wxStaticText *item9 = new wxStaticText( parent, ID_TEXT, "Height:", wxDefaultPosition, wxDefaultSize, 0 );
898  item7->Add( item9, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP, 5 );
899 
900  wxTextCtrl *item10 = new wxTextCtrl( parent, ID_T_WIDTH, "", wxDefaultPosition, wxDefaultSize, 0 );
901  item7->Add( item10, 0, wxALIGN_CENTRE|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
902 
903  wxStaticText *item11 = new wxStaticText( parent, ID_TEXT, "x", wxDefaultPosition, wxDefaultSize, 0 );
904  item7->Add( item11, 0, wxALIGN_CENTRE|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
905 
906  wxTextCtrl *item12 = new wxTextCtrl( parent, ID_T_HEIGHT, "", wxDefaultPosition, wxDefaultSize, 0 );
907  item7->Add( item12, 0, wxALIGN_CENTRE|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
908 
909  item1->Add( item7, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
910 
911  wxBoxSizer *item13 = new wxBoxSizer( wxHORIZONTAL );
912 
913  wxCheckBox *item14 = new wxCheckBox( parent, ID_C_ASPECT, "Maintain original aspect ratio", wxDefaultPosition, wxDefaultSize, 0 );
914  item14->SetValue( TRUE );
915  item13->Add( item14, 0, wxALIGN_CENTRE|wxALL, 5 );
916 
917  item13->Add( 20, 20, 0, wxALIGN_CENTRE|wxALL, 5 );
918 
919  wxCheckBox *item15 = new wxCheckBox( parent, ID_C_INTERLACE, "Interlaced", wxDefaultPosition, wxDefaultSize, 0 );
920  item15->SetValue( TRUE );
921  item13->Add( item15, 0, wxALIGN_CENTRE|wxALL, 5 );
922 
923  item1->Add( item13, 0, wxALIGN_CENTRE|wxLEFT|wxRIGHT|wxTOP, 5 );
924 
925  item0->Add( item1, 0, wxALIGN_CENTRE|wxALL, 5 );
926 
927  wxBoxSizer *item16 = new wxBoxSizer( wxHORIZONTAL );
928 
929  wxButton *item17 = new wxButton( parent, ID_B_OK, "OK", wxDefaultPosition, wxDefaultSize, 0 );
930  item17->SetDefault();
931  item16->Add( item17, 0, wxALIGN_CENTRE|wxALL, 5 );
932 
933  item16->Add( 20, 20, 0, wxALIGN_CENTRE|wxALL, 5 );
934 
935  wxButton *item18 = new wxButton( parent, ID_B_CANCEL, "Cancel", wxDefaultPosition, wxDefaultSize, 0 );
936  item16->Add( item18, 0, wxALIGN_CENTRE|wxALL, 5 );
937 
938  item0->Add( item16, 0, wxALIGN_CENTRE|wxALL, 5 );
939 
940  if (set_sizer)
941  {
942  parent->SetAutoLayout( TRUE );
943  parent->SetSizer( item0 );
944  if (call_fit)
945  {
946  item0->Fit( parent );
947  item0->SetSizeHints( parent );
948  }
949  }
950 
951  return item0;
952 }
#define static
EVT_CHECKBOX(ID_CADJUSTFEATURES_CHECKBOX, CAdjustFeaturesForGaps::OnKnownUnknownSelected) EVT_CHECKBOX(ID_CADJUSTFEATURES_CHECKBOX1
#define false
Definition: bool.h:36
#define False
Definition: bzip2.c:170
OpenGLRenderer * renderer
void SetGLFontFromRegistry(double fontScale=1.0)
void SuspendRendering(bool suspend)
void SetCurrent(void)
void PostRedrawAllStructures(void)
Definition: messenger.cpp:79
void RecreateQuadric(void) const
void Construct(void)
void RestoreSavedView(void)
void Init(void) const
void NewView(double eyeTranslateToAngleDegrees=0.0) const
bool HasASNViewSettings(void) const
void ComputeBestView(void)
bool Activate(int initialWidth, int initialHeight, bool initialInterlaced)
Definition: cn3d_png.cpp:291
void OnCloseWindow(wxCloseEvent &event)
Definition: cn3d_png.cpp:286
void OnCheckbox(wxCommandEvent &event)
Definition: cn3d_png.cpp:265
bool dontProcessChange
Definition: cn3d_png.cpp:127
double initialAspect
Definition: cn3d_png.cpp:126
void OnChangeSize(wxCommandEvent &event)
Definition: cn3d_png.cpp:209
void OnButton(wxCommandEvent &event)
Definition: cn3d_png.cpp:181
bool GetValues(wxString *outputFilename, int *width, int *height, bool *interlaced)
Definition: cn3d_png.cpp:324
void SetValue(int value, bool doYield=true)
bool IsWindowedMode(void)
Definition: cn3d_app.cpp:92
#define ID_T_NAME
Definition: cn3d_png.cpp:92
static const int MAX_BUFFER_PIXELS
Definition: cn3d_png.cpp:114
wxSizer * SetupPNGOptionsDialog(wxPanel *parent, bool call_fit=TRUE, bool set_sizer=TRUE)
Definition: cn3d_png.cpp:865
static int nRows
Definition: cn3d_png.cpp:115
#define DECLARE_AND_FIND_WINDOW_RETURN_ON_ERR(var, id, type)
Definition: cn3d_png.cpp:138
#define DECLARE_AND_FIND_WINDOW_RETURN_RESULT_ON_ERR(var, id, type, errResult)
Definition: cn3d_png.cpp:146
#define ID_T_WIDTH
Definition: cn3d_png.cpp:93
const wxWindowID ButtonID
Definition: cn3d_png.cpp:159
static bool GetOutputParameters(wxString *outputFilename, int *width, int *height, bool *interlaced)
Definition: cn3d_png.cpp:342
#define ID_B_CANCEL
Definition: cn3d_png.cpp:98
#define GET_AND_IS_VALID_SIZE(textctrl, var)
Definition: cn3d_png.cpp:156
#define ID_C_ASPECT
Definition: cn3d_png.cpp:95
#define ID_B_OK
Definition: cn3d_png.cpp:97
#define ID_T_HEIGHT
Definition: cn3d_png.cpp:94
#define ID_TEXT
Definition: cn3d_png.cpp:90
bool ExportPNG(Cn3DGLCanvas *glCanvas, OpenGLRenderer *renderer, const string &outputFilename, int outputWidth, int outputHeight, bool interlaced)
Definition: cn3d_png.cpp:392
static const int PROGRESS_RESOLUTION
Definition: cn3d_png.cpp:114
static void write_row_callback(png_structp png_ptr, png_uint_32 row, int pass)
Definition: cn3d_png.cpp:366
static ProgressMeter * progressMeter
Definition: cn3d_png.cpp:113
USING_NCBI_SCOPE
Definition: cn3d_png.cpp:108
#define ID_C_INTERLACE
Definition: cn3d_png.cpp:96
static void writepng_error_handler(png_structp png_ptr, png_const_charp msg)
Definition: cn3d_png.cpp:351
#define ID_B_BROWSE
Definition: cn3d_png.cpp:91
#define TRACEMSG(stream)
Definition: cn3d_tools.hpp:83
#define INFOMSG(stream)
Definition: cn3d_tools.hpp:84
#define WARNINGMSG(stream)
Definition: cn3d_tools.hpp:85
#define ERRORMSG(stream)
Definition: cn3d_tools.hpp:86
const std::string & GetUserDir(void)
Include a standard set of the NCBI C++ Toolkit most basic headers.
CS_CONTEXT * ctx
Definition: t0006.c:12
std::ofstream out("events_result.xml")
main entry point for tests
#define NULL
Definition: ncbistd.hpp:225
#define END_SCOPE(ns)
End the previously defined scope.
Definition: ncbistl.hpp:75
#define BEGIN_SCOPE(ns)
Define a new scope.
Definition: ncbistl.hpp:72
unsigned int
A callback function used to compare two keys in a database.
Definition: types.hpp:1210
END_EVENT_TABLE()
int i
Messenger * GlobalMessenger(void)
Definition: messenger.cpp:73
double r(size_t dimension_, const Int4 *score_, const double *prob_, double theta_)
static const char * str(char *buf, int n)
Definition: stats.c:84
@ TRUE
Definition: testodbc.c:27
#define const
Definition: zconf.h:230
#define Z_BEST_COMPRESSION
Definition: zlib.h:192
Modified on Tue Nov 28 02:27:32 2023 by modify_doxy.py rev. 669887