Umwandlung einer 2D-Kurve in Punkte zur Datenspeicherung


12

Ich habe einen Algorithmus erstellt, der jede Kurve, dh jeden Pfad, in eine Mindestanzahl von Punkten konvertiert, damit ich sie in einer Datei oder Datenbank speichern kann.

Die Methode ist einfach: Sie bewegt drei Punkte in gleichen Schritten und misst den Winkel zwischen den Linien, die diese Punkte bilden. Wenn der Winkel größer als die Toleranz ist, wird eine neue kubische Kurve zu diesem Punkt erstellt. Dann werden die Linien nach vorne verschoben und der Winkel erneut gemessen…

Für diejenigen, die Android Path Class kennen - Beachten Sie, dass der dstPath eine benutzerdefinierte Klasse ist, die die Punkte in einem Array aufzeichnet, damit ich die Punkte später speichern kann, während der srcPath das Ergebnis einer Regions-Union ist und daher keine Schlüsselpunkte für mich hat speichern.

Das Problem ist, dass der Kreis nicht glatt aussieht, wie Sie in diesem Bild sehen können, das durch den folgenden Code erzeugt wird, in dem der Quellpfad aus einem perfekten Kreis und einem Rechteck besteht. Ich habe versucht, den Toleranzwinkel und die Schrittlänge zu ändern, aber nichts hilft. Ich frage mich, ob Sie eine Verbesserung dieses Algorithmus oder einen anderen Ansatz vorschlagen können.

BEARBEITEN: Ich habe jetzt den gesamten Code für diejenigen veröffentlicht, die Android Java verwenden, damit sie es einfach ausprobieren und experimentieren können.

Bildbeschreibung hier eingeben

public class CurveSavePointsActivity extends Activity{

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new CurveView(this));
    }

    class CurveView extends View{

        Path srcPath, dstPath;
        Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        public CurveView(Context context) {
            super(context);

            srcPaint.setColor(Color.BLACK);
            srcPaint.setStyle(Style.STROKE);
            srcPaint.setStrokeWidth(2);
            srcPaint.setTextSize(20);

            dstPaint.setColor(Color.BLUE);
            dstPaint.setStyle(Style.STROKE);
            dstPaint.setStrokeWidth(2);
            dstPaint.setTextSize(20);

            srcPath = new Path();
            dstPath = new Path();

        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

            //make a circle path
            srcPath.addCircle(w/4, h/2, w/6 - 30, Direction.CW);

            //make a rectangle path
            Path rectPath = new Path();
            rectPath.addRect(new RectF(w/4, h/2 - w/16, w*0.5f, h/2 + w/16), Direction.CW);


            //create a path union of circle and rectangle paths
            RectF bounds = new RectF();
            srcPath.computeBounds(bounds, true);
            Region destReg = new Region();
            Region clip = new Region();
            clip.set(new Rect(0,0, w, h));
            destReg.setPath(srcPath, clip);
            Region srcReg = new Region();
            srcReg.setPath(rectPath, clip); 
            Region resultReg = new Region();
            resultReg.op(destReg, srcReg, Region.Op.UNION);
            if(!resultReg.isEmpty()){
                srcPath.reset();
                srcPath.addPath(resultReg.getBoundaryPath());
            }

            //extract a new path from the region boundary path
            extractOutlinePath();

            //shift the resulting path bottom left, so they can be compared
            Matrix matrix = new Matrix();
            matrix.postTranslate(10, 30);
            dstPath.transform(matrix);

        }

         @Override 
            public void onDraw(Canvas canvas) { 
                super.onDraw(canvas);    
                canvas.drawColor(Color.WHITE);
                canvas.drawPath(srcPath, srcPaint);
                canvas.drawPath(dstPath, dstPaint);

                canvas.drawText("Source path", 40, 50, srcPaint);
                canvas.drawText("Destination path", 40, 100, dstPaint);
         }


         public void extractOutlinePath() {

             PathMeasure pm = new PathMeasure(srcPath, false); //get access to curve points

             float p0[] = {0f, 0f}; //current position of the new polygon
             float p1[] = {0f, 0f}; //beginning of the first line
             float p2[] = {0f, 0f}; //end of the first & the beginning of the second line
             float p3[] = {0f, 0f}; //end of the second line

             float pxStep = 5; //sampling step for extracting points
             float pxPlace  = 0; //current place on the curve for taking x,y coordinates
             float angleT = 5; //angle of tolerance

             double a1 = 0; //angle of the first line
             double a2 = 0; //angle of the second line

             pm.getPosTan(0, p0, null); //get the beginning x,y of the original curve into p0
             dstPath.moveTo(p0[0], p0[1]); //start new path from the beginning of the curve
             p1 = p0.clone(); //set start of the first line

             pm.getPosTan(pxStep, p2, null); //set end of the first line & the beginning of the second

             pxPlace = pxStep * 2;
             pm.getPosTan(pxPlace, p3, null); //set end of the second line


             while(pxPlace < pm.getLength()){
             a1 = 180 - Math.toDegrees(Math.atan2(p1[1] - p2[1], p1[0] - p2[0])); //angle of the first line
             a2 = 180 - Math.toDegrees(Math.atan2(p2[1] - p3[1], p2[0] - p3[0])); //angle of the second line

             //check the angle between the lines
             if (Math.abs(a1-a2) > angleT){

               //draw a straight line to the first point if the current p0 is not already there
               if(p0[0] != p1[0] && p0[1] != p1[1]) dstPath.quadTo((p0[0] + p1[0])/2, (p0[1] + p1[1])/2, p1[0], p1[1]);

               dstPath.quadTo(p2[0] , p2[1], p3[0], p3[1]); //create a curve to the third point through the second

               //shift the three points by two steps forward
               p0 = p3.clone();
               p1 = p3.clone();
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p2, null); 
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p3, null);
               if (pxPlace > pm.getLength()) break;
             }else{
               //shift three points by one step towards the end of the curve
               p1 = p2.clone(); 
               p2 = p3.clone();
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p3, null); 
             }
             }
             dstPath.close();
         }
    }

}

