Die libfullcircle stellt die Definition des Datenformats und einige C++-Tools bereit, um mit den Daten zu arbeiten. Die Bibliothek ist im Moment noch nicht vollstaendig, da sie nur das Speichern in Dateien unterstuetzt. Das Grundkonzept ist folgendes:
- Ein Frame beschreibt eine Seite von Lightbox-Farben. Der Frame hat eine festgelegte Dimension und legt für jeden Pixel fest, welche Farbe (RGB) er hat.
- Ein Sprite ist ein Frame mit schwarzem Hintergrund, auf dem eine (statische) Grafik in anderen Farben dargestellt ist.
- Eine Sequenz ist eine Abfolge von Frames (mit konstanter Dimension). Die Sequenz hat eine Framerate, d.h. die Sequenz legt fest, mit welcher Geschwindigkeit die Frames abgespielt werden.
- Animationen werden durch die Kombination von Sequenzen erstellt. Im Prinzip kann man sich das wie verschiedene Videoschnipsel in einer Video-Editing-Software vorstellen:
- Einzelne Sequenzen können aneinandergehängt werden (<<-Operator)
- Sequenzen können überlagert werden (+-Operator). Dabei werden die einzelnen Frames einfach addiert, d.h. ein schwarzer Pixel (RGB=0,0,0) bewirkt, dass der Pixel sich nach der Farbe des anderen Frames richtet. Wenn durch die Addition der Farbwert größer 255 wird, dann bleibt der Farbwert 255 (alles darüber wird quasi abgeschnitten).
- Sprites (oder Frames) werden bewegt, indem sie durch ein FrameDisplacement verschoben werden. Spezifikation fehlt noch. (TODO: MD)
Mitarbeit
Eure Mithilfe bei der Entwicklung der Bibliothek ist wichtig. Jeder mit einem Sesam-Account kann auch committen, *wenn er in der Gruppe git ist*. @Admins: Bitte darauf achten, die Leute in diese Gruppe zu stecken.
Damit die Benutzung der Bibliothek jederzeit möglich ist, bitte die folgenden Regeln beachten (battle-proven, machen wir in mySmartGrid auch so):
- Der Master-Branch ist der stabile Zweig der Entwicklung. Wenn man ein git clone macht, ist man in diesem Branch. *Hier bitte nichts committen!*
- Für den aktuellen Entwicklungs-Zwischenstand gibt es den branch "development". Nach einem git clone kann man den mit git checkout -t development auschecken.
- Jeder möge sich bitte für seine Entwicklung einen Entwicklungsbranch machen - Goniums ist md-dev.
Die einzelnen Entwicklungsbranches werden dann - wenn sie stabil sind - in development gemerged. Wenn development stabil ist, wird dieser Branch in Master gemerged.
Für alle Features der Basisbibliothek muss es Testcases geben! Die entsprechende Infrastruktur ist in 'tests' zu finden.
Offene Arbeitspakete
Die libfullcircle soll auch Routinen enthalten, um z.B. Text in einen Frame zu schreiben. Die folgenden Subseiten diskutieren diese Features:
- FullCircle/LibFullCircle/Sprites
- FullCircle/LibFullCircle/FontRendering kann die Sprite-Infrastruktur benutzen
- Einfache Operationen auf Sequenzen, die wiederum auf Operationen auf Frames basieren.
- <<-Operator (Anhängen) -> DONE
- Plus-Operator (Überlagern) -> [DONE --Ollo (Diskussion) 15:53, 16. Jun. 2012 (CEST)]
- Füllen eines Frames [DONE im branch development --Ollo (Diskussion) 18:07, 3. Jun. 2012 (CEST)]
- fill_whole(color:RGB_t)
- fill_fade_horizontal(from:RGB_t, to:RGB_t)
- fill_fade_vertical(from:RGB_t, to:RGB_t)
- Converter XPM
- Mehree XPM-Dateien in eine Sequenz konvertieren. -- Das würde ich auf Operationen auf Sequenzen bzw. Frames abbilden. --Gonium (Diskussion) 14:35, 6. Jun. 2012 (CEST)
- Generator für Perlin-Noise (http://www.flipcode.com/archives/Perlin_Noise_Class.shtml)
- ... (gerne hier Wünsche einfügen!)
In C++ muss man sich um die Speicherverwaltung selbst kümmern. Wenn ich also z.B. in einer Klasse ein Objekt erstelle und das Objekt nach außen gebe (z.B. als Return-Wert), dann ist nicht klar, wer jetzt der Owner des Objekts ist (sprich: Wer gibt den Speicher wieder frei? Das empfangende Objekt könnte dies tun, weiß aber nicht, ob das erzeugende Objekt das Return-Objekt noch braucht. Umgekehrt genauso.) Die Boost-shared_ptr sind ein Weg aus diesem Dilemma. Das ist ein Template, welches mitzählt, wieviele Objekte gerade Zugriff auf ein Objekt haben. Im fullcircle-Code sieht das so aus:
- Jede Klasse definiert einen shared_ptr auf sich selbst. Das sieht so aus:
class Frame {
public:
typedef std::tr1::shared_ptr<Frame> Ptr;
Man kann dann ein neues Objekt als shared_ptr anlegen, indem man folgendes macht:
fullcircle::Frame::Ptr frame(new fullcircle::Frame(10,5));
Das Objekt frame hat den Typ 'fullcircle::Frame::Ptr', ist also keine Instanz von 'fullcircle::Frame'. Es ist quasi der Wächter, welcher überwacht, wieviele Objekte eine Referenz auf das darunter liegende Frame-Objekt haben. Das Frame-Objekt 'fullcircle::Frame(10,5)' wird erzeugt und direkt als Konstruktor-Parameter dem Ptr übergeben. Das ist wichtig, weil so keine Memory-Allokationen am shared_ptr vorbei gemacht werden können. (Wenn das Spanisch ist: Einfach genau so benutzen.) Der Ptr liegt dabei immer auf dem Stack, d.h. wird innerhalb einer Methode angelegt oder als return-value irgendwo zwischengespeichert.
- Der shared_ptr verhält sich wie ein Pointer auf das Objekt, welches er bewacht. Um z.B. den Frame auf der Konsole auszugeben, benutze ich
frame->dump_frame(std::cout);
Der Pfeil-Operator auf dem Ptr-Objekt ist dabei so verbogen, dass ich die Methode 'dump_frame' auf dem zugrunde liegenden Objekt aufrufe.
- Das Memory-Management ist nun ganz einfach: Wenn der Ptr aus dem Scope läuft (z.B. weil die Methode verlassen wird), dann prüft der Destruktor des Ptr-Objekts, ob das Objekt noch sonstwo benutzt wird. Falls dies nicht der Fall ist, wird der Destruktor des zugrunde liegenden Objekts aufgerufen. Andernfalls hat ja sonst noch jemand einen Ptr auf das Objekt - es muss also weiter leben und wird nicht gelöscht.
- Wichtig: Ein Ptr wird immer kopiert, sonst kann das Reference Counting nicht funktionieren. Daher bitte nie eine const-Referenz oder so benutzen, um einen Ptr zurückzuliefern. Also: Immer
Frame::Ptr operator+(Frame::Ptr rhs);
sowas verwenden: Die RHS kommt als normale Kopie in die Methode rein, und der Return-Wert ist auch ein normaler Ptr.
Für mehr Infos und ein Beispiel empfehle ich den Artikel von Rüdiger Berlich.
Git-Repository
Liegt auf sesam.
$ git clone ssh://sesam/home/git/repo/libfullcircle.git
Installation
Die Software wurde auf Mac OS (10.7) sowie Ubuntu (12.04) uebersetzt. Sie benoetigt folgende Bibliotheken/Programme:
- cmake
- boost >= 1.48.0
- QT4
- Protocol Buffers (protobuf)
- hiredis
Das sind alles Dinge, die fuer fast alle Plattformen ueber einen Paketmanager installiert werden koennen. Ich habe derzeit allerdings nur Entwicklungsmaschinen, auf denen schon alles installiert ist. Aus dem Kopf heraus:
# Ubuntu sudo apt-get install libqt4-dev protobuf-compiler libprotobuf-dev libboost1.48-all-dev cmake build-essential libhiredis-dev
(bzw. mit Boost 1.49 direkt von den Quellen Diskussion:FullCircle/LibFullCircle) und
# Mac mit homebrew brew install qt4 protobuf boost hiredis
Bei Gonium ist installiert:
$ brew info boost boost 1.49.0 http://www.boost.org /usr/local/Cellar/boost/1.47.0 (8615 files, 306M) https://github.com/mxcl/homebrew/commits/master/Library/Formula/boost.rb
$ brew info qt qt 4.8.1 http://qt.nokia.com/ /usr/local/Cellar/qt/4.8.1 (2747 files, 194M) * https://github.com/mxcl/homebrew/commits/master/Library/Formula/qt.rb
$ brew info protobuf protobuf 2.4.1 http://code.google.com/p/protobuf/ /usr/local/Cellar/protobuf/2.4.1 (62 files, 5.9M) * https://github.com/mxcl/homebrew/commits/master/Library/Formula/protobuf.rb
$ brew info hiredis hiredis: stable 0.10.1, HEAD https://github.com/antirez/hiredis /usr/local/Cellar/hiredis/0.10.1 (10 files, 268K) * https://github.com/mxcl/homebrew/commits/master/Library/Formula/hiredis.rb
TODO: Bitte verifizieren und ggf. anpassen.
Der FullCircle/LibFullCircle/Buildvorgang unter Mac OS X nochmal zur Fehlersuche.
Nutzungsbeispiel anhand eines Inputmoduls
TODO: Kleines Beispiel-Inputmodul als separates Repository erstellen - dieses kann dann als Vorlage fuer die Input-Module benutzt werden.
In src/fc-simpledemo.cpp ist ein kleiner C++-Code zum Erstellen einer Sequenz abgelegt. Er kann mit
$ make
übersetzt und dann mit
$ ./build/src/fc-simpledemo -s sequence.fcs
aufgerufen werden. Dann legt er eine Sequenz von 100 Frames in sequence.fcs ab. Der Code, der die Sequenz erzeugt, sieht so aus:
fullcircle::RGB_t white; white.red = white.green = white.blue = 255; fullcircle::RGB_t red; red.red = 255; red.green = red.blue = 0; fullcircle::Sequence::Ptr seq(new fullcircle::Sequence(2,10,5)); for( uint32_t frameID = 0; frameID < 100; ++frameID) { fullcircle::Frame::Ptr frame(new fullcircle::Frame(10,5)); uint16_t xpos=frameID % 10; uint16_t ypos=frameID % 5; std::cout << " Frame " << frameID << " Xpos: " << xpos << " Ypos: " << ypos << std::endl; frame->set_pixel(xpos, ypos, red); frame->set_pixel(0, 0, white); seq->add_frame(frame); frame->dump_frame(std::cout); }
Die Sequenz kann dann mit
std::fstream output(sequence.c_str(), std::ios::out | std::ios::trunc | std::ios::binary); seq->save(output, "Testcase: check_sequence_storage", "current"); output.close();
gespeichert werden.