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.

575 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
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 (C) 1992-2023 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 <bitmaps.h>
  30. #include <common.h>
  31. #include <kiface_base.h>
  32. #include <math/util.h> // for KiROUND
  33. #include <pgm_base.h>
  34. #include <potracelib.h>
  35. #include <wx/clipbrd.h>
  36. #include <wx/rawbmp.h>
  37. #include <wx/msgdlg.h>
  38. #include <wx/dcclient.h>
  39. #include <wx/log.h>
  40. #define DEFAULT_DPI 300 // the image DPI used in formats that do not define a DPI
  41. BITMAP2CMP_PANEL::BITMAP2CMP_PANEL( BITMAP2CMP_FRAME* aParent ) :
  42. BITMAP2CMP_PANEL_BASE( aParent ),
  43. m_parentFrame( aParent ), m_negative( false ),
  44. m_aspectRatio( 1.0 )
  45. {
  46. for( wxString unit : { _( "mm" ), _( "Inch" ), _( "DPI" ) } )
  47. m_PixelUnit->Append( unit );
  48. m_outputSizeX.SetUnit( getUnitFromSelection() );
  49. m_outputSizeY.SetUnit( getUnitFromSelection() );
  50. m_outputSizeX.SetOutputSize( 0, getUnitFromSelection() );
  51. m_outputSizeY.SetOutputSize( 0, getUnitFromSelection() );
  52. m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) );
  53. m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) );
  54. m_buttonExportFile->Enable( false );
  55. m_buttonExportClipboard->Enable( false );
  56. }
  57. BITMAP2CMP_PANEL::~BITMAP2CMP_PANEL()
  58. {
  59. }
  60. wxWindow* BITMAP2CMP_PANEL::GetCurrentPage()
  61. {
  62. return m_Notebook->GetCurrentPage();
  63. }
  64. void BITMAP2CMP_PANEL::LoadSettings( BITMAP2CMP_SETTINGS* cfg )
  65. {
  66. int u_select = cfg->m_Units;
  67. if( u_select < 0 || u_select > 2 ) // Validity control
  68. u_select = 0;
  69. m_PixelUnit->SetSelection( u_select );
  70. m_sliderThreshold->SetValue( cfg->m_Threshold );
  71. m_negative = cfg->m_Negative;
  72. m_checkNegative->SetValue( cfg->m_Negative );
  73. m_aspectRatio = 1.0;
  74. m_aspectRatioCheckbox->SetValue( true );
  75. int format = cfg->m_LastFormat;
  76. if( format < 0 || format > FINAL_FMT )
  77. format = PCBNEW_KICAD_MOD;
  78. m_rbOutputFormat->SetSelection( format );
  79. bool enable = format == PCBNEW_KICAD_MOD;
  80. m_chPCBLayer->Enable( enable );
  81. int last_layer = cfg->m_LastModLayer;
  82. if( last_layer < 0 || last_layer > static_cast<int>( MOD_LYR_FINAL ) ) // Out of range
  83. last_layer = MOD_LYR_FSILKS;
  84. m_chPCBLayer->SetSelection( last_layer );
  85. }
  86. void BITMAP2CMP_PANEL::SaveSettings( BITMAP2CMP_SETTINGS* cfg )
  87. {
  88. cfg->m_Threshold = m_sliderThreshold->GetValue();
  89. cfg->m_Negative = m_checkNegative->IsChecked();
  90. cfg->m_LastFormat = m_rbOutputFormat->GetSelection();
  91. cfg->m_LastModLayer = m_chPCBLayer->GetSelection();
  92. cfg->m_Units = m_PixelUnit->GetSelection();
  93. }
  94. void BITMAP2CMP_PANEL::OnPaintInit( wxPaintEvent& event )
  95. {
  96. #ifdef __WXMAC__
  97. // Otherwise fails due: using wxPaintDC without being in a native paint event
  98. wxClientDC pict_dc( m_InitialPicturePanel );
  99. #else
  100. wxPaintDC pict_dc( m_InitialPicturePanel );
  101. #endif
  102. m_InitialPicturePanel->PrepareDC( pict_dc );
  103. // OSX crashes with empty bitmaps (on initial refreshes)
  104. if( m_Pict_Bitmap.IsOk() )
  105. pict_dc.DrawBitmap( m_Pict_Bitmap, 0, 0, !!m_Pict_Bitmap.GetMask() );
  106. event.Skip();
  107. }
  108. void BITMAP2CMP_PANEL::OnPaintGreyscale( wxPaintEvent& event )
  109. {
  110. #ifdef __WXMAC__
  111. // Otherwise fails due: using wxPaintDC without being in a native paint event
  112. wxClientDC greyscale_dc( m_GreyscalePicturePanel );
  113. #else
  114. wxPaintDC greyscale_dc( m_GreyscalePicturePanel );
  115. #endif
  116. m_GreyscalePicturePanel->PrepareDC( greyscale_dc );
  117. // OSX crashes with empty bitmaps (on initial refreshes)
  118. if( m_Greyscale_Bitmap.IsOk() )
  119. greyscale_dc.DrawBitmap( m_Greyscale_Bitmap, 0, 0, !!m_Greyscale_Bitmap.GetMask() );
  120. event.Skip();
  121. }
  122. void BITMAP2CMP_PANEL::OnPaintBW( wxPaintEvent& event )
  123. {
  124. #ifdef __WXMAC__
  125. // Otherwise fails due: using wxPaintDC without being in a native paint event
  126. wxClientDC nb_dc( m_BNPicturePanel );
  127. #else
  128. wxPaintDC nb_dc( m_BNPicturePanel );
  129. #endif
  130. m_BNPicturePanel->PrepareDC( nb_dc );
  131. if( m_BN_Bitmap.IsOk() )
  132. nb_dc.DrawBitmap( m_BN_Bitmap, 0, 0, !!m_BN_Bitmap.GetMask() );
  133. event.Skip();
  134. }
  135. void BITMAP2CMP_PANEL::OnLoadFile( wxCommandEvent& event )
  136. {
  137. m_parentFrame->OnLoadFile();
  138. }
  139. bool BITMAP2CMP_PANEL::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
  140. {
  141. m_Pict_Image.Destroy();
  142. if( !m_Pict_Image.LoadFile( aFileSet[0] ) )
  143. {
  144. // LoadFile has its own UI, no need for further failure notification here
  145. return false;
  146. }
  147. m_Pict_Bitmap = wxBitmap( m_Pict_Image );
  148. // Determine image resolution in DPI (does not existing in all formats).
  149. // the resolution can be given in bit per inches or bit per cm in file
  150. int imageDPIx = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
  151. int imageDPIy = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY );
  152. if( imageDPIx > 1 && imageDPIy > 1 )
  153. {
  154. if( m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM )
  155. {
  156. imageDPIx = KiROUND( imageDPIx * 2.54 );
  157. imageDPIy = KiROUND( imageDPIy * 2.54 );
  158. }
  159. }
  160. else // fallback to a default value (DEFAULT_DPI)
  161. {
  162. imageDPIx = imageDPIy = DEFAULT_DPI;
  163. }
  164. m_InputXValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIx ) );
  165. m_InputYValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIy ) );
  166. int h = m_Pict_Bitmap.GetHeight();
  167. int w = m_Pict_Bitmap.GetWidth();
  168. m_aspectRatio = (double) w / h;
  169. m_outputSizeX.SetOriginalDPI( imageDPIx );
  170. m_outputSizeX.SetOriginalSizePixels( w );
  171. m_outputSizeY.SetOriginalDPI( imageDPIy );
  172. m_outputSizeY.SetOriginalSizePixels( h );
  173. // Update display to keep aspect ratio
  174. wxCommandEvent dummy;
  175. OnSizeChangeX( dummy );
  176. updateImageInfo();
  177. m_InitialPicturePanel->SetVirtualSize( w, h );
  178. m_GreyscalePicturePanel->SetVirtualSize( w, h );
  179. m_BNPicturePanel->SetVirtualSize( w, h );
  180. m_Greyscale_Image.Destroy();
  181. m_Greyscale_Image = m_Pict_Image.ConvertToGreyscale( );
  182. if( m_Pict_Bitmap.GetMask() )
  183. {
  184. for( int x = 0; x < m_Pict_Bitmap.GetWidth(); x++ )
  185. {
  186. for( int y = 0; y < m_Pict_Bitmap.GetHeight(); y++ )
  187. {
  188. if( m_Pict_Image.GetRed( x, y ) == m_Pict_Image.GetMaskRed() &&
  189. m_Pict_Image.GetGreen( x, y ) == m_Pict_Image.GetMaskGreen() &&
  190. m_Pict_Image.GetBlue( x, y ) == m_Pict_Image.GetMaskBlue() )
  191. {
  192. m_Greyscale_Image.SetRGB( x, y, 255, 255, 255 );
  193. }
  194. }
  195. }
  196. }
  197. if( m_negative )
  198. NegateGreyscaleImage( );
  199. m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image );
  200. m_NB_Image = m_Greyscale_Image;
  201. Binarize( (double) m_sliderThreshold->GetValue() / m_sliderThreshold->GetMax() );
  202. m_buttonExportFile->Enable( true );
  203. m_buttonExportClipboard->Enable( true );
  204. m_outputSizeX.SetOutputSizeFromInitialImageSize();
  205. m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) );
  206. m_outputSizeY.SetOutputSizeFromInitialImageSize();
  207. m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) );
  208. return true;
  209. }
  210. // return a string giving the output size, according to the selected unit
  211. wxString BITMAP2CMP_PANEL::FormatOutputSize( double aSize )
  212. {
  213. wxString text;
  214. if( getUnitFromSelection() == EDA_UNITS::MILLIMETRES )
  215. text.Printf( wxS( "%.1f" ), aSize );
  216. else if( getUnitFromSelection() == EDA_UNITS::INCHES )
  217. text.Printf( wxS( "%.2f" ), aSize );
  218. else
  219. text.Printf( wxT( "%d" ), KiROUND( aSize ) );
  220. return text;
  221. }
  222. void BITMAP2CMP_PANEL::updateImageInfo()
  223. {
  224. // Note: the image resolution text controls are not modified here, to avoid a race between
  225. // text change when entered by user and a text change if it is modified here.
  226. if( m_Pict_Bitmap.IsOk() )
  227. {
  228. int h = m_Pict_Bitmap.GetHeight();
  229. int w = m_Pict_Bitmap.GetWidth();
  230. int nb = m_Pict_Bitmap.GetDepth();
  231. m_SizeXValue->SetLabel( wxString::Format( wxT( "%d" ), w ) );
  232. m_SizeYValue->SetLabel( wxString::Format( wxT( "%d" ), h ) );
  233. m_BPPValue->SetLabel( wxString::Format( wxT( "%d" ), nb ) );
  234. }
  235. }
  236. EDA_UNITS BITMAP2CMP_PANEL::getUnitFromSelection()
  237. {
  238. // return the EDA_UNITS from the m_PixelUnit choice
  239. switch( m_PixelUnit->GetSelection() )
  240. {
  241. case 1: return EDA_UNITS::INCHES;
  242. case 2: return EDA_UNITS::UNSCALED;
  243. case 0:
  244. default: return EDA_UNITS::MILLIMETRES;
  245. }
  246. }
  247. void BITMAP2CMP_PANEL::OnSizeChangeX( wxCommandEvent& event )
  248. {
  249. double new_size;
  250. if( m_UnitSizeX->GetValue().ToDouble( &new_size ) )
  251. {
  252. if( m_aspectRatioCheckbox->GetValue() )
  253. {
  254. double calculatedY = new_size / m_aspectRatio;
  255. if( getUnitFromSelection() == EDA_UNITS::UNSCALED )
  256. {
  257. // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked.
  258. // just re-scale the other dpi
  259. double ratio = new_size / m_outputSizeX.GetOutputSize();
  260. calculatedY = m_outputSizeY.GetOutputSize() * ratio;
  261. }
  262. m_outputSizeY.SetOutputSize( calculatedY, getUnitFromSelection() );
  263. m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) );
  264. }
  265. m_outputSizeX.SetOutputSize( new_size, getUnitFromSelection() );
  266. }
  267. updateImageInfo();
  268. }
  269. void BITMAP2CMP_PANEL::OnSizeChangeY( wxCommandEvent& event )
  270. {
  271. double new_size;
  272. if( m_UnitSizeY->GetValue().ToDouble( &new_size ) )
  273. {
  274. if( m_aspectRatioCheckbox->GetValue() )
  275. {
  276. double calculatedX = new_size * m_aspectRatio;
  277. if( getUnitFromSelection() == EDA_UNITS::UNSCALED )
  278. {
  279. // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked.
  280. // just re-scale the other dpi
  281. double ratio = new_size / m_outputSizeX.GetOutputSize();
  282. calculatedX = m_outputSizeX.GetOutputSize() * ratio;
  283. }
  284. m_outputSizeX.SetOutputSize( calculatedX, getUnitFromSelection() );
  285. m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) );
  286. }
  287. m_outputSizeY.SetOutputSize( new_size, getUnitFromSelection() );
  288. }
  289. updateImageInfo();
  290. }
  291. void BITMAP2CMP_PANEL::OnSizeUnitChange( wxCommandEvent& event )
  292. {
  293. m_outputSizeX.SetUnit( getUnitFromSelection() );
  294. m_outputSizeY.SetUnit( getUnitFromSelection() );
  295. updateImageInfo();
  296. m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) );
  297. m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) );
  298. }
  299. void BITMAP2CMP_PANEL::SetOutputSize( const IMAGE_SIZE& aSizeX, const IMAGE_SIZE& aSizeY )
  300. {
  301. m_outputSizeX = aSizeX;
  302. m_outputSizeY = aSizeY;
  303. updateImageInfo();
  304. m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) );
  305. m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) );
  306. }
  307. void BITMAP2CMP_PANEL::ToggleAspectRatioLock( wxCommandEvent& event )
  308. {
  309. if( m_aspectRatioCheckbox->GetValue() )
  310. {
  311. // Force display update when aspect ratio is locked
  312. wxCommandEvent dummy;
  313. OnSizeChangeX( dummy );
  314. }
  315. }
  316. void BITMAP2CMP_PANEL::Binarize( double aThreshold )
  317. {
  318. int h = m_Greyscale_Image.GetHeight();
  319. int w = m_Greyscale_Image.GetWidth();
  320. unsigned char threshold = aThreshold * 255;
  321. unsigned char alpha_thresh = 0.7 * threshold;
  322. for( int y = 0; y < h; y++ )
  323. {
  324. for( int x = 0; x < w; x++ )
  325. {
  326. unsigned char pixout;
  327. unsigned char pixin = m_Greyscale_Image.GetGreen( x, y );
  328. unsigned char alpha = m_Greyscale_Image.HasAlpha() ? m_Greyscale_Image.GetAlpha( x, y )
  329. : wxALPHA_OPAQUE;
  330. if( pixin < threshold && alpha > alpha_thresh )
  331. pixout = 0;
  332. else
  333. pixout = 255;
  334. m_NB_Image.SetRGB( x, y, pixout, pixout, pixout );
  335. }
  336. }
  337. m_BN_Bitmap = wxBitmap( m_NB_Image );
  338. }
  339. void BITMAP2CMP_PANEL::NegateGreyscaleImage( )
  340. {
  341. unsigned char pix;
  342. int h = m_Greyscale_Image.GetHeight();
  343. int w = m_Greyscale_Image.GetWidth();
  344. for( int y = 0; y < h; y++ )
  345. {
  346. for( int x = 0; x < w; x++ )
  347. {
  348. pix = m_Greyscale_Image.GetGreen( x, y );
  349. pix = ~pix;
  350. m_Greyscale_Image.SetRGB( x, y, pix, pix, pix );
  351. }
  352. }
  353. }
  354. void BITMAP2CMP_PANEL::OnNegativeClicked( wxCommandEvent& )
  355. {
  356. if( m_checkNegative->GetValue() != m_negative )
  357. {
  358. NegateGreyscaleImage();
  359. m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image );
  360. Binarize( (double)m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() );
  361. m_negative = m_checkNegative->GetValue();
  362. Refresh();
  363. }
  364. }
  365. void BITMAP2CMP_PANEL::OnThresholdChange( wxScrollEvent& event )
  366. {
  367. Binarize( (double)m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() );
  368. Refresh();
  369. }
  370. void BITMAP2CMP_PANEL::OnExportToFile( wxCommandEvent& event )
  371. {
  372. // choices of m_rbOutputFormat are expected to be in same order as
  373. // OUTPUT_FMT_ID. See bitmap2component.h
  374. OUTPUT_FMT_ID format = (OUTPUT_FMT_ID) m_rbOutputFormat->GetSelection();
  375. exportBitmap( format );
  376. }
  377. void BITMAP2CMP_PANEL::OnExportToClipboard( wxCommandEvent& event )
  378. {
  379. // choices of m_rbOutputFormat are expected to be in same order as
  380. // OUTPUT_FMT_ID. See bitmap2component.h
  381. OUTPUT_FMT_ID format = (OUTPUT_FMT_ID) m_rbOutputFormat->GetSelection();
  382. std::string buffer;
  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::exportBitmap( OUTPUT_FMT_ID aFormat )
  400. {
  401. switch( aFormat )
  402. {
  403. case EESCHEMA_FMT: m_parentFrame->ExportEeschemaFormat(); break;
  404. case PCBNEW_KICAD_MOD: m_parentFrame->ExportPcbnewFormat(); break;
  405. case POSTSCRIPT_FMT: m_parentFrame->ExportPostScriptFormat(); break;
  406. case KICAD_WKS_LOGO: m_parentFrame->ExportDrawingSheetFormat(); break;
  407. }
  408. }
  409. void BITMAP2CMP_PANEL::ExportToBuffer( std::string& aOutput, OUTPUT_FMT_ID aFormat )
  410. {
  411. // Create a potrace bitmap
  412. int h = m_NB_Image.GetHeight();
  413. int w = m_NB_Image.GetWidth();
  414. potrace_bitmap_t* potrace_bitmap = bm_new( w, h );
  415. if( !potrace_bitmap )
  416. {
  417. wxString msg;
  418. msg.Printf( _( "Error allocating memory for potrace bitmap" ) );
  419. wxMessageBox( msg );
  420. return;
  421. }
  422. /* fill the bitmap with data */
  423. for( int y = 0; y < h; y++ )
  424. {
  425. for( int x = 0; x < w; x++ )
  426. {
  427. unsigned char pix = m_NB_Image.GetGreen( x, y );
  428. BM_PUT( potrace_bitmap, x, y, pix ? 0 : 1 );
  429. }
  430. }
  431. // choices of m_rbPCBLayer are expected to be in same order as
  432. // BMP2CMP_MOD_LAYER. See bitmap2component.h
  433. BMP2CMP_MOD_LAYER modLayer = MOD_LYR_FSILKS;
  434. if( aFormat == PCBNEW_KICAD_MOD )
  435. modLayer = (BMP2CMP_MOD_LAYER) m_chPCBLayer->GetSelection();
  436. BITMAPCONV_INFO converter( aOutput );
  437. converter.ConvertBitmap( potrace_bitmap, aFormat, m_outputSizeX.GetOutputDPI(),
  438. m_outputSizeY.GetOutputDPI(), modLayer );
  439. if( !converter.GetErrorMessages().empty() )
  440. wxMessageBox( converter.GetErrorMessages().c_str(), _( "Errors" ) );
  441. }
  442. void BITMAP2CMP_PANEL::OnFormatChange( wxCommandEvent& event )
  443. {
  444. bool enable = m_rbOutputFormat->GetSelection() == PCBNEW_KICAD_MOD;
  445. m_chPCBLayer->Enable( enable );
  446. }