Hier ist ein Vergleich zwischen dem Original und dem, was mein Algorithmus erzeugt:

Vergleich zwischen Pfaden;  merklich glattere Ecken auf dem Derivat


warum nicht B-Splines verwenden?
GriffinHeart

4
Wenn Sie wissen, dass es sich um einen Kreis und ein Rechteck handelt, warum nicht einen Kreis und ein Rechteck speichern? Und in verallgemeinerter Form - in welcher Eingabe auch immer Ihr Objekt generiert wurde, ist es wahrscheinlich ein vernünftiges Format, um es zu speichern. Wenn Sie nach einem Komprimierungsschema suchen, bei dem es sich um eine andere Frage handelt (oder zumindest benötigen wir viel mehr Informationen zu den Quelldaten) hilfreich sein).
Jeff Gates

Es kann jede unvorhersehbare Form sein, wie ich im ersten Satz sagte - der Kreis und die Rechteckigkeit sind hier nur ein Testbeispiel.
Lumis

@Lumis du solltest dir wirklich B-Splines ansehen, dafür sind sie da. Gibt es einen Grund, eine eigene Lösung zu implementieren?
GriffinHeart

1
Die Pfadklasse erstellt diese Kurven mit Splines, sodass Sie sie bereits verwenden. Ich habe einen anderen Vorschlag, der weniger mathematisch orientiert ist: Anstatt Punkte zu speichern, speichere die Benutzereingaben (Befehlsmuster) und spiele sie erneut ab, um dasselbe "Bild" zu erstellen.
GriffinHeart

Antworten:


6

Ich denke, Sie haben zwei Probleme:

Nicht symmetrische Kontrollpunkte

