Hello,
Currently, the color correction proposed by hyperion is a 1D correction by matrix transformation.
Matrix transformation is ideal for transforming a linear space into another linear space. For example, to transform a video from the Rec. 2020 space (4K) to the Rec. 709 space (HD).
However, the matrix transformation for color correction is really not suitable:
- when the gamma must be corrected (this is a curve)
- when the RGB channels are not independent (eg. asking for red will also lower the blue)
For these reasons, lookup tables (LUT) are used to correct screens and projectors. LEDs have the same problems... worse.
I have just coded this type of correction for FastLED. The result is quite satisfactory (15ms to correct 300leds by an arduino nano) but it's still quite expensive for such a small machine. That is why I think it would be interesting to propose this type of correction in hyperion.
Autodesk 3DLUT to C 3D array (html)
3DLUT color correction on GitHub:
/// 3D array index
struct LUTCoord {
union {
struct {
uint8_t redIndex;
uint8_t greenIndex;
uint8_t blueIndex;
};
uint8_t raw[3];
};
inline LUTCoord() __attribute__((always_inline))
{
}
inline LUTCoord( uint8_t ir, uint8_t ig, uint8_t ib) __attribute__((always_inline))
: redIndex(ir), greenIndex(ig), blueIndex(ib)
{
}
};
/// This 3D LUT can be used provided that the space between entries is the same for all dimensions.
template<uint8_t SIZE>
class Array3DLUT {
public:
CRGB (*cube)[SIZE][SIZE];
Array3DLUT(CRGB entries[SIZE][SIZE][SIZE]);
virtual ~Array3DLUT();
/// Similar to lookup but instead of a coordinate in the LUT,
/// we pass a point and the result will be interpolated.
/// @param input - RGB coordinates of a point in the LUT
/// @returns Interpolated value in the LUT
struct CRGB lookup3DTetrahedral(CRGB &input);
};
template<uint8_t SIZE>
Array3DLUT<SIZE>::Array3DLUT(CRGB (*entries)[SIZE][SIZE]) {
this->cube = entries;
}
template<uint8_t SIZE>
Array3DLUT<SIZE>::~Array3DLUT() {
// TODO Auto-generated destructor stub
}
// Ref implem: https://github.com/ampas/CLF/blob/master/python/aces/clf/Array.py
template<uint8_t SIZE>
struct CRGB Array3DLUT<SIZE>::lookup3DTetrahedral(CRGB &input) {
LUTCoord subcubeDarkestVertice; // Store the subcube's origin coordinates
uint8_t darkSpotDistances[3]; // Distance input <-> subcube's darkest vertice
#define STEP (255/(SIZE-1))
#define MAX_INDEX SIZE-2
for (int i = 0; i < 3; i++) { // for each rgb channel
subcubeDarkestVertice.raw[i] = input.raw[i] / STEP;
darkSpotDistances[i] = input.raw[i] % STEP;
if (subcubeDarkestVertice.raw[i] > MAX_INDEX) {
// But, if we are on an upper border then we must take the
// inner border or the subcube will not exist.
subcubeDarkestVertice.raw[i] = MAX_INDEX;
darkSpotDistances[i] = 255;
}
}
// Rebind for consistency with Truelight paper
#define fx darkSpotDistances[0]
#define fy darkSpotDistances[1]
#define fz darkSpotDistances[2]
#define N000 this->cube[subcubeDarkestVertice.redIndex ][subcubeDarkestVertice.greenIndex ][subcubeDarkestVertice.blueIndex ]
#define N001 this->cube[subcubeDarkestVertice.redIndex ][subcubeDarkestVertice.greenIndex ][subcubeDarkestVertice.blueIndex+1]
#define N010 this->cube[subcubeDarkestVertice.redIndex ][subcubeDarkestVertice.greenIndex+1][subcubeDarkestVertice.blueIndex ]
#define N011 this->cube[subcubeDarkestVertice.redIndex ][subcubeDarkestVertice.greenIndex+1][subcubeDarkestVertice.blueIndex+1]
#define N100 this->cube[subcubeDarkestVertice.redIndex+1][subcubeDarkestVertice.greenIndex ][subcubeDarkestVertice.blueIndex ]
#define N101 this->cube[subcubeDarkestVertice.redIndex+1][subcubeDarkestVertice.greenIndex ][subcubeDarkestVertice.blueIndex+1]
#define N110 this->cube[subcubeDarkestVertice.redIndex+1][subcubeDarkestVertice.greenIndex+1][subcubeDarkestVertice.blueIndex ]
#define N111 this->cube[subcubeDarkestVertice.redIndex+1][subcubeDarkestVertice.greenIndex+1][subcubeDarkestVertice.blueIndex+1]
struct CRGB result;
if (fx > fy) {
if (fy > fz) {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fx) * N000.raw[i] +
(uint16_t)(fx-fy) * N100.raw[i] +
(uint16_t)(fy-fz) * N110.raw[i] +
(uint16_t)(fz) * N111.raw[i] ) >> 8;
}
} else {
if (fx > fz) {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fx) * N000.raw[i] +
(uint16_t)(fx-fz) * N100.raw[i] +
(uint16_t)(fz-fy) * N101.raw[i] +
(uint16_t)(fy) * N111.raw[i] ) >> 8;
}
} else {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fz) * N000.raw[i] +
(uint16_t)(fz-fx) * N001.raw[i] +
(uint16_t)(fx-fy) * N101.raw[i] +
(uint16_t)(fy) * N111.raw[i] ) >> 8;
}
}
}
} else {
if (fz > fy) {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fz) * N000.raw[i] +
(uint16_t)(fz-fy) * N001.raw[i] +
(uint16_t)(fy-fx) * N011.raw[i] +
(uint16_t)(fx) * N111.raw[i] ) >> 8;
}
} else {
if (fz > fx) {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fy) * N000.raw[i] +
(uint16_t)(fy-fz) * N010.raw[i] +
(uint16_t)(fz-fx) * N011.raw[i] +
(uint16_t)(fx) * N111.raw[i] ) >> 8;
}
} else {
for (int i=0; i < 3; i++) {
result.raw[i] = (
(uint16_t)(255-fy) * N000.raw[i] +
(uint16_t)(fy-fx) * N010.raw[i] +
(uint16_t)(fx-fz) * N011.raw[i] +
(uint16_t)(fz) * N111.raw[i] ) >> 8;
}
}
}
}
return result;
}
Alles anzeigen