/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 1992-2010 jean-pierre.charras * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.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 // for KiROUND #include #include #include #include #include #include #include #define DEFAULT_DPI 300 // the image DPI used in formats that do not define a DPI BITMAP2CMP_PANEL::BITMAP2CMP_PANEL( BITMAP2CMP_FRAME* aParent ) : BITMAP2CMP_PANEL_BASE( aParent ), m_parentFrame( aParent ), m_negative( false ), m_aspectRatio( 1.0 ) { for( wxString unit : { _( "mm" ), _( "Inch" ), _( "DPI" ) } ) m_PixelUnit->Append( unit ); m_outputSizeX.SetUnit( getUnitFromSelection() ); m_outputSizeY.SetUnit( getUnitFromSelection() ); m_outputSizeX.SetOutputSize( 0, getUnitFromSelection() ); m_outputSizeY.SetOutputSize( 0, getUnitFromSelection() ); m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) ); m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) ); m_buttonExportFile->Enable( false ); m_buttonExportClipboard->Enable( false ); } BITMAP2CMP_PANEL::~BITMAP2CMP_PANEL() { } wxWindow* BITMAP2CMP_PANEL::GetCurrentPage() { return m_Notebook->GetCurrentPage(); } void BITMAP2CMP_PANEL::LoadSettings( BITMAP2CMP_SETTINGS* cfg ) { int u_select = cfg->m_Units; if( u_select < 0 || u_select > 2 ) // Validity control u_select = 0; m_PixelUnit->SetSelection( u_select ); m_sliderThreshold->SetValue( cfg->m_Threshold ); m_negative = cfg->m_Negative; m_checkNegative->SetValue( cfg->m_Negative ); m_aspectRatio = 1.0; m_aspectRatioCheckbox->SetValue( true ); int format = cfg->m_LastFormat; if( format < 0 || format > FINAL_FMT ) format = PCBNEW_KICAD_MOD; m_rbOutputFormat->SetSelection( format ); bool enable = format == PCBNEW_KICAD_MOD; m_chPCBLayer->Enable( enable ); int last_layer = cfg->m_LastModLayer; if( last_layer < 0 || last_layer > static_cast( MOD_LYR_FINAL ) ) // Out of range last_layer = MOD_LYR_FSILKS; m_chPCBLayer->SetSelection( last_layer ); } void BITMAP2CMP_PANEL::SaveSettings( BITMAP2CMP_SETTINGS* cfg ) { cfg->m_Threshold = m_sliderThreshold->GetValue(); cfg->m_Negative = m_checkNegative->IsChecked(); cfg->m_LastFormat = m_rbOutputFormat->GetSelection(); cfg->m_LastModLayer = m_chPCBLayer->GetSelection(); cfg->m_Units = m_PixelUnit->GetSelection(); } void BITMAP2CMP_PANEL::OnPaintInit( wxPaintEvent& event ) { #ifdef __WXMAC__ // Otherwise fails due: using wxPaintDC without being in a native paint event wxClientDC pict_dc( m_InitialPicturePanel ); #else wxPaintDC pict_dc( m_InitialPicturePanel ); #endif m_InitialPicturePanel->PrepareDC( pict_dc ); // OSX crashes with empty bitmaps (on initial refreshes) if( m_Pict_Bitmap.IsOk() ) pict_dc.DrawBitmap( m_Pict_Bitmap, 0, 0, !!m_Pict_Bitmap.GetMask() ); event.Skip(); } void BITMAP2CMP_PANEL::OnPaintGreyscale( wxPaintEvent& event ) { #ifdef __WXMAC__ // Otherwise fails due: using wxPaintDC without being in a native paint event wxClientDC greyscale_dc( m_GreyscalePicturePanel ); #else wxPaintDC greyscale_dc( m_GreyscalePicturePanel ); #endif m_GreyscalePicturePanel->PrepareDC( greyscale_dc ); // OSX crashes with empty bitmaps (on initial refreshes) if( m_Greyscale_Bitmap.IsOk() ) greyscale_dc.DrawBitmap( m_Greyscale_Bitmap, 0, 0, !!m_Greyscale_Bitmap.GetMask() ); event.Skip(); } void BITMAP2CMP_PANEL::OnPaintBW( wxPaintEvent& event ) { #ifdef __WXMAC__ // Otherwise fails due: using wxPaintDC without being in a native paint event wxClientDC nb_dc( m_BNPicturePanel ); #else wxPaintDC nb_dc( m_BNPicturePanel ); #endif m_BNPicturePanel->PrepareDC( nb_dc ); if( m_BN_Bitmap.IsOk() ) nb_dc.DrawBitmap( m_BN_Bitmap, 0, 0, !!m_BN_Bitmap.GetMask() ); event.Skip(); } void BITMAP2CMP_PANEL::OnLoadFile( wxCommandEvent& event ) { m_parentFrame->OnLoadFile(); } bool BITMAP2CMP_PANEL::OpenProjectFiles( const std::vector& aFileSet, int aCtl ) { m_Pict_Image.Destroy(); if( !m_Pict_Image.LoadFile( aFileSet[0] ) ) { // LoadFile has its own UI, no need for further failure notification here return false; } m_Pict_Bitmap = wxBitmap( m_Pict_Image ); // 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 int imageDPIx = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX ); int imageDPIy = m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY ); if( imageDPIx > 1 && imageDPIy > 1 ) { if( m_Pict_Image.GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM ) { imageDPIx = KiROUND( imageDPIx * 2.54 ); imageDPIy = KiROUND( imageDPIy * 2.54 ); } } else // fallback to a default value (DEFAULT_DPI) { imageDPIx = imageDPIy = DEFAULT_DPI; } m_InputXValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIx ) ); m_InputYValueDPI->SetLabel( wxString::Format( wxT( "%d" ), imageDPIy ) ); int h = m_Pict_Bitmap.GetHeight(); int w = m_Pict_Bitmap.GetWidth(); m_aspectRatio = (double) w / h; m_outputSizeX.SetOriginalDPI( imageDPIx ); m_outputSizeX.SetOriginalSizePixels( w ); m_outputSizeY.SetOriginalDPI( imageDPIy ); m_outputSizeY.SetOriginalSizePixels( h ); // Update display to keep aspect ratio wxCommandEvent dummy; OnSizeChangeX( dummy ); 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_Pict_Bitmap.GetMask() ) { for( int x = 0; x < m_Pict_Bitmap.GetWidth(); x++ ) { for( int y = 0; y < m_Pict_Bitmap.GetHeight(); y++ ) { if( m_Pict_Image.GetRed( x, y ) == m_Pict_Image.GetMaskRed() && m_Pict_Image.GetGreen( x, y ) == m_Pict_Image.GetMaskGreen() && m_Pict_Image.GetBlue( x, y ) == m_Pict_Image.GetMaskBlue() ) { m_Greyscale_Image.SetRGB( x, y, 255, 255, 255 ); } } } } 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_buttonExportFile->Enable( true ); m_buttonExportClipboard->Enable( true ); m_outputSizeX.SetOutputSizeFromInitialImageSize(); m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) ); m_outputSizeY.SetOutputSizeFromInitialImageSize(); m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) ); return true; } // return a string giving the output size, according to the selected unit wxString BITMAP2CMP_PANEL::FormatOutputSize( double aSize ) { wxString text; if( getUnitFromSelection() == EDA_UNITS::MILLIMETRES ) text.Printf( wxS( "%.1f" ), aSize ); else if( getUnitFromSelection() == EDA_UNITS::INCHES ) text.Printf( wxS( "%.2f" ), aSize ); else text.Printf( wxT( "%d" ), KiROUND( aSize ) ); return text; } void BITMAP2CMP_PANEL::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. if( m_Pict_Bitmap.IsOk() ) { 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 ) ); } } EDA_UNITS BITMAP2CMP_PANEL::getUnitFromSelection() { // return the EDA_UNITS from the m_PixelUnit choice switch( m_PixelUnit->GetSelection() ) { case 1: return EDA_UNITS::INCHES; case 2: return EDA_UNITS::UNSCALED; case 0: default: return EDA_UNITS::MILLIMETRES; } } void BITMAP2CMP_PANEL::OnSizeChangeX( wxCommandEvent& event ) { double new_size; if( m_UnitSizeX->GetValue().ToDouble( &new_size ) ) { if( m_aspectRatioCheckbox->GetValue() ) { double calculatedY = new_size / m_aspectRatio; if( getUnitFromSelection() == EDA_UNITS::UNSCALED ) { // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked. // just re-scale the other dpi double ratio = new_size / m_outputSizeX.GetOutputSize(); calculatedY = m_outputSizeY.GetOutputSize() * ratio; } m_outputSizeY.SetOutputSize( calculatedY, getUnitFromSelection() ); m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) ); } m_outputSizeX.SetOutputSize( new_size, getUnitFromSelection() ); } updateImageInfo(); } void BITMAP2CMP_PANEL::OnSizeChangeY( wxCommandEvent& event ) { double new_size; if( m_UnitSizeY->GetValue().ToDouble( &new_size ) ) { if( m_aspectRatioCheckbox->GetValue() ) { double calculatedX = new_size * m_aspectRatio; if( getUnitFromSelection() == EDA_UNITS::UNSCALED ) { // for units in DPI, keeping aspect ratio cannot use m_AspectRatioLocked. // just re-scale the other dpi double ratio = new_size / m_outputSizeX.GetOutputSize(); calculatedX = m_outputSizeX.GetOutputSize() * ratio; } m_outputSizeX.SetOutputSize( calculatedX, getUnitFromSelection() ); m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) ); } m_outputSizeY.SetOutputSize( new_size, getUnitFromSelection() ); } updateImageInfo(); } void BITMAP2CMP_PANEL::OnSizeUnitChange( wxCommandEvent& event ) { m_outputSizeX.SetUnit( getUnitFromSelection() ); m_outputSizeY.SetUnit( getUnitFromSelection() ); updateImageInfo(); m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) ); m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) ); } void BITMAP2CMP_PANEL::SetOutputSize( const IMAGE_SIZE& aSizeX, const IMAGE_SIZE& aSizeY ) { m_outputSizeX = aSizeX; m_outputSizeY = aSizeY; updateImageInfo(); m_UnitSizeX->ChangeValue( FormatOutputSize( m_outputSizeX.GetOutputSize() ) ); m_UnitSizeY->ChangeValue( FormatOutputSize( m_outputSizeY.GetOutputSize() ) ); } void BITMAP2CMP_PANEL::ToggleAspectRatioLock( wxCommandEvent& event ) { if( m_aspectRatioCheckbox->GetValue() ) { // Force display update when aspect ratio is locked wxCommandEvent dummy; OnSizeChangeX( dummy ); } } void BITMAP2CMP_PANEL::Binarize( double aThreshold ) { int h = m_Greyscale_Image.GetHeight(); int w = m_Greyscale_Image.GetWidth(); unsigned char threshold = aThreshold * 255; unsigned char alpha_thresh = 0.7 * threshold; for( int y = 0; y < h; y++ ) { for( int x = 0; x < w; x++ ) { unsigned char pixout; unsigned char pixin = m_Greyscale_Image.GetGreen( x, y ); unsigned char alpha = m_Greyscale_Image.HasAlpha() ? m_Greyscale_Image.GetAlpha( x, y ) : wxALPHA_OPAQUE; if( pixin < threshold && alpha > alpha_thresh ) pixout = 0; else pixout = 255; m_NB_Image.SetRGB( x, y, pixout, pixout, pixout ); } } m_BN_Bitmap = wxBitmap( m_NB_Image ); } void BITMAP2CMP_PANEL::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 BITMAP2CMP_PANEL::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 BITMAP2CMP_PANEL::OnThresholdChange( wxScrollEvent& event ) { Binarize( (double)m_sliderThreshold->GetValue()/m_sliderThreshold->GetMax() ); Refresh(); } void BITMAP2CMP_PANEL::OnExportToFile( wxCommandEvent& event ) { // choices of m_rbOutputFormat are expected to be in same order as // OUTPUT_FMT_ID. See bitmap2component.h OUTPUT_FMT_ID format = (OUTPUT_FMT_ID) m_rbOutputFormat->GetSelection(); exportBitmap( format ); } void BITMAP2CMP_PANEL::OnExportToClipboard( wxCommandEvent& event ) { // choices of m_rbOutputFormat are expected to be in same order as // OUTPUT_FMT_ID. See bitmap2component.h OUTPUT_FMT_ID format = (OUTPUT_FMT_ID) m_rbOutputFormat->GetSelection(); std::string buffer; ExportToBuffer( buffer, format ); wxLogNull doNotLog; // disable logging of failed clipboard actions // Write buffer to the clipboard if( wxTheClipboard->Open() ) { // This data objects are held by the clipboard, // so do not delete them in the app. wxTheClipboard->SetData( new wxTextDataObject( buffer.c_str() ) ); wxTheClipboard->Flush(); // Allow data to be available after closing KiCad wxTheClipboard->Close(); } else { wxMessageBox( _( "Unable to export to the Clipboard") ); } } void BITMAP2CMP_PANEL::exportBitmap( OUTPUT_FMT_ID aFormat ) { switch( aFormat ) { case EESCHEMA_FMT: m_parentFrame->ExportEeschemaFormat(); break; case PCBNEW_KICAD_MOD: m_parentFrame->ExportPcbnewFormat(); break; case POSTSCRIPT_FMT: m_parentFrame->ExportPostScriptFormat(); break; case KICAD_WKS_LOGO: m_parentFrame->ExportDrawingSheetFormat(); break; } } void BITMAP2CMP_PANEL::ExportToBuffer( std::string& aOutput, 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 ? 0 : 1 ); } } // choices of m_rbPCBLayer 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_chPCBLayer->GetSelection(); BITMAPCONV_INFO converter( aOutput ); converter.ConvertBitmap( potrace_bitmap, aFormat, m_outputSizeX.GetOutputDPI(), m_outputSizeY.GetOutputDPI(), modLayer ); if( !converter.GetErrorMessages().empty() ) wxMessageBox( converter.GetErrorMessages().c_str(), _( "Errors" ) ); } void BITMAP2CMP_PANEL::OnFormatChange( wxCommandEvent& event ) { bool enable = m_rbOutputFormat->GetSelection() == PCBNEW_KICAD_MOD; m_chPCBLayer->Enable( enable ); }