sporniket / ideas

My backlog of ideas, organized as a bunch of issues
0 stars 0 forks source link

[c++][assembly] Abstraction model of a "monochrome" (in effect dichrome, usually "black and white"), bitmap display #46

Open sporniket opened 2 months ago

sporniket commented 2 months ago

A display is a matrix of pixels, organised as a number W of vertical columns and a number of H horizontal rows, thus made of W×H pixels.

Each pixel is either "on"/1 or "off"/0 :

The model maintains an "active/selected" color to be used onwards for each operation, hereafter designated as "active color"

Each pixel is designated by a (x,y) coordinate :

Level 0 : Core set of primitives

setActiveColor(uint color)

following primitives will use this color, in effect pixels updated by a primitive will have the designated value.

drawHorizontalSegment(uint x, uint y, uint length)

e.g. : drawHorizontalSegment(0,5,10) will update 10 pixels, from pixel (0,5) to pixel (9,5).

drawVerticalSegment(uint x, uint y, uint length)

e.g. : drawHorizontalSegment(0,5,10) will update 10 pixels, from pixel (0,5) to pixel (0,14).

Level 1 : Surface optimisation

fillSurface(x, y, w, h)

e.g. : fillSurface(0,5,8,12) will update 96 pixels, from pixel (0,5) to pixel (7,16).

When there is no particular optimisation available, filling a surface will be done by drawing as much horizontal lines as required.

e.g. : fillSurface(0,5,8,12) will call drawHorizontalSegment(0,y,8)8 for y from 5 to 16.

sporniket commented 2 months ago

C++ typical code

#include <cstdint>

using ColorIndexForCanvas = uint8_t;
using CoordinateForCanvas = uint16_t;
using DimensionForCanvas = uint16_t;

enum class ReturnCodeOfCanvas {
    OK = 0,
    ERROR
};

class CanvasForMonochromePhysicalDisplay {
public:
    // Définit la couleur active
    virtual ReturnCodeOfCanvas setActiveColor(ColorIndexForCanvas c) = 0;

    // Récupère la couleur active
    virtual ColorIndexForCanvas getActiveColor() const = 0;

    // Dessine un segment horizontal à partir de (x, y) de longueur "length"
    virtual ReturnCodeOfCanvas drawHorizontalSegment(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas length) = 0;

    // Dessine un segment vertical à partir de (x, y) de longueur "length"
    virtual ReturnCodeOfCanvas drawVerticalSegment(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas length) = 0;

    // Remplit une surface rectangulaire avec les dimensions spécifiées
    virtual ReturnCodeOfCanvas fillSurface(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas w, DimensionForCanvas h) = 0;

    // Destructeur virtuel
    virtual ~CanvasForMonochromePhysicalDisplay() = default;
};

Sample, basic implementation (thanks chatGPT, to check)

#include <iostream>
#include <vector>

class MonochromeCanvas : public CanvasForMonochromePhysicalDisplay {
private:
    ColorIndexForCanvas activeColor;  // Couleur active
    std::vector<std::vector<ColorIndexForCanvas>> canvas;  // Surface de l'affichage (2D)

public:
    // Constructeur qui initialise la surface avec une taille spécifique (largeur et hauteur)
    MonochromeCanvas(DimensionForCanvas width, DimensionForCanvas height) 
        : activeColor(0), canvas(height, std::vector<ColorIndexForCanvas>(width, 0)) {}

    // Implémentation de setActiveColor
    ReturnCodeOfCanvas setActiveColor(ColorIndexForCanvas c) override {
        if (c > 1) {  // Seulement 0 ou 1 pour monochrome
            return ReturnCodeOfCanvas::ERROR;
        }
        activeColor = c;
        return ReturnCodeOfCanvas::OK;
    }

    // Implémentation de getActiveColor
    ColorIndexForCanvas getActiveColor() const override {
        return activeColor;
    }

    // Implémentation de drawHorizontalSegment
    ReturnCodeOfCanvas drawHorizontalSegment(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas length) override {
        if (y >= canvas.size() || x >= canvas[0].size() || x + length > canvas[0].size()) {
            return ReturnCodeOfCanvas::ERROR;
        }

        for (CoordinateForCanvas i = 0; i < length; ++i) {
            canvas[y][x + i] = activeColor;
        }
        return ReturnCodeOfCanvas::OK;
    }