Sie beginnen zunächst mit gleichen Abständen zwischen p0 bis p1 und p1 bis p2. Wenn der Toleranzwinkel zwischen den Liniensegmenten nicht eingehalten wird, bewegen Sie p1 und p2 vorwärts, aber behalten p0 bei, wo es war. Dies erhöht den Abstand zwischen p0 und p1, während der Abstand zwischen p1 und p2 gleich bleibt. Wenn Sie eine Kurve mit p1 als Kontrollpunkt erstellen, kann sie stark in Richtung p2 verschoben sein, je nachdem, wie viele Iterationen seit der letzten Kurve vergangen sind. Wenn Sie p2 doppelt so weit bewegen wie p1, erhalten Sie gleichmäßige Abstände zwischen den Punkten.

Quadratische Kurven

Wie auch in anderen Antworten erwähnt, ist die quadratische Kurve für diesen Fall nicht sehr gut. Benachbarte Kurven, die Sie erstellen, sollten einen Kontrollpunkt und eine Tangente gemeinsam haben . Wenn Ihre Eingabedaten nur Punkte sind, verwenden Sie Catmull-Rom-Spline eine gute Wahl für diesen Zweck. Es ist eine kubische Hermite-Kurve, bei der die Tangenten für die Kontrollpunkte aus den vorherigen und nächsten Punkten berechnet werden.

Die Pfad-API in Android unterstützt Bézier-Kurven, die sich hinsichtlich der Parameter geringfügig von Hermite-Kurven unterscheiden. Glücklicherweise können Hermite-Kurven in Bézier-Kurven umgewandelt werden. Hier ist der erste Beispielcode, den ich beim Googeln gefunden habe. Diese Stackoverflow-Antwort scheint auch die Formel zu geben.

Sie haben auch das Problem der scharfen Kanten erwähnt. Mit den eingegebenen Daten ist es unmöglich zu erkennen, ob es tatsächlich eine scharfe Ecke oder nur eine sehr steile Kurve gibt. Wenn dies zu einem Problem wird, können Sie die Iteration anpassungsfähiger machen, indem Sie den Direktschritt nach Bedarf vergrößern / verkleinern.

Edit: Nach weiterem Überlegen könnten doch quadratische Kurven verwendet werden. Anstatt eine quadratische Kurve von p0 nach p2 mit p1 als Kontrollpunkt zu zeichnen, zeichnen Sie sie von p0 nach p1 mit einem neuen Punkt p0_1 als Kontrollpunkt. Siehe das Bild unten. Neue Kontrollpunkte

Wenn sich p0_1 im Schnittpunkt der Tangenten in p0 und p1 befindet, sollte das Ergebnis glatt sein. Seitdem noch besserPathMeasure.getPosTan() auch Tangente als dritter Parameter zurückgegeben wird, können Sie tatsächlich genaue Tangenten anstelle von Approximationen benachbarter Punkte verwenden. Mit diesem Ansatz müssen Sie weniger Änderungen an Ihrer vorhandenen Lösung vornehmen.

Basierend auf dieser Antwort kann der Schnittpunkt mit der folgenden Formel berechnet werden:

getPosTan(pxPlace0, p0, t0); // Also get the tangent
getPosTan(pxPlace1, p1, t1);
t1 = -t1; // Reverse direction of second tangent
vec2 d = p1 - p0;
float det = t1.x * t0.y - t1.y * t0.x;
float u = (d.y * t1.x - d.x * t1.y) / det;
float v = (d.y * t0.x - d.x * t0.y) / det; // Not needed ... yet
p0_1 = p0 + u * t0;

Diese Lösung funktioniert jedoch nur, wenn sowohl u als auch v nicht negativ sind. Siehe das zweite Bild: Strahlen kreuzen sich nicht

