You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

682 lines
18 KiB

18 years ago
18 years ago
  1. /////////////////////////////////////////////////////////////////////////////
  2. // Name: 3d_canvas.cpp
  3. /////////////////////////////////////////////////////////////////////////////
  4. #ifdef __GNUG__
  5. #pragma implementation
  6. #pragma interface
  7. #endif
  8. #include "fctsys.h"
  9. #include "trigo.h"
  10. #include "wx/image.h"
  11. #if !wxUSE_GLCANVAS
  12. #error Please set wxUSE_GLCANVAS to 1 in setup.h.
  13. #endif
  14. #include "wx/dataobj.h"
  15. #include "wx/clipbrd.h"
  16. #include "fctsys.h"
  17. #include "common.h"
  18. #include "id.h"
  19. #include "3d_viewer.h"
  20. #include "trackball.h"
  21. /* Tool and button Bitmaps */
  22. #define XPM_3D_MAIN
  23. #include "bitmaps.h"
  24. enum onrclick_id {
  25. ID_POPUP_3D_VIEW_START = 2000,
  26. ID_POPUP_ZOOMIN,
  27. ID_POPUP_ZOOMOUT,
  28. ID_POPUP_VIEW_XPOS,
  29. ID_POPUP_VIEW_XNEG,
  30. ID_POPUP_VIEW_YPOS,
  31. ID_POPUP_VIEW_YNEG,
  32. ID_POPUP_VIEW_ZPOS,
  33. ID_POPUP_VIEW_ZNEG,
  34. ID_POPUP_MOVE3D_LEFT,
  35. ID_POPUP_MOVE3D_RIGHT,
  36. ID_POPUP_MOVE3D_UP,
  37. ID_POPUP_MOVE3D_DOWN,
  38. ID_POPUP_3D_VIEW_END
  39. };
  40. /*
  41. * Pcb3D_GLCanvas implementation
  42. */
  43. BEGIN_EVENT_TABLE( Pcb3D_GLCanvas, wxGLCanvas )
  44. EVT_SIZE( Pcb3D_GLCanvas::OnSize )
  45. EVT_PAINT( Pcb3D_GLCanvas::OnPaint )
  46. EVT_CHAR( Pcb3D_GLCanvas::OnChar )
  47. EVT_MOUSE_EVENTS( Pcb3D_GLCanvas::OnMouseEvent )
  48. EVT_ERASE_BACKGROUND( Pcb3D_GLCanvas::OnEraseBackground )
  49. EVT_MENU_RANGE( ID_POPUP_3D_VIEW_START, ID_POPUP_3D_VIEW_END,
  50. Pcb3D_GLCanvas::OnPopUpMenu )
  51. END_EVENT_TABLE()
  52. /*************************************************************************/
  53. Pcb3D_GLCanvas::Pcb3D_GLCanvas( WinEDA3D_DrawFrame* parent, const wxWindowID id,
  54. int* gl_attrib ) :
  55. wxGLCanvas( parent, id,
  56. wxPoint( -1, -1 ), wxSize( -1, -1 ), 0, wxT( "Pcb3D_glcanvas" ), gl_attrib )
  57. /*************************************************************************/
  58. {
  59. m_init = FALSE;
  60. m_gllist = 0;
  61. m_Parent = parent;
  62. DisplayStatus();
  63. }
  64. /*************************************/
  65. Pcb3D_GLCanvas::~Pcb3D_GLCanvas()
  66. /*************************************/
  67. {
  68. ClearLists();
  69. m_init = FALSE;
  70. }
  71. /*************************************/
  72. void Pcb3D_GLCanvas::ClearLists()
  73. /*************************************/
  74. {
  75. if( m_gllist > 0 )
  76. glDeleteLists( m_gllist, 1 );
  77. // m_init = FALSE;
  78. m_gllist = 0;
  79. }
  80. /*********************************************/
  81. void Pcb3D_GLCanvas::OnChar( wxKeyEvent& event )
  82. /*********************************************/
  83. {
  84. SetView3D( event.GetKeyCode() );
  85. event.Skip();
  86. }
  87. /*********************************************/
  88. void Pcb3D_GLCanvas::SetView3D( int keycode )
  89. /*********************************************/
  90. {
  91. int ii;
  92. double delta_move = 0.7 * g_Parm_3D_Visu.m_Zoom;
  93. switch( keycode )
  94. {
  95. case WXK_LEFT:
  96. g_Draw3d_dx -= delta_move;
  97. break;
  98. case WXK_RIGHT:
  99. g_Draw3d_dx += delta_move;
  100. break;
  101. case WXK_UP:
  102. g_Draw3d_dy += delta_move;
  103. break;
  104. case WXK_DOWN:
  105. g_Draw3d_dy -= delta_move;
  106. break;
  107. case WXK_HOME:
  108. g_Parm_3D_Visu.m_Zoom = 1.0;
  109. g_Draw3d_dx = g_Draw3d_dy = 0;
  110. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  111. break;
  112. case WXK_END:
  113. break;
  114. case WXK_F1:
  115. g_Parm_3D_Visu.m_Zoom /= 1.4;
  116. if( g_Parm_3D_Visu.m_Zoom <= 0.01 )
  117. g_Parm_3D_Visu.m_Zoom = 0.01;
  118. break;
  119. case WXK_F2:
  120. g_Parm_3D_Visu.m_Zoom *= 1.4;
  121. break;
  122. case '+':
  123. break;
  124. case '-':
  125. break;
  126. case 'r':
  127. case 'R':
  128. g_Draw3d_dx = g_Draw3d_dy = 0;
  129. for( ii = 0; ii < 4; ii++ )
  130. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  131. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  132. break;
  133. case 'x':
  134. for( ii = 0; ii < 4; ii++ )
  135. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  136. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  137. g_Parm_3D_Visu.m_ROTZ = -90;
  138. g_Parm_3D_Visu.m_ROTX = -90;
  139. break;
  140. case 'X':
  141. for( ii = 0; ii < 4; ii++ )
  142. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  143. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  144. g_Parm_3D_Visu.m_ROTZ = 90;
  145. g_Parm_3D_Visu.m_ROTX = -90;
  146. break;
  147. case 'y':
  148. for( ii = 0; ii < 4; ii++ )
  149. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  150. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  151. g_Parm_3D_Visu.m_ROTX = -90;
  152. break;
  153. case 'Y':
  154. for( ii = 0; ii < 4; ii++ )
  155. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  156. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  157. g_Parm_3D_Visu.m_ROTX = -90;
  158. g_Parm_3D_Visu.m_ROTZ = -180;
  159. break;
  160. case 'z':
  161. for( ii = 0; ii < 4; ii++ )
  162. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  163. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  164. break;
  165. case 'Z':
  166. for( ii = 0; ii < 4; ii++ )
  167. g_Parm_3D_Visu.m_Rot[ii] = 0.0;
  168. trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
  169. g_Parm_3D_Visu.m_ROTX = -180;
  170. break;
  171. default:
  172. return;
  173. }
  174. DisplayStatus();
  175. Refresh( FALSE );
  176. }
  177. /********************************************************/
  178. void Pcb3D_GLCanvas::OnMouseEvent( wxMouseEvent& event )
  179. /********************************************************/
  180. {
  181. wxSize size( GetClientSize() );
  182. double spin_quat[4];
  183. if( event.RightDown() )
  184. {
  185. OnRightClick( event ); return;
  186. }
  187. if( event.m_wheelRotation )
  188. {
  189. if( event.ShiftDown() )
  190. {
  191. if( event.GetWheelRotation() < 0 )
  192. {
  193. /* up */
  194. SetView3D( WXK_UP );
  195. }
  196. else
  197. {
  198. /* down */
  199. SetView3D( WXK_DOWN );
  200. }
  201. }
  202. else if( event.ControlDown() )
  203. {
  204. if( event.GetWheelRotation() > 0 )
  205. {
  206. /* right */
  207. SetView3D( WXK_RIGHT );
  208. }
  209. else
  210. {
  211. /* left */
  212. SetView3D( WXK_LEFT );
  213. }
  214. }
  215. else
  216. {
  217. if( event.GetWheelRotation() > 0 )
  218. {
  219. g_Parm_3D_Visu.m_Zoom /= 1.4;
  220. if( g_Parm_3D_Visu.m_Zoom <= 0.01 )
  221. g_Parm_3D_Visu.m_Zoom = 0.01;
  222. }
  223. else
  224. g_Parm_3D_Visu.m_Zoom *= 1.4;
  225. DisplayStatus();
  226. Refresh( FALSE );
  227. }
  228. }
  229. if( event.Dragging() )
  230. {
  231. if( event.LeftIsDown() )
  232. {
  233. /* drag in progress, simulate trackball */
  234. trackball( spin_quat,
  235. (2.0 * g_Parm_3D_Visu.m_Beginx - size.x) / size.x,
  236. (size.y - 2.0 * g_Parm_3D_Visu.m_Beginy) / size.y,
  237. ( 2.0 * event.GetX() - size.x) / size.x,
  238. ( size.y - 2.0 * event.GetY() ) / size.y );
  239. add_quats( spin_quat, g_Parm_3D_Visu.m_Quat, g_Parm_3D_Visu.m_Quat );
  240. }
  241. else if( event.MiddleIsDown() )
  242. {
  243. /* middle button drag -> pan */
  244. /* Current zoom and an additional factor are taken into account for the amount of panning. */
  245. const double PAN_FACTOR = 8.0 * g_Parm_3D_Visu.m_Zoom;
  246. g_Draw3d_dx -= PAN_FACTOR * ( g_Parm_3D_Visu.m_Beginx - event.GetX() ) / size.x;
  247. g_Draw3d_dy -= PAN_FACTOR * (event.GetY() - g_Parm_3D_Visu.m_Beginy) / size.y;
  248. }
  249. /* orientation has changed, redraw mesh */
  250. DisplayStatus();
  251. Refresh( FALSE );
  252. }
  253. g_Parm_3D_Visu.m_Beginx = event.GetX();
  254. g_Parm_3D_Visu.m_Beginy = event.GetY();
  255. }
  256. /*******************************************************/
  257. void Pcb3D_GLCanvas::OnRightClick( wxMouseEvent& event )
  258. /*******************************************************/
  259. /* Construit et affiche un menu Popup lorsque on actionne le bouton droit
  260. * de la souris
  261. */
  262. {
  263. wxPoint pos;
  264. wxMenu PopUpMenu;
  265. pos.x = event.GetX(); pos.y = event.GetY();
  266. wxMenuItem* item = new wxMenuItem( &PopUpMenu, ID_POPUP_ZOOMIN,
  267. _( "Zoom +" ) );
  268. item->SetBitmap( zoom_in_xpm );
  269. PopUpMenu.Append( item );
  270. item = new wxMenuItem( &PopUpMenu, ID_POPUP_ZOOMOUT,
  271. _( "Zoom -" ) );
  272. item->SetBitmap( zoom_out_xpm );
  273. PopUpMenu.Append( item );
  274. PopUpMenu.AppendSeparator();
  275. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_ZPOS,
  276. _( "Top View" ) );
  277. item->SetBitmap( axis3d_top_xpm );
  278. PopUpMenu.Append( item );
  279. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_ZNEG,
  280. _( "Bottom View" ) );
  281. item->SetBitmap( axis3d_bottom_xpm );
  282. PopUpMenu.Append( item );
  283. PopUpMenu.AppendSeparator();
  284. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_XPOS,
  285. _( "Right View" ) );
  286. item->SetBitmap( axis3d_right_xpm );
  287. PopUpMenu.Append( item );
  288. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_XNEG,
  289. _( "Left View" ) );
  290. item->SetBitmap( axis3d_left_xpm );
  291. PopUpMenu.Append( item );
  292. PopUpMenu.AppendSeparator();
  293. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_YPOS,
  294. _( "Front View" ) );
  295. item->SetBitmap( axis3d_front_xpm );
  296. PopUpMenu.Append( item );
  297. item = new wxMenuItem( &PopUpMenu, ID_POPUP_VIEW_YNEG,
  298. _( "Back View" ) );
  299. item->SetBitmap( axis3d_back_xpm );
  300. PopUpMenu.Append( item );
  301. PopUpMenu.AppendSeparator();
  302. item = new wxMenuItem( &PopUpMenu, ID_POPUP_MOVE3D_LEFT,
  303. _( "Move left <-" ) );
  304. item->SetBitmap( left_xpm );
  305. PopUpMenu.Append( item );
  306. item = new wxMenuItem( &PopUpMenu, ID_POPUP_MOVE3D_RIGHT,
  307. _( "Move right ->" ) );
  308. item->SetBitmap( right_xpm );
  309. PopUpMenu.Append( item );
  310. item = new wxMenuItem( &PopUpMenu, ID_POPUP_MOVE3D_UP,
  311. _( "Move Up ^" ) );
  312. item->SetBitmap( up_xpm );
  313. PopUpMenu.Append( item );
  314. item = new wxMenuItem( &PopUpMenu, ID_POPUP_MOVE3D_DOWN,
  315. _( "Move Down" ) );
  316. item->SetBitmap( down_xpm );
  317. PopUpMenu.Append( item );
  318. PopupMenu( &PopUpMenu, pos );
  319. }
  320. /*******************************************************/
  321. void Pcb3D_GLCanvas::OnPopUpMenu( wxCommandEvent& event )
  322. /*******************************************************/
  323. {
  324. int key = 0;
  325. switch( event.GetId() )
  326. {
  327. case ID_POPUP_ZOOMIN:
  328. key = WXK_F1;
  329. break;
  330. case ID_POPUP_ZOOMOUT:
  331. key = WXK_F2;
  332. break;
  333. case ID_POPUP_VIEW_XPOS:
  334. key = 'x';
  335. break;
  336. case ID_POPUP_VIEW_XNEG:
  337. key = 'X';
  338. break;
  339. case ID_POPUP_VIEW_YPOS:
  340. key = 'y';
  341. break;
  342. case ID_POPUP_VIEW_YNEG:
  343. key = 'Y';
  344. break;
  345. case ID_POPUP_VIEW_ZPOS:
  346. key = 'z';
  347. break;
  348. case ID_POPUP_VIEW_ZNEG:
  349. key = 'Z';
  350. break;
  351. case ID_POPUP_MOVE3D_LEFT:
  352. key = WXK_LEFT;
  353. break;
  354. case ID_POPUP_MOVE3D_RIGHT:
  355. key = WXK_RIGHT;
  356. break;
  357. case ID_POPUP_MOVE3D_UP:
  358. key = WXK_UP;
  359. break;
  360. case ID_POPUP_MOVE3D_DOWN:
  361. key = WXK_DOWN;
  362. break;
  363. default:
  364. return;
  365. }
  366. SetView3D( key );
  367. }
  368. /***************************************/
  369. void Pcb3D_GLCanvas::DisplayStatus()
  370. /***************************************/
  371. {
  372. wxString msg;
  373. msg.Printf( wxT( "dx %3.2f" ), g_Draw3d_dx );
  374. m_Parent->SetStatusText( msg, 1 );
  375. msg.Printf( wxT( "dy %3.2f" ), g_Draw3d_dy );
  376. m_Parent->SetStatusText( msg, 2 );
  377. msg.Printf( wxT( "View: %3.1f" ), 45 * g_Parm_3D_Visu.m_Zoom );
  378. m_Parent->SetStatusText( msg, 3 );
  379. }
  380. /*************************************************/
  381. void Pcb3D_GLCanvas::OnPaint( wxPaintEvent& event )
  382. /*************************************************/
  383. {
  384. wxPaintDC dc( this );
  385. #ifndef __WXMOTIF__
  386. if( !GetContext() )
  387. return;
  388. #endif
  389. Redraw();
  390. event.Skip();
  391. }
  392. /**********************************************/
  393. void Pcb3D_GLCanvas::OnSize( wxSizeEvent& event )
  394. /**********************************************/
  395. {
  396. int w, h;
  397. // set GL viewport (not called by wxGLCanvas::OnSize on all platforms...)
  398. // this is also necessary to update the context on some platforms
  399. wxGLCanvas::OnSize( event );
  400. GetClientSize( &w, &h );
  401. #ifndef __WXMOTIF__
  402. if( GetContext() )
  403. #endif
  404. {
  405. glViewport( 0, 0, (GLint) w, (GLint) h );
  406. }
  407. event.Skip();
  408. }
  409. /***********************************************************/
  410. void Pcb3D_GLCanvas::OnEraseBackground( wxEraseEvent& event )
  411. /***********************************************************/
  412. {
  413. // Do nothing, to avoid flashing.
  414. }
  415. /****************************/
  416. void Pcb3D_GLCanvas::InitGL()
  417. /****************************/
  418. /* Int parametres generaux pour OPENGL
  419. */
  420. {
  421. wxSize size = GetClientSize();
  422. if( !m_init )
  423. {
  424. m_init = TRUE;
  425. g_Parm_3D_Visu.m_Zoom = 1.0;
  426. ZBottom = 1.0; ZTop = 10.0;
  427. }
  428. /* set viewing projection */
  429. double ratio_HV = (double) size.x / size.y; // Ratio largeur /hauteur de la fenetre d'affichage
  430. glMatrixMode( GL_PROJECTION );
  431. glLoadIdentity();
  432. #define MAX_VIEW_ANGLE 160.0 / 45.0
  433. if( g_Parm_3D_Visu.m_Zoom > MAX_VIEW_ANGLE )
  434. g_Parm_3D_Visu.m_Zoom = MAX_VIEW_ANGLE;
  435. gluPerspective( 45.0 * g_Parm_3D_Visu.m_Zoom, ratio_HV, 1, 10 );
  436. // glFrustum(-1., 1.1F, -1.1F, 1.1F, ZBottom, ZTop);
  437. /* position viewer */
  438. glMatrixMode( GL_MODELVIEW );
  439. glLoadIdentity();
  440. glTranslatef( 0.0F, 0.0F, -( ZBottom + ZTop) / 2 );
  441. /* clear color and depth buffers */
  442. glClearColor( g_Parm_3D_Visu.m_BgColor.m_Red,
  443. g_Parm_3D_Visu.m_BgColor.m_Green,
  444. g_Parm_3D_Visu.m_BgColor.m_Blue, 1 );
  445. glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  446. /* Setup light souces: */
  447. SetLights();
  448. glDisable( GL_CULL_FACE ); // show back faces
  449. glEnable( GL_DEPTH_TEST ); // Enable z-buferring
  450. glEnable( GL_LINE_SMOOTH );
  451. glEnable( GL_COLOR_MATERIAL );
  452. glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
  453. /* speedups */
  454. glEnable( GL_DITHER );
  455. glShadeModel( GL_SMOOTH );
  456. glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST );
  457. glHint( GL_POLYGON_SMOOTH_HINT, GL_FASTEST );
  458. /* blend */
  459. glEnable( GL_BLEND );
  460. glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  461. }
  462. /***********************************/
  463. void Pcb3D_GLCanvas::SetLights()
  464. /***********************************/
  465. /* Init sources lumineuses pour OPENGL
  466. */
  467. {
  468. double light;
  469. GLfloat light_color[4];
  470. /* set viewing projection */
  471. light_color[3] = 1.0;
  472. GLfloat Z_axis_pos[4] = { 0.0, 0.0, 3.0, 0.0 };
  473. GLfloat lowZ_axis_pos[4] = { 0.0, 0.0, -3.0, 0.5 };
  474. /* activate light */
  475. light = 1.0;
  476. light_color[0] = light_color[1] = light_color[2] = light;
  477. glLightfv( GL_LIGHT0, GL_POSITION, Z_axis_pos );
  478. glLightfv( GL_LIGHT0, GL_DIFFUSE, light_color );
  479. light = 0.3;
  480. light_color[0] = light_color[1] = light_color[2] = light;
  481. glLightfv( GL_LIGHT1, GL_POSITION, lowZ_axis_pos );
  482. glLightfv( GL_LIGHT1, GL_DIFFUSE, light_color );
  483. glEnable( GL_LIGHT0 ); // White spot on Z axis
  484. glEnable( GL_LIGHT1 ); // White spot on Z axis ( bottom)
  485. glEnable( GL_LIGHTING );
  486. }
  487. /**********************************************************/
  488. void Pcb3D_GLCanvas::TakeScreenshot( wxCommandEvent& event )
  489. /**********************************************************/
  490. /* Create a Screenshot of the current 3D view.
  491. * Output file format is png or jpeg, or image is copied on clipboard
  492. */
  493. {
  494. wxString FullFileName;
  495. wxString file_ext, mask;
  496. bool fmt_is_jpeg = FALSE;
  497. if( event.GetId() == ID_MENU_SCREENCOPY_JPEG )
  498. fmt_is_jpeg = TRUE;
  499. if( event.GetId() != ID_TOOL_SCREENCOPY_TOCLIBBOARD )
  500. {
  501. file_ext = fmt_is_jpeg ? wxT( ".jpg" ) : wxT( ".png"; )
  502. mask = wxT( "*" ) + file_ext;
  503. FullFileName = m_Parent->m_Parent->GetScreen()->m_FileName;
  504. ChangeFileNameExt( FullFileName, file_ext );
  505. FullFileName =
  506. EDA_FileSelector( _( "3D Image filename:" ),
  507. wxEmptyString, /* Chemin par defaut */
  508. FullFileName, /* nom fichier par defaut */
  509. file_ext, /* extension par defaut */
  510. mask, /* Masque d'affichage */
  511. this,
  512. wxFD_SAVE,
  513. TRUE
  514. );
  515. if( FullFileName.IsEmpty() )
  516. return;
  517. }
  518. Redraw( true );
  519. wxSize image_size = GetClientSize();
  520. wxClientDC dc( this );
  521. wxBitmap bitmap( image_size.x, image_size.y );
  522. wxMemoryDC memdc;
  523. memdc.SelectObject( bitmap );
  524. memdc.Blit( 0, 0, image_size.x, image_size.y, &dc, 0, 0 );
  525. memdc.SelectObject( wxNullBitmap );
  526. if( event.GetId() == ID_TOOL_SCREENCOPY_TOCLIBBOARD )
  527. {
  528. wxBitmapDataObject* dobjBmp = new wxBitmapDataObject;
  529. dobjBmp->SetBitmap( bitmap );
  530. if( wxTheClipboard->Open() )
  531. {
  532. if( !wxTheClipboard->SetData( dobjBmp ) )
  533. wxLogError( _T( "Failed to copy image to clipboard" ) );
  534. wxTheClipboard->Flush(); /* the data on clipboard
  535. * will stay available after the application exits */
  536. wxTheClipboard->Close();
  537. }
  538. }
  539. else
  540. {
  541. wxImage image = bitmap.ConvertToImage();
  542. if( !image.SaveFile( FullFileName,
  543. fmt_is_jpeg ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG ) )
  544. wxLogError( wxT( "Can't save file" ) );
  545. image.Destroy();
  546. }
  547. }