3D Lut from PackedImageDesc processor


Gonzalo Garramuño <ggar...@...>
 

I have a GL Lut that is not created with OCIO.  Instead an initial identity lut is created and this lut is passed on to the gfx card by my own code.  So long, so good.  However, this lut is not a perfect match and it fails in some images, like the sample openexr syntheticCard.exr.

My code is largely based on OpenEXR code.  I init the lut like:

--
void GLLut3d::init_pixel_values( Imf::Array< float >& pixelValues )
{
//
// Compute lutMin, lutMax, and scale and offset
// values, lutM and lutT, so that
//
// lutM * lutMin + lutT == 0
// lutM * lutMax + lutT == 1
//

static const float MIDDLE_GRAY = 0.18f;
static const int NUM_STOPS = 10:

lutMin = MIDDLE_GRAY / (1 << NUM_STOPS);
lutMax = MIDDLE_GRAY * (1 << NUM_STOPS);
float logLutMin = logf (lutMin);
float logLutMax = logf (lutMax);

lutM = 1 / (logLutMax - logLutMin);
lutT = -lutM * logLutMin;

//
// Build a 3D array of RGB input pixel values.
// such that R, G and B are between lutMin and lutMax.
//
for (size_t ib = 0; ib < _lutN; ++ib)
{
float b = float(ib) / float(_lutN - 1.0);
float B = expf((b - lutT) / lutM);

for (size_t ig = 0; ig < _lutN; ++ig)
{
float g = float(ig) / float(_lutN - 1.0);
float G = expf ((g - lutT) / lutM);

for (size_t ir = 0; ir < _lutN; ++ir)
{
float r = float(ir) / float(_lutN - 1.0);
float R = expf ((r - lutT) / lutM);

size_t i = (ib * _lutN * _lutN + ig * _lutN + ir) * 4;
pixelValues[i + 0] = R;
pixelValues[i + 1] = G;
pixelValues[i + 2] = B;
pixelValues[i + 3] = 1.0f;
}
}
}
}

// This is the main OCIO calculation routine

bool GLLut3d::calculate_ocio( const CMedia* img )
{
//
// We build a 3D color lookup table by running a set of color
// samples through a series of OCIO transforms.
//
// The 3D lookup table covers a range from lutMin to lutMax or
// NUM_STOPS f-stops above and below 0.18 or MIDDLE_GRAY. The
// size of the table is _lutN by _lutN by _lutN samples.
//
// In order make the distribution of the samples in the table
// approximately perceptually uniform, the Cg shaders that use
// the table perform lookups in "log space":
// In a Cg shader, the lookup table is represented as a 3D texture.
// In order to apply the table to a pixel value, the Cg shader takes
// the logarithm of the pixel value and scales and offsets the result
// so that lutMin and lutMax map to 0 and 1 respectively. The scaled
// value is used to perform a texture lookup and the shader computes
// e raised to the power of the result of the texture lookup.
//
//
// Generate output pixel values by applying CTL transforms
// to the pixel values. (If the CTL transforms fail to
// write to the output values, zero-initialization, above,
// causes the displayed image to be black.)
//
if ( !_inited )
{
//
// Init lut table to 0
//
clear_lut();

//
// Init table of pixel values
//
init_pixel_values( lut );
}
try
{
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
const std::string& display = mrv::Preferences::OCIO_Display;
const std::string& view = mrv::Preferences::OCIO_View;
OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
std::string ics = img->ocio_input_color_space();
if ( ics.empty() )
{
OCIO::ConstColorSpaceRcPtr defaultcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
if(!defaultcs)
throw std::runtime_error( _("ROLE_SCENE_LINEAR not defined." ));
ics = defaultcs->getName();
}
transform->setInputColorSpaceName( ics.c_str() );
transform->setDisplay( display.c_str() );
transform->setView( view.c_str() );
OCIO::ConstContextRcPtr context = config->getCurrentContext();
OCIO::ConstProcessorRcPtr processor =
config->getProcessor(context, transform,
OCIO::TRANSFORM_DIR_FORWARD);

OCIO::PackedImageDesc img(&lut[0], lut_size()/4,
/*height*/ 1, /*channels*/ 4);
processor->apply( img );
_inited = true;
}
catch(OCIO::Exception& e)
{
OCIO_ERROR( e.what() );
return false;
}
catch( const std::exception& e )
{
LOG_ERROR( e.what() );
return false;
}
catch( ... )
{
LOG_ERROR( _("Unknown error returned from OCIO processor") );
return false;
}
return true;
}



//
// This is the main routine to create the gl 3d texture
// Create opengl texture from the log of lut values
//
void GLLut3d::create_gl_texture()
{
//
// Take the logarithm of the output values that were
// produced by the CTL transforms.
//

size_t num = lut_size();
for ( size_t i = 0; i < num; ++i )
{
if ( lut[i] >= std::numeric_limits<float>::min()
&& lut[i] <= std::numeric_limits<float>::max() )
{
//
// lut[i] is finite and positive.
//
lut[i] = (float) logf(lut[i]);
}
else
{
//
// lut[i] is zero, negative or not finite;
// log (lut[i]) is undefined.
//
lut[i] = (float) logf( std::numeric_limits<float>::min() );
}
}

//
// Convert the output values into a 3D texture.
//
glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

glActiveTexture( GL_TEXTURE3 );

glBindTexture( GL_TEXTURE_3D, texId );

glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

GLenum gl_clamp = GL_CLAMP;
if ( GLEW_EXT_texture_edge_clamp )
gl_clamp = GL_CLAMP_TO_EDGE;

glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, gl_clamp );
glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, gl_clamp );
glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, gl_clamp );


glTexImage3D( GL_TEXTURE_3D,
0, // level
GL_RGBA32F, // internal format
_lutN, _lutN, _lutN, // width, height, depth
0, // border
GL_RGBA, // format
GL_FLOAT, // type
(char *) &lut[0] );
}

--
 Gonzalo Garramuño

Join ocio-dev@lists.aswf.io to automatically receive all group messages.