Hier kreuzen sich die Strahlen nicht, obwohl die Linien dies tun würden, da u negativ ist. In diesem Fall ist es nicht möglich, eine quadratische Kurve zu zeichnen, die problemlos mit der vorherigen verbunden werden kann. Hier braucht man die schöneren Kurven. Sie können die Kontrollpunkte dafür entweder mit der zuvor in dieser Antwort angegebenen Methode berechnen oder sie direkt aus den Tangenten ableiten. Das Projizieren von p0 auf den Tangentenstrahl p0 + u * t0 und umgekehrt für den anderen Strahl ergibt beide Kontrollpunkte c0 und c1. Sie können die Kurve auch anpassen, indem Sie einen beliebigen Punkt zwischen p0 und c0 anstelle von c0 verwenden, solange er auf dem Tangentenstrahl liegt.

Edit2: Wenn Ihre Zeichnungsposition in p1 ist, können Sie die Bezier-Steuerpunkte zu p2 mit dem folgenden Pseudocode berechnen:

vec2 p0, p1, p2, p3; // These are calculated with PathMeasure
vec2 cp1 = p1 + (p2 - p0) / 6;
vec2 cp2 = p2 - (p3 - p1) / 6;

Mit diesen können Sie einen Pfad von p1 nach p2 anhängen:

path.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);

Ersetzen Sie die Vektoroperationen durch Pro-Komponenten-Operationen für float [ 2 ] -Arrays, damit sie Ihrem Code entsprechen. Sie beginnen mit der Initialisierung p1 = start;und p2 und p3 sind die nächsten Punkte. p0 ist anfänglich undefiniert. Für das erste Segment, in dem Sie p0 noch nicht haben, können Sie eine quadratische Kurve von p1 nach p2 mit cp2 als Kontrollpunkt verwenden. Dasselbe gilt für das Ende des Pfades, auf dem Sie nicht über p3 verfügen. Sie können eine quadratische Kurve von p1 nach p2 mit cp1 als Kontrollpunkt zeichnen. Alternativ können Sie p0 = p1 für das erste Segment und p3 = p2 für das letzte Segment initialisieren. Nach jedem Segment verschieben Sie die Werte, p0 = p1; p1 = p2; and p2 = p3;wenn Sie sich vorwärts bewegen.

Wenn Sie den Pfad speichern, speichern Sie einfach alle Punkte p0 ... pN. Die Kontrollpunkte cp1 und cp2 müssen nicht gespeichert werden, da sie nach Bedarf berechnet werden können.

Edit3: Da es anscheinend schwierig ist, gute Eingabewerte für die Kurvenerzeugung zu erhalten, schlage ich einen anderen Ansatz vor: Verwenden Sie die Serialisierung. Android Path scheint dies nicht zu unterstützen, glücklicherweise jedoch die Region-Klasse. Siehe diese Antwort für den Code. Dies sollte Ihnen das genaue Ergebnis liefern. Es kann etwas Platz in der serialisierten Form beanspruchen, wenn es nicht optimiert ist, aber in diesem Fall sollte es sehr gut komprimiert werden. In Android Java ist die Komprimierung mit GZIPOutputStream einfach .


Das hört sich vielversprechend an. Es werden jedoch nicht p0, sondern p1, p2, p3 verwendet, p0 dient nur zum Speichern neuer bestimmter Punkte, wenn sie berechnet werden, und zum Zwecke gerader Linien, so dass sie nicht bei jedem Schritt abgetastet werden. Können Sie mir helfen, x, y für neue Kontrollpunkte zu berechnen?
Lumis

Das könnte ich später machen, aber in der Zwischenzeit checkt stackoverflow.com/questions/2931573/… . Mit u und v können Sie den Schnittpunkt ermitteln.
Msell

Vielen Dank für die Hilfe, ich möchte dies versuchen, aber es muss in Java für Android geschrieben werden. Es gibt keinen Vektor2 und t1 und p1 usw. sind Float-Arrays, so dass ich keine direkte Operation auf sie wie t1 = -t1 oder u * t0 ausführen kann. Ich gehe davon aus, dass t1 = -t1 t1.x = -t1x bedeutet; t1.y = -t1.y etc, richtig?
Lumis

Ja, das war nur Pseudocode, um es kompakter und lesbarer zu machen.
Msell

