/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 1992-2010 jean-pierre.charras * Copyright (C) 1992-2017 Kicad Developers, see change_log.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "bitmap2component.h" #include "bitmap2cmp_gui_base.h" #define KEYWORD_FRAME_POSX wxT( "Bmconverter_Pos_x" ) #define KEYWORD_FRAME_POSY wxT( "Bmconverter_Pos_y" ) #define KEYWORD_FRAME_SIZEX wxT( "Bmconverter_Size_x" ) #define KEYWORD_FRAME_SIZEY wxT( "Bmconverter_Size_y" ) #define KEYWORD_LAST_INPUT_FILE wxT( "Last_input" ) #define KEYWORD_LAST_OUTPUT_FILE wxT( "Last_output" ) #define KEYWORD_LAST_FORMAT wxT( "Last_format" ) #define KEYWORD_LAST_MODLAYER wxT( "Last_modlayer" ) #define KEYWORD_BINARY_THRESHOLD wxT( "Threshold" ) #define KEYWORD_BW_NEGATIVE wxT( "Negative_choice" ) #define DEFAULT_DPI 300 // Default resolution in Bit per inches extern int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, OUTPUT_FMT_ID aFormat, int aDpi_X, int aDpi_Y, BMP2CMP_MOD_LAYER aModLayer ); /** * Class BM2CMP_FRAME_BASE * is the main frame for this application */ class BM2CMP_FRAME : public BM2CMP_FRAME_BASE { private: wxImage m_Pict_Image; wxBitmap m_Pict_Bitmap; wxImage m_Greyscale_Image; wxBitmap m_Greyscale_Bitmap; wxImage m_NB_Image; wxBitmap m_BN_Bitmap; wxSize m_imageDPI; // The initial image resolution. When unknown, // set to DEFAULT_DPI x DEFAULT_DPI per Inch bool m_Negative; wxString m_BitmapFileName; wxString m_ConvertedFileName; wxSize m_frameSize; wxPoint m_framePos; wxConfigBase* m_config; public: BM2CMP_FRAME( KIWAY* aKiway, wxWindow* aParent ); ~BM2CMP_FRAME(); // overload KIWAY_PLAYER virtual bool OpenProjectFiles( const std::vector& aFilenames, int aCtl=0 ) override; private: // Event handlers void OnPaint( wxPaintEvent& event ) override; void OnLoadFile( wxCommandEvent& event ) override; void OnExport( wxCommandEvent& event ) override; /** * Generate a schematic library which contains one component: * the logo */ void OnExportEeschema(); /** * Generate a module in S expr format */ void OnExportPcbnew(); /** * Generate a postscript file */ void OnExportPostScript(); /** * Generate a file suitable to be copied into a page layout * description file (.kicad_wks file */ void OnExportLogo(); void Binarize( double aThreshold ); // aThreshold = 0.0 (black level) to 1.0 (white level) void OnNegativeClicked( wxCommandEvent& event ) override; void OnThresholdChange( wxScrollEvent& event ) override; void OnResolutionChange( wxCommandEvent& event ) override; // called when texts controls which handle the image resolution // lose the focus, to ensure the right values are displayed // because the m_imageDPI are clipped to acceptable values, and // the text displayed could be differ during text edition // We are using ChangeValue here to avoid generating a wxEVT_TEXT event. void UpdateDPITextValueX( wxMouseEvent& event ) { m_DPIValueX->ChangeValue( wxString::Format( wxT( "%d" ), m_imageDPI.x ) ); } void UpdateDPITextValueY( wxMouseEvent& event ) { m_DPIValueY->ChangeValue( wxString::Format( wxT( "%d" ), m_imageDPI.y ) ); } void NegateGreyscaleImage( ); void ExportFile( FILE* aOutfile, OUTPUT_FMT_ID aFormat ); void updateImageInfo(); void OnFormatChange( wxCommandEvent& event ) override; }; BM2CMP_FRAME::BM2CMP_FRAME( KIWAY* aKiway, wxWindow* aParent ) : BM2CMP_FRAME_BASE( aParent ) { SetKiway( this, aKiway ); int tmp; m_config = GetNewConfig( Pgm().App().GetAppName() ); m_config->Read( KEYWORD_FRAME_POSX, & m_framePos.x, -1 ); m_config->Read( KEYWORD_FRAME_POSY, & m_framePos.y, -1 ); m_config->Read( KEYWORD_FRAME_SIZEX, & m_frameSize.x, -1 ); m_config->Read( KEYWORD_FRAME_SIZEY, & m_frameSize.y, -1 ); m_config->Read( KEYWORD_LAST_INPUT_FILE, &m_BitmapFileName ); m_config->Read( KEYWORD_LAST_OUTPUT_FILE, &m_ConvertedFileName ); if( m_config->Read( KEYWORD_BINARY_THRESHOLD, &tmp ) ) m_sliderThreshold->SetValue( tmp ); m_config->Read( KEYWORD_BW_NEGATIVE, &tmp, 0 ); m_Negative = tmp != 0; m_checkNegative->SetValue( m_Negative ); if( m_config->Read( KEYWORD_LAST_FORMAT, &tmp ) ) { if( tmp < 0 || tmp > FINAL_FMT ) tmp = PCBNEW_KICAD_MOD; m_radioBoxFormat->SetSelection( tmp ); } if( tmp == PCBNEW_KICAD_MOD ) m_radio_PCBLayer->Enable( true ); else m_radio_PCBLayer->Enable( false ); if( m_config->Read( KEYWORD_LAST_MODLAYER, &tmp ) ) { if( (unsigned) tmp > MOD_LYR_FINAL ) // Out of range m_radio_PCBLayer->SetSelection( MOD_LYR_FSILKS ); else m_radio_PCBLayer->SetSelection( tmp ); } // Give an icon wxIcon icon; icon.CopyFromBitmap( KiBitmap( icon_bitmap2component_xpm ) ); SetIcon( icon ); GetSizer()->SetSizeHints( this ); SetSize( m_framePos.x, m_framePos.y, m_frameSize.x, m_frameSize.y ); m_buttonExport->Enable( false ); m_imageDPI.x = m_imageDPI.y = DEFAULT_DPI; // Default resolution in Bit per inches if ( m_framePos == wxDefaultPosition ) Centre(); } BM2CMP_FRAME::~BM2CMP_FRAME() { if( !m_config || IsIconized() ) return; m_frameSize = GetSize(); m_framePos = GetPosition(); m_config->Write( KEYWORD_FRAME_POSX, (long) m_framePos.x ); m_config->Write( KEYWORD_FRAME_POSY, (long) m_framePos.y ); m_config->Write( KEYWORD_FRAME_SIZEX, (long) m_frameSize.x ); m_config->Write( KEYWORD_FRAME_SIZEY, (long) m_frameSize.y ); m_config->Write( KEYWORD_LAST_INPUT_FILE, m_BitmapFileName ); m_config->Write( KEYWORD_LAST_OUTPUT_FILE, m_ConvertedFileName ); m_config->Write( KEYWORD_BINARY_THRESHOLD, m_sliderThreshold->GetValue() ); m_config->Write( KEYWORD_BW_NEGATIVE, m_checkNegative->IsChecked() ? 1 : 0 ); m_config->Write( KEYWORD_LAST_FORMAT, m_radioBoxFormat->GetSelection() ); m_config->Write( KEYWORD_LAST_MODLAYER, m_radio_PCBLayer->GetSelection() ); delete m_config; /* This needed for OSX: avoids further OnDraw processing after this * destructor and before the native window is destroyed */ this->Freeze( ); } void BM2CMP_FRAME::OnPaint( wxPaintEvent& event ) { #ifdef __WXMAC__ // Otherwise fails due: using wxPaintDC without being in a native paint event wxClientDC pict_dc( m_InitialPicturePanel ); wxClientDC greyscale_dc( m_GreyscalePicturePanel ); wxClientDC nb_dc( m_BNPicturePanel ); #else wxPaintDC pict_dc( m_InitialPicturePanel ); wxPaintDC greyscale_dc( m_GreyscalePicturePanel ); wxPaintDC nb_dc( m_BNPicturePanel ); #endif m_InitialPicturePanel->PrepareDC( pict_dc ); m_GreyscalePicturePanel->PrepareDC( greyscale_dc ); m_BNPicturePanel->PrepareDC( nb_dc ); // OSX crashes with empty bitmaps (on initial refreshes) if( m_Pict_Bitmap.IsOk() && m_Greyscale_Bitmap.IsOk() && m_BN_Bitmap.IsOk() ) { pict_dc.DrawBitmap( m_Pict_Bitmap, 0, 0, false ); greyscale_dc.DrawBitmap( m_Greyscale_Bitmap, 0, 0, false ); nb_dc.DrawBitmap( m_BN_Bitmap, 0, 0, false ); } } void BM2CMP_FRAME::OnLoadFile( wxCommandEvent& event ) { wxFileName fn( m_BitmapFileName ); wxString path = fn.GetPath(); if( path.IsEmpty() || !wxDirExists( path ) ) path = m_mruPath; wxFileDialog fileDlg( this, _( "Choose Image" ), path, wxEmptyString, _( "Image Files " ) + wxImage::GetImageExtWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST ); int diag = fileDlg.ShowModal(); if( diag != wxID_OK ) return; wxString fullFilename = fileDlg.GetPath(); if( !OpenProjectFiles( std::vector( 1, fullFilename ) ) ) return; fn = fullFilename; m_mruPath = fn.GetPath(); SetStatusText( fullFilename ); Refresh(); } bool BM2CMP_FRAME::OpenProjectFiles( const std::vector& aFileSet, int aCtl ) { // Prj().MaybeLoadProjectSettings(); m_BitmapFileName = aFileSet[0]; if( !m_Pict_Image.LoadFile( m_BitmapFileName ) ) { // LoadFile has its own UI, no need for further failure notification here return false; } m_Pict_Bitmap = wxBitmap( m_Pict_Image ); int h = m_Pict_Bitmap.GetHeight(); int w = m_Pict_Bitmap.GetWidth(); // Determine image resolution in DPI (does not existing in all formats). // the resolution can be given in bit per inches or bit per cm in file m_imageDPI.x = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX ); m_imageDPI.y = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY ); if( m_imageDPI.x > 1 && m_imageDPI.y > 1 ) { if( m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM ) { // When the initial resolution is given in bits per cm, // experience shows adding 1.27 to the resolution converted in dpi // before convert to int value reduce the conversion error // but it is not perfect m_imageDPI.x = m_imageDPI.x * 2.54 + 1.27; m_imageDPI.y = m_imageDPI.y * 2.54 + 1.27; } } else // fallback to the default value m_imageDPI.x = m_imageDPI.y = DEFAULT_DPI; // Display image info: // We are using ChangeValue here to avoid generating a wxEVT_TEXT event. m_DPIValueX->ChangeValue( wxString::Format( wxT( "%d" ), m_imageDPI.x ) ); m_DPIValueY->ChangeValue( wxString::Format( wxT( "%d" ), m_imageDPI.y ) ); updateImageInfo(); m_InitialPicturePanel->SetVirtualSize( w, h ); m_GreyscalePicturePanel->SetVirtualSize( w, h ); m_BNPicturePanel->SetVirtualSize( w, h ); m_Greyscale_Image.Destroy(); m_Greyscale_Image = m_Pict_Image.ConvertToGreyscale( ); if( m_Negative ) NegateGreyscaleImage( ); m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image ); m_NB_Image = m_Greyscale_Image; Binarize( (double) m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() ); m_buttonExport->Enable( true ); return true; } void BM2CMP_FRAME::updateImageInfo() { // Note: the image resolution text controls are not modified // here, to avoid a race between text change when entered by user and // a text change if it is modified here. int h = m_Pict_Bitmap.GetHeight(); int w = m_Pict_Bitmap.GetWidth(); int nb = m_Pict_Bitmap.GetDepth(); m_SizeXValue->SetLabel( wxString::Format( wxT( "%d" ), w ) ); m_SizeYValue->SetLabel( wxString::Format( wxT( "%d" ), h ) ); m_BPPValue->SetLabel( wxString::Format( wxT( "%d" ), nb ) ); m_SizeXValue_mm->SetLabel( wxString::Format( wxT( "%.1f" ), (double) w / m_imageDPI.x * 25.4 ) ); m_SizeYValue_mm->SetLabel( wxString::Format( wxT( "%.1f" ), (double) h / m_imageDPI.y * 25.4 ) ); } void BM2CMP_FRAME::OnResolutionChange( wxCommandEvent& event ) { long tmp; if( m_DPIValueX->GetValue().ToLong( &tmp ) ) m_imageDPI.x = tmp; if( m_DPIValueY->GetValue().ToLong( &tmp ) ) m_imageDPI.y = tmp; if( m_imageDPI.x < 32 ) m_imageDPI.x = 32; if( m_imageDPI.y < 32 ) m_imageDPI.y = 32; updateImageInfo(); } void BM2CMP_FRAME::Binarize( double aThreshold ) { unsigned int pixin; unsigned char pixout; int h = m_Greyscale_Image.GetHeight(); int w = m_Greyscale_Image.GetWidth(); unsigned int threshold = (int)(aThreshold * 256); for( int y = 0; y < h; y++ ) for( int x = 0; x < w; x++ ) { pixin = m_Greyscale_Image.GetGreen( x, y ); if( pixin < threshold ) pixout = 0; else pixout = 255; m_NB_Image.SetRGB( x, y, pixout, pixout, pixout ); } m_BN_Bitmap = wxBitmap( m_NB_Image ); } void BM2CMP_FRAME::NegateGreyscaleImage( ) { unsigned char pix; int h = m_Greyscale_Image.GetHeight(); int w = m_Greyscale_Image.GetWidth(); for( int y = 0; y < h; y++ ) for( int x = 0; x < w; x++ ) { pix = m_Greyscale_Image.GetGreen( x, y ); pix = ~pix; m_Greyscale_Image.SetRGB( x, y, pix, pix, pix ); } } void BM2CMP_FRAME::OnNegativeClicked( wxCommandEvent& ) { if( m_checkNegative->GetValue() != m_Negative ) { NegateGreyscaleImage(); m_Greyscale_Bitmap = wxBitmap( m_Greyscale_Image ); Binarize( (double)m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() ); m_Negative = m_checkNegative->GetValue(); Refresh(); } } void BM2CMP_FRAME::OnThresholdChange( wxScrollEvent& event ) { Binarize( (double)m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() ); Refresh(); } void BM2CMP_FRAME::OnExport( wxCommandEvent& event ) { // choices of m_radioBoxFormat are expected to be in same order as // OUTPUT_FMT_ID. See bitmap2component.h OUTPUT_FMT_ID sel = (OUTPUT_FMT_ID) m_radioBoxFormat->GetSelection(); switch( sel ) { case EESCHEMA_FMT: OnExportEeschema(); break; case PCBNEW_KICAD_MOD: OnExportPcbnew(); break; case POSTSCRIPT_FMT: OnExportPostScript(); break; case KICAD_LOGO: OnExportLogo(); break; } } void BM2CMP_FRAME::OnExportLogo() { wxFileName fn( m_ConvertedFileName ); wxString path = fn.GetPath(); if( path.IsEmpty() || !wxDirExists(path) ) path = ::wxGetCwd(); wxFileDialog fileDlg( this, _( "Create Logo File" ), path, wxEmptyString, PageLayoutDescrFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); int diag = fileDlg.ShowModal(); if( diag != wxID_OK ) return; fn = fileDlg.GetPath(); fn.SetExt( PageLayoutDescrFileExtension ); m_ConvertedFileName = fn.GetFullPath(); FILE* outfile; outfile = wxFopen( m_ConvertedFileName, wxT( "w" ) ); if( outfile == NULL ) { wxString msg; msg.Printf( _( "File \"%s\" could not be created." ), GetChars( m_ConvertedFileName ) ); wxMessageBox( msg ); return; } ExportFile( outfile, KICAD_LOGO ); fclose( outfile ); } void BM2CMP_FRAME::OnExportPostScript() { wxFileName fn( m_ConvertedFileName ); wxString path = fn.GetPath(); if( path.IsEmpty() || !wxDirExists( path ) ) path = ::wxGetCwd(); wxFileDialog fileDlg( this, _( "Create Postscript File" ), path, wxEmptyString, PSFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); int diag = fileDlg.ShowModal(); if( diag != wxID_OK ) return; fn = fileDlg.GetPath(); fn.SetExt( wxT( "ps" ) ); m_ConvertedFileName = fn.GetFullPath(); FILE* outfile; outfile = wxFopen( m_ConvertedFileName, wxT( "w" ) ); if( outfile == NULL ) { wxString msg; msg.Printf( _( "File \"%s\" could not be created." ), GetChars( m_ConvertedFileName ) ); wxMessageBox( msg ); return; } ExportFile( outfile, POSTSCRIPT_FMT ); fclose( outfile ); } void BM2CMP_FRAME::OnExportEeschema() { wxFileName fn( m_ConvertedFileName ); wxString path = fn.GetPath(); if( path.IsEmpty() || !wxDirExists(path) ) path = ::wxGetCwd(); wxFileDialog fileDlg( this, _( "Create Symbol Library" ), path, wxEmptyString, SchematicLibraryFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); int diag = fileDlg.ShowModal(); if( diag != wxID_OK ) return; fn = fileDlg.GetPath(); fn.SetExt( SchematicLibraryFileExtension ); m_ConvertedFileName = fn.GetFullPath(); FILE* outfile = wxFopen( m_ConvertedFileName, wxT( "w" ) ); if( outfile == NULL ) { wxString msg; msg.Printf( _( "File \"%s\" could not be created." ), GetChars( m_ConvertedFileName ) ); wxMessageBox( msg ); return; } ExportFile( outfile, EESCHEMA_FMT ); fclose( outfile ); } void BM2CMP_FRAME::OnExportPcbnew() { wxFileName fn( m_ConvertedFileName ); wxString path = fn.GetPath(); if( path.IsEmpty() || !wxDirExists( path ) ) path = m_mruPath; wxFileDialog fileDlg( this, _( "Create Footprint Library" ), path, wxEmptyString, KiCadFootprintLibFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); int diag = fileDlg.ShowModal(); if( diag != wxID_OK ) return; fn = fileDlg.GetPath(); fn.SetExt( KiCadFootprintFileExtension ); m_ConvertedFileName = fn.GetFullPath(); FILE* outfile = wxFopen( m_ConvertedFileName, wxT( "w" ) ); if( outfile == NULL ) { wxString msg; msg.Printf( _( "File \"%s\" could not be created." ), GetChars( m_ConvertedFileName ) ); wxMessageBox( msg ); return; } ExportFile( outfile, PCBNEW_KICAD_MOD ); fclose( outfile ); m_mruPath = fn.GetPath(); } void BM2CMP_FRAME::ExportFile( FILE* aOutfile, OUTPUT_FMT_ID aFormat ) { // Create a potrace bitmap int h = m_NB_Image.GetHeight(); int w = m_NB_Image.GetWidth(); potrace_bitmap_t* potrace_bitmap = bm_new( w, h ); if( !potrace_bitmap ) { wxString msg; msg.Printf( _( "Error allocating memory for potrace bitmap" ) ); wxMessageBox( msg ); return; } /* fill the bitmap with data */ for( int y = 0; y < h; y++ ) { for( int x = 0; x < w; x++ ) { unsigned char pix = m_NB_Image.GetGreen( x, y ); BM_PUT( potrace_bitmap, x, y, pix ? 1 : 0 ); } } // choices of m_radio_PCBLayer are expected to be in same order as // BMP2CMP_MOD_LAYER. See bitmap2component.h BMP2CMP_MOD_LAYER modLayer = MOD_LYR_FSILKS; if( aFormat == PCBNEW_KICAD_MOD ) modLayer = (BMP2CMP_MOD_LAYER) m_radio_PCBLayer->GetSelection(); bitmap2component( potrace_bitmap, aOutfile, aFormat, m_imageDPI.x, m_imageDPI.y, modLayer ); } //---------------------------------------------------------------------- namespace BMP2CMP { static struct IFACE : public KIFACE_I { bool OnKifaceStart( PGM_BASE* aProgram, int aCtlBits ) override; wxWindow* CreateWindow( wxWindow* aParent, int aClassId, KIWAY* aKiway, int aCtlBits = 0 ) override { return new BM2CMP_FRAME( aKiway, aParent ); } /** * Function IfaceOrAddress * return a pointer to the requested object. The safest way to use this * is to retrieve a pointer to a static instance of an interface, similar to * how the KIFACE interface is exported. But if you know what you are doing * use it to retrieve anything you want. * * @param aDataId identifies which object you want the address of. * * @return void* - and must be cast into the know type. */ void* IfaceOrAddress( int aDataId ) override { return NULL; } IFACE( const char* aDSOname, KIWAY::FACE_T aType ) : KIFACE_I( aDSOname, aType ) {} } kiface( "BMP2CMP", KIWAY::FACE_BMP2CMP ); } // namespace BMP2CMP using namespace BMP2CMP; static PGM_BASE* process; KIFACE_I& Kiface() { return kiface; } // KIFACE_GETTER's actual spelling is a substitution macro found in kiway.h. // KIFACE_GETTER will not have name mangling due to declaration in kiway.h. KIFACE* KIFACE_GETTER( int* aKIFACEversion, int aKIWAYversion, PGM_BASE* aProgram ) { process = (PGM_BASE*) aProgram; return &kiface; } #if defined(BUILD_KIWAY_DLLS) PGM_BASE& Pgm() { wxASSERT( process ); // KIFACE_GETTER has already been called. return *process; } #endif bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits ) { return start_common( aCtlBits ); } void BM2CMP_FRAME::OnFormatChange( wxCommandEvent& event ) { if( m_radioBoxFormat->GetSelection() == PCBNEW_KICAD_MOD ) m_radio_PCBLayer->Enable( true ); else m_radio_PCBLayer->Enable( false ); event.Skip(); }