Hallo Besucher, der Thread wurde 14k mal aufgerufen und enthält 81 Antworten

letzter Beitrag von bbock am

Vektorgrafik für den C64

  • Sehr cool! Ich konnte es, auch angeregt durch GoDots BASIC-Version, nicht lassen, eine Version in reinem BASIC V2 zusammenzuklöppeln...inkl. Linienziehen und Kreisemalen in glorreichem BASIC. Zumindest die mit MOSpeed compilierte Fassung läuft gar nicht mal sooo schlecht. Für den Lehrer Lämpel sind die Zeiten im Vergleich:


    • C von bblock: 1m 40s
    • TSB von GoDot: 15m 35s
    • BASIC V2: 25m 28s
    • BASIC V2 compiliert: 8m 55s


    Bei den kleinen Figuren fällt die BASIC-Version natürlich im Vergleich stärker ab, weil dann der Großteil der Zeit im Linienziehen verdödelt wird, und das ist in BASIC eben nicht so wirklich rasant.


    Hat jemand eine vec-Datei mit Kreisen oder Ellipsen? Ich habe den Support dafür zwar eingebaut, aber ich glaube, es gibt keinen Testfall dafür bei den Dateien auf dem Diskimage, oder?

  • Hat jemand eine vec-Datei mit Kreisen oder Ellipsen?

    Hier ist eine ganz kurze: ein Verkehrsschild (Durchfahrt verboten) - ein Strich, ein Kreis, ein Rechteck.


    Arndt

  • nämlich dass die Genauigkeit weit über die Bildschirmauflösung hinausgeht.

    Nicht wenn man die Scalierung (wie bei VEC) vorher auf die Bildschirmauflösung herunterrechnet...


    Es wäre daher erforderlich, das VEC-Format im Header um Informationen über die maximale X-Y-Auflösung des folgenden Bildes zu erweitern (so ähnlich geht das bei HPGL). Das wäre dann "VEC2", "VEC+" oder Extended-VEC ("EVC"; "VEX"; "EXV"; "XVC"; VEX gefällt mir am besten). Mit diesen Infos müssen dann alle Koordinaten vom Zeichenprogramm scaliert werden. Und könnten dann natürlich auch mit zusätzlichen Scalierungsparametern (vergrößern [Ausschnitt], verkleinern) behandelt werden. Zudem könnte man die Daten dann passend scaliert auf einen Plotter ausgeben. Ich habe das VEC-Format auch noch nicht ganz durchdrungen, da steht was von dezimal bin mir aber nicht sicher, ob das auch für die Koordinaten gilt. Die sollte man bei VEX in jedem Fall als HEX angeben, da ja nur 2 Bytes vorgesehen sind.

  • Hier mal ein Update für meinen Viewer. War noch ein Bug drin. Und es hat sich - durch die Testerei - herausgestellt, dass mein kürzlich in TSB implementierter Befehl DRAW TO auch noch einen Bug hatte. Daher im Anhang noch mal alles zusammen mit ein paar neuen Bildern.


    Darunter die Datei "test.vec". Die hab ich von Hand zusammengebastelt. Sie verwendet bis auf Polygon-Fill alle TinyVec-Funktionen, also Dot, Line, Box, Square, Ellipse, Circle, Multi-Dot und Multi-Line. Das Haus (in der Datei) ist Multi-Line, das Eulenauge ist Circle, die Sonne Ellipse, das Fenster Square, das Fensterkreuz und der Türgriff Line, das Schlüsselloch ist Dot, die Blumen und das Sonnengesicht sind Multi-Dot und die Tür ist Box.


    Arndt


    Edit: Mit "x" kann man nun den Bildaufbau abbrechen. :-)

  • Darunter die Datei "test.vec". Die hab ich von Hand zusammengebastelt. Sie verwendet bis auf Polygon-Fill alle TinyVec-Funktionen, also Dot, Line, Box, Square, Ellipse, Circle, Multi-Dot und Multi-Line. Das Haus (in der Datei) ist Multi-Line, das Eulenauge ist Circle, die Sonne Ellipse, das Fenster Square, das Fensterkreuz und der Türgriff Line, das Schlüsselloch ist Dot, die Blumen und das Sonnengesicht sind Multi-Dot und die Tür ist Box.

    Sehr hilfreich, danke. Ich hatte auch noch eine Macke im Zeichnen der Quadrate und habe jetzt Multi-Dot und Multi-Line in meinem Pure-BASIC-Kram ergänzt.

  • Wegen Polygon-Fill noch mal eine Frage an bbock : Schließt der die Fläche umgebende Linienzug automatisch oder muss man den Startpunkt am Ende noch einmal angeben?


    Arndt


    (Ach und noch eine Frage: Wird das Format weiterentwickelt?)

  • Wegen Polygon-Fill noch mal eine Frage an bbock : Schließt der die Fläche umgebende Linienzug automatisch oder muss man den Startpunkt am Ende noch einmal angeben?


    Arndt


    (Ach und noch eine Frage: Wird das Format weiterentwickelt?)

    Da die VEC-Spezifikaton von einer in Turbo Pascal und Z80-Assembler geschriebenen Joyce-Grafikbibliothek abstammt, ist die Antwort aus den Routinen der GRAPHLIB zu entnehmen: der Startpunkt muss am Ende noch einmal angegeben werden.


    Hier ist die Beschreibung der GRAPHLIB; die relevante Passage habe ich in rot hervorgehoben:


    Um eine Linie, einen Kreis oder andere Objekte auf den Bildschirm zu zaubern, müssen zunächst alle Punkte berechnet und einer nach dem anderen gesetzt werden.
    Dafür sind die Prozeduren LINE() bis CIRCLE() zuständig.
    LINE() zieht eine Linie zwischen den Bildschirmpunkten, deren Koordinaten im Aufruf übergeben wurden.
    Die einfachste Art dies zu tun, wäre, mit Hilfe der Geradengleichung f(x) = Steigung * x + y-Achsenabschnitt für die xKoordinaten die Werte für f(x) zu berechnen, sie zu Runden, da der Bildschirm ja nur diskrete Schritte erlaubt, und sie anschließend mit PLOT() zu setzen.
    Da die Berechnung über REAL-Werte erfolgen müßte, wäre dies aber mit Sicherheit auch die langsamste Art.
    LINE() rechnet nur mit INTEGER-Variablen.
    Ausgangspunkt ist auch hier wieder die Geradengleichung — diesmal aber ohne Achsenabschnitt.
    Vielleicht erinnern Sie sich noch mehr oder weniger dunkel an den Mathematikunterricht?!
    Die Steigung einer Geraden läßt sich aus den Differenzen der Koordinaten zweier Punkte — in unserem Fall von Start und Endpunkt — berechnen.
    Umgeformt lautet die Gleichung dann: x * DY = y * DX.
    Mit DX und DY werden bei jedem Schritt die Abweichungen der gesetzten Punkte von der vorgegebenen Gerade bestimmt und zur Auswahl der nächsten Schrittrichtung benutzt.
    Mehr Details will ich Ihnen und mir ersparen.
    BOX() bringt ein Rechteck auf den Bildschirm.
    Übergeben werden die Ecken links oben und rechts unten und — wie auch bei den anderen Prozeduren — der Modus (setzen, löschen oder invertieren).
    Um die Box nicht unnötig zu bremsen, wird nicht LINE() zum ziehen der Linien benutzt, sondern die Sonderfälle für horizontale und vertikale Linien.


    POINT_SWAP() sorgt dafür, daß die Startkoordinate immer kleiner als die Endkoordinate ist und nicht über den Rand gemalt wird.
    SQUARE() ist ein Spezialfall von BOX() und zeichnet — wie zu erwarten — ein Quadrat.
    Als Parameter werden die linke obere Ecke und die Breite in Pixeln übergeben.
    Wie wird nun das Quadrat zum Quadrat?
    Hierfür ist die Konstante ASPECT_RATIO zuständig;
    sie gibt das Verhältnis von Pixelhöhe zu Pixelbreite an.
    Ihr Wert kann durch Ausmessen von »Boxen« am Bildschirm ermittelt werden — probieren geht auch hier über studieren.
    Auch ELLIPSE() und CIRCLE() erhalten durch das Einbeziehen der ASPECT_RATIO erst das gewünschte Aussehen.
    Ebenso wie die Prozedur LINE() vermeidet auch ELLIPSE() weitgehend REALzahlen.
    Auch hier wird nur die Abweichung betrachtet, die durch jeden Schritt entsteht.
    Außerdem macht sich die Symmetrie der Figur nutzbringend bemerkbar:
    Wir müssen nur ein Achtel aller Punkte berechnen, alle ändern erhalten wir durch Spiegelungen entsprechende Kombination von Addition und Subtraktion der errechneten Werte von den Koordinaten des Mittelpunktes.
    Radius_x gibt die Anzahl der Pixel der x-Halbachse an, die y-Halbachse wird so umgerechnet, daß das Größenverhältnis auf dem Bild dem gewünschten Verhältnis von Radius_y zu Radius_x entspricht.
    CIRCLE() wird als Ellipse mit gleich langen Halbachsen berechnet;
    übergeben wird dementsprechend außer den Mittelpunktskoordinaten nur noch der Radius.


    Soweit die »Grundfunktionen«.
    Aber es gibt noch mehr;
    Die Prozedur PLOT_MARKER() ist vor allem zur Darstellung von Meßwerten u.a. gedacht.
    Sie setzt auf den Punkt x,y einen »Marker« aus den in MARKER_TYPE deklarierten Elementen.
    PLOT_MARKER(10,20,Kreis,0) zieht also einen Kreis um den Punkt 10,20.
    Wenn Sie MARKER_HEIGHT mit im Aufruf übergeben, können Sie auch verschiedene Größen erhalten, was mir persönlich allerdings nicht besonders sinnvoll erschien, da die Marker dann zum Teil doch etwas unförmig werden.
    Als nächstes folgen vier POLY_...-Prozeduren.
    Wie der Name vermuten läßt, wird hier etwas mehrfach ausgeführt.
    Die erforderlichen Koordinaten werden als zweidimensionales Array übergeben;
    POINT_ARRAY(.i,0.) enthält die x-, POINT_ARRAY(.i,1.) die y-Werte.
    POINT_ARRAY ist auf 250 dimensioniert, was für die meisten Anwendungen völlig ausreichen dürfte.
    Die Zahl der wirklich benutzten Arrayelemente wird als Parameter mit übergeben.
    POLY_PLOT() setzt lediglich Punkte auf die Koordinatenpaare des Arrays;
    POLY_MARKER() die in MARKER_TYPE definierten Marker.
    POLY_LINE() verbindet die Punkte in der Reihenfolge, in der sie im Array enthalten sind, mit Linien.
    Alle Prozeduren können vielseitig eingesetzt werden.
    Etwa um Funktionen oder Meßwerte darzustellen, wie im Beispiel GRAPHIC.PRO demonstriert, oder um Polygone zu zeichnen.

    Bei POLY_LINE() muß dann das erste Koordinatenpaar noch einmal als letztes im POINT_ARRAY enthalten sein, damit eine geschlossene Figur entsteht.
    Ebenso verhält es sich bei der vierten POLY_..-Prozedur POLY_FILL().

    Sie füllt ein Polygon, dessen Eckpunkte als POINT_ARRAY übergeben wurden, im entsprechenden Modus, d.h. sie füllt den Inhalt »weiß«.
    löscht oder invertiert alle Punkte.


    Allerdings wird es durch die Art der Berechnung notwendig, im Anschluß an das Füllen mit PLOY_LINE() noch die 'Begrenzung' zu zeichnen.
    Ihre Anwendung im Demo-Programm DREID (entnommen aus PASCAL INTERNATIONAL 2(2), Januar 1987) zeigt am besten den Effekt, den man so erzielen kann:
    Hidden-LineDarstellungen ohne komplizierte Umrechnungen und große Arrays.
    Soweit zum Inhalt von GRAPHLIB.INC.


    Der erste Entwurf des VEC-Formats war wohl ein wenig naiv und nur auf die Erfordernisse der GRAPHLIB der Schneider Joyce angelehnt. Aber es macht wohl Sinn, das Format zu erweitern um mehr Möglichkeiten zu eröffnen. Mir fallen auf Anhieb folgende Erweiterungen ein:

    • Minima und Maxima der Koordinaten im Header (für einfachere Skalierung)
    • Länge der Koordinateneinträge für X- und Y-Koordinate getrennt zur Speicherplatzoptimierung (C64: X = 2 Byte, Y = 1 Byte)
    • LINETO-Befehl (spart 2 Koordinaten)
    • Profile für unterschiedliche Zielsysteme; so wird im einfachsten Fall nur Plot und Line unterstützt, je nach verfügbarer Grafikbibliothek dann auch Kreise, Ellipsen, usw.
  • LINETO-Befehl (spart 2 Koordinaten)

    Den verwende ich beim Linienzug (Poly-Line) bereits. Heißt in TSB dann DRAW TO. Die LINE-Funktion sollte ruhig so bleiben (mit Start- und Zielkoordinaten). Nur dein TinyVec-Konverter könnte von Line auf Poly-Line umgestellt werden (wo das Sinn macht).


    Arndt

  • Mir als Grafiker fehlt vor allem eine Bezier- oder Spline-Funktion (wie ich sie in meinem Mockup andeutete). In modernen Vektorgrafik-Programmen basiert quasi alles auf Bezierkurven – selbst ein Kreis. Auch einen Linienzug kann man durch Umwandlung eines Eckpunktes zu einem Übergangspunkt in eine Kurve verwandeln. Und natürlich besteht auch quasi jede Schrift aus entsprechenden Kurven (die keine Kreissegmente darstellen).


    Die Frage ist aber, ob ein 1MHz-Rechner dazu fähig ist, solche Kurven einigermaßen performant zu zeichnen.

  • LINETO-Befehl (spart 2 Koordinaten)

    Den verwende ich beim Linienzug (Poly-Line) bereits. Heißt in TSB dann DRAW TO. Die LINE-Funktion sollte ruhig so bleiben (mit Start- und Zielkoordinaten). Nur dein TinyVec-Konverter könnte von Line auf Poly-Line umgestellt werden (wo das Sinn macht).


    Arndt

    Guter Punkt; werde ich umsetzen.


    Weitere Vorschläge für die erweiterte VEC-Spezifikation?


    Bernd

  • Mir als Grafiker fehlt vor allem eine Bezier- oder Spline-Funktion (wie ich sie in meinem Mockup andeutete). In modernen Vektorgrafik-Programmen basiert quasi alles auf Bezierkurven – selbst ein Kreis. Auch einen Linienzug kann man durch Umwandlung eines Eckpunktes zu einem Übergangspunkt in eine Kurve verwandeln. Und natürlich besteht auch quasi jede Schrift aus entsprechenden Kurven (die keine Kreissegmente darstellen).


    Die Frage ist aber, ob ein 1MHz-Rechner dazu fähig ist, solche Kurven einigermaßen performant zu zeichnen.

    Das Ziel meines Ansatzes war, ein einfaches Vektorformat zu erzeugen, das durch 8-Bit-Computer einfach und schnell dargestellt werden kann. Bézier-Kurven und Ellipsenbögen (Arc) werden in TinySVG bereits in Linienzüge umgewandelt, eben weil das recht komplex und aufwändig ist (Catmull-Rom-Kurven übrigens nicht, weil die es nicht in die SVG-Spezifikation des W3C geschafft haben). Je nach Zielformat (das muss bei TinySVG nicht immer VEC sein) werden auch andere Grafikobjekte wie Kreise oder Ellipsen in Linienzüge umgewandelt.


    Man muss einen brauchbaren Kompromiss finden, was auf dem PC und was auf dem 8-Bitter gerechnet werden soll, denke ich. Sonst kann man gleich versuchen SVG direkt auf dem C64 zu interpretieren. Etwas Derartiges gibt es übrigens in der Atari-Ecke: https://github.com/savetz/Renderific

    Hier hat man sich zur Vereinfachung auf die SVG Tiny Spezifikation beschränkt.


    Anm.: Der Name TinySVG hat nichts mit der Spezifikation SVG Tiny zu tun; ich habe den Namen für das Programm festgelegt, bevor ich von der Existenz der SVG Tiny Spezifikation des W3C wusste.

  • Anm.: Der Name TinySVG hat nichts mit der Spezifikation SVG Tiny zu tun; ich habe den Namen für das Programm festgelegt, bevor ich von der Existenz der SVG Tiny Spezifikation des W3C wusste.

    Warum nur muss ich gerade an Monty python und die "Judäische Volksfront" denken?

  • Schöne Analyse; sowas habe ich auch in anderem Zusammenhang schon gemacht, z.B. einen Vergleich der Performance verschiedener Retro-Computer mit dem Algorithmus "Sieb des Erathostenes" (Primzahlberechnung).


    Bzgl. TSB von GoDot: Das Zeichnen selbst sollte in TSB ähnlich schnell laufen wie mit der TGI library des cc65. Der Flaschenhals ist das Lesen von der Floppy.


    GoDot: Statt einzelne Zeichen mit GET zu lesen, wäre es effizienter, gleich einen ganzen Block (z.B. 512 Bytes) von der Diskette zu lesen. Dann kann man Byte für Byte aus dem Puffer lesen, und wenn man das letzte Zeichen gelesen hat, dann liest man den nächsten Block ein - bis EOF. So habe ich das in meinem C-Programm DRAWVECTOR gemacht. Gibt es eine Load-Routine in TSB, die einen Block an eine bestimmte Speicheradresse (oder in eine Puffervariable) lesen kann? Dann könntest du dein Programm deutlich schneller machen.


    Übrigens: meine ersten Versuche die Vektorgrafiken auf dem C64 anzuzeigen waren mit Simons' BASIC. Da ist mir auch nur der langsame GET-Befehl eingefallen...

  • Gibt es eine Load-Routine in TSB, die einen Block an eine bestimmte Speicheradresse (oder in eine Puffervariable) lesen kann?

    Ja, ich hab da eine, die ist aber nicht Teil von TSB. Der Viewer war eigentlich nicht als ernsthafte Anwendung gedacht, sondern ist in Wahrheit nur mal schnell zusammengeklöppelt, um rauszufinden, ob ich das Format auch in GoDot unterstützen kann. Da es noch in den Anfängen liegt, warte ich da aber erst mal ab. Wenn es denn irgendwann mal einen fertigen Renderer geben sollte, müsste ich dann für GoDot völlig neu denken (GoDot ist ja bitmap-orientiert).


    Arndt

  • Ach das mit dem get# hatten wir doch schon. Das verwendet doch wohl keiner zum Einlesen von mehr als 16 Bytes?

    Doch, wenn man die schnellere Variante nicht kennt. Ich z.B. kannte die nicht. Mit dieser Variante läuft der Lehrer Lämpel in der compilierten BASIC V2-Version in 4:46 statt in 8:55.

  • Wie sehr spielt bei der Performance eigentlich der Prozessor-Typ mit rein? Auf den ersten 16-Bit-Maschinen (Macintosh, ST, Amiga) ließen sich 2D-Vektor-Grafiken ja schon recht komfortabel bearbeiten (MacDraw, GEM-Draw etc ...) – das wird doch sicherlich nicht nur an den (bis zu 8 mal höheren) Megahertzen gelegen haben, oder? Und auch der Z80 hat doch (neben seinem meist höheren Takt), glaube ich, architekturbedingte Vorteile bei Vektorgrafik, oder? Ist der 6502 quasi die schlechteste Wahl (selbst gegenüber anderen 8-Bittern), wenn es um Vektorgrafik geht?

  • Ich habe jetzt auch eine Vektorgrafik beizusteuern. Es war gar nicht so einfach, aus der Flächen-bezogenen Grafik manuell nur die reinen Linien (und ohne doppelte an aneinander-stoßenden Flächen) zu extrahieren. Als VEC und SVG enthalten.


    Da ja schon der Java-Konverter die Grafik auf die passende Zielauflösung umrechnet: Was passiert eigentlich, wenn man eine "falsche" VEC-Datei öffnet? Und kann man vorab an irgendwas erkennen, für welche Zielauflösung die Grafik generiert wurde?