Nun, die Handlung verdickt sich. Da der Bereichsschnittpunkt zweier Pfade in Android einen Pfad zurückgibt, der NICHT geglättet ist, befinden sich die Tangenten über der Stelle. Die richtige Lösung wäre also, zuerst eine glatte Kurve durch die angegebenen Punkte zu fahren und sie dann abzutasten. Ihr Code funktioniert auf einem Pfad mit Anti-Aliasing-Effekt einwandfrei. Er erzeugt die richtigen Kontrollpunkte.
Lumis

13

Was würde der W3C tun?

Das Internet hatte dieses Problem. Das World Wide Web Consortium hat es bemerkt. Seit 1999 gibt es eine empfohlene Standardlösung : Scalable Vector Graphics (SVG) . Es ist ein XML- basiertes Dateiformat, das speziell zum Speichern von 2D-Formen entwickelt wurde.

" Skalierbar - was? "

Skalierbare Vektorgrafiken !

  • Skalierbar : Es kann problemlos auf jede Größe skaliert werden.
  • Vektor : Es basiert auf dem mathematischen Begriff der Vektoren .
  • Grafik . Es soll Bilder machen.

Hier ist die technische Spezifikation für SVG Version 1.1.
(Hab keine Angst vor dem Namen; es ist eigentlich angenehm zu lesen.)

Sie haben genau aufgeschrieben, wie Grundformen wie Kreise oder Rechtecke gespeichert werden sollen. Zum Beispiel Rechtecken haben diese Eigenschaften: x, y, width, height, rx, ry. (Das rxund rykann für abgerundete Ecken verwendet werden.)

Hier ist ihr Beispielrechteck in SVG: (Nun, zwei wirklich - eines für den Canvas-Umriss.)

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="12cm" height="4cm" viewBox="0 0 1200 400"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
  <desc>Example rect01 - rectangle with sharp corners</desc>

  <!-- Show outline of canvas using 'rect' element -->
  <rect x="1" y="1" width="1198" height="398"
        fill="none" stroke="blue" stroke-width="2"/>

  <rect x="400" y="100" width="400" height="200"
        fill="yellow" stroke="navy" stroke-width="10"  />
</svg>

Hier ist, was es darstellt:

ein gelbes Rechteck mit einem blauen Umriss

Wie die Spezifikation besagt, können Sie einige der Eigenschaften weglassen, wenn Sie sie nicht benötigen. (Zum Beispiel rxund ryAttribute wurden hier nicht verwendet.) Ja, es gibt eine Tonne Cruft oben, über DOCTYPEdie Sie nicht nur für Ihr Spiel benötigen. Sie sind auch optional.

Pfade

SVG- Pfade sind "Pfade" in dem Sinne, dass Sie einen Pfad haben , wenn Sie einen Stift auf ein Papier legen, es bewegen und schließlich wieder anheben . Sie haben nicht haben werden geschlossen , aber sie auch sein mögen.

Jeder Pfad hat ein dAttribut (ich denke, es steht für "Zeichnen"), das Pfaddaten enthält , eine Folge von Befehlen, mit denen man im Grunde nur einen Stift auf ein Papier legt und es herumbewegt .

Sie geben das Beispiel eines Dreiecks:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="4cm" height="4cm" viewBox="0 0 400 400"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
  <title>Example triangle01- simple example of a 'path'</title>
  <desc>A path that draws a triangle</desc>
  <rect x="1" y="1" width="398" height="398"
        fill="none" stroke="blue" />
  <path d="M 100 100 L 300 100 L 200 300 z"
        fill="red" stroke="blue" stroke-width="3" />
</svg>

ein rotes Dreieck

Siehe das dAttribut in der path?

d="M 100 100 L 300 100 L 200 300 z"

Das Mist ein Befehl für Verschieben nach (gefolgt von Koordinaten), das Ls steht für Linie nach (mit Koordinaten) und zist ein Befehl zum Schließen des Pfades (dh eine Linie zurück zum ersten Ort ziehen, der keine Koordinaten benötigt).

