Commit 51a84547 authored by jean-pierre charras's avatar jean-pierre charras
Browse files

bitmap2component: use BOOST:POLYGON instead of KBool, and minor enhancements.

parent 4cbcf56c
Loading
Loading
Loading
Loading
+17 −6
Original line number Original line Diff line number Diff line
@@ -45,6 +45,8 @@
#define KEYWORD_FRAME_SIZEY wxT( "Bmconverter_Size_y" )
#define KEYWORD_FRAME_SIZEY wxT( "Bmconverter_Size_y" )
#define KEYWORD_LAST_INPUT_FILE wxT( "Last_input" )
#define KEYWORD_LAST_INPUT_FILE wxT( "Last_input" )
#define KEYWORD_LAST_OUTPUT_FILE wxT( "Last_output" )
#define KEYWORD_LAST_OUTPUT_FILE wxT( "Last_output" )
#define KEYWORD_BINARY_THRESHOLD wxT( "Threshold" )
#define KEYWORD_BW_NEGATIVE wxT( "Negative_choice" )


extern int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, int aFormat );
extern int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, int aFormat );


@@ -88,6 +90,7 @@ private:


BM2CMP_FRAME::BM2CMP_FRAME() : BM2CMP_FRAME_BASE( NULL )
BM2CMP_FRAME::BM2CMP_FRAME() : BM2CMP_FRAME_BASE( NULL )
{
{
    int tmp;
    m_Config = new wxConfig();
    m_Config = new wxConfig();
    m_Config->Read( KEYWORD_FRAME_POSX, & m_FramePos.x, -1 );
    m_Config->Read( KEYWORD_FRAME_POSX, & m_FramePos.x, -1 );
    m_Config->Read( KEYWORD_FRAME_POSY, & m_FramePos.y, -1 );
    m_Config->Read( KEYWORD_FRAME_POSY, & m_FramePos.y, -1 );
@@ -95,6 +98,11 @@ BM2CMP_FRAME::BM2CMP_FRAME() : BM2CMP_FRAME_BASE( NULL )
    m_Config->Read( KEYWORD_FRAME_SIZEY, & m_FrameSize.y, -1 );
    m_Config->Read( KEYWORD_FRAME_SIZEY, & m_FrameSize.y, -1 );
    m_Config->Read( KEYWORD_LAST_INPUT_FILE, &m_BitmapFileName );
    m_Config->Read( KEYWORD_LAST_INPUT_FILE, &m_BitmapFileName );
    m_Config->Read( KEYWORD_LAST_OUTPUT_FILE, &m_ConvertedFileName );
    m_Config->Read( KEYWORD_LAST_OUTPUT_FILE, &m_ConvertedFileName );
    if( m_Config->Read( KEYWORD_BINARY_THRESHOLD, &tmp ) )
        m_sliderThreshold->SetValue( tmp );
    if( m_Config->Read( KEYWORD_BW_NEGATIVE, &tmp ) )
        m_rbOptions->SetSelection( tmp  ? 1 : 0 );



    // Give an icon
    // Give an icon
    wxIcon icon;
    wxIcon icon;
@@ -127,6 +135,8 @@ BM2CMP_FRAME::~BM2CMP_FRAME()
    m_Config->Write( KEYWORD_FRAME_SIZEY, (long) m_FrameSize.y );
    m_Config->Write( KEYWORD_FRAME_SIZEY, (long) m_FrameSize.y );
    m_Config->Write( KEYWORD_LAST_INPUT_FILE, m_BitmapFileName );
    m_Config->Write( KEYWORD_LAST_INPUT_FILE, m_BitmapFileName );
    m_Config->Write( KEYWORD_LAST_OUTPUT_FILE, m_ConvertedFileName );
    m_Config->Write( KEYWORD_LAST_OUTPUT_FILE, m_ConvertedFileName );
    m_Config->Write( KEYWORD_BINARY_THRESHOLD, m_sliderThreshold->GetValue() );
    m_Config->Write( KEYWORD_BW_NEGATIVE, m_rbOptions->GetSelection() );


    delete m_Config;
    delete m_Config;


@@ -390,16 +400,12 @@ void BM2CMP_FRAME::ExportFile( FILE* aOutfile, int aFormat )
}
}




