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.

602 lines
19 KiB

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