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.

477 lines
13 KiB

// Dick Hollenbeck's KiROUND R&D // This provides better project control over rounding to int from double // than wxRound() did. This scheme provides better logging in Debug builds // and it provides for compile time calculation of constants. #include <stdio.h> #include <assert.h> #include <limits.h> //-----<KiROUND KIT>------------------------------------------------------------ /** * KiROUND * rounds a floating point number to an int using * "round halfway cases away from zero". * In Debug build an assert fires if will not fit into an int. */ #if defined( DEBUG ) // DEBUG: a macro to capture line and file, then calls this inline static inline int KiRound( double v, int line, const char* filename ) { v = v < 0 ? v - 0.5 : v + 0.5; if( v > INT_MAX + 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' > 0 ' for int\n", __FUNCTION__, filename, line, v ); } else if( v < INT_MIN - 0.5 ) { printf( "%s: in file %s on line %d, val: %.16g too ' < 0 ' for int\n", __FUNCTION__, filename, line, v ); } return int( v ); } #define KiROUND( v ) KiRound( v, __LINE__, __FILE__ ) #else // RELEASE: a macro so compile can pre-compute constants. #define KiROUND( v ) int( (v) < 0 ? (v) - 0.5 : (v) + 0.5 ) #endif //-----</KiROUND KIT>----------------------------------------------------------- // Only a macro is compile time calculated, an inline function causes a static constructor // in a situation like this. // Therefore the Release build is best done with a MACRO not an inline function. int Computed = KiROUND( 14.3 * 8 ); int main( int argc, char** argv ) { for( double d = double(INT_MAX)-1; d < double(INT_MAX)+8; d += 2.0 ) { int i = KiROUND( d ); printf( "t: %d %.16g\n", i, d ); } return 0; }
14 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 jean-pierre.charras
  5. * Copyright (C) 2011-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 <bitmap_base.h>
  25. #include <gr_basic.h>
  26. #include <math/util.h> // for KiROUND
  27. #include <memory> // for make_unique, unique_ptr
  28. #include <plotters/plotter.h>
  29. #include <richio.h>
  30. #include <wx/bitmap.h> // for wxBitmap
  31. #include <wx/mstream.h>
  32. BITMAP_BASE::BITMAP_BASE( const VECTOR2I& pos )
  33. {
  34. m_scale = 1.0; // 1.0 = original bitmap size
  35. m_bitmap = nullptr;
  36. m_image = nullptr;
  37. m_originalImage = nullptr;
  38. m_ppi = 300; // the bitmap definition. the default is 300PPI
  39. m_pixelSizeIu = 254000.0 / m_ppi; // a pixel size value OK for bitmaps using 300 PPI
  40. // for Eeschema which uses currently 254000PPI
  41. m_isMirroredX = false;
  42. m_isMirroredY = false;
  43. m_rotation = ANGLE_0;
  44. }
  45. BITMAP_BASE::BITMAP_BASE( const BITMAP_BASE& aSchBitmap )
  46. {
  47. m_scale = aSchBitmap.m_scale;
  48. m_ppi = aSchBitmap.m_ppi;
  49. m_pixelSizeIu = aSchBitmap.m_pixelSizeIu;
  50. m_isMirroredX = aSchBitmap.m_isMirroredX;
  51. m_isMirroredY = aSchBitmap.m_isMirroredY;
  52. m_rotation = aSchBitmap.m_rotation;
  53. m_image = nullptr;
  54. m_bitmap = nullptr;
  55. m_originalImage = nullptr;
  56. if( aSchBitmap.m_image )
  57. {
  58. m_image = new wxImage( *aSchBitmap.m_image );
  59. m_bitmap = new wxBitmap( *m_image );
  60. m_originalImage = new wxImage( *aSchBitmap.m_originalImage );
  61. m_imageId = aSchBitmap.m_imageId;
  62. }
  63. }
  64. void BITMAP_BASE::SetImage( wxImage* aImage )
  65. {
  66. delete m_image;
  67. m_image = aImage;
  68. delete m_originalImage;
  69. m_originalImage = new wxImage( *aImage );
  70. rebuildBitmap();
  71. updatePPI();
  72. }
  73. void BITMAP_BASE::rebuildBitmap( bool aResetID )
  74. {
  75. if( m_bitmap )
  76. delete m_bitmap;
  77. m_bitmap = new wxBitmap( *m_image );
  78. if( aResetID )
  79. m_imageId = KIID();
  80. }
  81. void BITMAP_BASE::updatePPI()
  82. {
  83. // Todo: eventually we need to support dpi / scaling in both dimensions
  84. int dpiX = m_originalImage->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
  85. if( dpiX > 1 )
  86. {
  87. if( m_originalImage->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM )
  88. m_ppi = KiROUND( dpiX * 2.54 );
  89. else
  90. m_ppi = dpiX;
  91. }
  92. }
  93. void BITMAP_BASE::ImportData( BITMAP_BASE* aItem )
  94. {
  95. *m_image = *aItem->m_image;
  96. *m_bitmap = *aItem->m_bitmap;
  97. *m_originalImage = *aItem->m_originalImage;
  98. m_imageId = aItem->m_imageId;
  99. m_scale = aItem->m_scale;
  100. m_ppi = aItem->m_ppi;
  101. m_pixelSizeIu = aItem->m_pixelSizeIu;
  102. m_isMirroredX = aItem->m_isMirroredX;
  103. m_isMirroredY = aItem->m_isMirroredY;
  104. m_rotation = aItem->m_rotation;
  105. }
  106. bool BITMAP_BASE::ReadImageFile( wxInputStream& aInStream )
  107. {
  108. std::unique_ptr<wxImage> new_image = std::make_unique<wxImage>();
  109. if( !new_image->LoadFile( aInStream ) )
  110. return false;
  111. delete m_image;
  112. m_image = new_image.release();
  113. delete m_originalImage;
  114. m_originalImage = new wxImage( *m_image );
  115. rebuildBitmap();
  116. updatePPI();
  117. return true;
  118. }
  119. bool BITMAP_BASE::ReadImageFile( const wxString& aFullFilename )
  120. {
  121. wxImage* new_image = new wxImage();
  122. if( !new_image->LoadFile( aFullFilename ) )
  123. {
  124. delete new_image;
  125. return false;
  126. }
  127. delete m_image;
  128. m_image = new_image;
  129. delete m_originalImage;
  130. m_originalImage = new wxImage( *m_image );
  131. rebuildBitmap();
  132. updatePPI();
  133. return true;
  134. }
  135. bool BITMAP_BASE::SaveData( FILE* aFile ) const
  136. {
  137. if( m_image )
  138. {
  139. wxMemoryOutputStream stream;
  140. m_image->SaveFile( stream, wxBITMAP_TYPE_PNG );
  141. // Write binary data in hexadecimal form (ASCII)
  142. wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
  143. char* begin = (char*) buffer->GetBufferStart();
  144. for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
  145. {
  146. if( ii >= 32 )
  147. {
  148. ii = 0;
  149. if( fprintf( aFile, "\n" ) == EOF )
  150. return false;
  151. }
  152. if( fprintf( aFile, "%2.2X ", *begin & 0xFF ) == EOF )
  153. return false;
  154. }
  155. }
  156. return true;
  157. }
  158. void BITMAP_BASE::SaveData( wxArrayString& aPngStrings ) const
  159. {
  160. if( m_image )
  161. {
  162. wxMemoryOutputStream stream;
  163. m_image->SaveFile( stream, wxBITMAP_TYPE_PNG );
  164. // Write binary data in hexadecimal form (ASCII)
  165. wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
  166. char* begin = (char*) buffer->GetBufferStart();
  167. wxString line;
  168. for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
  169. {
  170. if( ii >= 32 )
  171. {
  172. ii = 0;
  173. aPngStrings.Add( line );
  174. line.Empty();
  175. }
  176. line << wxString::Format( wxT( "%2.2X " ), *begin & 0xFF );
  177. }
  178. // Add last line:
  179. if( !line.IsEmpty() )
  180. aPngStrings.Add( line );
  181. }
  182. }
  183. bool BITMAP_BASE::LoadData( LINE_READER& aLine, wxString& aErrorMsg )
  184. {
  185. wxMemoryOutputStream stream;
  186. char* line;
  187. while( true )
  188. {
  189. if( !aLine.ReadLine() )
  190. {
  191. aErrorMsg = wxT("Unexpected end of data");
  192. return false;
  193. }
  194. line = aLine.Line();
  195. if( strncasecmp( line, "EndData", 4 ) == 0 )
  196. {
  197. // all the PNG date is read.
  198. // We expect here m_image and m_bitmap are void
  199. m_image = new wxImage();
  200. wxMemoryInputStream istream( stream );
  201. m_image->LoadFile( istream, wxBITMAP_TYPE_PNG );
  202. m_bitmap = new wxBitmap( *m_image );
  203. m_originalImage = new wxImage( *m_image );
  204. break;
  205. }
  206. // Read PNG data, stored in hexadecimal,
  207. // each byte = 2 hexadecimal digits and a space between 2 bytes
  208. // and put it in memory stream buffer
  209. int len = strlen( line );
  210. for( ; len > 0; len -= 3, line += 3 )
  211. {
  212. int value = 0;
  213. if( sscanf( line, "%X", &value ) == 1 )
  214. stream.PutC( (char) value );
  215. else
  216. break;
  217. }
  218. }
  219. return true;
  220. }
  221. const BOX2I BITMAP_BASE::GetBoundingBox() const
  222. {
  223. BOX2I bbox;
  224. VECTOR2I size = GetSize();
  225. bbox.Inflate( size.x / 2, size.y / 2 );
  226. return bbox;
  227. }
  228. void BITMAP_BASE::DrawBitmap( wxDC* aDC, const VECTOR2I& aPos )
  229. {
  230. if( m_bitmap == nullptr )
  231. return;
  232. VECTOR2I pos = aPos;
  233. VECTOR2I size = GetSize();
  234. // This fixes a bug in OSX that should be fixed in the 3.0.3 version or later.
  235. if( ( size.x == 0 ) || ( size.y == 0 ) )
  236. return;
  237. // To draw the bitmap, pos is the upper left corner position
  238. pos.x -= size.x / 2;
  239. pos.y -= size.y / 2;
  240. double scale;
  241. int logicalOriginX, logicalOriginY;
  242. aDC->GetUserScale( &scale, &scale );
  243. aDC->GetLogicalOrigin( &logicalOriginX, &logicalOriginY );
  244. // We already have issues to draw a bitmap on the wxDC, depending on wxWidgets version.
  245. // Now we have an issue on wxWidgets 3.1.6 to fix the clip area
  246. // and the bitmap position when using TransformMatrix
  247. // So for version == 3.1.6 do not use it
  248. // Be carefull before changing the code.
  249. bool useTransform = aDC->CanUseTransformMatrix();
  250. #if wxCHECK_VERSION( 3, 1, 6 ) && !wxCHECK_VERSION( 3, 1, 7 )
  251. useTransform = false;
  252. #endif
  253. wxAffineMatrix2D init_matrix = aDC->GetTransformMatrix();
  254. // Note: clipping bitmap area was made to fix a minor issue in old versions of
  255. // Kicad/wxWidgets (5.1 / wx 3.0)
  256. // However SetClippingRegion creates a lot of issues (different ways to fix the
  257. // position and size of the area, depending on wxWidget version)because it changes with
  258. // each versions of wxWigets, so it is now disabled
  259. // However the code is still here, just in case
  260. // #define USE_CLIP_AREA
  261. wxPoint clipAreaPos;
  262. if( useTransform )
  263. {
  264. wxAffineMatrix2D matrix = aDC->GetTransformMatrix();
  265. matrix.Translate( pos.x, pos.y );
  266. matrix.Scale( GetScalingFactor(), GetScalingFactor() );
  267. aDC->SetTransformMatrix( matrix );
  268. // Needed on wx <= 3.1.5, and this is strange...
  269. // Nevertheless, this code has problem (the bitmap is not seen)
  270. // with wx version > 3.1.5
  271. clipAreaPos.x = pos.x;
  272. clipAreaPos.y = pos.y;
  273. pos.x = pos.y = 0;
  274. }
  275. else
  276. {
  277. aDC->SetUserScale( scale * GetScalingFactor(), scale * GetScalingFactor() );
  278. aDC->SetLogicalOrigin( logicalOriginX / GetScalingFactor(),
  279. logicalOriginY / GetScalingFactor() );
  280. pos.x = KiROUND( pos.x / GetScalingFactor() );
  281. pos.y = KiROUND( pos.y / GetScalingFactor() );
  282. size.x = KiROUND( size.x / GetScalingFactor() );
  283. size.y = KiROUND( size.y / GetScalingFactor() );
  284. clipAreaPos.x = pos.x;
  285. clipAreaPos.y = pos.y;
  286. }
  287. #ifdef USE_CLIP_AREA
  288. aDC->DestroyClippingRegion();
  289. aDC->SetClippingRegion( clipAreaPos, wxSize( size.x, size.y ) );
  290. #endif
  291. if( GetGRForceBlackPenState() )
  292. {
  293. wxBitmap result( m_bitmap->ConvertToImage().ConvertToGreyscale() );
  294. aDC->DrawBitmap( result, pos.x, pos.y, true );
  295. }
  296. else
  297. {
  298. aDC->DrawBitmap( *m_bitmap, pos.x, pos.y, true );
  299. }
  300. if( useTransform )
  301. aDC->SetTransformMatrix( init_matrix );
  302. else
  303. {
  304. aDC->SetUserScale( scale, scale );
  305. aDC->SetLogicalOrigin( logicalOriginX, logicalOriginY );
  306. }
  307. #ifdef USE_CLIP_AREA
  308. aDC->DestroyClippingRegion();
  309. #endif
  310. }
  311. VECTOR2I BITMAP_BASE::GetSize() const
  312. {
  313. VECTOR2I size;
  314. if( m_bitmap )
  315. {
  316. size.x = m_bitmap->GetWidth();
  317. size.y = m_bitmap->GetHeight();
  318. size.x = KiROUND( size.x * GetScalingFactor() );
  319. size.y = KiROUND( size.y * GetScalingFactor() );
  320. }
  321. return size;
  322. }
  323. void BITMAP_BASE::Mirror( bool aVertically )
  324. {
  325. if( m_image )
  326. {
  327. // wxImage::Mirror() clear some parameters of the original image.
  328. // We need to restore them, especially resolution and unit, to be
  329. // sure image parameters saved in file are the right parameters, not
  330. // the defualt values
  331. int resX = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
  332. int resY = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY );
  333. int unit = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT );
  334. *m_image = m_image->Mirror( not aVertically );
  335. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONUNIT , unit);
  336. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONX, resX);
  337. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONY, resY);
  338. if( aVertically )
  339. m_isMirroredY = !m_isMirroredY;
  340. else
  341. m_isMirroredX = !m_isMirroredX;
  342. rebuildBitmap( false );
  343. }
  344. }
  345. void BITMAP_BASE::Rotate( bool aRotateCCW )
  346. {
  347. if( m_image )
  348. {
  349. // wxImage::Rotate90() clear some parameters of the original image.
  350. // We need to restore them, especially resolution and unit, to be
  351. // sure image parameters saved in file are the right parameters, not
  352. // the defualt values
  353. int resX = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
  354. int resY = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY );
  355. int unit = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT );
  356. *m_image = m_image->Rotate90( aRotateCCW );
  357. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONUNIT , unit);
  358. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONX, resX);
  359. m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONY, resY);
  360. m_rotation += ( aRotateCCW ? -ANGLE_90 : ANGLE_90 );
  361. rebuildBitmap( false );
  362. }
  363. }
  364. void BITMAP_BASE::ConvertToGreyscale()
  365. {
  366. if( m_image )
  367. {
  368. *m_image = m_image->ConvertToGreyscale();
  369. *m_originalImage = m_originalImage->ConvertToGreyscale();
  370. rebuildBitmap();
  371. }
  372. }
  373. void BITMAP_BASE::PlotImage( PLOTTER* aPlotter, const VECTOR2I& aPos,
  374. const COLOR4D& aDefaultColor,
  375. int aDefaultPensize ) const
  376. {
  377. if( m_image == nullptr )
  378. return;
  379. // These 2 lines are useful only for plotters that cannot plot a bitmap
  380. // and plot a rectangle instead of.
  381. aPlotter->SetColor( aDefaultColor );
  382. aPlotter->SetCurrentLineWidth( aDefaultPensize );
  383. aPlotter->PlotImage( *m_image, aPos, GetScalingFactor() );
  384. }