1499 lines
47 KiB
C++
Raw Normal View History

2022-04-07 18:46:57 +02:00
///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2006, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
//
// Play an OpenEXR image sequence.
//
// This is the display thread of the playExr program.
// It does the following:
//
// * Reads the first frame in the image sequence to find out how
// big the images are and what channels they contain.
//
// * Allocates a ring buffer for receiving images from the file
// file reading thread.
//
// * Creates an OpenGL window for displaying the images.
//
// * Launches a file reading thread, which reads the frames in
// the image sequence and passes them to the display thread.
//
// * Enters an infinite loop: get the next frame from the
// ring buffer, display the frame.
//
//----------------------------------------------------------------------------
#include "playExr.h"
#include "ctlToLut.h"
#include "fileNameForFrame.h"
#include "FileReadingThread.h"
#include "osDependent.h"
#include "ImageBuffers.h"
#include "Timer.h"
#include <ImfThreading.h>
#include <ImfInputFile.h>
#include <ImfChannelList.h>
#include <ImfStandardAttributes.h>
#include <ImfRgbaYca.h>
#include <ImfArray.h>
#include <half.h>
#include <stdlib.h>
#include <sstream>
#include <iomanip>
#include <cmath>
using namespace OPENEXR_IMF_NAMESPACE;
using namespace IMATH_NAMESPACE;
using namespace std;
using namespace ILMTHREAD_NAMESPACE;
namespace {
//
// Static variables.
//
ImageBuffers ib; // Ring buffer; transports frames from
// the file reading thread to the display
// thread.
int i = 0; // Index of current frame buffer
int frameNumber = 0; // Frame number of current frame
int firstFrameNumber = 0; // Frame number of first and last frame
int lastFrameNumber = 0; // in the sequence
Timer timer; // Timing control
GLuint texId[3]; // For display, a frame's three image channels
// are converted to OpenGL textures, eigher
// one three-channel texture or three one-
// channel textures. texId contains the
// names by which OpenGL refers to those
// textures.
int glWindowWidth = 0; // Preferred width and height of the on-screen
int glWindowHeight = 0; // window where the images will be displayed
Box2i drawRect; // The size and location of the images' data
// window relative to the on-screen window.
V3f yWeights (1, 1, 1); // Weights for converting the pixels in
// luminance chroma channels to RGB,
// computed from the chromaticities of
// the frames' primaries and white point
float exposure = 0; // Current exposure setting. All pixels
// are multiplied by pow(2,exposure) before
// they appear on the screen
bool enableCtl = true; // If enableCtl is true, CTL transforms
// are applied to the pixels after exposure
// has been applied. If enableCtl is false,
// no CTL transforms are applied.
const size_t lutN = 64; // The 3D color lookup table that is used
// to approximate the CTL transforms has
// lutN by lutN by lutN entries.
bool hwTexInterpolation = true; // Flag that controls whether the Cg shader
// that performs 3D color table lookups
// relies on hardware-interpolated texture
// lookups or if the shader itself interpolates
// between texture samples
bool showTextOverlay = true; // Flag that controls whether the actual
// frame rate and the current exposure
// setting are displayed
bool fullScreenMode = false; // Flag that controls whether the images
// are displayed in full-screen mode or not
//
// Initialization of the ring buffer, ib.
// We allocate space for the pixels of ib.numBuffers() frames,
// and we initialize Imf::FrameBuffer objects that allow the
// the file reading thread to fill the pixel buffers.
//
char *
addSlice
(FrameBuffer &fb,
const Box2i &dw,
const char name[],
int xSampling,
int ySampling)
{
int w = dw.max.x - dw.min.x + 1;
int h = dw.max.y - dw.min.y + 1;
size_t pixelSize = sizeof (half);
size_t lineSize = pixelSize * (w / xSampling);
size_t numLines = h / ySampling;
char *pixels = new char [lineSize * numLines];
fb.insert (name,
Slice (HALF,
pixels - (dw.min.y / ySampling) * lineSize -
(dw.min.x / xSampling) * pixelSize,
pixelSize,
lineSize,
xSampling,
ySampling));
return pixels;
}
void
initializeImageBuffers
(ImageBuffers &ib,
Header &header,
const char fileNameTemplate[])
{
InputFile
in (fileNameForFrame (fileNameTemplate, firstFrameNumber).c_str());
header = in.header();
const ChannelList &ch = in.header().channels();
const Box2i &dw = in.header().dataWindow();
ib.dataWindow = dw;
if (ch.findChannel ("Y") || ch.findChannel ("RY") || ch.findChannel ("BY"))
{
//
// Luminance/chroma mode
//
// The image channels go into three separate pixel buffers.
// The buffer for the luminance channel has the same width and
// height as the frame. The buffers for the two chroma channels
// have half the width and half the height of the frame.
//
ib.rgbMode = false;
for (int i = 0; i < ib.numBuffers(); ++i)
{
FrameBuffer &fb = ib.frameBuffer (i);
ib.pixels (i, 0) = addSlice (fb, dw, "Y", 1, 1);
ib.pixels (i, 1) = addSlice (fb, dw, "RY", 2, 2);
ib.pixels (i, 2) = addSlice (fb, dw, "BY", 2, 2);
}
Chromaticities chroma;
if (hasChromaticities (in.header()))
chroma = chromaticities (in.header());
yWeights = RgbaYca::computeYw (chroma);
}
else
{
//
// RGB mode
//
// The pixel buffers for the tree image channels (RGB)
// are padded with a fourth dummy channel (A) and interleaved
// (RGBARGBARGBA...). All three buffers have the same width
// and height as the frame.
//
ib.rgbMode = true;
for (int i = 0; i < ib.numBuffers(); ++i)
{
FrameBuffer &fb = ib.frameBuffer (i);
int w = dw.max.x - dw.min.x + 1;
int h = dw.max.y - dw.min.y + 1;
size_t pixelSize = sizeof (half) * 4;
size_t lineSize = pixelSize * w;
char *pixels = new char [lineSize * h];
ib.pixels (i, 0) = pixels;
ib.pixels (i, 1) = pixels + sizeof (half);
ib.pixels (i, 2) = pixels + sizeof (half) * 2;
fb.insert ("R",
Slice (HALF,
ib.pixels (i, 0) - dw.min.y * lineSize -
dw.min.x * pixelSize,
pixelSize,
lineSize,
1, 1));
fb.insert ("G",
Slice (HALF,
ib.pixels (i, 1) - dw.min.y * lineSize -
dw.min.x * pixelSize,
pixelSize,
lineSize,
1, 1));
fb.insert ("B",
Slice (HALF,
ib.pixels (i, 2) - dw.min.y * lineSize -
dw.min.x * pixelSize,
pixelSize,
lineSize,
1, 1));
}
}
}
//
// Compute the size of the window on the screen where the image
// sequence will be displayed, and the size and location of the
// images within that window.
//
void
computeWindowSizes
(Box2i &dataWindow,
Box2i &displayWindow,
float pixelAspectRatio,
float xyScale)
{
//
// Beginning with the data and display window of the first frame
// in the image sequence, translate both windows so that the upper
// left corner of the display window is at coordinates (0,0) in
// OpenEXR's pixel space (with y going from top to bottom).
//
dataWindow.min -= displayWindow.min;
dataWindow.max -= displayWindow.min;
displayWindow.max -= displayWindow.min;
displayWindow.min = V2i (0, 0);
//
// If the pixel aspect is not 1.0, stretch the display
// and data window so that the pixels become square.
//
if (pixelAspectRatio < 1.0)
{
dataWindow.min.y =
int (floor (dataWindow.min.y / pixelAspectRatio + 0.5));
dataWindow.max.y =
int (floor (dataWindow.max.y / pixelAspectRatio + 0.5));
displayWindow.max.y =
int (floor (displayWindow.max.y / pixelAspectRatio + 0.5));
}
if (pixelAspectRatio > 1.0)
{
dataWindow.min.x =
int (floor (dataWindow.min.x * pixelAspectRatio + 0.5));
dataWindow.max.x =
int (floor (dataWindow.max.x * pixelAspectRatio + 0.5));
displayWindow.max.x =
int (floor (displayWindow.max.x * pixelAspectRatio + 0.5));
}
//
// The size of the OpenGL window on the screen is equal to the
// size of the (possibly stretched) display window.
//
glWindowWidth = displayWindow.max.x + 1;
glWindowHeight = displayWindow.max.y + 1;
//
// The size and location of the image within the OpenGL window
// is determined by the (possibly stretched) data window.
// The data window must be transformed from OpenEXR pixel space
// to OpenGL coordinates (with y going from bottom to top).
//
drawRect.min.x = dataWindow.min.x;
drawRect.min.y = displayWindow.max.y - dataWindow.max.y;
drawRect.max.x = dataWindow.max.x + 1;
drawRect.max.y = displayWindow.max.y - dataWindow.min.y + 1;
//
// The user may have requested that the images be
// displayed smaller or larger than their original size.
//
glWindowWidth = int (floor (glWindowWidth * xyScale + 0.5));
glWindowHeight = int (floor (glWindowHeight * xyScale + 0.5));
drawRect.min.x = int (floor (drawRect.min.x * xyScale + 0.5));
drawRect.min.y = int (floor (drawRect.min.y * xyScale + 0.5));
drawRect.max.x = int (floor (drawRect.max.x * xyScale + 0.5));
drawRect.max.y = int (floor (drawRect.max.y * xyScale + 0.5));
}
//
// Cg shaders. For each frame, the drawFrame() function, below, draws
// a big rectangle that fills the entire OpenGL window. The current
// frame is stored in one or three textures; a Cg shader projects the
// textures onto the rectangle, making the frame appear in the window.
//
CGcontext cgContext;
CGprogram cgProgram;
CGprofile cgProfile;
void
handleCgErrors ()
{
cerr << cgGetErrorString (cgGetError()) << endl;
cerr << cgGetLastListing (cgContext) << endl;
exit (1);
}
//
// Shader for luminance/chroma images:
// R, G and B are computed from the full-resolution Y (luminance)
// channel and the half-resolution RY and BY (chroma) channels.
//
const char shaderLuminanceChromaSource[] =
" \n"
" struct Out \n"
" { \n"
" half3 pixel: COLOR; \n"
" }; \n"
" \n"
" Out \n"
" main (float2 tc: TEXCOORD0, \n"
" uniform sampler2D yImage: TEXUNIT0, \n"
" uniform sampler2D ryImage: TEXUNIT1, \n"
" uniform sampler2D byImage: TEXUNIT2, \n"
" uniform sampler3D lut: TEXUNIT3, \n"
" uniform float3 yw, \n"
" uniform float expMult, \n"
" uniform float videoGamma, \n"
" uniform float lutMin, \n"
" uniform float lutMax, \n"
" uniform float lutM, \n"
" uniform float lutT, \n"
" uniform float lutF, \n"
" uniform float enableLut) \n"
" { \n"
" // \n"
" // Sample luminance and chroma, convert to RGB. \n"
" // \n"
" \n"
" half Y = tex2D (yImage, tc).r; \n"
" half RY = tex2D (ryImage, tc).r; \n"
" half BY = tex2D (byImage, tc).r; \n"
" \n"
" float r = (RY + 1) * Y; \n"
" float b = (BY + 1) * Y; \n"
" float g = (Y - r * yw.x - b * yw.z) / yw.y; \n"
" \n"
" // \n"
" // Apply exposure \n"
" // \n"
" \n"
" half3 color = half3 (r, g, b) * expMult; \n"
" \n"
" // \n"
" // Apply 3D color lookup table (in log space). \n"
" // \n"
" \n"
" if (enableLut) \n"
" { \n"
" if (lutF) \n"
" { \n"
" // \n"
" // Texture hardware does not support \n"
" // interpolation between texture samples. \n"
" // \n"
" \n"
" half3 i = lutF * half3 \n"
" (lutT + lutM * log (clamp (color, lutMin, lutMax))); \n"
" \n"
" half3 fi = floor (i); \n"
" half3 fj = fi + 1; \n"
" half3 s = i - fi; \n"
" \n"
" fi = fi / lutF; \n"
" fj = fj / lutF; \n"
" \n"
" half3 c0 = tex3D (lut, half3 (fi.x, fi.y, fi.z)).rgb; \n"
" half3 c1 = tex3D (lut, half3 (fj.x, fi.y, fi.z)).rgb; \n"
" half3 c2 = tex3D (lut, half3 (fi.x, fj.y, fi.z)).rgb; \n"
" half3 c3 = tex3D (lut, half3 (fj.x, fj.y, fi.z)).rgb; \n"
" half3 c4 = tex3D (lut, half3 (fi.x, fi.y, fi.z)).rgb; \n"
" half3 c5 = tex3D (lut, half3 (fj.x, fi.y, fj.z)).rgb; \n"
" half3 c6 = tex3D (lut, half3 (fi.x, fj.y, fj.z)).rgb; \n"
" half3 c7 = tex3D (lut, half3 (fj.x, fj.y, fj.z)).rgb; \n"
" \n"
" color = ((c0 * (1-s.x) + c1 * s.x) * (1-s.y) + \n"
" (c2 * (1-s.x) + c3 * s.x) * s.y) * (1-s.z) + \n"
" ((c4 * (1-s.x) + c5 * s.x) * (1-s.y) + \n"
" (c6 * (1-s.x) + c7 * s.x) * s.y) * s.z; \n"
" \n"
" color = exp (color); \n"
" } \n"
" else \n"
" { \n"
" // \n"
" // Texture hardware supports trilinear \n"
" // interpolation between texture samples. \n"
" // \n"
" \n"
" color = lutT + lutM * log (clamp (color, lutMin, lutMax));\n"
" color = exp (tex3D (lut, color).rgb); \n"
" } \n"
" } \n"
" \n"
" // \n"
" // Apply video gamma correction. \n"
" // \n"
" \n"
" Out output; \n"
" output.pixel = pow (color, videoGamma); \n"
" return output; \n"
" } \n"
" \n";
void
initShaderLuminanceChroma
(float lutMin,
float lutMax,
float lutM,
float lutT)
{
cgSetErrorCallback (handleCgErrors);
cgContext = cgCreateContext();
cgProfile = cgGLGetLatestProfile (CG_GL_FRAGMENT);
cgGLSetOptimalOptions (cgProfile);
cgProgram =
cgCreateProgram (cgContext, CG_SOURCE, shaderLuminanceChromaSource,
cgProfile, "main", 0);
cgGLLoadProgram (cgProgram);
cgGLBindProgram (cgProgram);
cgGLEnableProfile (cgProfile);
CGparameter ywParam = cgGetNamedParameter (cgProgram, "yw");
cgSetParameter3f (ywParam, yWeights.x, yWeights.y, yWeights.z);
CGparameter emParam = cgGetNamedParameter (cgProgram, "expMult");
cgSetParameter1f (emParam, pow (2.0f, exposure));
CGparameter vgParam = cgGetNamedParameter (cgProgram, "videoGamma");
cgSetParameter1f (vgParam, displayVideoGamma());
CGparameter lutMinParam = cgGetNamedParameter (cgProgram, "lutMin");
cgSetParameter1f (lutMinParam, lutMin);
CGparameter lutMaxParam = cgGetNamedParameter (cgProgram, "lutMax");
cgSetParameter1f (lutMaxParam, lutMax);
CGparameter lutMParam = cgGetNamedParameter (cgProgram, "lutM");
cgSetParameter1f (lutMParam, lutM);
CGparameter lutTParam = cgGetNamedParameter (cgProgram, "lutT");
cgSetParameter1f (lutTParam, lutT);
CGparameter enableLutParam = cgGetNamedParameter (cgProgram, "enableLut");
cgSetParameter1f (enableLutParam, enableCtl? 1.0: 0.0);
CGparameter lutFParam = cgGetNamedParameter (cgProgram, "lutF");
cgSetParameter1f (lutFParam, hwTexInterpolation? 0: lutN - 1);
}
//
// Shader for RGB images
//
const char shaderRgbSource[] =
" \n"
" struct Out \n"
" { \n"
" half3 pixel: COLOR; \n"
" }; \n"
" \n"
" Out \n"
" main (float2 tc: TEXCOORD0, \n"
" uniform sampler2D rgbImage: TEXUNIT0, \n"
" uniform sampler3D lut: TEXUNIT3, \n"
" uniform float expMult, \n"
" uniform float videoGamma, \n"
" uniform float lutMin, \n"
" uniform float lutMax, \n"
" uniform float lutM, \n"
" uniform float lutT, \n"
" uniform float lutF, \n"
" uniform float enableLut) \n"
" { \n"
" // \n"
" // Sample RGB image, apply exposure. \n"
" // \n"
" \n"
" half3 color = tex2D (rgbImage, tc).rgb * expMult; \n"
" \n"
" // \n"
" // Apply 3D color lookup table (in log space). \n"
" // \n"
" \n"
" if (enableLut) \n"
" { \n"
" if (lutF) \n"
" { \n"
" // \n"
" // Texture hardware does not support \n"
" // interpolation between texture samples. \n"
" // \n"
" \n"
" half3 i = lutF * half3 \n"
" (lutT + lutM * log (clamp (color, lutMin, lutMax))); \n"
" \n"
" half3 fi = floor (i); \n"
" half3 fj = fi + 1; \n"
" half3 s = i - fi; \n"
" \n"
" fi = fi / lutF; \n"
" fj = fj / lutF; \n"
" \n"
" half3 c0 = tex3D (lut, half3 (fi.x, fi.y, fi.z)).rgb; \n"
" half3 c1 = tex3D (lut, half3 (fj.x, fi.y, fi.z)).rgb; \n"
" half3 c2 = tex3D (lut, half3 (fi.x, fj.y, fi.z)).rgb; \n"
" half3 c3 = tex3D (lut, half3 (fj.x, fj.y, fi.z)).rgb; \n"
" half3 c4 = tex3D (lut, half3 (fi.x, fi.y, fi.z)).rgb; \n"
" half3 c5 = tex3D (lut, half3 (fj.x, fi.y, fj.z)).rgb; \n"
" half3 c6 = tex3D (lut, half3 (fi.x, fj.y, fj.z)).rgb; \n"
" half3 c7 = tex3D (lut, half3 (fj.x, fj.y, fj.z)).rgb; \n"
" \n"
" color = ((c0 * (1-s.x) + c1 * s.x) * (1-s.y) + \n"
" (c2 * (1-s.x) + c3 * s.x) * s.y) * (1-s.z) + \n"
" ((c4 * (1-s.x) + c5 * s.x) * (1-s.y) + \n"
" (c6 * (1-s.x) + c7 * s.x) * s.y) * s.z; \n"
" \n"
" color = exp (color); \n"
" } \n"
" else \n"
" { \n"
" // \n"
" // Texture hardware supports trilinear \n"
" // interpolation between texture samples. \n"
" // \n"
" \n"
" color = lutT + lutM * log (clamp (color, lutMin, lutMax));\n"
" color = exp (tex3D (lut, color).rgb); \n"
" } \n"
" } \n"
" \n"
" // \n"
" // Apply video gamma correction. \n"
" // \n"
" \n"
" Out output; \n"
" output.pixel = pow (color, videoGamma); \n"
" return output; \n"
" } \n"
" \n";
void
initShaderRgb
(float lutMin,
float lutMax,
float lutM,
float lutT)
{
cgSetErrorCallback (handleCgErrors);
cgContext = cgCreateContext();
cgProfile = cgGLGetLatestProfile (CG_GL_FRAGMENT);
cgGLSetOptimalOptions (cgProfile);
cgProgram =
cgCreateProgram (cgContext, CG_SOURCE, shaderRgbSource,
cgProfile, "main", 0);
cgGLLoadProgram (cgProgram);
cgGLBindProgram (cgProgram);
cgGLEnableProfile (cgProfile);
CGparameter emParam = cgGetNamedParameter (cgProgram, "expMult");
cgSetParameter1f (emParam, pow (2.0f, exposure));
CGparameter vgParam = cgGetNamedParameter (cgProgram, "videoGamma");
cgSetParameter1f (vgParam, displayVideoGamma());
CGparameter lutMinParam = cgGetNamedParameter (cgProgram, "lutMin");
cgSetParameter1f (lutMinParam, lutMin);
CGparameter lutMaxParam = cgGetNamedParameter (cgProgram, "lutMax");
cgSetParameter1f (lutMaxParam, lutMax);
CGparameter lutMParam = cgGetNamedParameter (cgProgram, "lutM");
cgSetParameter1f (lutMParam, lutM);
CGparameter lutTParam = cgGetNamedParameter (cgProgram, "lutT");
cgSetParameter1f (lutTParam, lutT);
CGparameter enableLutParam = cgGetNamedParameter (cgProgram, "enableLut");
cgSetParameter1f (enableLutParam, enableCtl? 1.0: 0.0);
CGparameter lutFParam = cgGetNamedParameter (cgProgram, "lutF");
cgSetParameter1f (lutFParam, hwTexInterpolation? 0: lutN - 1);
}
//
// GL drawing code
//
void
checkGlErrors (const char where[])
{
GLenum error = glGetError();
if (error != GL_NO_ERROR)
{
cerr << where << ": " << gluErrorString (error) << endl;
exit (1);
}
}
void
handleReshape (int w, int h)
{
int xOffset = (w - glWindowWidth) / 2;
int yOffset = (h - glWindowHeight) / 2;
glViewport (xOffset, yOffset, glWindowWidth, glWindowHeight);
glScissor (xOffset, yOffset, glWindowWidth, glWindowHeight);
checkGlErrors ("handleReshape");
}
void
initTexturesLuminanceChroma ()
{
const Box2i &dw = ib.dataWindow;
int w = dw.max.x - dw.min.x + 1;
int h = dw.max.y - dw.min.y + 1;
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
glGenTextures (3, texId);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texId[0]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D (GL_TEXTURE_2D,
0, // level
GL_LUMINANCE16F_ARB, // internalFormat
w, h,
0, // border
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
0); // pixels
glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, texId[1]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D (GL_TEXTURE_2D,
0, // level
GL_LUMINANCE16F_ARB, // internalFormat
w / 2, h / 2,
0, // border
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
0); // pixels
glActiveTexture (GL_TEXTURE2);
glBindTexture (GL_TEXTURE_2D, texId[2]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D (GL_TEXTURE_2D,
0, // level
GL_LUMINANCE16F_ARB, // internalFormat
w / 2, h / 2,
0, // border
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
0); // pixels
checkGlErrors ("initTexturesLuminanceChroma");
}
void
initTexturesRgb ()
{
const Box2i &dw = ib.dataWindow;
int w = dw.max.x - dw.min.x + 1;
int h = dw.max.y - dw.min.y + 1;
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
glGenTextures (1, texId);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texId[0]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D (GL_TEXTURE_2D,
0, // level
GL_RGBA16F_ARB, // internalFormat
w, h,
0, // border
GL_RGBA, // format
GL_HALF_FLOAT_ARB, // type
0); // pixels
checkGlErrors ("initTexturesRgb");
}
void
init3DLut
(const vector<string> &transformNames, // names of CTL transforms
const Header &header, // header of first frame
float &lutMin,
float &lutMax,
float &lutM,
float &lutT)
{
//
// We build a 3D color lookup table by running a set of color
// samples through a series of CTL 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.
//
//
// Compute lutMin, lutMax, and scale and offset
// values, lutM and lutT, so that
//
// lutM * lutMin + lutT == 0
// lutM * lutMax + lutT == 1
//
static const int NUM_STOPS = 7;
static const float MIDDLE_GRAY = 0.18;
lutMin = MIDDLE_GRAY / (1 << NUM_STOPS);
lutMax = MIDDLE_GRAY * (1 << NUM_STOPS);
float logLutMin = log (lutMin);
float logLutMax = log (lutMax);
lutM = 1 / (logLutMax - logLutMin);
lutT = -lutM * logLutMin;
size_t LUT_SIZE = lutN * lutN * lutN * 4;
//
// Build a 3D array of RGB input pixel values.
// such that R, G and B are between lutMin and lutMax.
//
Array<half> pixelValues (LUT_SIZE);
for (size_t ib = 0; ib < lutN; ++ib)
{
float b = ib / (lutN - 1.0);
half B = exp ((b - lutT) / lutM);
for (size_t ig = 0; ig < lutN; ++ig)
{
float g = ig / (lutN - 1.0);
half G = exp ((g - lutT) / lutM);
for (int ir = 0; ir < lutN; ++ir)
{
float r = ir / (lutN - 1.0);
half R = exp ((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;
}
}
}
//
// Initialize an array output pixel values to zero.
//
Array<half> lut (LUT_SIZE);
for (int i = 0; i < LUT_SIZE; ++i)
lut[i] = 0;
//
// 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.)
//
ctlToLut (transformNames, header, LUT_SIZE, pixelValues, lut);
//
// Take the logarithm of the output values that were
// produced by the CTL transforms.
//
for (int i = 0; i < LUT_SIZE; ++i)
{
if (lut[i] >= HALF_MIN && lut[i] <= HALF_MAX)
{
//
// lut[i] is finite and positive.
//
lut[i] = log (lut[i]);
}
else
{
//
// lut[i] is zero, negative or not finite;
// log (lut[i]) is undefined.
//
lut[i] = log (HALF_MIN);
}
}
//
// Convert the output values into a 3D texture.
//
GLuint lutId;
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
glGenTextures (1, &lutId);
glActiveTexture (GL_TEXTURE3);
glBindTexture (GL_TEXTURE_3D, lutId);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage3D (GL_TEXTURE_3D,
0, // level
GL_RGBA16F_ARB, // internalFormat
lutN, lutN, lutN, // width, height, depth
0, // border
GL_RGBA, // format
GL_HALF_FLOAT_ARB, // type
(char *) &lut[0]);
checkGlErrors ("init3DLut");
}
void
drawString (GLfloat x, GLfloat y, const char str[])
{
//
// Draw a text string.
//
glPushMatrix();
glTranslatef (x, y, 0);
glScalef (0.10, 0.15, 0);
while (*str)
glutStrokeCharacter (GLUT_STROKE_MONO_ROMAN, *str++);
glPopMatrix();
}
void
drawStringWithBorder (GLfloat x, GLfloat y, const char str[])
{
//
// Draw text string where each character is surrounded
// by a one pixel wide black border.
//
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable (GL_BLEND);
glEnable (GL_LINE_SMOOTH);
glColor4f (0, 0, 0, 1);
glLineWidth (2);
drawString (x - 1, y - 1, str);
drawString (x + 1, y - 1, str);
drawString (x - 1, y + 1, str);
drawString (x + 1, y + 1, str);
glLineWidth (2);
glColor4f (0.8, 0.8, 0.8, 1);
drawString (x, y, str);
glDisable (GL_LINE_SMOOTH);
glDisable (GL_BLEND);
}
void
drawFrame ()
{
//
// Draw the current frame
//
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
glOrtho (0, glWindowWidth, 0, glWindowHeight, -1, 1);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glDisable (GL_SCISSOR_TEST);
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear (GL_COLOR_BUFFER_BIT);
glEnable (GL_SCISSOR_TEST);
glClearColor (0.3, 0.3, 0.3, 1.0);
glClear (GL_COLOR_BUFFER_BIT);
//
// Convert the pixels of the current frame into OpenGL textures.
//
const Box2i &dw = ib.dataWindow;
int w = dw.max.x - dw.min.x + 1;
int h = dw.max.y - dw.min.y + 1;
if (ib.rgbMode)
{
glActiveTexture (GL_TEXTURE0);
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, texId[0]);
glTexSubImage2D (GL_TEXTURE_2D,
0, // level
0, 0, // xoffset, yoffset
w, h,
GL_RGBA, // format
GL_HALF_FLOAT_ARB, // type
(GLvoid *) ib.pixels (i, 0));
}
else
{
glActiveTexture (GL_TEXTURE0);
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, texId[0]);
glTexSubImage2D (GL_TEXTURE_2D,
0, // level
0, 0, // xoffset, yoffset
w, h,
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
(GLvoid *) ib.pixels (i, 0));
glActiveTexture (GL_TEXTURE1);
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, texId[1]);
glTexSubImage2D (GL_TEXTURE_2D,
0, // level
0, 0, // xoffset, yoffset
w / 2, h / 2,
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
(GLvoid *) ib.pixels (i, 1));
glActiveTexture (GL_TEXTURE2);
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, texId[2]);
glTexSubImage2D (GL_TEXTURE_2D,
0, // level
0, 0, // xoffset, yoffset
w / 2, h / 2,
GL_LUMINANCE, // format
GL_HALF_FLOAT_ARB, // type
(GLvoid *) ib.pixels (i, 2));
}
//
// Enable Cg shading and draw a rectangle that fills
// the entire data window. The textures will be mapped
// onto this rectangle.
//
glActiveTexture (GL_TEXTURE3);
glEnable (GL_TEXTURE_3D);
cgGLEnableProfile (cgProfile);
glBegin (GL_POLYGON);
glTexCoord2f (0, 1);
glVertex2i (drawRect.min.x, drawRect.min.y);
glTexCoord2f (1, 1);
glVertex2i (drawRect.max.x, drawRect.min.y);
glTexCoord2f (1, 0);
glVertex2i (drawRect.max.x, drawRect.max.y);
glTexCoord2f (0, 0);
glVertex2i (drawRect.min.x, drawRect.max.y);
glEnd ();
//
// Disable texture mapping and Cg shading and draw the text overlay
// that indicates the frame rate and exposure settings.
//
if (showTextOverlay)
{
glActiveTexture (GL_TEXTURE0);
glDisable (GL_TEXTURE_2D);
glActiveTexture (GL_TEXTURE1);
glDisable (GL_TEXTURE_2D);
glActiveTexture (GL_TEXTURE2);
glDisable (GL_TEXTURE_2D);
glActiveTexture (GL_TEXTURE3);
glDisable (GL_TEXTURE_3D);
cgGLDisableProfile (cgProfile);
glShadeModel (GL_FLAT);
stringstream ss;
ss << setw (6) << frameNumber << " ";
if (timer.playState == RUNNING)
{
ss << setiosflags (ios_base::fixed) <<
setprecision (2) << setw (7) <<
timer.actualFrameRate() << " fps";
}
else
{
ss << " pause";
}
ss << " " <<
setiosflags (ios_base::showpos | ios_base::fixed) <<
setprecision (1) << setw (5) <<
exposure << " stops";
if (ib.rgbMode)
ss << " RGB";
else
ss << " YC";
if (!enableCtl)
ss << " CTL off";
drawStringWithBorder (20, 20, ss.str().c_str());
}
checkGlErrors ("drawFrame");
}
void
redrawWindow ()
{
//
// Display the next image on the screen
//
//
// Exit if the file reading thread has terminated.
//
if (ib.exitSemaphore2.tryWait())
exit (1);
//
// Wait until it is time to display the next image
//
timer.waitUntilNextFrameIsDue();
//
// Wait until the file reading thread has made the next frame available
//
if (timer.playState == RUNNING || timer.playState == PREPARE_TO_PAUSE)
ib.fullBuffersSemaphore.wait();
if (timer.playState == PREPARE_TO_PAUSE)
timer.playState = PAUSE;
//
// Draw the frame
//
frameNumber = ib.frameNumber (i);
drawFrame ();
//
// Return the image buffer to the file reading thread
//
if (timer.playState == RUNNING || timer.playState == PREPARE_TO_RUN)
{
i = (i + 1) % ib.numBuffers();
ib.emptyBuffersSemaphore.post();
}
if (timer.playState == PREPARE_TO_RUN)
timer.playState = RUNNING;
//
// Flush and ѕwap buffers to make the frame visible
//
glFlush();
glutSwapBuffers();
//
// Make sure this function gets called again immediately
//
if (timer.playState == RUNNING || timer.playState == PREPARE_TO_RUN)
glutPostRedisplay();
}
void
handleKeypress (unsigned char key, int, int)
{
if (key == 'q' || key == 0x1b)
{
//
// Quit: In order to make sure that the file reading thread
// won't crash by trying to use shared resources while we
// exit, we first tell the file reading thread to exit.
// Then we wait until the file reading thread signals that
// it has received the exit command. At this point, it is
// safe to exit.
//
ib.exitSemaphore1.post();
ib.emptyBuffersSemaphore.post();
ib.exitSemaphore2.wait();
exit (0);
}
if (key == '>' || key == '.' || key == '<' || key == ',')
{
//
// Change exposure: 1 f-stop brighter or darker
//
if ((key == '>' || key == '.') && exposure < 10)
exposure += 1;
if ((key == '<' || key == ',') && exposure > -10)
exposure -= 1;
CGparameter emParam = cgGetNamedParameter (cgProgram, "expMult");
cgSetParameter1f (emParam, pow (2.0f, exposure));
glutPostRedisplay();
}
#if HAVE_CTL_INTERPRETER
if (key == 'c' || key == 'C')
{
//
// Toggle CTL transforms on/off
//
CGparameter enableLutParam =
cgGetNamedParameter (cgProgram, "enableLut");
enableCtl = !enableCtl;
cgSetParameter1f (enableLutParam, enableCtl? 1.0: 0.0);
glutPostRedisplay();
}
#endif
if (key == 'o' || key == 'O')
{
//
// Toggle text overlay on/off
//
showTextOverlay = !showTextOverlay;
glutPostRedisplay();
}
if (key == 'p' || key == 'P' || key == 'l' || key == 'L')
{
//
// Toggle between playing forward and pause
//
if (timer.playState == RUNNING && ib.forward)
timer.playState = PREPARE_TO_PAUSE;
if (timer.playState == PAUSE)
timer.playState = PREPARE_TO_RUN;
ib.forward = true;
glutPostRedisplay();
}
if (key == 'h' || key == 'H')
{
//
// Toggle between playing backward and pause
//
if (timer.playState == RUNNING && !ib.forward)
timer.playState = PREPARE_TO_PAUSE;
if (timer.playState == PAUSE)
timer.playState = PREPARE_TO_RUN;
ib.forward = false;
glutPostRedisplay();
}
if (key == 'j' || key == 'J' || key == 'k' || key == 'K')
{
//
// Step one frame forward or backward
//
if (timer.playState == RUNNING || timer.playState == PREPARE_TO_PAUSE)
ib.fullBuffersSemaphore.wait();
if (key == 'k' || key == 'K')
ib.forward = true;
else
ib.forward = false;
timer.playState = PAUSE;
int newFrameNumber;
if (ib.forward)
{
if (frameNumber >= lastFrameNumber)
newFrameNumber = firstFrameNumber;
else
newFrameNumber = frameNumber + 1;
}
else
{
if (ib.frameNumber(i) <= firstFrameNumber)
newFrameNumber = lastFrameNumber;
else
newFrameNumber = frameNumber - 1;
}
while (ib.frameNumber(i) != newFrameNumber)
{
i = (i + 1) % ib.numBuffers();
ib.emptyBuffersSemaphore.post();
ib.fullBuffersSemaphore.wait();
}
glutPostRedisplay();
}
if (key == 'f' || key == 'F')
{
//
// Toggle full-screen mode on/off
//
fullScreenMode = !fullScreenMode;
if (fullScreenMode)
glutFullScreen();
else
glutReshapeWindow (glWindowWidth, glWindowHeight);
glutPostRedisplay();
}
}
} // namespace
void
playExr (const char fileNameTemplate[],
int firstFrame,
int lastFrame,
int numThreads,
float fps,
float xyScale,
const vector<string> &transformNames,
bool useHwTexInterpolation)
{
//
// Set the number of threads the IlmImf library
// will use internally for OpenEXR file reading.
//
OPENEXR_IMF_NAMESPACE::setGlobalThreadCount (numThreads);
//
// Allocate buffers for the images, initialize semaphores
// for synchronization between the file reading thread
// and the display loop in the main thread
//
firstFrameNumber = firstFrame;
lastFrameNumber = lastFrame;
Header header;
initializeImageBuffers (ib, header, fileNameTemplate);
//
// Determine the playback frame rate.
//
if (fps < 0)
{
if (hasFramesPerSecond (header) && framesPerSecond (header) >= 1)
fps = framesPerSecond (header);
else
fps = 24;
}
//
// Compute on-screen window sizes
//
computeWindowSizes (header.dataWindow(),
header.displayWindow(),
header.pixelAspectRatio(),
xyScale);
//
// Create an OpenGL window
//
glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowSize (glWindowWidth, glWindowHeight);
glutCreateWindow (fileNameTemplate);
glutKeyboardFunc (handleKeypress);
glutReshapeFunc (handleReshape);
glutDisplayFunc (redrawWindow);
//
// Verify that OpenGL supports the extensions we need
//
initAndCheckGlExtensions();
//
// Initialize textures and Cg shaders
//
float xMin;
float xMax;
float m;
float t;
init3DLut (transformNames, header, xMin, xMax, m, t);
hwTexInterpolation = useHwTexInterpolation;
if (ib.rgbMode)
{
initShaderRgb (xMin, xMax, m, t);
initTexturesRgb();
}
else
{
initShaderLuminanceChroma (xMin, xMax, m, t);
initTexturesLuminanceChroma();
}
//
// Start the file reading thread and the display loop
//
FileReadingThread frt (fileNameTemplate, firstFrame, lastFrame, ib);
timer.playState = (firstFrame != lastFrame)? RUNNING: PREPARE_TO_PAUSE;
timer.setDesiredFrameRate (fps);
glutMainLoop();
}