    // Implémentation de drawVerticalSegment
    ReturnCodeOfCanvas drawVerticalSegment(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas length) override {
        if (x >= canvas[0].size() || y >= canvas.size() || y + length > canvas.size()) {
            return ReturnCodeOfCanvas::ERROR;
        }

        for (CoordinateForCanvas i = 0; i < length; ++i) {
            canvas[y + i][x] = activeColor;
        }
        return ReturnCodeOfCanvas::OK;
    }

    // Implémentation de fillSurface
    ReturnCodeOfCanvas fillSurface(CoordinateForCanvas x, CoordinateForCanvas y, DimensionForCanvas w, DimensionForCanvas h) override {
        if (x >= canvas[0].size() || y >= canvas.size() || x + w > canvas[0].size() || y + h > canvas.size()) {
            return ReturnCodeOfCanvas::ERROR;
        }

        for (CoordinateForCanvas i = 0; i < h; ++i) {
            for (CoordinateForCanvas j = 0; j < w; ++j) {
                canvas[y + i][x + j] = activeColor;
            }
        }
        return ReturnCodeOfCanvas::OK;
    }

    // Fonction d'affichage du canvas (pour tester visuellement)
    void displayCanvas() const {
        for (const auto& row : canvas) {
            for (auto pixel : row) {
                std::cout << (pixel == 1 ? '#' : '.') << " ";  // '#' pour 1 et '.' pour 0
            }
            std::cout << std::endl;
        }
    }
};

Sample code using the implementation :

int main() {
    MonochromeCanvas canvas(10, 5);  // Crée un canvas 10x5

    canvas.setActiveColor(1);  // Définit la couleur active sur 1 (blanc)
    canvas.drawHorizontalSegment(2, 1, 5);  // Dessine un segment horizontal
    canvas.drawVerticalSegment(7, 0, 4);    // Dessine un segment vertical
    canvas.fillSurface(0, 0, 2, 2);         // Remplit un rectangle en haut à gauche
    canvas.displayCanvas();  // Affiche le canvas

    return 0;
}

Expected :

# # . . . . . # . . 
# # # # # # # # . . 
. . . . . . . # . . 
. . . . . . . # . . 
. . . . . . . . . . 
sporniket commented 2 months ago

Naming of concrete memory area

The naming will describe the properties of the storage of the pixels (format and organisation) : Bitmap_<format>_<pixel_order>_<bit_width_and_ordering_of_storage>

Example : Bitmap_i1i_ltr_8msb

Pixel format

i/rgb + bits per channel(s) + i/p

With the following meaning :

Examples

Pixel order

ltr or ttb.

Bit width and ordering of storage

Bit width will be a multiple of 8 (8, 16, 32, ...). For interleaved pixel format, it will represent the size of the set of pixels described together (e.g. 16 on an Atari ST)

Bit ordering will be :

Example

Complete examples

sporniket commented 2 months ago

Model of a memory area

Ported here, to be verified : https://github.com/sporniket/esp32-idf-experiment-spi-st7789/commit/f7885879e5c7dd09a17fecc3f142c138d94eb3a1

Généré avec chatgpt.

#include <cstdint>
#include <iostream>

class MemoryArea {
public:
    // Constructeur avec paramètres
    MemoryArea(uint8_t* start, uint32_t length)
        : start(start), length(length), end(start + length) {}

    // Destructeur
    ~MemoryArea() = default;

    // Méthode pour afficher les informations
    void displayInfo() const {
        std::cout << "Start address: " << static_cast<void*>(start)
                  << "\nLength: " << length << " bytes"
                  << "\nEnd address: " << static_cast<void*>(end) << std::endl;
    }

    // Accesseur pour start
    uint8_t* getStart() const { return start; }

    // Accesseur pour length
    uint32_t getLength() const { return length; }

    // Accesseur pour l'adresse de fin
    uint8_t* getEnd() const { return end; }

private:
    const uint8_t* start;  // Pointeur immuable vers le début de la zone mémoire
    const uint32_t length; // Taille immuable de la zone mémoire en octets
    const uint8_t* end;    // Adresse immuable de fin calculée et stockée à la construction
};

