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.

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