povray/source/base/image/colourspace.cpp
2013-11-06 13:07:19 -05:00

391 lines
13 KiB
C++

/*******************************************************************************
* colourspace.cpp
*
* ---------------------------------------------------------------------------
* Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
* Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
*
* POV-Ray is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* POV-Ray is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------------
* POV-Ray is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
* ---------------------------------------------------------------------------
* $File: //depot/public/povray/3.x/source/base/image/colourspace.cpp $
* $Revision: #1 $
* $Change: 6069 $
* $DateTime: 2013/11/06 11:59:40 $
* $Author: chrisc $
*******************************************************************************/
#include <vector>
#include <algorithm>
#include <cassert>
// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/image/colourspace.h"
#include "base/image/encoding.h"
#include "base/povmsgid.h"
// this must be the last file included
#include "base/povdebug.h"
namespace pov_base
{
// definitions of static GammaCurve member variables to satisfy the linker
list<boost::weak_ptr<GammaCurve> > GammaCurve::cache;
boost::mutex GammaCurve::cacheMutex;
// definitions of static GammaCurve-derivatives' member variables to satisfy the linker
SimpleGammaCurvePtr NeutralGammaCurve::instance;
SimpleGammaCurvePtr SRGBGammaCurve::instance;
GammaCurvePtr ITURBT709GammaCurve::instance;
GammaCurvePtr Rec1361GammaCurve::instance;
/*******************************************************************************/
float* GammaCurve::GetLookupTable(unsigned int max)
{
assert(max == 255 || max == 65535); // shouldn't happen, but it won't hurt to check in debug versions
// Get a reference to the lookup table pointer we're dealing with, so we don't need to duplicate all the remaining code.
float*& lookupTable = (max == 255 ? lookupTable8 : lookupTable16);
// Make sure we're not racing any other thread that might currently be busy creating the LUT.
boost::mutex::scoped_lock lock(lutMutex);
// Create the LUT if it doesn't exist yet.
if (!lookupTable)
{
float* tempTable = new float[max+1];
for (unsigned int i = 0; i <= max; i ++)
tempTable[i] = Decode(IntDecode(i, max));
// hook up the table only as soon as it is completed, so that querying the table does not need to
// care about thread-safety.
lookupTable = tempTable;
}
return lookupTable;
}
GammaCurvePtr GammaCurve::GetMatching(const GammaCurvePtr& newInstance)
{
GammaCurvePtr oldInstance;
bool cached = false;
// See if we have a matching gamma curve in our chache already
// make sure the cache doesn't get tampered with while we're working on it
boost::mutex::scoped_lock lock(cacheMutex);
// Check if we already have created a matching gamma curve object; if so, return that object instead.
// Also, make sure we get the new object stored (as we're using weak pointers, we may have stale entries;
// it also won't hurt if we store the new instance, even if we decide to discard it)
for(list<boost::weak_ptr<GammaCurve> >::iterator i(cache.begin()); i != cache.end(); i++)
{
oldInstance = (*i).lock();
if (!oldInstance)
{
// Found a stale entry in the cache where we could store the new instance, in case we don't find any match.
// As the cache uses weak pointers, we can just as well store the new instance now right away,
// and leave it up to the weak pointer mechanism to clean up in case we find an existing instance.
if (!cached)
(*i) = newInstance;
cached = true;
}
else if (oldInstance->Matches(newInstance))
{
// Found a matching curve in the cache, so use that instead, and (as far as we're concerned)
// just forget that the new instance ever existed (allowing the shared_ptr mechanism to garbage-collect it)
return oldInstance;
}
}
// No matching gamma curve in the cache yet
// Store the new entry in the cache if we haven't done so already.
if (!cached)
cache.push_back(newInstance);
return newInstance;
}
/*******************************************************************************/
NeutralGammaCurve::NeutralGammaCurve() {}
SimpleGammaCurvePtr NeutralGammaCurve::Get()
{
if (!instance)
instance.reset(new NeutralGammaCurve());
return SimpleGammaCurvePtr(instance);
}
float NeutralGammaCurve::Encode(float x) const
{
return x;
}
float NeutralGammaCurve::Decode(float x) const
{
return x;
}
float NeutralGammaCurve::ApproximateDecodingGamma() const
{
return 1.0f;
}
int NeutralGammaCurve::GetTypeId() const
{
return kPOVList_GammaType_Neutral;
}
bool NeutralGammaCurve::Matches(const GammaCurvePtr& p) const
{
return GammaCurve::IsNeutral(p);
}
bool NeutralGammaCurve::IsNeutral() const
{
return true;
}
/*******************************************************************************/
SRGBGammaCurve::SRGBGammaCurve() {}
SimpleGammaCurvePtr SRGBGammaCurve::Get()
{
if (!instance)
instance.reset(new SRGBGammaCurve());
return SimpleGammaCurvePtr(instance);
}
float SRGBGammaCurve::Encode(float x) const
{
// (the threshold of 0.00304 occasionally found on the net was from an older draft)
if (x <= 0.0031308f) return x * 12.92f;
else return 1.055f * pow(x, 1.0f/2.4f) - 0.055f;
}
float SRGBGammaCurve::Decode(float x) const
{
// (the threshold of 0.03928 occasionally found on the net was from an older draft)
if (x < 0.04045f) return x / 12.92f;
else return pow((x + 0.055f) / 1.055f, 2.4f);
}
float SRGBGammaCurve::ApproximateDecodingGamma() const
{
return 2.2f;
}
int SRGBGammaCurve::GetTypeId() const
{
return kPOVList_GammaType_SRGB;
}
/*******************************************************************************/
ITURBT709GammaCurve::ITURBT709GammaCurve() {}
GammaCurvePtr ITURBT709GammaCurve::Get()
{
if (!instance)
instance.reset(new ITURBT709GammaCurve());
return GammaCurvePtr(instance);
}
float ITURBT709GammaCurve::Encode(float x) const
{
if (x < 0.018f) return x * 4.5f;
else return 1.099f * pow(x, 0.45f) - 0.099f;
}
float ITURBT709GammaCurve::Decode(float x) const
{
if (x < 0.081f) return x / 4.5f;
else return pow((x + 0.099f) / 1.099f, 1.0f/0.45f);
}
float ITURBT709GammaCurve::ApproximateDecodingGamma() const
{
return 1.9f; // very rough approximation
}
/*******************************************************************************/
Rec1361GammaCurve::Rec1361GammaCurve() {}
GammaCurvePtr Rec1361GammaCurve::Get()
{
if (!instance)
instance.reset(new Rec1361GammaCurve());
return GammaCurvePtr(instance);
}
float Rec1361GammaCurve::Encode(float x) const
{
if (x < -0.0045f) return (1.099f * pow(-4*x, 0.45f) - 0.099f) / 4;
else if (x < 0.018f) return x * 4.5f;
else return 1.099f * pow(x,0.45f) - 0.099f;
}
float Rec1361GammaCurve::Decode(float x) const
{
if (x < -0.02025f) return pow((4*x + 0.099f) / 1.099f, 1.0f/0.45f) / -4;
else if (x < 0.081f) return x / 4.5f;
else return pow((x + 0.099f) / 1.099f, 1.0f/0.45f);
}
float Rec1361GammaCurve::ApproximateDecodingGamma() const
{
return 1.9f; // very rough approximation of the x>0 section
}
/*******************************************************************************/
PowerLawGammaCurve::PowerLawGammaCurve(float gamma) :
encGamma(gamma)
{}
SimpleGammaCurvePtr PowerLawGammaCurve::GetByEncodingGamma(float gamma)
{
if (IsNeutral(gamma))
return NeutralGammaCurve::Get();
return boost::dynamic_pointer_cast<SimpleGammaCurve,GammaCurve>(GetMatching(GammaCurvePtr(new PowerLawGammaCurve(gamma))));
}
SimpleGammaCurvePtr PowerLawGammaCurve::GetByDecodingGamma(float gamma)
{
return GetByEncodingGamma(1.0f/gamma);
}
float PowerLawGammaCurve::Encode(float x) const
{
return pow(max(x,0.0f), encGamma);
}
float PowerLawGammaCurve::Decode(float x) const
{
return pow(max(x,0.0f), 1.0f/encGamma);
}
float PowerLawGammaCurve::ApproximateDecodingGamma() const
{
return 1.0f/encGamma;
}
int PowerLawGammaCurve::GetTypeId() const
{
return kPOVList_GammaType_PowerLaw;
}
float PowerLawGammaCurve::GetParam() const
{
return 1.0f/encGamma;
}
bool PowerLawGammaCurve::Matches(const GammaCurvePtr& p) const
{
PowerLawGammaCurve* other = dynamic_cast<PowerLawGammaCurve*>(p.get());
if (!other) return false;
return IsNeutral(this->encGamma / other->encGamma);
}
bool PowerLawGammaCurve::IsNeutral(float gamma)
{
return fabs(1.0 - gamma) <= 0.01;
}
/*******************************************************************************/
ScaledGammaCurve::ScaledGammaCurve(const GammaCurvePtr& gamma, float factor) :
baseGamma(gamma), encFactor(factor)
{
ScaledGammaCurve* other = dynamic_cast<ScaledGammaCurve*>(baseGamma.get());
if (other) // if base gamma curve is a scaled one as well, compute a combined scaling factor instead of nesting
{
baseGamma = other->baseGamma;
encFactor *= other->encFactor;
}
}
GammaCurvePtr ScaledGammaCurve::GetByEncoding(const GammaCurvePtr& gamma, float factor)
{
if (IsNeutral(factor))
return GammaCurvePtr(gamma);
return GetMatching(GammaCurvePtr(new ScaledGammaCurve(
GammaCurve::IsNeutral(gamma) ? GammaCurvePtr(NeutralGammaCurve::Get()) : gamma,
factor)));
}
GammaCurvePtr ScaledGammaCurve::GetByDecoding(float factor, const GammaCurvePtr& gamma)
{
return GetByEncoding(gamma, 1.0f/factor);
}
float ScaledGammaCurve::Encode(float x) const
{
return baseGamma->Encode(x) * encFactor;
}
float ScaledGammaCurve::Decode(float x) const
{
return baseGamma->Decode(x / encFactor);
}
float ScaledGammaCurve::ApproximateDecodingGamma() const
{
return baseGamma->ApproximateDecodingGamma();
}
bool ScaledGammaCurve::Matches(const GammaCurvePtr& p) const
{
ScaledGammaCurve* other = dynamic_cast<ScaledGammaCurve*>(p.get());
if (!other) return false;
return (this->baseGamma == other->baseGamma) && IsNeutral(this->encFactor / other->encFactor);
}
bool ScaledGammaCurve::IsNeutral(float scale) { return fabs(1.0 - scale) <= 1e-6; }
/*******************************************************************************/
TranscodingGammaCurve::TranscodingGammaCurve(const GammaCurvePtr& working, const GammaCurvePtr& encoding) :
workGamma(working), encGamma(encoding)
{}
GammaCurvePtr TranscodingGammaCurve::Get(const GammaCurvePtr& working, const GammaCurvePtr& encoding)
{
// if the working gamma space is linear, we only need the encoding gamma
if (GammaCurve::IsNeutral(working))
return GammaCurvePtr(encoding);
// if both gamma spaces are the same, we can replace them with a neutral gamma curve
if (working->Matches(encoding))
return NeutralGammaCurve::Get();
// check if we can replace the combination of gamma curves with a single power-law gamma curve
PowerLawGammaCurve* powerLawWork = dynamic_cast<PowerLawGammaCurve*>(working.get());
if (powerLawWork)
{
// if the encoding gamma space is linear, we only need the inverse of the working gamma
if (GammaCurve::IsNeutral(encoding))
return PowerLawGammaCurve::GetByEncodingGamma(powerLawWork->ApproximateDecodingGamma());
// if both gamma spaces are based on a simple power-law, we only need to combine them into a single one
PowerLawGammaCurve* powerLawEnc = dynamic_cast<PowerLawGammaCurve*>(encoding.get());
if (powerLawEnc)
return PowerLawGammaCurve::GetByEncodingGamma(powerLawWork->ApproximateDecodingGamma() / powerLawEnc->ApproximateDecodingGamma());
}
// we really need a combo of two gamma curves
return GetMatching(GammaCurvePtr(new TranscodingGammaCurve(working, encoding ? encoding : GammaCurvePtr(NeutralGammaCurve::Get()))));
}
float TranscodingGammaCurve::Encode(float x) const
{
return encGamma->Encode(workGamma->Decode(x));
}
float TranscodingGammaCurve::Decode(float x) const
{
return workGamma->Encode(encGamma->Decode(x));
}
float TranscodingGammaCurve::ApproximateDecodingGamma() const
{
return encGamma->ApproximateDecodingGamma() / workGamma->ApproximateDecodingGamma();
}
bool TranscodingGammaCurve::Matches(const GammaCurvePtr& p) const
{
TranscodingGammaCurve* other = dynamic_cast<TranscodingGammaCurve*>(p.get());
if (!other) return false;
return (this->encGamma->Matches(other->encGamma) && this->workGamma->Matches(other->workGamma));
}
/*******************************************************************************/
SimpleGammaCurvePtr GetGammaCurve(int type, float param)
{
switch (type)
{
case kPOVList_GammaType_Neutral: return NeutralGammaCurve::Get();
case kPOVList_GammaType_PowerLaw: return PowerLawGammaCurve::GetByDecodingGamma(param);
case kPOVList_GammaType_SRGB: return SRGBGammaCurve::Get();
default: return PowerLawGammaCurve::GetByDecodingGamma(DEFAULT_FILE_GAMMA);
}
}
}