2022-04-07 18:46:57 +02:00

1499 lines
47 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

///////////////////////////////////////////////////////////////////////////
//
// 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();
}