// BM_TO_CMP_APP
// EDA_APP

void EDA_APP::MacOpenFile(const wxString &fileName)
{
}


IMPLEMENT_APP( EDA_APP )
IMPLEMENT_APP( EDA_APP )


///-----------------------------------------------------------------------------
///-----------------------------------------------------------------------------
// BM_TO_CMP_APP
// EDA_APP
// main program
// main program
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------


@@ -415,3 +421,8 @@ bool EDA_APP::OnInit()


    return true;
    return true;
}
}


void EDA_APP::MacOpenFile(const wxString &fileName)
{
}
+123 −188
Original line number Original line Diff line number Diff line
@@ -21,8 +21,10 @@
 * or you may write to the Free Software Foundation, Inc.,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
 */
#include <math.h>


#include "kbool/booleng.h"
// For some unknown reasons, polygon.hpp shoul be included first
#include "boost/polygon/polygon.hpp"


#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
@@ -34,6 +36,7 @@
#include "potracelib.h"
#include "potracelib.h"
#include "auxiliary.h"
#include "auxiliary.h"



#ifndef max
#ifndef max
    #define max( a, b ) ( ( (a) > (b) ) ? (a) : (b) )
    #define max( a, b ) ( ( (a) > (b) ) ? (a) : (b) )
#endif
#endif
@@ -41,6 +44,16 @@
    #define min( a, b ) ( ( (a) < (b) ) ? (a) : (b) )
    #define min( a, b ) ( ( (a) < (b) ) ? (a) : (b) )
#endif
#endif


// Define some types used here from boost::polygon
namespace bpl = boost::polygon;         // bpl = boost polygon library
using namespace bpl::operators;         // +, -, =, ...

typedef int                                coordinate_type;

typedef bpl::polygon_data<coordinate_type> KPolygon;        // define a basic polygon
typedef std::vector<KPolygon>              KPolygonSet;     // define a set of polygons

typedef bpl::point_data<coordinate_type>   KPolyPoint;      // define a corner of a polygon


enum output_format {
enum output_format {
    POSTSCRIPT_FMT = 1,
    POSTSCRIPT_FMT = 1,
@@ -48,8 +61,10 @@ enum output_format {
    EESCHEMA_FMT
    EESCHEMA_FMT
};
};
/* free a potrace bitmap */
/* free a potrace bitmap */
static void bm_free(potrace_bitmap_t *bm) {
static void bm_free( potrace_bitmap_t* bm )
  if (bm != NULL) {
{
    if( bm != NULL )
    {
        free( bm->map );
        free( bm->map );
    }
    }
    free( bm );
    free( bm );
@@ -69,8 +84,7 @@ public:
    double             m_ScaleY;    // the conversion scale
    double             m_ScaleY;    // the conversion scale
    potrace_path_t*    m_Paths;     // the list of paths, from potrace (list of lines and bezier curves)
    potrace_path_t*    m_Paths;     // the list of paths, from potrace (list of lines and bezier curves)
    FILE* m_Outfile;
    FILE* m_Outfile;
public:
public: BITMAPCONV_INFO();
    BITMAPCONV_INFO();
};
};


static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
@@ -96,101 +110,6 @@ BITMAPCONV_INFO::BITMAPCONV_INFO()
}
}




/**
 * Function ArmBoolEng
 * Initialise parameters used in kbool
 * @param aBooleng = pointer to the Bool_Engine to initialise
 * @param aConvertHoles = mode for holes when a boolean operation is made
 *   true: in resulting polygon, holes are linked into outer contours by double overlapping segments
 *   false: in resulting polygons, holes are not linked: they are separate polygons
 */
