Newer
Older
* Effect that controls the total brightness of another effect,
* and measures average brightness as well as brightness change rate.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
*
* Copyright (c) 2014 Micah Elizabeth Scott <micah@scanlime.org>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "effect.h"
class Brightness : public Effect {
public:
Brightness(Effect &next);
void set(float averageBrightness);
void set(float lowerLimit, float upperLimit);
// Set the gamma value we assume when performing total brightness calculations.
// Doesn't affect the actual output gamma! We need to sum the brightness in a
// physically linear space, but we perform the scaling back in our perceptually
// linear-ish space.
void setAssumedGamma(float gamma);
// Gets the last brightness average; will be roughly between lowerLimit and upperLimit.
float getAverageBrightness() const;
// Get the total squared brightness delta between the last two frames
float getTotalBrightnessDelta() const;
virtual void beginFrame(const FrameInfo& f);
virtual void endFrame(const FrameInfo& f);
virtual void debug(const DebugInfo& f);
virtual void shader(Vec3& rgb, const PixelInfo& p) const;
private:
Effect &next;
float lowerLimit, upperLimit;
float currentScale;
float latestAverage;
float totalBrightnessDelta;
unsigned numIters;
std::vector<Vec3> *prevColors;
std::vector<Vec3> *nextColors;
std::vector<Vec3> colorBuffer[2];
static const unsigned gammaTableSize = 256;
float gammaTable[gammaTableSize];
/*****************************************************************************************
* Implementation
*****************************************************************************************/
inline Brightness::Brightness(Effect &next)
: next(next),
lowerLimit(0),
upperLimit(1),
currentScale(1),
latestAverage(0),
totalBrightnessDelta(0),
numIters(0)
{
// Fadecandy default
setAssumedGamma(2.5);
prevColors = &colorBuffer[0];
nextColors = &colorBuffer[1];
}
inline void Brightness::set(float averageBrightness)
{
lowerLimit = upperLimit = averageBrightness;
}
inline void Brightness::set(float lowerLimit, float upperLimit)
{
this->lowerLimit = lowerLimit;
this->upperLimit = upperLimit;
}
inline void Brightness::setAssumedGamma(float gamma)
{
this->gamma = gamma;
for (unsigned i = 0; i < gammaTableSize; i++) {
gammaTable[i] = powf(i / float(gammaTableSize + 1), gamma);
}
}
inline float Brightness::getAverageBrightness() const
{
return latestAverage;
}
inline float Brightness::getTotalBrightnessDelta() const
{
return totalBrightnessDelta;
}
inline void Brightness::beginFrame(const FrameInfo& f)
{
next.beginFrame(f);
std::swap(nextColors, prevColors);
if (colorBuffer[0].size() != f.pixels.size()) {
for (unsigned i = 0; i < 2; i++) {
colorBuffer[i].resize(f.pixels.size());
std::fill(colorBuffer[i].begin(), colorBuffer[i].end(), Vec3(0,0,0));
}
}
float deltaAccumulator = 0;
// Calculate the next effect's pixels, storing them all. Also count the total number
// of mapped pixels, ignoring any unmapped ones.
{
PixelInfoIter pi = f.pixels.begin();
PixelInfoIter pe = f.pixels.end();
std::vector<Vec3>::iterator nci = nextColors->begin();
std::vector<Vec3>::iterator pci = prevColors->begin();
for (;pi != pe; ++pi, ++nci, ++pci) {
if (pi->isMapped()) {
Vec3 rgb(0, 0, 0);
next.shader(rgb, *pi);
next.postProcess(rgb, *pi);
*nci = rgb;
deltaAccumulator += sqrlen(rgb - *pci);
const float deltaAccumulatorFilterRate = 0.05;
totalBrightnessDelta += (deltaAccumulator - totalBrightnessDelta) * deltaAccumulatorFilterRate;
if (!(totalBrightnessDelta >= 0)) {
// NaN or negative
totalBrightnessDelta = 0;
}
if (count == 0) {
// No LEDs mapped
return;
}
// Iterative algorithm to adjust brightness scaling. I'm not sure a closed-form
// solution exists- this is complicated for multiple reasons. We want to scale the
// entire image in a perceptually linear way, but the final brightness we're interested
// in is related to the total linear intensity of all LEDs. Additionally, the brightness
// is clamped at each LED, so we may need to increase the brightness of other LEDs to
// compensate for individual LEDs that can't get any brighter. Usually this only takes
// a few iterations to converge.
const unsigned maxIters = 50;
const float epsilon = 1e-3;
unsigned iter = 0;
float avg;
float scale = 1.0;
for (; iter < maxIters; iter++) {
std::vector<Vec3>::iterator ci = nextColors->begin();
std::vector<Vec3>::iterator ce = nextColors->end();
PixelInfoIter pi = f.pixels.begin();
avg = 0;
for (;ci != ce; ++ci, ++pi) {
Vec3& rgb = *ci;
// Simulated linear brightness, using current scale
if (pi->isMapped()) {
for (unsigned i = 0; i < 3; i++) {
float c = rgb[i] * scale;
avg += gammaTable[std::max<int>(0, std::min<int>(gammaTableSize - 1, c * float(gammaTableSize - 1)))];
}
}
}
float adjustment;
// Make brighter, operate against the lower limit
adjustment = powf(lowerLimit / avg, 1.0f / gamma);
// Make dimmer, operate against the upper limit
adjustment = powf(upperLimit / avg, 1.0f / gamma);
// Not hitting any limits
break;
scale = std::max(epsilon, scale * adjustment);
// Was this adjustment negligible? We can quit early.
if (fabsf(adjustment - 1.0f) < epsilon) {
break;
numIters = iter;
currentScale = scale;
latestAverage = avg;
}
inline void Brightness::endFrame(const FrameInfo& f)
{
next.endFrame(f);
}
inline void Brightness::debug(const DebugInfo& d)
{
next.debug(d);
fprintf(stderr, "\t[brightness] limits = [%f, %f]\n", lowerLimit, upperLimit);
fprintf(stderr, "\t[brightness] currentScale = %f\n", currentScale);
fprintf(stderr, "\t[brightness] latestAverage = %f\n", latestAverage);
fprintf(stderr, "\t[brightness] totalBrightnessDelta = %f\n", totalBrightnessDelta);
fprintf(stderr, "\t[brightness] iterations = %d\n", numIters);
}
inline void Brightness::shader(Vec3& rgb, const PixelInfo& p) const
rgb = (*nextColors)[p.index] * currentScale;