Fragen zu Windows Bitmap einlesen #2



  • Jo danke dann 🙂

    Und gute Idee, mal schauen ob man nach top-down BMPs goglen kannn, bzw was findet.



  • Super, hatte mehrere top-down Beispiele gefunden und bei allen wurde der korrekte Wert negativ angezeigt. Danke nochmals.



  • Na super 🙂

    Nochwas...
    Ich würde folgende Änderungen vorschlagen:

    int32_t make_int32 (std::vector <unsigned char> bmp_data, std::size_t pos)
    {
        uint32_t u_val = bmp_data.at(pos + 0)
                        + (static_cast <uint32_t> (bmp_data.at(pos + 1)) << 8)
                        + (static_cast <uint32_t> (bmp_data.at(pos + 2)) << 16)
                        + (static_cast <uint32_t> (bmp_data.at(pos + 3)) << 24);
    
        int32_t s_val;
        if ((u_val >> 31) & 1)
        {
            s_val = - static_cast <int32_t> (~u_val) - 1;
        }
        else
        {
            s_val = u_val;
        }
    
        return s_val;
    }
    
    // =>
    
    int32_t better_make_int32 (std::vector <unsigned char> const& /* 1 */ bmp_data, std::size_t pos)
    {
        uint32_t const /* 2 */ u_val = /* 3 */ make_uint32(bmp_data, pos);
    
        if ((u_val >> 31) & 1)
        {
            return - static_cast <int32_t> (~u_val) - 1; /* 4 */
        }
        else
        {
            return u_val;                                /* 4 */
        }
    }
    

    1: Du solltest nicht den ganzen std::vector kopieren. Das ist sogar im günstigsten Fall (wenig Daten) schon viel langsamer als nötig (dynamische Speicheranforderung!). Und wenn mal viel Daten drinnen wird's richtig langsam.

    2: Lokale Variablen const machen wenn man sie (einfach, ohne grosse Umwege) const machen kann. Damit man gleich sieht dass der Wert der Variable sich nach der Initialisierung nie mehr ändert.

    3: Code-Duplizierung vermeiden.

    4: Mutable-State vermeiden, uninitialisierte Variablen vermeiden.
    Uninitialisierte Variablen vermeiden allerdings nur, wenn man ihnen gleich bei der Initialisierung einen sinnvollen Wert geben kann. Wenn wirklich nur die Wahl besteht zwischen "uninitialisiert" oder "mit Dummy-Wert initialisiert", dann eher uninitialisiert. Meist kann man aber gleich mit dem passenden Wert initialisieren.

    Einige dieser Dinge (z.B. (1)) betreffen auch andere Funktionen, dort natürlich ebenso nachziehen.

    Und wie bei allen Richtlinien gibt es natürlich Ausnahmen. Viel Aufwand zu betreiben um eine dieser Regeln zu befolgen ist nicht immer sinnvoll.

    ----

    Und ich würde vorschlagen die Leerzeichen vor öffnenden Klammern und nach schliessenden Klammern weg zu lassen. Ausnahme: zwischen Keywords und runde Klammer kommt ein Leerzeichen
    Also z.B.

    int x = MeineFunktion(param); // nach MeineFunktion nicht
    for (int i = 0; i < x; i++)   // nach for schon
    {
        if (static_cast<int32_t>(i * i) < x)  // nach if auch, nach static_cast aber nicht
    

    usw.

    Das, also wo bei Klammern Leerzeichen gesetzt werden, ist nämlich eine der wenigen Regeln wo sich die meisten Programmierer einig sind. Zumindest ist das mein Eindruck anhand des Codes den ich so sehe.

    EDIT: Copy/Paste/Edit Fehler korrigiert.



  • Und wenn wir schon dabei sind, gleich nochmal weiter:

    int32_t int32_from_uint32(uint32_t u_val)
    {
        if ((u_val >> 31) & 1)
            return -static_cast<int32_t>(~u_val) - 1;
        else
            return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const& bmp_data, std::size_t pos)
    {
        return int32_from_uint32(make_uint32(bmp_data, pos));
    }
    

    EDIT: Copy/Paste/Edit Fehler korrigiert.



  • Gleichheitszeichen zwischen return und der Variablen finde ich wiederum merkwürdig - mein Compiler auch...



  • war bestimmt nur ein Tippfehler ...

    Ok, danke für die Hinweise. Wenn ich richtig verstanden habe, müssten die drei Funktionen dann so aussehen?:

    uint16_t make_uint16(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint16_t const u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint16_t>(bmp_data.at(pos + 1)) << 8);
    
        return u_val;
    }
    
    uint32_t make_uint32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint32_t const u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 1)) << 8)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 2)) << 16)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 3)) << 24);
    
        return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint32_t const u_val = make_uint32 (bmp_data, pos);
    
        if ((u_val >> 31) & 1)
        {
            return -static_cast<int32_t>(~u_val) - 1;
        }
        else
        {
            return u_val;
        }
    }
    

    Nur das doppelt ausschreiben in Deinem letzten Beitrag habe ich nicht ganz verstanden?

    ---

    Ich hoffe, das wird jetzt nicht zuviel, aber ich hätte noch ein anderes Anliegen. Und zwar bin ich mit meiner switch-Lösung nicht ganz zufrieden. Ich würde lieber einen Funktionszeiger nehmen, bekomme das aber nicht hin.

    Vorgestellt habe ich mir, das hier der second-Wert das Argument für den Zeiger sein soll. Welcher Typ das sein muss, ist mir unklar.

    struct Head //Kopf
    {
        static const auto Size = 4;
        std::array <std::pair <std::string, int>, Size> bType
        {
            {
                { "bfType", 1 }, //uint16_t
                { "bfSize", 2 }, //uint32_t
                { "bfReserved", 2 },
                { "bfOffbits", 2 }
            }
        };
        std::array <int, Size> data;
    
    } head;
    
    //int (*make_data)(std::vector <unsigned char>, std::size_t);
    

    Unklar ist mir auch, wie eine Funktion mit diesem Argument dann aufgerufen wird. Ich hatte bisher nur einmal mit Funktionszeigern zu tun, mir fehlt die Übung und ich kann den ersten Einsatz nicht für diesen Fall nachvollziehen.



  • wob schrieb:

    Gleichheitszeichen zwischen return und der Variablen finde ich wiederum merkwürdig - mein Compiler auch...

    Danke, hab's korrigiert.



  • lemon03 schrieb:

    (...)müssten die drei Funktionen dann so aussehen?:

    uint16_t make_uint16(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint16_t const u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint16_t>(bmp_data.at(pos + 1)) << 8);
    
        return u_val;
    }
    
    uint32_t make_uint32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint32_t const u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 1)) << 8)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 2)) << 16)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 3)) << 24);
    
        return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        uint32_t const u_val = make_uint32 (bmp_data, pos);
    
        if ((u_val >> 31) & 1)
        {
            return -static_cast<int32_t>(~u_val) - 1;
        }
        else
        {
            return u_val;
        }
    }
    

    Sieht gut aus würde ich sagen.

    lemon03 schrieb:

    Nur das doppelt ausschreiben in Deinem letzten Beitrag habe ich nicht ganz verstanden?

    Keine ahnung was du meinst. Steh vermutlich grad auf dem Schlauch 🙂



  • Danke. Meinte dies:

    int32_t int32_from_uint32(uint32_t u_val)
    {
        if ((u_val >> 31) & 1)
            return -static_cast<int32_t>(~u_val) - 1;
        else
            return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const& bmp_data, std::size_t pos)
    {
        return int32_from_uint32(make_uint32(bmp_data, pos));
    }
    

    Mir scheint das da zwei Funktionsteile genommen und daraus zwei Funktionen gemacht wurden?



  • Ja, genau. Und?



  • Dann würde aus

    uint32_t make_uint32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint32_t u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 1)) << 8)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 2)) << 16)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 3)) << 24);
    
        return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint32_t u_val = make_uint32(bmp_data, pos);
        if ((u_val >> 31) & 1)
        {
            return -static_cast<int32_t>(~u_val) - 1;
        }
        else
        {
            return u_val;
        }
    }
    

    -->

    uint32_t make_uint32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint32_t u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 1)) << 8)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 2)) << 16)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 3)) << 24);
    
        return u_val;
    }
    
    int32_t int32_from_uint32(uint32_t u_val)
    {
        if ((u_val >> 31) & 1)
            return -static_cast<int32_t>(~u_val) - 1;
        else
            return u_val;
    }
    
    int32_t make_int32(std::vector <unsigned char> const& bmp_data, std::size_t pos)
    {
        return int32_from_uint32(make_uint32(bmp_data, pos));
    }
    

    Die Aufsplittung auf zwei Funktionen verstehe ich vom Nutzen her nicht. Da die einzelnen Teile nirgendwo als in dieser Funktion gebraucht werden, finde ich das irgendwie überflüssig?



  • lemon03 schrieb:

    Die Aufsplittung auf zwei Funktionen verstehe ich vom Nutzen her nicht. Da die einzelnen Teile nirgendwo als in dieser Funktion gebraucht werden, finde ich das irgendwie überflüssig?

    Der Code wird übersichtlicher und du kannst die einzelnen Funktionen einfacher testen. Eine Funktion sollte für genau eine Sache verantwortlich sein, diese darf gerne klein sein.



  • Das mag allgemein so stimmen, aber doch im obigen Fall nicht?

    Der erst Block mit zwei Funktionen ist doch leichter auf einen Blick zu erfassen, als der untere mit drei verschachtelten Funktionen?

    Und im Programm werden drei Werte durch drei ganz einfache Funktionen bestimmt, warum sollte man diese drei einfachen Funktionen auf vier aufsplitten?



  • wob schrieb:

    und du kannst die einzelnen Funktionen einfacher testen.

    Da die oberen Funktionen alle voneinander abhängig sind, kann man doch gar nicht die einzelnen Funktionen testen, ohne auch die anderen zu testen?

    Also warum nicht nur eine Funktion testen, statt zwei?



  • Also sorry, wollte nicht aufmüpfig erscheinen. Bin ja froh, wenn man mir hilft und erklärt.

    Ich würde gerne noch mal auf den Funktionszeiger zurück kommen, ob das hier möglich und sinnvoll ist. Vorgestellt habe ich mir das mit sozusagen Pseudocode etwa so:

    int (*make_dec)(std::vector <unsigned char> const, std::size_t); //hier const richtig?
    
    uint16_t m_uint16(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint16_t u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint16_t>(bmp_data.at(pos + 1)) << 8);
    
        return u_val;
    }
    
    uint32_t m_uint32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint32_t u_val = bmp_data.at(pos + 0)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 1)) << 8)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 2)) << 16)
                        + (static_cast<uint32_t>(bmp_data.at(pos + 3)) << 24);
    
        return u_val;
    }
    
    int32_t m_int32(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    {
        const uint32_t u_val = make_uint32(bmp_data, pos);
        if ((u_val >> 31) & 1)
        {
            return -static_cast<int32_t>(~u_val) - 1;
        }
        else
        {
            return u_val;
        }
    }
    
    struct Head //Kopf
    {
        static const auto Size = 4;
        std::array <std::pair <std::string, |Typ?|>, Size> bType
        {
            {
                { "bfType",     m_uint16 }, //uint16_t
                { "bfSize",     m_uint32 }, //uint32_t
                { "bfReserved", m_uint32 },
                { "bfOffbits",  m_uint32 }
            }
        };
        std::array <int, Size> data;
    
    } head;
    
    head.data.at(i) = Funktionsaufruf mit <head.bType.at(i).second>;
    

    Ich weiß leider nicht, wie ich das korrekt ausformuliere.



  • lemon03 schrieb:

    Da die oberen Funktionen alle voneinander abhängig sind, kann man doch gar nicht die einzelnen Funktionen testen, ohne auch die anderen zu testen?

    Naja die sind eben nicht voneinander abhängig. (Also schon die eine von der anderen, aber nicht die andere von der einen.)
    Wie man aus einem uint32_t mit nem 2s complement Bitmuster eines signed Wertes einen int32_t (mit diesem signed Wert) macht, und zwar plattformunabhängig und so dass per C++ Standard garantiert das Ergebnis passt, ist nicht davon abhängig wo man dieses Bitmuster her hat.
    D.h. die uint -> int Funktion kann man super testen.

    Weiters könnte man sie in ein Template umbauen, so dass sie für alle Bitbreiten funktioniert. Dann kann man sie in anderen make_intXX Funktionen wiederverwenden. Bzw. allgemein könnte es Stellen geben wo man die selbe Funktion braucht.

    Und es wird leichter dadurch zu lesen. z.B. schonmal dadurch dass die Funktion jetzt einen Namen hat. Wenn man diesen gut wählt (und nicht so wie du kryptische Abkürzungen wie "m_uint32" verwendet ;)), dann sagt alleine der Name schon was die Funktion macht. Wenn ich nur verstehen will was der Code macht brauche ich mir also nichtmal ansehen wie die Funktion implementiert ist. (Das müsste ich nur, wenn ich überprüfen will ob die Implementierung korrekt ist. Und auch da ist es besser viele kleinere Teile zu haben, da man die Funktion einer Funktion oder Klasse viel leichter überprüfen kann wenn diese klein ist und nur wenig tut.)

    ps:

    lemon03 schrieb:

    Ich würde gerne noch mal auf den Funktionszeiger zurück kommen,

    Jo, sorry. Dafür brauch ich ne ruhige Minute (die ich jetzt nicht habe und auch gestern nicht hatte) 🙂



  • hustbaer schrieb:

    Jo, sorry. Dafür brauch ich ne ruhige Minute (die ich jetzt nicht habe und auch gestern nicht hatte) 🙂

    Kein Ding 😉 Und danke nochmals für die Erklärung.



  • lemon03 schrieb:

    Also sorry, wollte nicht aufmüpfig erscheinen.

    Tust du nicht! Höchstens eigenwillig, aber das ist bis zu einem bestimmten Grad würd' ich mal sagen sogar was gutes. Alles ohne nachzufragen einfach zu akzeptieren wäre nicht unbedingt ein Zeichen von Intelligenz 😉

    lemon03 schrieb:

    Ich würde gerne noch mal auf den Funktionszeiger zurück kommen, ob das hier möglich und sinnvoll ist. Vorgestellt habe ich mir das mit sozusagen Pseudocode etwa so:

    int (*make_dec)(std::vector <unsigned char> const, std::size_t); //hier const richtig?
    
    uint16_t m_uint16(std::vector <unsigned char> const &bmp_data, std::size_t pos)
    ...
    

    Das const ist schon richtig, aber das "&" fehlt noch - das gehört ja schliesslich mit zum Typ. Weswegen ich es auch immer Type& name schreibe und nicht Type &name wie du 😉
    =>
    int (*make_dec)(std::vector <unsigned char> const&, std::size_t);

    Nur bringt dir das nicht viel. Damit deklarierst du eine Variable namens "make_dec" die den Typ hat: Zeiger auf: Funktion mit Signatur:
    int fun(std::vector <unsigned char> const&, std::size_t);

    Du brauchst aber nicht eine solche Variable, du brauchst den Typ. Damit du ihn später in dem pair<> verwenden kannst. Also z.B. einfach:

    typedef int (*make_int_function_ptr)(std::vector <unsigned char> const&, std::size_t);
    

    lemon03 schrieb:

    struct Head //Kopf
    {
        static const auto Size = 4;
        std::array <std::pair <std::string, |Typ?|>, Size> bType
        {
            {
                { "bfType",     m_uint16 }, //uint16_t
                { "bfSize",     m_uint32 }, //uint32_t
                { "bfReserved", m_uint32 },
                { "bfOffbits",  m_uint32 }
            }
        };
        std::array <int, Size> data;
    
    } head;
    

    Also |Typ?| hier ist einfach: make_int_function_ptr .
    Allerdings stimmt der Typ von &m_uint16 etc. nicht mit make_int_function_ptr überein (unterschiedlicher Returnwert).
    Also Hilfsfunktionen.
    Oder evtl. mit Lambdas:

    struct Head //Kopf
    {
    	static const auto Size = 4;
    
    	std::array<std::pair<std::string, make_int_function_ptr>, Size> bType =
    	{
    		{
    			{ "bfType",     [](std::vector<unsigned char> const& d, size_t p) -> int { return m_uint16(d, p); } },
    			{ "bfSize",     [](std::vector<unsigned char> const& d, size_t p) -> int { return m_uint32(d, p); } },
    			{ "bfReserved", [](std::vector<unsigned char> const& d, size_t p) -> int { return m_uint32(d, p); } },
    			{ "bfOffbits",  [](std::vector<unsigned char> const& d, size_t p) -> int { return m_uint32(d, p); } },
    		}
    	};
    
    	std::array <int, Size> data;
    
    } head;
    

    lemon03 schrieb:

    head.data.at(i) = Funktionsaufruf mit <head.bType.at(i).second>;
    

    Das wäre dann

    head.data.at(i) = head.bType[i].second(theVectorWithTheData, theOffsetInThatVector);
    

    Aber merkst du 'was? Woher willst du theOffsetInThatVector nehmen?

    Also vielleicht doch nicht so der optimale Ansatz.
    Natürlich kann man das auch lösen - gibt mehrere Varianten wie man das ohne all zu grosse Änderungen umbauen kann so dass der Offset verfügbar ist.

    Die Frage ist aber, ob das den ganzen Aufwand wert ist?

    Wieso nicht einfach eine Funktion der du nen vector oder auch einfach nen Zeiger auf die unsigned char s übergibst, und die dir ne BitmapHeader struct zurückgibt? Und einfach die ganzen make_this , make_that Funktionen der Reihe nach aufruft um das zu machen. Wäre vermutlich viel einfacher.



  • ps:
    Einfacher Trick wenn man die Function-Pointer Syntax nicht mag:

    typedef int make_int_function(std::vector <unsigned char> const&, std::size_t);
    typedef make_int_function* make_int_function_ptr;
    

    (Oder alternativ zum 2. typedef dann einfach überall make_int_function* statt make_int_function_ptr schreiben.)



  • Aye, na klasse für die ausführlichen Anmerkungen.

    Zum const& , dies war auch mein erster Impuls, weil mir das in dem Sinn logisch erschien, wollte dann aber nachfragen. Ich schreibe das sonst immer so, weil ich den Operator(?) sonst nur von Referenzen kannte. Also bei

    void func(int &val)
        { val ++; }
    

    wäre es doch der Wert und nicht der Typ, der per Referenz übergeben wird?

    Zu dem Problem mit dem offset als Übergabe musste ich mir schon Gedanken machen, weil es noch mehrere Notwendigkeiten im Code geben wird, den aktuellen offset zu kennen.
    Dies wollte ich durch einen std::tuple lösen. Nur habe ich noch Probleme dieses in ein array zu packen. Vorgestellt hatte ich mir das so:

    struct Head //Kopf
    {
        static const auto Size = 4;
        std::array <std::tuple <std::string, |Typ?|, int>, Size> bType;
        std::make_tuple //??
        {
            {
                { "bfType",     m_uint16, 2 }, //uint16_t //anzahl bytes
                { "bfSize",     m_uint32, 4 }, //uint32_t
                { "bfReserved", m_uint32, 4 },
                { "bfOffbits",  m_uint32, 4 }
            }
        };
        std::array <int, Size> data;
    
    } head;
    

    Der aktuelle offset wäre dann die vorherigen addierten Werte. Oder gleich so:

    struct Head //Kopf
    {
        static const auto Size = 4;
        std::array <std::tuple <std::size_t, std::string, |Typ?|>, Size> bType;
        std::make_tuple //??
        {
            {
                { 0,  "bfType",     m_uint16 }, //offset//name//uint16_t
                { 2,  "bfSize",     m_uint32 }, //uint32_t
                { 6,  "bfReserved", m_uint32 },
                { 10, "bfOffbits",  m_uint32 }
            }
        };
        std::array <int, Size> data;
    
    } head;
    

    Das zweite wäre natürlich einfacher.

    Zu dem Funktionszeiger selbst, den muss ich erst mal sacken lassen 😉 Allerdings scheint Deine letzte Idee mit

    und die dir ne BitmapHeader struct zurückgibt?

    ganz interessant. Kannst Du mir zeigen, wie in etwas Du dies gemeint hast?


Anmelden zum Antworten