int main() {
    // Exemple d'utilisation
    uint8_t memory[100];
    MemoryArea area(memory, sizeof(memory));

    area.displayInfo();

    return 0;
}
sporniket commented 2 months ago

Ported/amended here : https://github.com/sporniket/esp32-idf-experiment-spi-st7789/commit/8367d12fa926be49904d737da4be1b626ed79635

Model of pixel format

generated by chatgpt

#include <cstdint>
#include <iostream>
#include <variant>

// Énumération pour le type de format de pixel
enum class PixelType {
    Indexed,
    RGB
};

// Énumération pour l'organisation des canaux
enum class PixelLayout {
    Interleaved,
    Progressive
};

// Classe pour les formats indexés (avec largeur d'index) - Immuable
class IndexedFormat {
public:
    // Constructeur pour initialiser l'indexWidth
    IndexedFormat(uint8_t indexWidth) : indexWidth(indexWidth) {}

    // Accesseur pour l'indexWidth
    uint8_t getIndexWidth() const { return indexWidth; }

private:
    const uint8_t indexWidth;  // Largeur de l'index en bits (const)
};

// Classe pour les formats RGB (avec largeur des canaux rouge, vert et bleu) - Immuable
class RGBFormat {
public:
    // Constructeur pour initialiser les largeurs des canaux
    RGBFormat(uint8_t redWidth, uint8_t greenWidth, uint8_t blueWidth)
        : redWidth(redWidth), greenWidth(greenWidth), blueWidth(blueWidth) {}

    // Accesseurs pour redWidth, greenWidth et blueWidth
    uint8_t getRedWidth() const { return redWidth; }
    uint8_t getGreenWidth() const { return greenWidth; }
    uint8_t getBlueWidth() const { return blueWidth; }

private:
    const uint8_t redWidth;    // Largeur du canal rouge en bits (const)
    const uint8_t greenWidth;  // Largeur du canal vert en bits (const)
    const uint8_t blueWidth;   // Largeur du canal bleu en bits (const)
};

// Classe PixelFormat
class PixelFormat {
public:
    // Constructeur pour le format indexé
    PixelFormat(uint8_t indexWidth, PixelLayout layout)
        : type(PixelType::Indexed), layout(layout), format(IndexedFormat(indexWidth)) {}

    // Constructeur pour le format RGB (le layout est toujours "progressive" pour RGB)
    PixelFormat(uint8_t redWidth, uint8_t greenWidth, uint8_t blueWidth)
        : type(PixelType::RGB), layout(PixelLayout::Progressive), format(RGBFormat(redWidth, greenWidth, blueWidth)) {}

    // Accesseur pour le type de pixel
    PixelType getType() const { return type; }

    // Accesseur pour l'organisation des canaux
    PixelLayout getLayout() const { return layout; }

    // Accesseur pour le format
    const std::variant<IndexedFormat, RGBFormat>& getFormat() const { return format; }

    // Méthode pour afficher les informations du format
    void displayInfo() const {
        std::cout << "Pixel Type: " << (type == PixelType::Indexed ? "Indexed" : "RGB") << "\n";
        if (type == PixelType::Indexed) {
            const IndexedFormat& indexed = std::get<IndexedFormat>(format);
            std::cout << "Index Width: " << static_cast<int>(indexed.getIndexWidth()) << " bits\n";
        } else if (type == PixelType::RGB) {
            const RGBFormat& rgb = std::get<RGBFormat>(format);
            std::cout << "Red Width: " << static_cast<int>(rgb.getRedWidth()) << " bits\n"
                      << "Green Width: " << static_cast<int>(rgb.getGreenWidth()) << " bits\n"
                      << "Blue Width: " << static_cast<int>(rgb.getBlueWidth()) << " bits\n";
        }
        std::cout << "Layout: " << (layout == PixelLayout::Interleaved ? "Interleaved" : "Progressive") << "\n";
    }

private:
    const PixelType type;                                  // Type de pixel (Indexed ou RGB)
    const PixelLayout layout;                              // Organisation des canaux (Interleaved ou Progressive)
    const std::variant<IndexedFormat, RGBFormat> format;   // Format spécifique (indexé ou RGB) - immuable
};

