Interface Design für eine Grafik Library



  • Original erstellt von Headhunter:
    Außerdem sind globale Funktionen hässlich.
    Stell dir vor nachher kommen noch ein paar Funktionen für Dreiecke, Rechtecke, Kreise usw hinzu. Da platzt der globale Namensraum ja fast 😉

    Naja, ich dachte auch mehr an sowas wie krlib (kingruedis lib) als namespace oder so...
    Und wirklich schwer wird das mit mehreren Ausgabemöglichkeiten auch nicht.



  • krlib : rofl 😃

    Zugriff auf mehrere Ausgabegeräte mit globalen Funktionen (was putpixel ja ist, auch wenn es in einem namespace steckt) :

    int ausgabe;
    
    void putpixel(int x, int y, color farbe)
    {
         if (ausgabe == SDL)
         {
               //blabla
         }
         else if (ausgabe == TEXT)
         {
               //zugriff auf ein paar funktionen
         }
         else if (ausgabe == DRUCKER)
         {
               //tu was
         }
    }
    
    int main ()
    {
         ausgabe = SDL;
         putpixel (20,20,2);
    }
    

    Zugriff auf mehrere Ausgabegeräte mit Klassen

    //abc == abstract basic class
    class CABCGraphic
    {
    public :
    
           virtual void putpixel(int x, int y, color farbe) = 0;      
           //hier kommt noch mehr
    };
    
    //
    class CSDLGraphic : public CABCGraphic
    {
    public :
           virtual void putpixel (int,int,color);
    };
    
    //
    class CTextGraphic : public CABCGraphic
    {
    public :
           virtual void putpixel (int,int,color);
    };
    
    //
    class CPrinterGraphic : public CABCGraphic
    {
    public :
           virtual void putpixel (int,int,color);
    };
    
    int main ()
    {
         CABCGraphic* g = new CSDLGraphic;
         g->putpixel (20,20,2);      
    }
    

    Mh, die C++ Variante hat zwar was mehr Text, ist aber -imo- besser. Versuch mal bei der globalen Variante ein neues Ausgabegerät, z.b. OpenGL, hinzuzufügen wenn die krlib schon 1000+ Zeilen hat. Viel Spaß :p



  • @Headhunter
    meine Library arbeitet anders. Es gibt eine Klasse grafic, die initalisiert die Library und ersetzt die Buffer von cout und cin, durch die entsprechenden Buffer, die für die Umleitung zuständig sind und räumt im Destruktor diese auch wieder auf.

    Die anderen Library Funktionen/Klassen greifen auf das Handle mit dem Singleton Prinzip zu, deswegen wär das mit den einzelnen Funktionen kein Problem.

    Version 3 find ich aber ein bisschen schlecht, dass ist mir zu C mäßig. Sides Vorschlag ist irgend wie ganz vernünftig, ich sehe aber 2 Probleme

    1. der Programmierer neigt dazu ein globales grafic Object anzulegen
    2. der Programmierer könnte auf die Idee kommen mehrere grafic Objekte anzulegen

    Was haltet ihr von Version 2?



  • Eh, was macht das Schlüsselwort explicit ?? Kenn ich nich 🙂

    Was hälst du davon deine Funktionen statisch zu machen ? Also Zugriff auf die Elemente ohne eine Instanz davon anzulegen. Das hat den Vorteil, dass du in jeder Datei in der du die Grafikklasse benötigst auf export verzichten kannst.
    Außerdem würde das das Singletonprinzip überflüssig machen.

    Mit Sides Vorschlag kann ich leider nichts anfangen. Was meinst du mit basicscreen und pixelscreen ?



  • Alle Member statisch? Dann würde ich doch trotzdem mit dem Singleton Prinzip arbeiten müssen, da static Member ja nur andere static Member benutzen dürfen. Aber was soll an Singleton schlimm sein?

    explicit sorgt dafür, dass man den Konstruktor explizit aufrufen muss und der Compiler folgendes nicht mehr erlaubt

    pixel p=1;
    

    Side meint sicher so etwas

    class grafic
    {
      handle hnd;
    public:
      grafic() { create_hnd(hnd); }
      void putpixel(int x, int y, color col);
      //...
    }
    


  • Wo ist das Problem mit Static ?

    Mach's doch so :

    class CGraphic
    {
    public : 
    
         static void putpixel (int, int, color);
         static void setbg (color);
    
    private :
    
         static handle m_handle;
         static bool bereit;
         static int fensterx, fenstery;
    };
    

    Du brauchst wahrscheinlich eh nur eine Instanz von CGraphic. Es passt ganz gut dort alles static zu machen. Um zu verhindern dass jm eine Instanz deiner Klasse bildet, kannst du den Konstruktor ja private machen.

    So ähnlich wird das auch bei der Clanlib (www.clanlib.org, wie SDL nur in C++ && mehr highlevel) gemacht. Es ist einfach viel bequemer auf CL_Display::get_width() zuzugreifen anstatt auf g_display.get_width () 🙂



  • irgend wie mag ich das nicht so. Was hälst du den von Version2?



  • Könntest du deine Variante 2 nochmal was ausführlicher Erläutern ?
    Was machst du mit der Klasse point ?



  • Die Klassen beinhalten nur die Eigenschaften der grafischen Elemente.

    class point
    {
      int x_, y_;
      color col;
    public:
      explicit point(int x=0, int y=0, color c=0)
       : x_(x), y_(y), col(c) { }
      inline ~point() { }
      void paint(void) const;
      inline int x(void) const { return x_; }
      //...
    };
    

    Wenn man nun ein Punkt malen will, dann macht man das so:

    point pt(1,5,gruen);
    pt.paint();
    


  • Ich finde das extrem umständlich

    Stell dir vor du willst ein paar Punkte mehr zeichnen, für mich wäre das zuviel Schreibarbeit.

    Mh, wie wäre dies hier :

    class CGraphicalObject
    {
    protected
         //jedes objekt hat eine farbe
         color m_color;
    
         //linke obere Ecke des Objektes
         int m_x, m_y;
    
    public :
    
         //jedes objekt kann gezeichnet werden
         virtual void paint () = 0;
    };
    
    class CRect : public CGraphicalObject
    {
    private :
    
          //höhe und breite
          int m_h, m_w;
    
    public :
          virtual void paint (int x, int y, int w, int h, in color);
    };
    
    class CCircle : public CGraphicalObject 
    {
    /////
    }
    

    Mit diesem Ansatz hast du aber immer noch das Problem, dass es recht umständlich ist verschiedene Ausgabegeräte anzusprechen.

    Mein "Lieblingsansatz" ist der 9.te Post von oben, wo alles von der CABCGraphic abgeleitet wird. Im Prinzip muss deine Graphiclib nur Punkte zeichnen können. Aus Punkten kannst du dann alles zusammensetzen.
    Dann kannst du auch eine Klasse CRect, CCircle oder sonstwas bauen. Diese greifen auf ein -am Besten 100% statisches- von CABCGraphic abgeleitetes Objekt zu. D.h. deiner Zeichenklassen ist es Kackegal ob du mit OGL, SDL, Text oder sonstwas arbeitest.
    Darauf kommt es imo auch an : Versuche vor den Zeichenfunktionen die unterliegenden Ausgabegeräte zu verstecken und zu kapseln !



  • @kingruedi liege ich richtig? du hast http://www.c-plusplus.net/titel_21.htm noch nicht gelesen?
    das fliegen gewicht muster könnte passen und ein oder zwei ander muster(habe das buch jetzt nicht da)

    also was ich mir unter user freundlich vorstele ist

    int main()
    {
       pixel pix( kordinate( 10, 10 ), farbe( 255, 255, 255 ) );
       pix.draw()
       line lin( kordinate( 100, 100 ), kordinate( 200, 200 ), farbe( 255, 255, 255 ) );
    }
    

    überlege einfach wie würdes du diese zeichen arbeit am tisch machen und versuche das zu programmieren.
    eine funktion putpixel ist so als ob du mit ein stift zu einer kordinate gehst und ein punkt malst, wenn du jetzt den punkt in bewegng haben willst, muss du das blatt wegschmeissen und ein neuen pixel malen
    das wird bei vielen objekten ziemlich aufwändig.

    dagegen, ein pixel objekt ist so als ob du ein stück papier abschneidest und auf das blatt legst, du kannst es verschieben und co. (aber pixel ausschneiden ist aufwändiger als ein pixel mit den stift zu malen)

    [ Dieser Beitrag wurde am 19.01.2003 um 06:32 Uhr von Dimah editiert. ]



  • Ich denke der Ansatz ist zu Granular... das macht Sinn für eine Lib, die sich auf Vektorgrafiken abstützt. Aber wenn es pixelbasiert sein soll, dann arbeitet man so nicht.

    Schaut man sich andere Implementationen an, so wird regelmäßig als kleinstes Objekt eher auf der Ebene des Canvas mit der Klassenbildung begonnen - der Hintergrund ist doch klar:

    Wenn ich ein

    pix(400, 100, farbe(255, 128, 0)).draw();
    

    schreiben muß um einen Punkt zu zeichnen, dann wird das Objekt auf dem Stack angelegt, die Werte werden in Membervariablen umkopiert, intern dann die Zeichenfunktion aufgerufen und die Membervariablen vom Stack auf den Stack umkopiert und die eigentliche Grafikfunktion aufgerufen. Das klingt nicht schnell. Und sich darauf verlassen, daß der Compiler das schon durchoptimiert... da hätte ich Bauchschmerzen.

    Außerdem will ich gerne so ein Problem gelöst haben:

    pix(400, 100, farbe(255, 128, 0)).draw();
    line(200, 100).draw(); // soll die gleiche Farbe wie der Punkt haben!
    

    Macht man die Farbe zu einer Eigenschaft des Punktes, so verzerrt das etwas... eigentlich ist die Farbe eine Eigenschaft des aktuellen Zeichenstiftes. Also muß bei der Linienkonstruktion - da die Farbe wieder als Attribut der Linie auftaucht - die aktuelle Farbe ermittelt werden (woher? vom letzten Punkt?) und zwischengespeichert zu werden, um erneut verwendet zu werden im draw(). Der Informationsfluß ist zu aufwendig, zu viele Kopien.



  • hmm dann stimm ich für beides..

    in php habe ich es genossen, auch sowas machen zu können

    for ($x=0; $x<=$breite; $x++){
       for ($y=0; $y<=$breite; $y++){
           $fn =sqrt($x*$x +$y*$y);
           $rundung=$fn%$breite;
           imagesetpixel($bild,$x,$y,$farbe[$rundung]);
       }
    }
    

    also daß der letzte parameter, die farbe.. auch pixelweise gesetzt werden kann..
    damit farbverläufe herstellen ist nett.

    aber nur am rande



  • @Elise: was wäre in Deinem Beispiel der Unterschied dazu, die Farbe als Stifteigenschaft zu betrachten und den Stift am Canvas aufzuhängen? Ok, ich brauche dann zwei Aufrufe, aber es entstehen dadurch doch keine neuen Möglichkeiten?



  • Hi !

    Die Variante mit dem Stift klingt sehr vielversprechend. OpenGL mach das so ähnlich :

    glBegin (GL_QUADS); //Vierecke zeichnen
    
         glColor3f (1.0f,0.0f,0.0f); //rote Farbe setzen
    
         glVertex3f (....);//rechteck rendern, Farbe rot wird übernommen
         glVertex3f (....);
         glVertex3f (....);
         glVertex3f (....);
    
    glEnd (); //nix mehr zeichnen
    

    Das ist sehr praktisch, zumal man sich den Overhead spart die Stiftfarbe jedesmal per Parameter zu übergeben 🙂



  • menschlicher ist die stift idee..

    aber bei farbverläufen müsste ich aber immer den stift weglegen, einen neuen nehmen, punkt, stift weglegen einen neuen nehmen...

    aber die sache mit der linie, die die vorherige farbe hat, überzeugt ebenso..

    hm .. ich seh grad, in mfc ist es mal so mal so?

    CClientDC dc(this);     //Geraetekontext herstellen
    RECT rect;
    GetClientRect(&rect);
    
    //dc.FillRect(&rect, new CBrush(RGB (255,0,0)));  //hier würde ich einen stift nehmen
    
    for (int i=rect.left; i<rect.right; i++)
        for (int j= rect.top; j<rect.bottom; j++){
    
        dc.SetPixel(i, j, RGB(255,0,0));   //hier kann ich dem punkt direkt eine farbe zuweisen
    }
    

    ps: ich kann kein mfc 😉

    [ Dieser Beitrag wurde am 19.01.2003 um 10:50 Uhr von elise editiert. ]



  • Deswegen gibt's ja auch den Brush... weil Du einen Farbverlauf ja nicht mit einem Stift machen würdest. Naja, wenn man die Analogie so weit treibt.

    Da wünsche ich mir eher einen Brush, Größe 40 x 20, Startfarbe (0,0,0), Endfarbe(0,0,255), Farbverlauf linear, von oben-links nach unten-rechts. Brushobjekt fertig. [Gleichzeitig kann der Brush natürlich auch Texturen unterstützen, die aus einem File kommen.]

    Danach zeichne ich mit diesem Brush auf dem Canvas.

    Vermutlich kommt zum Schluß eine Zwitterlösung raus:

    Der Canvas bekommt einige elementare Zeichenroutinen (Pixel, Move, Line) verpasst, die er direkt als Methoden ausführt. Für komplexere Gebilde führt man ein Objekt-Interface ein, von dem man dann Kreise, Rechtecke etc ableiten kann und die auch z.B. farbige Punkte umfassen können.

    Aha, wenn ich darüber nachdenke kommt man auf den Unterschied zwischen Pixel und Punkt - ein Pixel ist eine Speicherstelle im Canvas, schon fast was Binäres. Das Setzen eines Pixels im Canvas eine elementare Zeichenoperation, schnell und direkt, Lowlevel. Ein Punkt ist aber bereits ein Objekt, das Informationen wie Position und Farbe umfasst. D.h. es gäbe durchaus die Berechtigung für Punkt-Klassen, solange man nicht gezwungen wird jedes grafische Element auf der Punkt-Klasse abzustützen.



  • Original erstellt von Marc++us:
    Da wünsche ich mir eher einen Brush, Größe 40 x 20, Startfarbe (0,0,0), Endfarbe(0,0,255), Farbverlauf linear, von oben-links nach unten-rechts. Brushobjekt fertig. [Gleichzeitig kann der Brush natürlich auch Texturen unterstützen, die aus einem File kommen.]

    Interessant fände ich auch sowas:

    Brush::Brush(Color (*fn)(int, int));
    

    Wobei die Paramater an fn immer relativ zur Ecke sind...



  • @Marc++us
    würde das deinem Ansatz entsprechen?

    class canvas
    {
      friend class pen;
      friend class brush;
      static hnd handler;
    public:
      canvas(int breite, int hoehe);
      //...
    };
    
    class pen
    {
    public:
      pen(color x);
      //...
      setpixel(int x, int y);
    };
    
    class brush
    {
    public:
      typedef color (*farb)(int,int);
      brush(color c, int breite, int hoehe);
      brush(const char *texture);
      brush(color start, color end, int breite, int hoehe);
      brush(farb c);
      //...
      move(int x0, int y0, int x1, int y1);
    };
    

    @Dimah
    Nein, das Buch habe ich noch nicht gelesen, steht aber schon in meiner "zu-lesen" Liste (die ist nur so unendlich lang und das Budget ist so unendlich klein 😞 )

    solange helf ich mir mit http://home.earthlink.net/~huston2/dp/patterns.html

    [ Dieser Beitrag wurde am 19.01.2003 um 12:31 Uhr von kingruedi editiert. ]

    [ Dieser Beitrag wurde am 19.01.2003 um 14:28 Uhr von kingruedi editiert. ]



  • und wie übersetzt man Farbverlauf ins englische?


Anmelden zum Antworten