Gerade Linien sind langweilig? Verwenden Sie die kubischen oder quadratischen Bézier-Befehle!

einige kubische Béziers

Die Theorie hinter den Bézier-Kurven wird an anderer Stelle (z. B. bei Wikipedia ) ausführlich behandelt. Hier jedoch die Zusammenfassung: Béziers haben einen Start- und einen Endpunkt mit möglicherweise vielen Kontrollpunkten, die die Richtung der dazwischen liegenden Kurve beeinflussen.

Verfolgung eines quadratischen Bézier

Die Spezifikation enthält auch Anweisungen zum Konvertieren der meisten Grundformen in Pfade, falls Sie dies möchten.

Warum und wann wird SVG verwendet?

Entscheiden Sie sorgfältig, ob Sie diesen Weg gehen möchten (Wortspiel beabsichtigt), da es sehr kompliziert ist, beliebige 2D-Formen im Text darzustellen! Sie können Ihr Leben so viel einfacher machen, wenn Sie sich z. B. auf Pfade beschränken, die aus (möglicherweise wirklich vielen) geraden Linien bestehen.

Wenn Sie sich jedoch für beliebige Formen entscheiden, ist SVG der richtige Weg: Es bietet eine hervorragende Toolunterstützung: Auf niedriger Ebene finden Sie viele Bibliotheken für XML-Parsing und auf hoher Ebene SVG-Editor-Tools .

Unabhängig davon ist der SVG-Standard ein gutes Beispiel.


Es geht darum, eine Kurve in Punkte umzuwandeln, nicht zu speichern. Aber danke für diesen Hinweis, es ist gut, über den SVG-Standard Bescheid zu wissen.
Lumis

@ Lumis Der Titel und der Inhalt würden etwas anderes vorschlagen. Überlegen Sie sich, die Frage umzuformulieren. (Oder, jetzt, da dieser ziemlich etabliert ist und einen anderen fragt.)
Anko

4

Ihr Code enthält einen irreführenden Kommentar:

dstPath.quadTo(p2[0] , p2[1], p3[0], p3[1]); //create a curve to the third point through the second

Eine quadratische Bezier - Kurve ist nicht gehen durch den zweiten Punkt. Wenn Sie den zweiten Punkt durchlaufen möchten, benötigen Sie einen anderen Kurventyp , z. B. eine Hermitenkurve . Möglicherweise können Sie die Einsiedlerkurven in Beziere konvertieren, damit Sie die Path-Klasse verwenden können.

Ein weiterer Vorschlag ist, anstatt die Punkte abzutasten, den Mittelwert der Punkte zu verwenden, die Sie überspringen.

Ein weiterer Vorschlag besteht darin, anstelle eines Winkels als Schwellenwert die Differenz zwischen der tatsächlichen Kurve und der ungefähren Kurve zu verwenden. Winkel sind nicht das eigentliche Problem. Das eigentliche Problem ist, wenn die Punktmenge nicht zu einer Bezierkurve passt.

Ein weiterer Vorschlag ist die Verwendung von kubischen Beziern, wobei der Tangens des einen mit dem Tangens des nächsten übereinstimmt. Andernfalls (mit Quadraten) passen Ihre Kurven meiner Meinung nach nicht reibungslos zusammen.


Sie haben recht, der zweite Punkt "zieht" nur die Kurve in Richtung. Der cubicTo benötigt zwei Kontrollpunkte anstelle von einem als quadTo. Das Problem ist natürlich, wie man die richtigen Kontrollpunkte bekommt. Beachten Sie, dass ich keine scharfen Ecken verlieren möchte, da der Quellpfad eine Kombination aus einer geraden oder runden Form sein kann. Im Grunde genommen erstelle ich ein Bildauswahlwerkzeug, mit dem ich den ausgewählten Pfad speichern kann.
Lumis

4

Um eine gleichmäßigere Schnittmenge zwischen zwei Pfaden zu erzielen, können Sie diese vor der Schnittmenge vergrößern und anschließend verkleinern.