int main() {
    // Exemple d'utilisation
    PixelFormat indexedFormat(8, PixelLayout::Interleaved);  // Format indexé avec 8 bits
    indexedFormat.displayInfo();

    std::cout << "\n";

    PixelFormat rgbFormat(8, 8, 8);  // Format RGB avec 8 bits par canal
    rgbFormat.displayInfo();

    // Accéder au format spécifique
    if (rgbFormat.getType() == PixelType::RGB) {
        const RGBFormat& rgb = std::get<RGBFormat>(rgbFormat.getFormat());
        std::cout << "Accessing RGB format: Red Width = " << static_cast<int>(rgb.getRedWidth()) << "\n";
    }

    return 0;
}
sporniket commented 1 month ago

Model of an outpstream to a byte array with big endian orientation

Not needed for the model, but for sending command data to the display.

Ported (slight renaming) with test suite (to be effectively run) : https://github.com/sporniket/esp32-idf-experiment-spi-st7789/commit/f7885879e5c7dd09a17fecc3f142c138d94eb3a1

The test suite to verify :


#include <cstdint>
#include <cassert>

class MemoryArea {
    public:
    // Constructeur avec paramètres
    MemoryArea(uint8_t *start, uint32_t length) : start(start), length(length), end(start + length) {}

    // Destructeur
    ~MemoryArea() = default;

    // Accesseur pour start
    uint8_t *getStart() const { return start; }

    // Accesseur pour length
    uint32_t getLength() const { return length; }

    // Accesseur pour l'adresse de fin
    uint8_t *getEnd() const { return end; }

    private:
    uint8_t *const start;  // Pointeur immuable vers le début de la zone mémoire
    const uint32_t length; // Taille immuable de la zone mémoire en octets
    uint8_t *const end;    // Adresse immuable de fin calculée et stockée à la construction
};

uint8_t bufferRaw[5]
MemoryArea buffer(buffer, sizeof(buffer))

ByteArrayOutputStream ostream(buffer) ;
uint8_t one = 1 ;
uint16_t two = 2 ;
uint32_t three = 3 ;

ostream << one << two ;
assert((ostream.status() == ByteArrayOutputStreamStatus::E_OK))
assert((ostream.length() == 3))
assert((1 == bufferRaw[0]))
assert((0 == bufferRaw[1]))
assert((2 == bufferRaw[2]))

ostream << three ;
assert((ostream.status() == ByteArrayOutputStreamStatus::E_KO__OUT_OF_BOUND));
assert((ostream.length() == 3));

ostream.reset() ;
assert((ostream.status() == ByteArrayOutputStreamStatus::E_OK))
assert((ostream.length() == 0))

ostream << three ;
assert((ostream.status() == ByteArrayOutputStreamStatus::E_OK))
assert((ostream.length() == 4))
assert((0 == bufferRaw[0]))
assert((0 == bufferRaw[1]))
assert((0 == bufferRaw[2]))
assert((3 == bufferRaw[3]))

from chatgpt :

#include <cstdint>
#include <iostream>

enum class ByteArrayOutputStreamStatus {
    E_OK,
    E_KO__OUT_OF_BOUND
};

class ByteArrayOutputStream {
public:
    ByteArrayOutputStream(MemoryArea &area) 
        : area(area), position(area.getStart()), status(ByteArrayOutputStreamStatus::E_OK), length(0) {}

    ByteArrayOutputStreamStatus getStatus() const {
        return status;
    }

    uint32_t getLength() const {
        return length;
    }

    void reset() {
        position = area.getStart();
        status = ByteArrayOutputStreamStatus::E_OK;
        length = 0;
    }

    template <typename T>
    ByteArrayOutputStream& operator<<(const T& value) {
        uint8_t numBytes = sizeof(T);

        if (position + numBytes > area.getEnd()) {
            status = ByteArrayOutputStreamStatus::E_KO__OUT_OF_BOUND;
            return *this;
        }

        // Copier les octets en big-endian (octet le plus significatif en premier)
        for (int i = numBytes - 1; i >= 0; --i) {
            *position++ = (value >> (i * 8)) & 0xFF;
        }

        length += numBytes;
        return *this;
    }

private:
    MemoryArea &area;
    uint8_t *position;
    ByteArrayOutputStreamStatus status;
    uint32_t length;
};