Oppure

Loading
25/06/20 15:48
AldoBaldo
Per i miei giochetti uso alcune parti di Win32. Per la grafica GDI e GDI+.
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
25/06/20 20:35
AldoBaldo
Uno spunto. A me serve tanto quanto una ruota quadrata, però magari a qualcun altro può suggerire qualche soluzione da passarmi...

...molti, invece di trasformare gli archi SVG in archi Gdi+, approssimano gli archi SVG in sequenze di curve di bezier che emulano gli archi originali, quindi trasformano quelle curve nelle curve equivalenti in Gdi+. Il passaggio da curve SVG a curve Gdi+ l'ho già fatto, quindi questo modo di procedere potrebbe andar benissimo.

Per la cronaca, Inkscape trasforma gli archi in curve senza che l'occhio riesca a rilevare alcuna differenza tra l'arco originale e l'arco approssimato. Ad esempio, questi dati SVG danno il risultato visibile nell'immagine allegata:

<circle id="originale"
    cx="56.8202"
    cy="58.3079"
     r="38.3645" />

<path id="tipo_a"
    d="m 187.652,58.3079
       a 38.3645,38.3645 0 0 1 -38.3645, 38.3645
         38.3645,38.3645 0 0 1 -38.3645,-38.3645
         38.3645,38.3645 0 0 1  38.3645,-38.3645
         38.3645,38.3645 0 0 1  38.3645, 38.3645
       z" />

<path id="tipo_c"
    d="m 280.119,58.3079
       c  0,21.188  -17.177, 38.3645  -38.3645, 38.3645
         -21.188,0  -38.364,-17.1766  -38.3645,-38.3645
         0,-21.188   17.176,-38.3645   38.3645,-38.3645
          21.188,0   38.3645,17.1762   38.3645, 38.3645
       z" />


Dunque, il meccanismo funziona. Sì, ma come? :noway:
Ultima modifica effettuata da AldoBaldo 25/06/20 20:36
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
26/06/20 9:19
Carlo
Già lo sai perché hai affermato che esistono DLL che svolgono il lavoro di conversione.
Infatti su: github.com/vvvv/… c'è un progetto imponente che fa inchinare davanti a tanta bravura.

Ho provato la DLL compilata e funziona alla grande, ma a te interessa solo il path, e vuoi implementarlo nel progetto farina del tuo sacco :k:

Ho recuperato solo i sorgenti che convertono il path SVG in GDI e ho realizzato un progetto semplice in VS2017 Framework 4.5.2 che utilizza tale peculiarità (allegato)

Le lettere supportate sono M,m; A,a; L,l; H,h; V,v; Q,q; T,t; C,c; S,s; Z,z

Nell'allegato anche SVG.DLL per .Net4.5.2, ma non collegata al progetto.

Il sorgente C# è molto ordinato e comprensibile, oscuri i calcoli matematici che raggiungono il risultato voluto.

Buono studio :asd::asd::asd:
Ultima modifica effettuata da Carlo 26/06/20 10:43
in programmazione tutto è permesso
26/06/20 9:41
AldoBaldo
...e invece quel particolare progetto non lo conoscevo per niente! Grazie millissime per la segnalazione. Ho scaricato il tuo zip, e oggi do un bella spulciata tanto al progetto su github quanto a quel che c'è nell'archivio che mi hai passato. Visti i miei limiti, non è detto che riesca a ricavarne qualcosa, però vale la pena provarci.

Tornerò a scriverne appena avrò qualcosa da dire in merito.

EDIT:
Credo di avere individuato qualcosa di utile nel file SvgArcSegment.cs , dove i due metodi CalculateVectorAngle() e AddToPath() sembrano fare quel che chiedevo. Ora dovrò cercare di capirci quanto basta per convertirli da C# a C, usando le funzioni standard invece di quelle correntemente presenti nel codice. Quindi dovrò adattare il tutto al contesto di Svg2GraphicsPath() e fare delle prove per vedere se mantengono quel che sembrano promettere. Magari riesco pure a capire almeno in parte come ottengono l'eventuale risultato -- c'è una bella botta di trigonometria, vedo, e la trigonometria non è (più;) il mio forte. Forse intravedo un sentiero, ma... dita incrociate...
Ultima modifica effettuata da AldoBaldo 26/06/20 11:07
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
26/06/20 11:19
Carlo
Postato originariamente da AldoBaldo:
Credo di avere individuato qualcosa di utile nel file SvgArcSegment.cs , dove i due metodi CalculateVectorAngle() e AddToPath() sembrano fare quel che chiedevo. Ora dovrò cercare di capirci quanto basta per convertirli da C# a C, usando le funzioni standard invece di quelle correntemente presenti nel codice. Quindi dovrò adattare il tutto al contesto di Svg2GraphicsPath() e fare delle prove per vedere se mantengono quel che sembrano promettere. Magari riesco pure a capire almeno in parte come ottengono l'eventuale risultato -- c'è una bella botta di trigonometria, vedo, e la trigonometria non è (più;) il mio forte. Forse intravedo un sentiero, ma... dita incrociate...