Ich weiß nicht, ob es eine gute Lösung ist, aber es hat bei mir gut funktioniert. Es ist auch schnell. In meinem Beispiel schneide ich einen abgerundeten Pfad mit einem von mir erstellten Muster (Streifen). Es sieht auch im skalierten Zustand gut aus.

Hier mein Code:

    Path mypath=new Path(<desiredpath to fill with a pattern>);
    String sPatternType=cpath.getsPattern();

    Path pathtempforbounds=new Path(cpath.getPath());
    RectF rectF = new RectF();
     if (sPatternType.equals("1")){
         turnPath(pathtempforbounds, -45);
     }
     pathtempforbounds.computeBounds(rectF, true);

     float ftop=rectF.top;
     float fbottom=rectF.bottom;
     float fleft=rectF.left;
     float fright=rectF.right;
     float xlength=fright-fleft;

     Path pathpattern=new Path();

     float ypos=ftop;
     float xpos=fleft;

     float fStreifenbreite=4f;

     while(ypos<fbottom){
         pathpattern.moveTo(xpos,ypos);
         xpos=xpos+xlength;
         pathpattern.lineTo(xpos, ypos);
         ypos=ypos+fStreifenbreite;
         pathpattern.lineTo(xpos, ypos);
         xpos=xpos-xlength;
         pathpattern.lineTo(xpos, ypos);
         ypos=ypos-fStreifenbreite;
         pathpattern.lineTo(xpos, ypos);
         pathpattern.close();
         ypos=ypos+2*fStreifenbreite;

     }

     // Original vergrössern

     scalepath(pathpattern,10);
     scalepath(mypath,10);

     if (sPatternType.equals("1")){
         Matrix mdrehen=new Matrix();
         RectF bounds=new RectF();
         pathpattern.computeBounds(bounds, true);
         mdrehen.postRotate(45, (bounds.right + bounds.left)/2,(bounds.bottom + bounds.top)/2);
         pathpattern.transform(mdrehen);
     }

     RectF rectF2 = new RectF();
     mypath.computeBounds(rectF2, true);

     Region clip = new Region();
     clip.set((int)(rectF2.left-100f),(int)(rectF2.top -100f), (int)(rectF2.right+100f),(int)( rectF2.bottom+100f));
     Region region1 = new Region();
     region1.setPath(pathpattern, clip);

     Region region2 = new Region();
     region2.setPath(mypath, clip);

     region1.op(region2, Region.Op.INTERSECT);


     Path pnew=region1.getBoundaryPath();


     scalepath(pnew, 0.1f);
     cpath.setPathpattern(pnew);




public void turnPath(Path p,int idegree){
     Matrix mdrehen=new Matrix();
     RectF bounds=new RectF();
     p.computeBounds(bounds, true);
     mdrehen.postRotate(idegree, (bounds.right + bounds.left)/2,(bounds.bottom + bounds.top)/2);
     p.transform(mdrehen);
}

public void scalepath(Path p,float fscale){
     Matrix mverkleinern=new Matrix();
     mverkleinern.preScale(fscale,fscale);
     p.transform(mverkleinern);
}

Bildbeschreibung hier eingeben

Sieht beim Zoomen mit canvas.scale () noch flüssig aus: Bildbeschreibung hier eingeben


Vielen Dank, wer hat mich je 10 Ruf ausgegeben, um die Bilder hinzuzufügen :-)
user1344545

1
Erstaunlicherweise löst dieser einfache Trick zwei Probleme: Erstens macht er den resultierenden Schnitt- oder Vereinigungspfad glatt, und zweitens erzeugt mein Code in der Frage, wenn er denselben vergrößerten Pfad abtastet, ein perfekt glattes Ergebnis. Was für eine unerwartete und einfache Lösung, danke!
Lumis

@user Die Bearbeitung ist kostenlos. Für <2k-rep-Benutzer ist es eigentlich eine +2.
Anko

@Lumis Ich bin ein wenig verwirrt - ich dachte, Sie fragten, wie man Pfade speichert ?
Anko

