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.

550 lines
18 KiB

5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KICAD, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2019 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 <algorithm> // std::max
  25. #include <cerrno>
  26. #include <cmath>
  27. #include <cstdio>
  28. #include <cstdlib>
  29. #include <cstring>
  30. #include <string>
  31. #include <vector>
  32. #include <kiid.h>
  33. #include <build_version.h>
  34. #include <locale_io.h>
  35. #include <macros.h>
  36. #include <potracelib.h>
  37. #include <reporter.h>
  38. #include <fmt/format.h>
  39. #include <wx/translation.h>
  40. #include "bitmap2component.h"
  41. /* free a potrace bitmap */
  42. static void bm_free( potrace_bitmap_t* bm )
  43. {
  44. if( bm )
  45. free( bm->map );
  46. free( bm );
  47. }
  48. static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
  49. potrace_dpoint_t p1, potrace_dpoint_t p2,
  50. potrace_dpoint_t p3, potrace_dpoint_t p4 );
  51. BITMAPCONV_INFO::BITMAPCONV_INFO( std::string& aData, REPORTER& aReporter ):
  52. m_Data( aData ),
  53. m_reporter( aReporter )
  54. {
  55. m_Format = POSTSCRIPT_FMT;
  56. m_PixmapWidth = 0;
  57. m_PixmapHeight = 0;
  58. m_ScaleX = 1.0;
  59. m_ScaleY = 1.0;
  60. m_Paths = nullptr;
  61. m_CmpName = "LOGO";
  62. }
  63. int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap, OUTPUT_FMT_ID aFormat,
  64. int aDpi_X, int aDpi_Y, const wxString& aLayer )
  65. {
  66. potrace_param_t* param;
  67. potrace_state_t* st;
  68. // set tracing parameters, starting from defaults
  69. param = potrace_param_default();
  70. if( !param )
  71. {
  72. m_reporter.Report( fmt::format( "Error allocating parameters: {}\n", strerror( errno ) ),
  73. RPT_SEVERITY_ERROR );
  74. return 1;
  75. }
  76. // For parameters: see http://potrace.sourceforge.net/potracelib.pdf
  77. param->turdsize = 0; // area (in pixels) of largest path to be ignored.
  78. // Potrace default is 2
  79. param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
  80. /* convert the bitmap to curves */
  81. st = potrace_trace( param, aPotrace_bitmap );
  82. if( !st || st->status != POTRACE_STATUS_OK )
  83. {
  84. if( st )
  85. potrace_state_free( st );
  86. potrace_param_free( param );
  87. m_reporter.Report( fmt::format( "Error tracing bitmap: {}\n", strerror( errno ) ),
  88. RPT_SEVERITY_ERROR );
  89. return 1;
  90. }
  91. m_PixmapWidth = aPotrace_bitmap->w;
  92. m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
  93. m_Paths = st->plist;
  94. m_Format = aFormat;
  95. switch( aFormat )
  96. {
  97. case DRAWING_SHEET_FMT:
  98. m_ScaleX = PL_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to micron
  99. m_ScaleY = PL_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom
  100. createOutputData();
  101. break;
  102. case POSTSCRIPT_FMT:
  103. m_ScaleX = 1.0; // the conversion scale
  104. m_ScaleY = m_ScaleX;
  105. createOutputData();
  106. break;
  107. case SYMBOL_FMT:
  108. case SYMBOL_PASTE_FMT:
  109. m_ScaleX = SCH_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to eeschema iu
  110. m_ScaleY = -SCH_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is bottom to Top for components in libs
  111. createOutputData();
  112. break;
  113. case FOOTPRINT_FMT:
  114. m_ScaleX = PCB_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to IU
  115. m_ScaleY = PCB_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom in Footprint Editor
  116. createOutputData( aLayer );
  117. break;
  118. }
  119. bm_free( aPotrace_bitmap );
  120. potrace_state_free( st );
  121. potrace_param_free( param );
  122. return 0;
  123. }
  124. void BITMAPCONV_INFO::outputDataHeader( const wxString& aBrdLayerName )
  125. {
  126. double Ypos = ( m_PixmapHeight / 2.0 * m_ScaleY ); // fields Y position in mm
  127. double fieldSize; // fields text size in mm
  128. switch( m_Format )
  129. {
  130. case POSTSCRIPT_FMT:
  131. m_Data += "%!PS-Adobe-3.0 EPSF-3.0\n";
  132. m_Data += fmt::format( "%%BoundingBox: 0 0 {} {}\n", m_PixmapWidth, m_PixmapHeight );
  133. m_Data += "gsave\n";
  134. break;
  135. case FOOTPRINT_FMT:
  136. // fields text size = 1.5 mm
  137. // fields text thickness = 1.5 / 5 = 0.3mm
  138. m_Data += fmt::format( "(footprint \"{}\" (version 20221018) (generator \"bitmap2component\") (generator_version \"{}\")\n"
  139. " (layer \"F.Cu\")\n",
  140. m_CmpName.c_str(),
  141. GetMajorMinorVersion().ToStdString() );
  142. m_Data += fmt::format( " (attr board_only exclude_from_pos_files exclude_from_bom)\n" );
  143. m_Data += fmt::format( " (fp_text reference \"G***\" (at 0 0) (layer \"{}\")\n"
  144. " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
  145. " (uuid {})\n )\n",
  146. aBrdLayerName.ToStdString().c_str(),
  147. KIID().AsString().ToStdString().c_str() );
  148. m_Data += fmt::format( " (fp_text value \"{}\" (at 0.75 0) (layer \"{}\") hide\n"
  149. " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
  150. " (uuid {})\n )\n",
  151. m_CmpName.c_str(),
  152. aBrdLayerName.ToStdString().c_str(),
  153. KIID().AsString().ToStdString().c_str() );
  154. break;
  155. case DRAWING_SHEET_FMT:
  156. m_Data += fmt::format( "(kicad_wks (version 20220228) (generator \"bitmap2component\") (generator_version \"{}\")\n",
  157. GetMajorMinorVersion().ToStdString() );
  158. m_Data += " (setup (textsize 1.5 1.5)(linewidth 0.15)(textlinewidth 0.15)\n";
  159. m_Data += " (left_margin 10)(right_margin 10)(top_margin 10)(bottom_margin 10))\n";
  160. m_Data += " (polygon (name \"\") (pos 0 0) (linewidth 0.01)\n";
  161. break;
  162. case SYMBOL_FMT:
  163. m_Data += fmt::format( "(kicad_symbol_lib (version 20220914) (generator \"bitmap2component\") (generator_version \"{}\")\n",
  164. GetMajorMinorVersion().ToStdString() );
  165. KI_FALLTHROUGH;
  166. case SYMBOL_PASTE_FMT:
  167. fieldSize = 1.27; // fields text size in mm (= 50 mils)
  168. Ypos /= SCH_IU_PER_MM;
  169. Ypos += fieldSize / 2;
  170. m_Data += fmt::format( " (symbol \"{}\" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)\n",
  171. m_CmpName.c_str() );
  172. m_Data += fmt::format( " (property \"Reference\" \"#G\" (at 0 {:g} 0)\n"
  173. " (effects (font (size {:g} {:g})) hide)\n )\n",
  174. -Ypos,
  175. fieldSize,
  176. fieldSize );
  177. m_Data += fmt::format( " (property \"Value\" \"{}\" (at 0 {:g} 0)\n"
  178. " (effects (font (size {:g} {:g})) hide)\n )\n",
  179. m_CmpName.c_str(),
  180. Ypos,
  181. fieldSize,
  182. fieldSize );
  183. m_Data += fmt::format( " (property \"Footprint\" \"\" (at 0 0 0)\n"
  184. " (effects (font (size {:g} {:g})) hide)\n )\n",
  185. fieldSize,
  186. fieldSize );
  187. m_Data += fmt::format( " (property \"Datasheet\" \"\" (at 0 0 0)\n"
  188. " (effects (font (size {:g} {:g})) hide)\n )\n",
  189. fieldSize,
  190. fieldSize );
  191. m_Data += fmt::format( " (symbol \"{}_0_0\"\n", m_CmpName.c_str() );
  192. break;
  193. }
  194. }
  195. void BITMAPCONV_INFO::outputDataEnd()
  196. {
  197. switch( m_Format )
  198. {
  199. case POSTSCRIPT_FMT:
  200. m_Data += "grestore\n";
  201. m_Data += "%%EOF\n";
  202. break;
  203. case FOOTPRINT_FMT:
  204. m_Data += ")\n";
  205. break;
  206. case DRAWING_SHEET_FMT:
  207. m_Data += " )\n)\n";
  208. break;
  209. case SYMBOL_PASTE_FMT:
  210. m_Data += " )\n"; // end symbol_0_0
  211. m_Data += " )\n"; // end symbol
  212. break;
  213. case SYMBOL_FMT:
  214. m_Data += " )\n"; // end symbol_0_0
  215. m_Data += " )\n"; // end symbol
  216. m_Data += ")\n"; // end lib
  217. break;
  218. }
  219. }
  220. void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN& aPolygon, const wxString& aBrdLayerName )
  221. {
  222. // write one polygon to output file.
  223. // coordinates are expected in target unit.
  224. int ii, jj;
  225. VECTOR2I currpoint;
  226. int offsetX = KiROUND( m_PixmapWidth / 2.0 * m_ScaleX );
  227. int offsetY = KiROUND( m_PixmapHeight / 2.0 * m_ScaleY );
  228. const VECTOR2I startpoint = aPolygon.CPoint( 0 );
  229. switch( m_Format )
  230. {
  231. case POSTSCRIPT_FMT:
  232. offsetY = (int)( m_PixmapHeight * m_ScaleY );
  233. m_Data += fmt::format( "newpath\n{} {} moveto\n", startpoint.x, offsetY - startpoint.y );
  234. jj = 0;
  235. for( ii = 1; ii < aPolygon.PointCount(); ii++ )
  236. {
  237. currpoint = aPolygon.CPoint( ii );
  238. m_Data += fmt::format( " {} {} lineto", currpoint.x, offsetY - currpoint.y );
  239. if( jj++ > 6 )
  240. {
  241. jj = 0;
  242. m_Data += "\n";
  243. }
  244. }
  245. m_Data += "\nclosepath fill\n";
  246. break;
  247. case FOOTPRINT_FMT:
  248. m_Data += " (fp_poly\n (pts\n";
  249. jj = 0;
  250. for( ii = 0; ii < aPolygon.PointCount(); ii++ )
  251. {
  252. currpoint = aPolygon.CPoint( ii );
  253. m_Data += fmt::format( " (xy {} {})\n",
  254. ( currpoint.x - offsetX ) / PCB_IU_PER_MM,
  255. ( currpoint.y - offsetY ) / PCB_IU_PER_MM );
  256. }
  257. // No need to close polygon
  258. m_Data += " )\n\n";
  259. m_Data += fmt::format( " (stroke (width {:f}) (type solid)) (fill solid) (layer \"{}\") (uuid {}))\n",
  260. 0.0,
  261. aBrdLayerName.ToStdString().c_str(),
  262. KIID().AsString().ToStdString().c_str() );
  263. break;
  264. case DRAWING_SHEET_FMT:
  265. m_Data += " (pts";
  266. // Internal units = micron, file unit = mm
  267. jj = 1;
  268. for( ii = 0; ii < aPolygon.PointCount(); ii++ )
  269. {
  270. currpoint = aPolygon.CPoint( ii );
  271. m_Data += fmt::format( " (xy {:.3f} {:.3f})",
  272. ( currpoint.x - offsetX ) / PL_IU_PER_MM,
  273. ( currpoint.y - offsetY ) / PL_IU_PER_MM );
  274. if( jj++ > 4 )
  275. {
  276. jj = 0;
  277. m_Data += "\n ";
  278. }
  279. }
  280. // Close polygon
  281. m_Data += fmt::format( " (xy {:.3f} {:.3f}) )\n",
  282. ( startpoint.x - offsetX ) / PL_IU_PER_MM,
  283. ( startpoint.y - offsetY ) / PL_IU_PER_MM );
  284. break;
  285. case SYMBOL_FMT:
  286. case SYMBOL_PASTE_FMT:
  287. // The polygon outline thickness is fixed here to 0.01 ( 0.0 is the default thickness)
  288. #define SCH_LINE_THICKNESS_MM 0.01
  289. //snprintf( strbuf, sizeof(strbuf), "P %d 0 0 %d", (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
  290. m_Data += " (polyline\n (pts\n";
  291. for( ii = 0; ii < aPolygon.PointCount(); ii++ )
  292. {
  293. currpoint = aPolygon.CPoint( ii );
  294. m_Data += fmt::format( " (xy {:f} {:f})\n",
  295. ( currpoint.x - offsetX ) / SCH_IU_PER_MM,
  296. ( currpoint.y - offsetY ) / SCH_IU_PER_MM );
  297. }
  298. // Close polygon
  299. m_Data += fmt::format( " (xy {:f} {:f})\n",
  300. ( startpoint.x - offsetX ) / SCH_IU_PER_MM,
  301. ( startpoint.y - offsetY ) / SCH_IU_PER_MM );
  302. m_Data += " )\n"; // end pts
  303. m_Data += fmt::format( " (stroke (width {:g}) (type default))\n"
  304. " (fill (type outline))\n",
  305. SCH_LINE_THICKNESS_MM );
  306. m_Data += " )\n"; // end polyline
  307. break;
  308. }
  309. }
  310. void BITMAPCONV_INFO::createOutputData( const wxString& aLayer )
  311. {
  312. std::vector <potrace_dpoint_t> cornersBuffer;
  313. // polyset_areas is a set of polygon to draw
  314. SHAPE_POLY_SET polyset_areas;
  315. // polyset_holes is the set of holes inside polyset_areas outlines
  316. SHAPE_POLY_SET polyset_holes;
  317. potrace_dpoint_t( *c )[3];
  318. LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
  319. // The layer name has meaning only for .kicad_mod files.
  320. // For these files the header creates 2 invisible texts: value and ref
  321. // (needed but not useful) on silk screen layer
  322. outputDataHeader( "F.SilkS" );
  323. bool main_outline = true;
  324. /* draw each as a polygon with no hole.
  325. * Bezier curves are approximated by a polyline
  326. */
  327. potrace_path_t* paths = m_Paths; // the list of paths
  328. if( !m_Paths )
  329. {
  330. m_reporter.Report( _( "No shape in black and white image to convert: no outline created." ),
  331. RPT_SEVERITY_ERROR );
  332. }
  333. while( paths != nullptr )
  334. {
  335. int cnt = paths->curve.n;
  336. int* tag = paths->curve.tag;
  337. c = paths->curve.c;
  338. potrace_dpoint_t startpoint = c[cnt - 1][2];
  339. for( int i = 0; i < cnt; i++ )
  340. {
  341. switch( tag[i] )
  342. {
  343. case POTRACE_CORNER:
  344. cornersBuffer.push_back( c[i][1] );
  345. cornersBuffer.push_back( c[i][2] );
  346. startpoint = c[i][2];
  347. break;
  348. case POTRACE_CURVETO:
  349. BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] );
  350. startpoint = c[i][2];
  351. break;
  352. }
  353. }
  354. // Store current path
  355. if( main_outline )
  356. {
  357. main_outline = false;
  358. // build the current main polygon
  359. polyset_areas.NewOutline();
  360. for( const potrace_dpoint_s& pt : cornersBuffer )
  361. {
  362. polyset_areas.Append( int( pt.x * m_ScaleX ),
  363. int( pt.y * m_ScaleY ) );
  364. }
  365. }
  366. else
  367. {
  368. // Add current hole in polyset_holes
  369. polyset_holes.NewOutline();
  370. for( const potrace_dpoint_s& pt : cornersBuffer )
  371. {
  372. polyset_holes.Append( int( pt.x * m_ScaleX ),
  373. int( pt.y * m_ScaleY ) );
  374. }
  375. }
  376. cornersBuffer.clear();
  377. // at the end of a group of a positive path and its negative children, fill.
  378. if( paths->next == nullptr || paths->next->sign == '+' )
  379. {
  380. polyset_areas.Simplify();
  381. polyset_holes.Simplify();
  382. polyset_areas.BooleanSubtract( polyset_holes );
  383. // Ensure there are no self intersecting polygons
  384. if( polyset_areas.NormalizeAreaOutlines() )
  385. {
  386. // Convert polygon with holes to a unique polygon
  387. polyset_areas.Fracture();
  388. // Output current resulting polygon(s)
  389. for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ )
  390. {
  391. SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii );
  392. outputOnePolygon( poly, aLayer );
  393. }
  394. polyset_areas.RemoveAllContours();
  395. polyset_holes.RemoveAllContours();
  396. main_outline = true;
  397. }
  398. }
  399. paths = paths->next;
  400. }
  401. outputDataEnd();
  402. }
  403. // a helper function to calculate a square value
  404. inline double square( double x )
  405. {
  406. return x * x;
  407. }
  408. // a helper function to calculate a cube value
  409. inline double cube( double x )
  410. {
  411. return x * x * x;
  412. }
  413. /* render a Bezier curve. */
  414. void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
  415. potrace_dpoint_t p1,
  416. potrace_dpoint_t p2,
  417. potrace_dpoint_t p3,
  418. potrace_dpoint_t p4 )
  419. {
  420. double dd0, dd1, dd, delta, e2, epsilon, t;
  421. // p1 = starting point
  422. /* we approximate the curve by small line segments. The interval
  423. * size, epsilon, is determined on the fly so that the distance
  424. * between the true curve and its approximation does not exceed the
  425. * desired accuracy delta. */
  426. delta = 0.25; /* desired accuracy, in pixels */
  427. /* let dd = maximal value of 2nd derivative over curve - this must
  428. * occur at an endpoint. */
  429. dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
  430. dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
  431. dd = 6 * sqrt( std::max( dd0, dd1 ) );
  432. e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
  433. epsilon = sqrt( e2 ); /* necessary interval size */
  434. for( t = epsilon; t<1; t += epsilon )
  435. {
  436. potrace_dpoint_t intermediate_point;
  437. intermediate_point.x = p1.x * cube( 1 - t ) +
  438. 3* p2.x* square( 1 - t ) * t +
  439. 3 * p3.x * (1 - t) * square( t ) +
  440. p4.x* cube( t );
  441. intermediate_point.y = p1.y * cube( 1 - t ) +
  442. 3* p2.y* square( 1 - t ) * t +
  443. 3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
  444. aCornersBuffer.push_back( intermediate_point );
  445. }
  446. aCornersBuffer.push_back( p4 );
  447. }