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.

564 lines
17 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KICAD, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2010 jean-pierre.charras
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <bitmap2cmp_frame.h>
  25. #include <bitmap2component.h>
  26. #include <bitmap2cmp_panel.h>
  27. #include <bitmap2cmp_settings.h>
  28. #include <bitmap_io.h>
  29. #include <common.h>
  30. #include <math/util.h> // for KiROUND
  31. #include <potracelib.h>
  32. #include <wx/clipbrd.h>
  33. #include <wx/rawbmp.h>
  34. #include <wx/msgdlg.h>
  35. #include <wx/dcclient.h>
  36. #include <wx/log.h>
  37. #define DEFAULT_DPI 300 // the image DPI used in formats that do not define a DPI
  38. BITMAP2CMP_PANEL::BITMAP2CMP_PANEL( BITMAP2CMP_FRAME* aParent ) :
  39. BITMAP2CMP_PANEL_BASE( aParent ),
  40. m_parentFrame( aParent ),
  41. m_negative( false ),
  42. m_aspectRatio( 1.0 )
  43. {
  44. for( const wxString& unit : { _( "mm" ), _( "Inch" ), _( "DPI" ) } )
  45. m_PixelUnit->Append( unit );
  46. m_outputSizeX.SetUnit( getUnitFromSelection() );
  47. m_outputSizeY.SetUnit( getUnitFromSelection() );
  48. m_outputSizeX.SetOutputSize( 0, getUnitFromSelection() );
  49. m_outputSizeY.SetOutputSize( 0, getUnitFromSelection() );
  50. m_UnitSizeX->ChangeValue( formatOutputSize( m_outputSizeX.GetOutputSize() ) );
  51. m_UnitSizeY->ChangeValue( formatOutputSize( m_outputSizeY.GetOutputSize() ) );
  52. m_buttonExportFile->Enable( false );
  53. m_buttonExportClipboard->Enable( false );
  54. }
  55. wxWindow* BITMAP2CMP_PANEL::GetCurrentPage()
  56. {
  57. return m_Notebook->GetCurrentPage();
  58. }
  59. void BITMAP2CMP_PANEL::LoadSettings( BITMAP2CMP_SETTINGS* cfg )
  60. {
  61. if( cfg->m_Units >= 0 && cfg->m_Units < (int) m_PixelUnit->GetCount() )
  62. m_PixelUnit->SetSelection( cfg->m_Units );
  63. m_sliderThreshold->SetValue( cfg->m_Threshold );
  64. m_negative = cfg->m_Negative;
  65. m_checkNegative->SetValue( cfg->m_Negative );
  66. m_aspectRatio = 1.0;
  67. m_aspectRatioCheckbox->SetValue( true );
  68. switch( cfg->m_LastFormat )
  69. {
  70. default:
  71. case FOOTPRINT_FMT: m_rbFootprint->SetValue( true ); break;
  72. case SYMBOL_FMT:
  73. case SYMBOL_PASTE_FMT: m_rbSymbol->SetValue( true ); break;
  74. case POSTSCRIPT_FMT: m_rbPostscript->SetValue( true ); break;
  75. case DRAWING_SHEET_FMT: m_rbWorksheet->SetValue( true ); break;
  76. }
  77. m_layerLabel->Enable( cfg->m_LastFormat == FOOTPRINT_FMT );
  78. m_layerCtrl->Enable( cfg->m_LastFormat == FOOTPRINT_FMT );
  79. if( cfg->m_LastLayer >= 0 && cfg->m_LastLayer < (int) m_layerCtrl->GetCount() )
  80. m_layerCtrl->SetSelection( cfg->m_LastLayer );
  81. }
  82. void BITMAP2CMP_PANEL::SaveSettings( BITMAP2CMP_SETTINGS* cfg )
  83. {
  84. cfg->m_Threshold = m_sliderThreshold->GetValue();
  85. cfg->m_Negative = m_checkNegative->IsChecked();
  86. cfg->m_LastFormat = getOutputFormat();
  87. cfg->m_LastLayer = m_layerCtrl->GetSelection();
  88. cfg->m_Units = m_PixelUnit->GetSelection();
  89. }
  90. void BITMAP2CMP_PANEL::OnPaintInit( wxPaintEvent& event )
  91. {
  92. #ifdef __WXMAC__
  93. // Otherwise fails due: using wxPaintDC without being in a native paint event
  94. wxClientDC pict_dc( m_InitialPicturePanel );
  95. #else
  96. wxPaintDC pict_dc( m_InitialPicturePanel );
  97. #endif
  98. m_InitialPicturePanel->PrepareDC( pict_dc );
  99. // OSX crashes with empty bitmaps (on initial refreshes)
  100. if( m_Pict_Bitmap.IsOk() )
  101. pict_dc.DrawBitmap( m_Pict_Bitmap, 0, 0, !!m_Pict_Bitmap.GetMask() );
  102. event.Skip();
  103. }
  104. void BITMAP2CMP_PANEL::OnPaintGreyscale( wxPaintEvent& event )
  105. {
  106. #ifdef __WXMAC__
  107. // Otherwise fails due: using wxPaintDC without being in a native paint event
  108. wxClientDC greyscale_dc( m_GreyscalePicturePanel );
  109. #else
  110. wxPaintDC greyscale_dc( m_GreyscalePicturePanel );
  111. #endif
  112. m_GreyscalePicturePanel->PrepareDC( greyscale_dc );
  113. // OSX crashes with empty bitmaps (on initial refreshes)
  114. if( m_Greyscale_Bitmap.IsOk() )
  115. greyscale_dc.DrawBitmap( m_Greyscale_Bitmap, 0, 0, !!m_Greyscale_Bitmap.GetMask() );
  116. event.Skip();
  117. }
  118. void BITMAP2CMP_PANEL::OnPaintBW( wxPaintEvent& event )
  119. {
  120. #ifdef __WXMAC__
  121. // Otherwise fails due: using wxPaintDC without being in a native paint event
  122. wxClientDC nb_dc( m_BNPicturePanel );
  123. #else
  124. wxPaintDC nb_dc( m_BNPicturePanel );
  125. #endif
  126. m_BNPicturePanel->PrepareDC( nb_dc );
  127. if( m_BN_Bitmap.IsOk() )
  128. nb_dc.DrawBitmap( m_BN_Bitmap, 0, 0, !!m_BN_Bitmap.GetMask() );
  129. event.Skip();
  130. }
  131. void BITMAP2CMP_PANEL::OnLoadFile( wxCommandEvent& event )
  132. {
  133. m_parentFrame->OnLoadFile();
  134. }
  135. bool BITMAP2CMP_PANEL::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
  136. {
  137. m_Pict_Image.Destroy();
  138. if( !m_Pict_Image.LoadFile( aFileSet[0] ) )
  139. {
  140. // LoadFile has its own UI, no need for further failure notification here
  141. return false;
  142. }
  143. m_Pict_Bitmap = wxBitmap( m_Pict_Image );
  144. // Determine image resolution in DPI (does not existing in all formats).
  145. // the resolution can be given in bit per inches or bit per cm in file
  146. int imageDPIx = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
  147. int imageDPIy = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY );
  148. if( imageDPIx > 1 && imageDPIy > 1 )
  149. {
  150. if( m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM )
  151. {
  152. imageDPIx = KiROUND( imageDPIx * 2.54 );
  153. imageDPIy = KiROUND( imageDPIy * 2.54 );
  154. }
  155. }
  156. else // fallback to a default value (DEFAULT_DPI)
  157. {
  158. imageDPIx = imageDPIy = DEFAULT_DPI;
  159. }
  160. m_InputXValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIx ) );
  161. m_InputYValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIy ) );
  162. int h = m_Pict_Bitmap.GetHeight();
  163. int w = m_Pict_Bitmap.GetWidth();
  164. m_aspectRatio = (double) w / h;
  165. m_outputSizeX.SetOriginalDPI( imageDPIx );
  166. m_outputSizeX.SetOriginalSizePixels( w );
  167. m_outputSizeY.SetOriginalDPI( imageDPIy );
  168. m_outputSizeY.SetOriginalSizePixels( h );
  169. // Update display to keep aspect ratio
  170. wxCommandEvent dummy;
  171. OnSizeChangeX( dummy );
  172. updateImageInfo();
  173. m_InitialPicturePanel->SetVirtualSize( w, h );
  174. m_GreyscalePicturePanel->SetVirtualSize( w, h );
  175. m_BNPicturePanel->SetVirtualSize( w, h );
  176. m_Greyscale_Image.Destroy();
  177. m_Greyscale_Image = m_Pict_Image.ConvertToGreyscale( );
  178. if( m_Pict_Bitmap.GetMask() )
  179. {
  180. for( int x = 0; x < m_Pict_Bitmap.GetWidth(); x++ )
  181. {
  182. for( int y = 0; y < m_Pict_Bitmap.GetHeight(); y++ )
  183. {
  184. if( m_Pict_Image.GetRed( x, y ) == m_Pict_Image.GetMaskRed()
  185. && m_Pict_Image.GetGreen( x, y ) == m_Pict_Image.GetMaskGreen()
  186. && m_Pict_Image.GetBlue( x, y ) == m_Pict_Image.GetMaskBlue() )
  187. {
  188. m_Greyscale_Image.SetRGB( x, y, 255, 255, 255 );
  189. }
  190. }
  191. }
  192. }
  193. if( m_negative )
  194. negateGreyscaleImage();
  195. m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image );
  196. m_NB_Image = m_Greyscale_Image;
  197. binarize( (double)m_sliderThreshold->GetValue() / m_sliderThreshold->GetMax() );
  198. m_buttonExportFile->Enable( true );
  199. m_buttonExportClipboard->Enable( true );
  200. m_outputSizeX.SetOutputSizeFromInitialImageSize();
  201. m_UnitSizeX->ChangeValue( formatOutputSize( m_outputSizeX.GetOutputSize() ) );
  202. m_outputSizeY.SetOutputSizeFromInitialImageSize();
  203. m_UnitSizeY->ChangeValue( formatOutputSize( m_outputSizeY.GetOutputSize() ) );
  204. return true;
  205. }
  206. // return a string giving the output size, according to the selected unit
  207. wxString BITMAP2CMP_PANEL::formatOutputSize( double aSize )
  208. {
  209. wxString text;
  210. if( getUnitFromSelection() == EDA_UNITS::MM )
  211. text.Printf( wxS( "%.1f" ), aSize );
  212. else if( getUnitFromSelection() == EDA_UNITS::INCH )
  213. text.Printf( wxS( "%.2f" ), aSize );
  214. else
  215. text.Printf( wxT( "%d" ), KiROUND( aSize ) );
  216. return text;
  217. }
  218. void BITMAP2CMP_PANEL::updateImageInfo()
  219. {
  220. // Note: the image resolution text controls are not modified here, to avoid a race between
  221. // text change when entered by user and a text change if it is modified here.
  222. if( m_Pict_Bitmap.IsOk() )
  223. {
  224. m_SizeXValue->SetLabel( wxString::Format( wxT( "%d" ), m_Pict_Bitmap.GetWidth() ) );
  225. m_SizeYValue->SetLabel( wxString::Format( wxT( "%d" ), m_Pict_Bitmap.GetHeight() ) );
  226. m_BPPValue->SetLabel( wxString::Format( wxT( "%d" ), m_Pict_Bitmap.GetDepth() ) );
  227. }
  228. }
  229. EDA_UNITS BITMAP2CMP_PANEL::getUnitFromSelection()
  230. {
  231. // return the EDA_UNITS from the m_PixelUnit choice
  232. switch( m_PixelUnit->GetSelection() )
  233. {
  234. case 1: return EDA_UNITS::INCH;
  235. case 2: return EDA_UNITS::UNSCALED;
  236. case 0:
  237. default: return EDA_UNITS::MM;
  238. }
  239. }
  240. void BITMAP2CMP_PANEL::OnSizeChangeX( wxCommandEvent& event )
  241. {
  242. double new_size;
  243. if( m_UnitSizeX->GetValue().ToDouble( &new_size ) )
  244. {
  245. if( m_aspectRatioCheckbox->GetValue() )
  246. {
  247. double calculatedY = new_size / m_aspectRatio;
  248. if( getUnitFromSelection() == EDA_UNITS::UNSCALED )
  249. {
  250. // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked.
  251. // just re-scale the other dpi
  252. double ratio = new_size / m_outputSizeX.GetOutputSize();
  253. calculatedY = m_outputSizeY.GetOutputSize() * ratio;
  254. }
  255. m_outputSizeY.SetOutputSize( calculatedY, getUnitFromSelection() );
  256. m_UnitSizeY->ChangeValue( formatOutputSize( m_outputSizeY.GetOutputSize() ) );
  257. }
  258. m_outputSizeX.SetOutputSize( new_size, getUnitFromSelection() );
  259. }
  260. updateImageInfo();
  261. }
  262. void BITMAP2CMP_PANEL::OnSizeChangeY( wxCommandEvent& event )
  263. {
  264. double new_size;
  265. if( m_UnitSizeY->GetValue().ToDouble( &new_size ) )
  266. {
  267. if( m_aspectRatioCheckbox->GetValue() )
  268. {
  269. double calculatedX = new_size * m_aspectRatio;
  270. if( getUnitFromSelection() == EDA_UNITS::UNSCALED )
  271. {
  272. // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked.
  273. // just re-scale the other dpi
  274. double ratio = new_size / m_outputSizeX.GetOutputSize();
  275. calculatedX = m_outputSizeX.GetOutputSize() * ratio;
  276. }
  277. m_outputSizeX.SetOutputSize( calculatedX, getUnitFromSelection() );
  278. m_UnitSizeX->ChangeValue( formatOutputSize( m_outputSizeX.GetOutputSize() ) );
  279. }
  280. m_outputSizeY.SetOutputSize( new_size, getUnitFromSelection() );
  281. }
  282. updateImageInfo();
  283. }
  284. void BITMAP2CMP_PANEL::OnSizeUnitChange( wxCommandEvent& event )
  285. {
  286. m_outputSizeX.SetUnit( getUnitFromSelection() );
  287. m_outputSizeY.SetUnit( getUnitFromSelection() );
  288. updateImageInfo();
  289. m_UnitSizeX->ChangeValue( formatOutputSize( m_outputSizeX.GetOutputSize() ) );
  290. m_UnitSizeY->ChangeValue( formatOutputSize( m_outputSizeY.GetOutputSize() ) );
  291. }
  292. void BITMAP2CMP_PANEL::SetOutputSize( const IMAGE_SIZE& aSizeX, const IMAGE_SIZE& aSizeY )
  293. {
  294. m_outputSizeX = aSizeX;
  295. m_outputSizeY = aSizeY;
  296. updateImageInfo();
  297. m_UnitSizeX->ChangeValue( formatOutputSize( m_outputSizeX.GetOutputSize() ) );
  298. m_UnitSizeY->ChangeValue( formatOutputSize( m_outputSizeY.GetOutputSize() ) );
  299. }
  300. void BITMAP2CMP_PANEL::ToggleAspectRatioLock( wxCommandEvent& event )
  301. {
  302. if( m_aspectRatioCheckbox->GetValue() )
  303. {
  304. // Force display update when aspect ratio is locked
  305. wxCommandEvent dummy;
  306. OnSizeChangeX( dummy );
  307. }
  308. }
  309. void BITMAP2CMP_PANEL::binarize( double aThreshold )
  310. {
  311. unsigned char threshold = aThreshold * 255;
  312. unsigned char alpha_thresh = 0.7 * threshold;
  313. for( int y = 0; y < m_Greyscale_Image.GetHeight(); y++ )
  314. {
  315. for( int x = 0; x < m_Greyscale_Image.GetWidth(); x++ )
  316. {
  317. unsigned char pixel = m_Greyscale_Image.GetGreen( x, y );
  318. unsigned char alpha = m_Greyscale_Image.HasAlpha() ? m_Greyscale_Image.GetAlpha( x, y )
  319. : wxALPHA_OPAQUE;
  320. if( pixel < threshold && alpha > alpha_thresh )
  321. pixel = 0;
  322. else
  323. pixel = 255;
  324. m_NB_Image.SetRGB( x, y, pixel, pixel, pixel );
  325. }
  326. }
  327. m_BN_Bitmap = wxBitmap( m_NB_Image );
  328. }
  329. void BITMAP2CMP_PANEL::negateGreyscaleImage( )
  330. {
  331. for( int y = 0; y < m_Greyscale_Image.GetHeight(); y++ )
  332. {
  333. for( int x = 0; x < m_Greyscale_Image.GetWidth(); x++ )
  334. {
  335. unsigned char pixel = m_Greyscale_Image.GetGreen( x, y );
  336. pixel = ~pixel;
  337. m_Greyscale_Image.SetRGB( x, y, pixel, pixel, pixel );
  338. }
  339. }
  340. }
  341. void BITMAP2CMP_PANEL::OnNegativeClicked( wxCommandEvent& )
  342. {
  343. if( m_checkNegative->GetValue() != m_negative )
  344. {
  345. negateGreyscaleImage();
  346. m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image );
  347. binarize( (double)m_sliderThreshold->GetValue() / m_sliderThreshold->GetMax() );
  348. m_negative = m_checkNegative->GetValue();
  349. Refresh();
  350. }
  351. }
  352. void BITMAP2CMP_PANEL::OnThresholdChange( wxScrollEvent& event )
  353. {
  354. binarize( (double)m_sliderThreshold->GetValue() / m_sliderThreshold->GetMax() );
  355. Refresh();
  356. }
  357. void BITMAP2CMP_PANEL::OnExportToFile( wxCommandEvent& event )
  358. {
  359. switch( getOutputFormat() )
  360. {
  361. case SYMBOL_FMT:
  362. case SYMBOL_PASTE_FMT: m_parentFrame->ExportEeschemaFormat(); break;
  363. case FOOTPRINT_FMT: m_parentFrame->ExportPcbnewFormat(); break;
  364. case POSTSCRIPT_FMT: m_parentFrame->ExportPostScriptFormat(); break;
  365. case DRAWING_SHEET_FMT: m_parentFrame->ExportDrawingSheetFormat(); break;
  366. }
  367. }
  368. OUTPUT_FMT_ID BITMAP2CMP_PANEL::getOutputFormat()
  369. {
  370. if( m_rbSymbol->GetValue() )
  371. return SYMBOL_FMT;
  372. else if( m_rbPostscript->GetValue() )
  373. return POSTSCRIPT_FMT;
  374. else if( m_rbWorksheet->GetValue() )
  375. return DRAWING_SHEET_FMT;
  376. else
  377. return FOOTPRINT_FMT;
  378. }
  379. void BITMAP2CMP_PANEL::OnExportToClipboard( wxCommandEvent& event )
  380. {
  381. std::string buffer;
  382. OUTPUT_FMT_ID format = getOutputFormat() == SYMBOL_FMT ? SYMBOL_PASTE_FMT : getOutputFormat();
  383. ExportToBuffer( buffer, format );
  384. wxLogNull doNotLog; // disable logging of failed clipboard actions
  385. // Write buffer to the clipboard
  386. if( wxTheClipboard->Open() )
  387. {
  388. // This data objects are held by the clipboard,
  389. // so do not delete them in the app.
  390. wxTheClipboard->SetData( new wxTextDataObject( buffer.c_str() ) );
  391. wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
  392. wxTheClipboard->Close();
  393. }
  394. else
  395. {
  396. wxMessageBox( _( "Unable to export to the Clipboard") );
  397. }
  398. }
  399. void BITMAP2CMP_PANEL::ExportToBuffer( std::string& aOutput, OUTPUT_FMT_ID aFormat )
  400. {
  401. // Create a potrace bitmap
  402. potrace_bitmap_t* potrace_bitmap = bm_new( m_NB_Image.GetWidth(), m_NB_Image.GetHeight() );
  403. if( !potrace_bitmap )
  404. {
  405. wxMessageBox( _( "Error allocating memory for potrace bitmap" ) );
  406. return;
  407. }
  408. /* fill the bitmap with data */
  409. for( int y = 0; y < m_NB_Image.GetHeight(); y++ )
  410. {
  411. for( int x = 0; x < m_NB_Image.GetWidth(); x++ )
  412. {
  413. unsigned char pixel = m_NB_Image.GetGreen( x, y );
  414. BM_PUT( potrace_bitmap, x, y, pixel ? 0 : 1 );
  415. }
  416. }
  417. wxString layer = wxT( "F.SilkS" );
  418. if( aFormat == FOOTPRINT_FMT )
  419. {
  420. switch( m_layerCtrl->GetSelection() )
  421. {
  422. case 0: layer = wxT( "F.Cu" ); break;
  423. case 1: layer = wxT( "F.SilkS" ); break;
  424. case 2: layer = wxT( "F.Mask" ); break;
  425. case 3: layer = wxT( "Dwgs.User" ); break;
  426. case 4: layer = wxT( "Cmts.User" ); break;
  427. case 5: layer = wxT( "Eco1.User" ); break;
  428. case 6: layer = wxT( "Eco2.User" ); break;
  429. case 7: layer = wxT( "F.Fab" ); break;
  430. }
  431. }
  432. WX_STRING_REPORTER reporter;
  433. BITMAPCONV_INFO converter( aOutput, reporter );
  434. converter.ConvertBitmap( potrace_bitmap, aFormat, m_outputSizeX.GetOutputDPI(),
  435. m_outputSizeY.GetOutputDPI(), layer );
  436. if( reporter.HasMessage() )
  437. wxMessageBox( reporter.GetMessages(), _( "Errors" ) );
  438. }
  439. void BITMAP2CMP_PANEL::OnFormatChange( wxCommandEvent& event )
  440. {
  441. m_layerLabel->Enable( m_rbFootprint->GetValue() );
  442. m_layerCtrl->Enable( m_rbFootprint->GetValue() );
  443. }