void ArmBoolEng( Bool_Engine* aBooleng, bool aConvertHoles )
{
    // set some global vals to arm the boolean engine

    // input points are scaled up with GetDGrid() * GetGrid()

    // DGRID is only meant to make fractional parts of input data which

    /*
     *  The input data scaled up with DGrid is related to the accuracy the user has in his input data.
     *  User data with a minimum accuracy of 0.001, means set the DGrid to 1000.
     *  The input data may contain data with a minimum accuracy much smaller, but by setting the DGrid
     *  everything smaller than 1/DGrid is rounded.
     *
     *  DGRID is only meant to make fractional parts of input data which can be
     *  doubles, part of the integers used in vertexes within the boolean algorithm.
     *  And therefore DGRID bigger than 1 is not usefull, you would only loose accuracy.
     *  Within the algorithm all input data is multiplied with DGRID, and the result
     *  is rounded to an integer.
     */
    double DGRID = 1000.0;      // round coordinate X or Y value in calculations to this (initial value = 1000.0 in kbool example)
                                // kbool uses DGRID to convert float user units to integer
                                // kbool unit = (int)(user unit * DGRID)
                                // Note: in kicad, coordinates are already integer so DGRID could be set to 1
                                // we can choose 1.0,
                                // but choose DGRID = 1000.0 solves some filling problems
//                             (perhaps because this allows a better precision in kbool internal calculations

    double MARGE = 1.0 / DGRID;         // snap with in this range points to lines in the intersection routines
                                        // should always be >= 1/DGRID  a  MARGE >= 10/DGRID is ok
                                        // this is also used to remove small segments and to decide when
                                        // two segments are in line. ( initial value = 0.001 )
                                        // For kicad we choose MARGE = 1/DGRID

    double CORRECTIONFACTOR = 0.0;      // correct the polygons by this number: used in BOOL_CORRECTION operation
                                        // this operation shrinks a polygon if CORRECTIONFACTOR < 0
                                        // or stretch it if CORRECTIONFACTOR > 0
                                        // the size change is CORRECTIONFACTOR (holes are correctly handled)
    double CORRECTIONABER = 1.0;        // the accuracy for the rounded shapes used in correction
    double ROUNDFACTOR    = 1.5;        // when will we round the correction shape to a circle
    double SMOOTHABER     = 10.0;       // accuracy when smoothing a polygon
    double MAXLINEMERGE   = 1000.0;     // leave as is, segments of this length in smoothen


    /*
     *    Grid makes sure that the integer data used within the algorithm has room for extra intersections
     *    smaller than the smallest number within the input data.
     *    The input data scaled up with DGrid is related to the accuracy the user has in his input data.
     *    Another scaling with Grid is applied on top of it to create space in the integer number for
     *    even smaller numbers.
     */
    int GRID = (int) ( 10000 / DGRID );    // initial value = 10000 in kbool example

    // But we use 10000/DGRID because the scalling is made
    // by DGRID on integer pcbnew units and
    // the global scalling ( GRID*DGRID) must be < 30000 to avoid
    // overflow in calculations (made in long long in kbool)
    if( GRID <= 1 )     // Cannot be null!
        GRID = 1;

    aBooleng->SetMarge( MARGE );
    aBooleng->SetGrid( GRID );
    aBooleng->SetDGrid( DGRID );
    aBooleng->SetCorrectionFactor( CORRECTIONFACTOR );
    aBooleng->SetCorrectionAber( CORRECTIONABER );
    aBooleng->SetSmoothAber( SMOOTHABER );
    aBooleng->SetMaxlinemerge( MAXLINEMERGE );
    aBooleng->SetRoundfactor( ROUNDFACTOR );
    aBooleng->SetWindingRule( true );           // This is the default kbool value

    if( aConvertHoles )
    {
#if 1                                                       // Can be set to 1 for kbool version >= 2.1, must be set to 0 for previous versions
        // SetAllowNonTopHoleLinking() exists only in kbool >= 2.1
        aBooleng->SetAllowNonTopHoleLinking( false );       // Default = , but i have problems (filling errors) when true
#endif
        aBooleng->SetLinkHoles( true );                     // holes will be connected by double overlapping segments
        aBooleng->SetOrientationEntryMode( false );         // all polygons are contours, not holes
    }
    else
    {
        aBooleng->SetLinkHoles( false );                // holes will not be connected by double overlapping segments
        aBooleng->SetOrientationEntryMode( true );      // holes are entered counter clockwise
    }
}


int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, int aFormat )
int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, int aFormat )
{
{
    potrace_param_t* param;
    potrace_param_t* param;
@@ -329,25 +248,32 @@ static void OuputEnd( BITMAPCONV_INFO& aInfo )
    }
    }
}
}



