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?
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
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
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.