Buon lavoro...
L'arco una volta calcolato viene rappresentato con un curva di Bezier in GDI
in programmazione tutto è permesso
26/06/20 16:55
AldoBaldo
F U N Z I O N A !!!

Mi dispiace solo che non sono davvero riuscito a capire il senso del procedimento geometrico che c'è dietro, però Svg2GraphicsPath() ora "legge" anche i comandi a e A, il che è una gran cosa (non ci speravo più;).

Ora non mi resta che togliere dai piedi le variabili globali e rivedere qualche dettaglio, però l'impianto generale funziona come deve funzionare per i miei scopi, e anche così la minilibreria compilata (statica) è 12 kb in tutto.

Qui sotto, le funzioni per la gestione degli archi, con la seconda e la terza che sono solo in parte farina del mio sacco, risultando da un riadattamento semi-acefalo di quelle che mi ha fatto avere Carlo. Ovvio che fanno riferimento a un sacco di elementi che nello spezzone non ci sono. Più in là ho intenzione di mettere a disposizione tutto, ma ora è prematuro.

static bool EstraiEllipticalArc( const char **s, arc_t *d, char cmnd ) {
    REAL tmp = 0.0f;

    bool abs     = isupper( cmnd );
    char lowCmnd = tolower( cmnd );

    d->cmnd  = cmnd;
    d->start = gCp;

    if( lowCmnd == 'a' ) {
        if( !EstraiNumero(s,&d->rx) ) return false;
        if( !EstraiNumero(s,&d->ry) )  return false;
        if( !EstraiNumero(s,&d->rot) )  return false;
        if( !EstraiNumero(s,&tmp) )  return false;
        d->large_arc = roundf( tmp );
        if( !EstraiNumero(s,&tmp) )  return false;
        d->sweep = roundf( tmp );
        if( !EstraiPunto(s,&d->dest) ) return false;

        if( !abs ) {
            d->dest.X += d->start.X;  d->dest.Y += d->start.Y;
        }
    } else return false;

    gCp = d->dest;
    gLastCmnd = cmnd;

    return true;
}

static double CalcolaAngoloVettore( double ux,double uy,double vx,double vy ) {
    static const double DoublePI = 2.0*M_PI;
    double ta = atan2( uy, ux );
    double tb = atan2( vy, vx );

    if (tb >= ta)
        return tb - ta;
    return DoublePI - (ta - tb);
}