1
Leider habe ich nach weiteren Tests festgestellt, dass die App aufgrund der Verwendung von Pixeln, die der Pfad beim Zeichnen belegen würde, leicht keinen Speicher mehr hat, wenn der Pfad stark skaliert und wiederholt wird. Diese Lösung ist also begrenzt und riskant, aber gut zu bedenken.
Lumis

3

Schauen Sie sich die Polygoninterpolation an ( http://en.wikipedia.org/wiki/Polynomial_interpolation) )

Grundsätzlich nehmen Sie n Knoten mit gleichem Abstand (optimale Interpolation ist nicht gleich, sollte aber für Ihren Fall gut genug und einfach zu implementieren sein)

Sie erhalten ein Polygon der Ordnung n, das den Fehler zwischen Ihrer Kurve verringert, wenn (<- big if) Ihre Linie glatt genug ist.

In Ihrem Fall führen Sie eine lineare Interpolation (Reihenfolge 1) durch.

Der andere Fall (wie von GriffinHeart empfohlen) war die Verwendung von Splines ( http://en.wikipedia.org/wiki/Spline_interpolation ).

In beiden Fällen erhalten Sie eine Art Polynom, das für Ihre Kurve geeignet ist.


2

Wenn der Konvertierungspunkt nur für die Speicherung bestimmt ist und wenn Sie ihn wieder auf dem Bildschirm rendern, müssen Sie ihn reibungslos wiedergeben, um den Speicher mit der höchsten Wiedergabetreue zu erhalten und gleichzeitig den Gesamtspeicher zu minimieren, der erforderlich ist, um eine bestimmte Kurve beizubehalten um die Attribute des Kreises (oder eher eines Bogens) tatsächlich zu speichern und ihn nach Bedarf neu zu zeichnen.

Ursprung. Radius. Start- / Stoppwinkel zum Zeichnen des Bogens.

Wenn Sie den Kreis / Bogen ohnehin zum Rendern in Punkte konvertieren müssen, können Sie dies möglicherweise beim Laden aus dem Speicher tun, während Sie immer nur die Attribute speichern.


Der Quellpfad / die Quellkurve kann eine beliebige Form haben, einschließlich des Zeichnens einer freien Linie. Ich habe über diese Lösung nachgedacht, bei der jede Komponente separat gespeichert und beim Laden kombiniert werden müsste, die jedoch einen hohen Arbeitsaufwand erfordert und die Manipulation eines so komplexen Objekts verlangsamt, dass jede Transformation auf jede Komponente angewendet werden müsste seine Komponenten, um es wieder speichern zu können.
Lumis

2

Gibt es einen Grund für Kurven im Gegensatz zu geraden Linien? Gerade Linien sind einfacher zu bearbeiten und können in Hardware effizient gerendert werden.

Der andere in Betracht zu ziehende Ansatz besteht darin, ein paar Bits pro Pixel zu speichern und anzugeben, ob es sich um eine Innen-, Außen- oder Außenkontur der Form handelt. Dies sollte gut komprimiert werden und ist möglicherweise effizienter als Zeilen für komplexe Auswahlen.

Diese Artikel könnten Sie auch interessieren / nützlich finden:


1

Schauen Sie sich die Kurveninterpolation an - es gibt einige verschiedene Arten, die Sie implementieren können, um Ihre Kurve zu glätten. Je mehr Punkte Sie auf diesem Kreis sammeln können, desto besser. Speicher ist ziemlich billig - wenn also das Extrahieren von 360 Knoten in der Nähe billig genug ist (selbst bei 8 Bytes für die Position; das Speichern von 360 Knoten ist kaum teuer).

Sie können mit einigen Interpolation Proben stellen hier mit nur vier Punkten; und die Ergebnisse sind ziemlich gut (mein Favorit ist der Bezier für diesen Fall, obwohl andere vielleicht andere effektive Lösungen vorschlagen).

Sie können in spielen , um hier auch.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.