I would like to hear your opinion on something i came up with. Basically, a code base I am working on specializes in handling picture processing and therefore has a bunch of different pixel formats, image file format reader/writer functionality and stuff. One thing I always disliked is the fact of having a huge enum or whatever constant around that sits somewhere in the code base containing an identifier for any kind of pixel format. In both cases when you add or remove a pixel format you most likely end up changing code somewhere else in the code base (not meaning client code necessarily).
So, to get rid of the step maintaining the enum and to somewhat automate the process of having some kind of UID for a pixel format, I tried to encode the format’s attributes into a uint64_t at compile time which apparently gives an accurate result. In my current draft, I am focusing on encoding each channel’s bits and position in bits in one byte of uint64_t.
While I surely can be more clever in the implementation and how it is encoded aside, do you think this is a maintainable approach? For now I am not able to see any deal-breaking downside with an approach like this – it got me rid of this enum and the constant is more or less automatically there too. However, maybe the enthusiasm that this actually seems to work out distorts my perception of things… Happy to hear your thoughts on it.
template<
uint8_t Rbits, uint8_t Rpos,
uint8_t Gbits, uint8_t Gpos,
uint8_t Bbits, uint8_t Bpos,
uint8_t Abits, uint8_t Apos
>
struct pixel_format {
static constexpr uint8_t red_bits = Rbits;
static constexpr uint8_t red_pos = Rpos;
static constexpr uint8_t green_bits = Gbits;
static constexpr uint8_t green_pos = Gpos;
static constexpr uint8_t blue_bits = Bbits;
static constexpr uint8_t blue_pos = Bpos;
static constexpr uint8_t alpha_bits = Abits;
static constexpr uint8_t alpha_pos = Apos;
static constexpr uint64_t uid = std::invoke([]() constexpr {
std::array<uint8_t, 8> temp{
red_bits, red_pos,
green_bits, green_pos,
blue_bits, blue_pos,
alpha_bits, alpha_pos
};
return std::bit_cast<uint64_t>(temp);
});
};
struct RGB8 : pixel_format<8, 0, 8, 8, 8, 16, 0, 0> {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct RGBA8 : pixel_format<8, 0, 8, 8, 8, 16, 8, 24> {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
struct RGB5A1 : pixel_format<5, 0, 5, 5, 5, 10, 1, 15> {
uint16_t r : 5;
uint16_t g : 5;
uint16_t b : 5;
uint16_t a : 1;
};
struct RGB5 : pixel_format<5, 0, 5, 10, 5, 15, 0, 0> {
uint16_t r : 5;
uint16_t g : 5;
uint16_t b : 5;
uint16_t unused : 1;
};
// ... many more, most of them also in reverse order, etc...
static constexpr uint64_t get_pixel_format(const std::string& filename) noexcept {
// Do something to determine the pixel format of an image file...
return RGB5A1::uid;
}
constexpr void load_image(const std::string& filename, uint64_t format) {
switch (format) {
case RGB8::uid:
// something like ... ImageReader<RGB8>{}(filename)....
break;
case RGBA8::uid:
// something like ... ImageReader<RGBA8>{}(filename)....
break;
case RGB5A1::uid:
// something like ... ImageReader<RGB5A1>{}(filename)....
break;
case RGB5::uid:
// something like ... ImageReader<RGB5>{}(filename)....
break;
// ...
default:
break;
}
}