/**
static void OuputOnePolygon( BITMAPCONV_INFO&                aInfo,
 * Function OuputOnePolygon
                             std::vector <potrace_dpoint_t>& aPolygonBuffer )
 * write one polygon to output file.
 * Polygon coordinates are expected scaled by the polugon extraction function
 */
static void OuputOnePolygon( BITMAPCONV_INFO&  aInfo, KPolygon & aPolygon )
{
{
    unsigned ii;
    unsigned ii;
    KPolyPoint currpoint;

    int   offsetX = (int)( aInfo.m_PixmapWidth / 2 * aInfo.m_ScaleX );
    int   offsetY = (int)( aInfo.m_PixmapHeight / 2 * aInfo.m_ScaleY );


    double   offsetX = aInfo.m_PixmapWidth / 2 * aInfo.m_ScaleX;
    KPolyPoint startpoint = *aPolygon.begin();
    double   offsetY = aInfo.m_PixmapHeight / 2 * aInfo.m_ScaleY;


    switch( aInfo.m_Format )
    switch( aInfo.m_Format )
    {
    {
    case POSTSCRIPT_FMT:
    case POSTSCRIPT_FMT:
        fprintf( aInfo.m_Outfile, "%f %f moveto\n",
        fprintf( aInfo.m_Outfile, "%d %d moveto\n",
                 aPolygonBuffer[0].x * aInfo.m_ScaleX,
                 startpoint.x(), startpoint.y() );
                 aPolygonBuffer[0].y * aInfo.m_ScaleY );
        for( ii = 1; ii < aPolygon.size(); ii++ )
        for( ii = 1; ii < aPolygonBuffer.size(); ii++ )
        {
            fprintf( aInfo.m_Outfile, "%f %f lineto\n",
            currpoint = *(aPolygon.begin() + ii);
                     aPolygonBuffer[ii].x * aInfo.m_ScaleX,
            fprintf( aInfo.m_Outfile, "%d %d lineto\n",
                     aPolygonBuffer[ii].y * aInfo.m_ScaleY );
                     currpoint.x(), currpoint.y() );
        }


        fprintf( aInfo.m_Outfile, "0 setgray fill\n" );
        fprintf( aInfo.m_Outfile, "0 setgray fill\n" );
        break;
        break;
@@ -359,30 +285,33 @@ static void OuputOnePolygon( BITMAPCONV_INFO& aInfo,
        int width = 1;
        int width = 1;
        fprintf( aInfo.m_Outfile, "DP %d %d %d %d %d %d %d\n",
        fprintf( aInfo.m_Outfile, "DP %d %d %d %d %d %d %d\n",
                 0, 0, 0, 0,
                 0, 0, 0, 0,
                       int(aPolygonBuffer.size()+1),
                 aPolygon.size() + 1, width, layer );
                       width, layer );


        for( ii = 0; ii < aPolygonBuffer.size();  ii++ )
        for( ii = 0; ii < aPolygon.size(); ii++ )
        {
            currpoint = *( aPolygon.begin() + ii );
            fprintf( aInfo.m_Outfile, "Dl %d %d\n",
            fprintf( aInfo.m_Outfile, "Dl %d %d\n",
                    (int) ( aPolygonBuffer[ii].x * aInfo.m_ScaleX - offsetX ),
                    currpoint.x() - offsetX, currpoint.y() - offsetY );
                    (int) ( aPolygonBuffer[ii].y * aInfo.m_ScaleY - offsetY ) );
        }

        // Close polygon
        // Close polygon
        fprintf( aInfo.m_Outfile, "Dl %d %d\n",
        fprintf( aInfo.m_Outfile, "Dl %d %d\n",
                (int) ( aPolygonBuffer[0].x * aInfo.m_ScaleX - offsetX ),
                 startpoint.x() - offsetX, startpoint.y() - offsetY );
                (int) ( aPolygonBuffer[0].y * aInfo.m_ScaleY - offsetY ) );
    }
    }
    break;
    break;


    case EESCHEMA_FMT:
    case EESCHEMA_FMT:
        fprintf( aInfo.m_Outfile, "P %d 0 0 1", int(aPolygonBuffer.size()+1) );
        fprintf( aInfo.m_Outfile, "P %d 0 0 1", aPolygon.size() + 1 );
        for( ii = 0; ii < aPolygonBuffer.size(); ii++ )
        for( ii = 0; ii < aPolygon.size(); ii++ )
        {
            currpoint = *(aPolygon.begin() + ii);
            fprintf( aInfo.m_Outfile, " %d %d",
            fprintf( aInfo.m_Outfile, " %d %d",
                    (int) ( aPolygonBuffer[ii].x * aInfo.m_ScaleX - offsetX ),
                     currpoint.x() - offsetX, currpoint.y() - offsetY );
                    (int) ( aPolygonBuffer[ii].y * aInfo.m_ScaleY - offsetY ) );
        }

        // Close polygon
        // Close polygon
        fprintf( aInfo.m_Outfile, " %d %d",
        fprintf( aInfo.m_Outfile, " %d %d",
                (int) ( aPolygonBuffer[0].x * aInfo.m_ScaleX - offsetX ),
                 startpoint.x() - offsetX, startpoint.y() - offsetY );
                (int) ( aPolygonBuffer[0].y * aInfo.m_ScaleY - offsetY ) );


        fprintf( aInfo.m_Outfile, " F\n" );
        fprintf( aInfo.m_Outfile, " F\n" );
        break;
        break;
@@ -392,16 +321,21 @@ static void OuputOnePolygon( BITMAPCONV_INFO& aInfo,


static void CreateOutputFile( BITMAPCONV_INFO& aInfo )
static void CreateOutputFile( BITMAPCONV_INFO& aInfo )
{
{
    unsigned int i, n;
    KPolyPoint currpoint;
    int*         tag;


    std::vector <potrace_dpoint_t> cornersBuffer;
    std::vector <potrace_dpoint_t> cornersBuffer;


    // This KPolygonSet polyset_areas is a complex polygon to draw
    // and can be complex depending on holes inside this polygon
    KPolygonSet polyset_areas;

    // This KPolygonSet polyset_holes is the set of holes inside polyset_areas
    KPolygonSet polyset_holes;

    potrace_dpoint_t( *c )[3];
    potrace_dpoint_t( *c )[3];
    OuputHeader( aInfo );
    OuputHeader( aInfo );


    bool main_outline = true;
    bool main_outline = true;
    Bool_Engine* booleng = NULL;


    /* draw each as a polygon with no hole.
    /* draw each as a polygon with no hole.
     * Bezier curves are approximated by a polyline
     * Bezier curves are approximated by a polyline
@@ -409,17 +343,11 @@ static void CreateOutputFile( BITMAPCONV_INFO& aInfo )
    potrace_path_t* paths = aInfo.m_Paths;    // the list of paths
    potrace_path_t* paths = aInfo.m_Paths;    // the list of paths
    while( paths != NULL )
    while( paths != NULL )
    {
    {
        n   = paths->curve.n;
        int cnt  = paths->curve.n;
        tag = paths->curve.tag;
        int* tag = paths->curve.tag;
        c = paths->curve.c;
        c = paths->curve.c;
        potrace_dpoint_t startpoint = c[n - 1][2];
        potrace_dpoint_t startpoint = c[cnt - 1][2];
        cornersBuffer.push_back( startpoint );
        for( int i = 0; i < cnt; i++ )
        if( booleng == NULL )
        {
            booleng = new Bool_Engine();
            ArmBoolEng( booleng, true );
        }
        for( i = 0; i < n; i++ )
        {
        {
            switch( tag[i] )
            switch( tag[i] )
            {
            {
@@ -440,46 +368,53 @@ static void CreateOutputFile( BITMAPCONV_INFO& aInfo )
        if( main_outline )
        if( main_outline )
        {
        {
            main_outline = false;
            main_outline = false;
            booleng->StartPolygonAdd( GROUP_A );
            for( i = 1; i < cornersBuffer.size(); i++ )
                booleng->AddPoint( cornersBuffer[i].x, cornersBuffer[i].y );


            booleng->EndPolygonAdd();
            // build the current main polygon
            std::vector<KPolyPoint> cornerslist; // a simple boost polygon
            for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
            {
                currpoint.x( (coordinate_type) (cornersBuffer[i].x * aInfo.m_ScaleX) );
                currpoint.y( (coordinate_type) (cornersBuffer[i].y * aInfo.m_ScaleY) );
                cornerslist.push_back( currpoint );
            }

            KPolygon poly;
            bpl::set_points( poly, cornerslist.begin(), cornerslist.end() );
            polyset_areas.push_back( poly );
        }
        }
        else
        else
        {
        {
            booleng->StartPolygonAdd( GROUP_B );
            // Add current hole in polyset_holes
            for( i = 1; i < cornersBuffer.size(); i++ )
            std::vector<KPolyPoint> cornerslist; // a simple boost polygon
                booleng->AddPoint( cornersBuffer[i].x, cornersBuffer[i].y );
            for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
            {
                currpoint.x( (coordinate_type) (cornersBuffer[i].x * aInfo.m_ScaleX) );
                currpoint.y( (coordinate_type) (cornersBuffer[i].y * aInfo.m_ScaleY) );
                cornerslist.push_back( currpoint );
            }


            booleng->EndPolygonAdd();
            KPolygon poly;
            bpl::set_points( poly, cornerslist.begin(), cornerslist.end() );
            polyset_holes.push_back( poly );
        }
        }
        cornersBuffer.clear();
        cornersBuffer.clear();


        /* at the end of a group of a positive path and its negative
        /* at the end of a group of a positive path and its negative children, fill.
         *  children, fill. */
         */
        if( paths->next == NULL || paths->next->sign == '+' )
        if( paths->next == NULL || paths->next->sign == '+' )
        {
        {
            booleng->Do_Operation( BOOL_A_SUB_B );
            // Substract holes to main polygon:
            std::vector <potrace_dpoint_t> PolygonBuffer;
            polyset_areas -= polyset_holes;
            while( booleng->StartPolygonGet() )
            {
                potrace_dpoint_t corner;
                PolygonBuffer.clear();
                while( booleng->PolygonHasMorePoints() )
                {
                    corner.x = booleng->GetPolygonXPoint();
                    corner.y = booleng->GetPolygonYPoint();
                    PolygonBuffer.push_back( corner );
                }


                booleng->EndPolygonGet();
            // Output current resulting polygon(s)
                OuputOnePolygon( aInfo, PolygonBuffer );
            for( unsigned ii = 0; ii < polyset_areas.size(); ii++ )
                PolygonBuffer.clear();
            {
                KPolygon& poly = polyset_areas[ii];
                OuputOnePolygon( aInfo, poly );
            }
            }


            delete booleng;
            polyset_areas.clear();
            booleng = NULL;
            polyset_holes.clear();
            main_outline = true;
            main_outline = true;
        }
        }
        paths = paths->next;
        paths = paths->next;
@@ -505,7 +440,7 @@ void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
     *  between the true curve and its approximation does not exceed the
     *  between the true curve and its approximation does not exceed the
     *  desired accuracy delta. */
     *  desired accuracy delta. */


    delta = 0.5; /* desired accuracy, in pixels */
    delta = 0.25; /* desired accuracy, in pixels */


    /* let dd = maximal value of 2nd derivative over curve - this must
    /* let dd = maximal value of 2nd derivative over curve - this must
     *  occur at an endpoint. */
     *  occur at an endpoint. */
+5 −4
Original line number Original line Diff line number Diff line
@@ -77,10 +77,11 @@ typedef struct potrace_dpoint_s potrace_dpoint_t;
/* closed curve segment */
/* closed curve segment */
struct potrace_curve_s
struct potrace_curve_s
{
{
    int  n;                 /* number of segments */
    int  n;                 // number of segments
    int* tag;               /* tag[n]: POTRACE_CURVETO or POTRACE_CORNER */
    int* tag;               // tag[n]: POTRACE_CURVETO or POTRACE_CORNER
    potrace_dpoint_t( * c )[3]; /* c[n][3]: control points.
    potrace_dpoint_t( * c )[3]; /* c[n][3]: control points.
                             *  c[n][0] is unused for tag[n]=POTRACE_CORNER */
                                 *  c[n][0] is unused for tag[n]=POTRACE_CORNER
                                 */
};
};
typedef struct potrace_curve_s potrace_curve_t;
typedef struct potrace_curve_s potrace_curve_t;