1499 lines
47 KiB
C++
1499 lines
47 KiB
C++
///////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// 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();
|
||
}
|