static bool ProcessaEllipticalArc(const char **s,char cmnd,bool solo_analisi) {
    static const double M_PI_x2 = 2.0*M_PI;
    static const double radXGrado = M_PI/180.0;
    arc_t d;

    if( !EstraiEllipticalArc(s,&d,cmnd) ) return false;

    if( d.start.X==d.dest.X && d.start.Y==d.dest.Y ) return true; // arco nullo!

    if( 0.0f==d.rx && 0.0f==d.ry ) {
        // l'arco e' in realta' una linea retta
        if( !solo_analisi ) {
            gPt[gQDati] = d.dest;
            gTp[gQDati] = PathPointTypeLine;
        }

        ++gQDati;
        return true;
    }

    double sinPhi = sin( d.rot*radXGrado );
    double cosPhi = cos( d.rot*radXGrado );

    double x1dash = cosPhi*0.5*(d.start.X-d.dest.X) + sinPhi*0.5*(d.start.Y-d.dest.Y);
    double y1dash = -sinPhi*0.5*(d.start.X - d.dest.X) + cosPhi*0.5*(d.start.Y-d.dest.Y);

    double rx = d.rx;
    double ry = d.ry;

    double rxq = rx*rx; // rxq = rx al quadrato
    double ryq = ry*ry; // ryq = ry al quadrato

    double radice;
    double numeratore = rxq*ryq - rxq*y1dash*y1dash - ryq*x1dash*x1dash;

    if (numeratore < 0.0) {
        double s = sqrt( 1.0-numeratore/(rxq*ryq) );
        rx *= s;  /**/  ry *= s;  /**/  radice = 0.0;
    }
    else {
        bool large = d.large_arc!=0;
        bool sweep = d.sweep!=0;

        radice = ((large&&sweep)||(!large&&!sweep) ? -1.0 : 1.0) *
                  sqrt( numeratore/(rxq*y1dash*y1dash+ryq*x1dash*x1dash) );
    }

    double cxdash =  radice*rx*y1dash/ry;
    double cydash = -radice*ry*x1dash/rx;

    double cx = cosPhi*cxdash - sinPhi*cydash + 0.5*(d.start.X+d.dest.X);
    double cy = sinPhi*cxdash + cosPhi*cydash + 0.5*(d.start.Y+d.dest.Y);

    // th: theta
    double th1 = CalcolaAngoloVettore( 1.0, 0.0,
                                       (x1dash-cxdash)/rx,(y1dash-cydash)/ry );
    double dth = CalcolaAngoloVettore( (x1dash-cxdash)/rx,(y1dash-cydash)/ry,
                                      (-x1dash-cxdash)/rx,(-y1dash-cydash)/ry );

    if( 0==d.sweep && dth>0 )
        dth -= M_PI_x2;
    else if( 0!=d.sweep && dth<0 )
        dth += M_PI_x2;

    int segmenti = (int)ceil( (double)fabs(dth/M_PI_2) );

    if( !solo_analisi ) {
        double delta = dth/((double)segmenti);
        double t = 8.0/3.0 * sin(delta/4.0) * sin(delta/4.0) / sin(delta/2.0);

        float startX = d.start.X;
        float startY = d.start.Y;

        for( int i=0; i<segmenti; ++i ) {
            double cosTh1 = cos(th1);
            double sinTh1 = sin(th1);
            double th2 = th1 + delta;
            double cosTh2 = cos(th2);
            double sinTh2 = sin(th2);

            float endpointX = cosPhi*rx*cosTh2 - sinPhi*ry*sinTh2 + cx;
            float endpointY = sinPhi*rx*cosTh2 + cosPhi*ry*sinTh2 + cy;

            float dx1 = t * (-cosPhi*rx*sinTh1 - sinPhi*ry*cosTh1);
            float dy1 = t * (-sinPhi*rx*sinTh1 + cosPhi*ry*cosTh1);

            float dxe = t * (cosPhi*rx*sinTh2 + sinPhi*ry*cosTh2);
            float dye = t * (sinPhi*rx*sinTh2 - cosPhi*ry*cosTh2);

            if( !solo_analisi ) {
                gPt[gQDati].X = startX + dx1;
                gPt[gQDati].Y = startY + dy1;
                gTp[gQDati] = PathPointTypeBezier;
                gQDati++;

                gPt[gQDati].X = endpointX + dxe;
                gPt[gQDati].Y = endpointY + dye;
                gTp[gQDati] = PathPointTypeBezier;
                gQDati++;

                gPt[gQDati].X = endpointX;
                gPt[gQDati].Y = endpointY;
                gTp[gQDati] = PathPointTypeBezier;
                gQDati++;
            }

            th1    = th2;
            startX = endpointX;
            startY = endpointY;
        }
    }
    else { // solo analisi
        gQDati += 3*segmenti;
    }

    return true;
}


Ancora un colossale grazie per Carlo (e per chi ha scritto quella dll). Da solo non sarei andato molto in là.
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
26/06/20 17:21
Carlo
Oltre alla segnalazione, spero che ti sia tornato utile il mio progetto minimale VS, con i soli sorgenti per calcolare il path.
Il ringraziamento all'autore è d'obbligo, ma gli farei anche i complimenti, sappiamo solo lo pseudonimo H1Gdev.
Bravo anche a te AldoBaldo, che hai saputo sfruttare un'opportunità.
in programmazione tutto è permesso
26/06/20 20:26
AldoBaldo
Sì, infatti le due funzioni CalcolaAngoloVettore() e ProcessaEllipticalArc() le ho prese "ispirandomi" pesantemente al file SvgArcSegment.cs che mi hai fornito in quel progetto. Come vedi però, le ho dovute modificare, perché il mio approccio alla lettura della stringa coi dati e alla preparazione del GraphicsPath è diverso da quello della dll -- invece di chiamare i metodi AddQuesto() e AddQuello() del GraphicsPath, io analizzo con una prima passata la stringa dei dati, quindi alloco degli array dinamici per i valori numerici che questa contiene, quindi con una seconda passata sulla stringa dei dati copio i valori numerici negli array, e per finire creo un GraphicsPath in un sol colpo con una singola chiamata a new GraphicsPath(Point* points, BYTE* types, INT count). Inoltre, le funzioni originali sono metodi di una classe, mentre nel mio codice non c'è traccia di OOP.

Quello che ho copiato pedestremente, peraltro senza capirne la logica, è il calcolo vero e proprio. Vola troppo alto sopra la mia testa. Forse trent'anni fa o giù di lì, relativamente fresco di liceo, avrei potuto capirci di più.
ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.