Open sporniket opened 2 months ago
#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 :
# # . . . . . # . .
# # # # # # # # . .
. . . . . . . # . .
. . . . . . . # . .
. . . . . . . . . .
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
i
/rgb
+ bits per channel(s) + i
/p
With the following meaning :
i
/rgb
: i
for indexed colors (0 to n) ; rgb
for direct encoding with Red, Green and Blue channels.i
/p
: only for indexed colors i
for interleaved (a set of pixels are described by destructuring the bits and regrouping them by rank, e.g. all the MSB of each pixel first, then the less MSB bits etc) ; p
for progressive (each pixel is fully described by an integer number of bytes, a byte contains only data for a single pixel). rgb colors are implicitely progressivei1i
: monochrome (2 indexed colors) interleaved, a byte contains 8 pixels.i2i
: e.g. on the Atari ST medium resolution (640×200 pixels, 4 indexed colors), pixels are described by groups of 16, with all the LSB forming the first 2 bytes (pixel 0 is the MSB), followed by the 16 MSBs in the last 2 bytes.i4p
: 16 indexed colors, each pixel would be stored in a byte value (at least)rgb332p
: direct encoding of the colors with a 3-bits value (8 levels) for the Red channel, a 3-bits value (8 levels) for the Green channel, and a 2-bits value (4 levels) for the Blue channel. The 8 bits are stored in the same byte.rgb888p
: the classical direct encoding using 24 bits per pixels.ltr
or ttb
.
ltr
: Left To Right ; pixels (or pixel groups for interleaved format) are stored in memory line by line, from left to right.ttb
: Top To Bottom ; pixels (or pixel groups for interleaved format) are stored in memory column by column, from top to bottomBit 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 :
msb
/lsb
for interleaved pixel formats, to designate wich bit is used to store the leftest pixel of the group ; _be
/_le
for width larger than 8.16msb_be
: on the Atari ST, pixels are grouped by 16 to match the width of the memory data bus, with the leftest pixel being stored on the the most significant bit. Also, the CPU of those computer are BigEndian.24_le
: for a rgb888 pixel format, where bytes are stored in (Blue, Green, Red) order instead of (Red, Green, Blue).i4i_ltr_16msb_be
: describe the Atari ST 16 colors mode.i1i_ltr_8msb
: a monochrome display where bits are stored line by line i1i_ttb_8lsb
: a monochrome display where bits are stored backward in a byte, column by columni8p_ltr_8
: a 256 indexed colors display stored line by line rgb332_ltr_8
: a RGB display using one byte to encode its RGB value. rgb565_ltr_16_be
: the "True color" mode of the Atari Falcon030 rgb666_ltr_24_be
: a 18 bits per pixel encodingrgb888_ltr_24_be
: a 24 bits per pixel encoding 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;
}
Ported/amended here : https://github.com/sporniket/esp32-idf-experiment-spi-st7789/commit/8367d12fa926be49904d737da4be1b626ed79635
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;
}
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;
};
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 :
x
varies from 0 (on the left/west border) to W-1 (on the right/east border)y
varies from 0 (on the top/north border) to H-1 (on the bottom/south border)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.