I'm trying to come up with flexible color classes and conversions between them (sumotoy and I are discussing a flexible framework for writing display drivers, but more flexible than adafruit's gfx library). Have a look at this please:
colors.h
main.cpp
So it's possible to create custom color classes that a device-specific encoding (look at the RGB565 class for an example) and to convert between them with a generic method as long as they expose a common interface for the templates. Any other conversion can be achieved with a template specialization of convert(). I'm still working on this, but it looks promising. It seems that the compiler can highly optimize this code.
Regards
Christoph
colors.h
Code:
#ifndef COLORS_H
#define COLORS_H
#include <stdint.h>
namespace thirdExperiment
{
using namespace std;
namespace channel
{
static constexpr struct left_aligned_t {} left_aligned = left_aligned_t();
static constexpr struct right_aligned_t {} right_aligned = right_aligned_t();
template<typename T, unsigned int Offset_, unsigned int Width_>
class Proxy
{
public:
/* Some checks and typedefs */
static_assert(std::is_unsigned<T>::value, "ChannelProxy: T must be an unsigned arithmetic type.");
typedef T data_type;
static constexpr unsigned int Width = Width_;
static_assert(Width <= 8, "ChannelProxy: Width must be <= 8.");
static constexpr unsigned int Offset = Offset_;
static_assert((Offset + Width) <= 8*sizeof(T), "ChannelProxy: Channel is out of the data type's bounds. Check data type, offset and width.");
Proxy(T& data) : data_(data) {}
uint8_t read(right_aligned_t) const
{
return ((data_ & read_mask) >> Offset);
}
uint8_t read(left_aligned_t) const
{
return read(right_aligned) << (8-Width);
}
void write(const uint8_t& value, right_aligned_t)
{
// input data is right aligned
data_ = (data_ & write_mask) | ((value & value_mask) << Offset);
}
void write(const uint8_t& value, left_aligned_t)
{
// input data is left aligned, so shift right to right align, then write
write(value >> (8-Width), right_aligned);
}
private:
static constexpr uint8_t value_mask = (uint8_t)((1<<Width)-1);
static constexpr T read_mask = (value_mask << Offset);
static constexpr T write_mask = (T)~read_mask;
T& data_;
};
} // namespace channel
struct RGB24
{
typedef channel::Proxy<uint8_t, 0, 8> proxy;
typedef channel::Proxy<const uint8_t, 0, 8> const_proxy;
RGB24() : r_(0), g_(0), b_(0) {}
RGB24(const uint8_t& r, const uint8_t& g, const uint8_t& b)
: r_(r), g_(g), b_(b) {}
// unfortunately, we need different proxies for read and write access (data_type constness)
const_proxy r() const {return const_proxy(r_);}
proxy r() {return proxy(r_);}
const_proxy g() const {return const_proxy(g_);}
proxy g() {return proxy(g_);}
const_proxy b() const {return const_proxy(b_);}
proxy b() {return proxy(b_);}
template <typename From>
RGB24& operator=(const From& from)
{
convert(*this, from);
return *this;
}
uint8_t r_;
uint8_t g_;
uint8_t b_;
};
struct RGB565 // 16 bits: MSB | RRRRR GGGGGG BBBBB | LSB
{
typedef uint16_t data_type;
typedef channel::Proxy<data_type, 0, 5> b_proxy;
typedef channel::Proxy<const data_type, 0, 5> const_b_proxy;
typedef channel::Proxy<data_type, 5, 6> g_proxy;
typedef channel::Proxy<const data_type, 5, 6> const_g_proxy;
typedef channel::Proxy<data_type, 11, 5> r_proxy;
typedef channel::Proxy<const data_type, 11, 5> const_r_proxy;
RGB565() : data_(0) {}
template <typename alignment_type = channel::right_aligned_t>
RGB565(const uint8_t& r_, const uint8_t& g_, const uint8_t& b_, alignment_type = alignment_type())
{
alignment_type alignment;
r().write(r_, alignment);
g().write(g_, alignment);
b().write(b_, alignment);
}
template <typename From>
RGB565& operator=(const From& from)
{
convert(*this, from);
return *this;
}
const_r_proxy r() const {return const_r_proxy(data_);}
r_proxy r() {return r_proxy(data_);}
const_g_proxy g() const {return const_g_proxy(data_);}
g_proxy g() {return g_proxy(data_);}
const_b_proxy b() const {return const_b_proxy(data_);}
b_proxy b() {return b_proxy(data_);}
data_type data_;
};
typedef bool Monochrome;
template <typename To, typename From>
void convert(To& to, const From& from)
{
to.r().write(from.r().read(channel::left_aligned), channel::left_aligned);
to.g().write(from.g().read(channel::left_aligned), channel::left_aligned);
to.b().write(from.b().read(channel::left_aligned), channel::left_aligned);
}
template <>
void convert<RGB565, Monochrome>(RGB565& to, const Monochrome& from)
{
to.data_ = from ? 0xFFFF : 0;
}
template<>
void convert<RGB24, Monochrome>(RGB24& to, const Monochrome& from)
{
to.r_ = from ? 0xFF : 0;
to.g_ = from ? 0xFF : 0;
to.b_ = from ? 0xFF : 0;
}
}
#endif // COLORS_H
main.cpp
Code:
#include <WProgram.h>
#include <algorithm>
#include <array>
#include "colors.h"
#define N 1000
static const uint8_t analogPin = A0; // leave unconnected, we need noise
template <typename T>
T generateRGBfromAdc()
{
uint8_t val = analogRead(analogPin);
uint8_t r = val;
val = analogRead(analogPin);
uint8_t g = val;
val = analogRead(analogPin);
uint8_t b = val;
return T(r,g,b);
}
template <typename T>
T generateMonochromeFromAdc()
{
uint8_t val = analogRead(analogPin);
return T(val & 0x01);
}
void setup()
{
elapsedMicros t;
uint32_t us;
while(!Serial.available());
while(Serial.available())
{
Serial.read();
}
analogReadResolution(16);
{
using namespace thirdExperiment;
std::array<RGB24, N> rgb;
std::fill(rgb.begin(), rgb.end(), RGB24());
std::array<RGB565, N> rgb565;
std::generate(rgb.begin(), rgb.end(), generateRGBfromAdc<RGB565>);
Serial.printf("rgb565[0].r = 0x%02x, .g = 0x%02x, .b = 0x%02x\n",
rgb565[0].r().read(channel::right_aligned),
rgb565[0].g().read(channel::right_aligned),
rgb565[0].b().read(channel::right_aligned));
Serial.printf("Third Experiment (channel templates): RGB565 to RGB24\n");
t = elapsedMicros();
std::copy(rgb565.begin(), rgb565.end(), rgb.begin());
us = t;
Serial.printf("time = %u us\n", us);
Serial.printf("rgb[0].r = 0x%02x, .g = 0x%02x, .b = 0x%02x\n", rgb[0].r_, rgb[0].g_, rgb[0].b_);
std::array<Monochrome, N> m;
std::generate(m.begin(), m.end(), generateMonochromeFromAdc<Monochrome>);
Serial.printf("Third Experiment (channel templates): Monochrome to RGB24\n");
t = elapsedMicros();
std::copy(m.begin(), m.end(), rgb.begin());
us = t;
Serial.printf("time = %u us\n", us);
Serial.printf("rgb[0].r = 0x%02x, .g = 0x%02x, .b = 0x%02x\n", rgb[0].r_, rgb[0].g_, rgb[0].b_);
Serial.printf("rgb[1].r = 0x%02x, .g = 0x%02x, .b = 0x%02x\n", rgb[1].r_, rgb[1].g_, rgb[1].b_);
}
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
digitalWriteFast(LED_BUILTIN, true);
delay(500);
digitalWriteFast(LED_BUILTIN, false);
delay(500);
}
So it's possible to create custom color classes that a device-specific encoding (look at the RGB565 class for an example) and to convert between them with a generic method as long as they expose a common interface for the templates. Any other conversion can be achieved with a template specialization of convert(). I'm still working on this, but it looks promising. It seems that the compiler can highly optimize this code.
Regards
Christoph