712 lines
17 KiB
C
712 lines
17 KiB
C
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) 2003, 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.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// PhotoRealistic RenderMan display driver that outputs
|
|
// floating-point image files, using ILM's IlmImf library.
|
|
//
|
|
// When you use this display driver for RGBA or Z output, you should
|
|
// turn RGBA and Z quantization off by adding the following lines to
|
|
// your RIB file:
|
|
//
|
|
// Quantize "rgba" 0 0 0 0
|
|
// Quantize "z" 0 0 0 0
|
|
//
|
|
// Like Pixar's Tiff driver, this display driver can output image
|
|
// channels other than R, G, B and A; for details on RIB file and
|
|
// shader syntax, see the Renderman Release Notes (New Display
|
|
// System, RGBAZ Output Images, Arbitrary Output Variables).
|
|
//
|
|
// This driver maps Renderman's output variables to image channels
|
|
// as follows:
|
|
//
|
|
// Renderman output image channel image channel
|
|
// variable name name type
|
|
// --------------------------------------------------------------
|
|
//
|
|
// "r" "R" HALF
|
|
//
|
|
// "g" "G" HALF
|
|
//
|
|
// "b" "B" HALF
|
|
//
|
|
// "a" "A" HALF
|
|
//
|
|
// "z" "Z" FLOAT
|
|
//
|
|
// other same as output preferred type
|
|
// variable name (see below)
|
|
//
|
|
// By default, the "preferred" channel type is HALF; the
|
|
// preferred type can be changed by adding an "exrpixeltype"
|
|
// argument to the Display command in the RIB file.
|
|
// For example:
|
|
//
|
|
// Declare "exrpixeltype" "string"
|
|
//
|
|
// # Store point positions in FLOAT format
|
|
// Display "gnome.points.exr" "exr" "P" "exrpixeltype" "float"
|
|
//
|
|
// The default compression method for the image's pixel data
|
|
// is defined in ImfHeader.h. You can select a different
|
|
// compression method by adding an "exrcompression" argument
|
|
// to the Display command. For example:
|
|
//
|
|
// Declare "exrcompression" "string"
|
|
//
|
|
// # Store RGBA using run-length encoding
|
|
// Display "gnome.rgba.exr" "exr" "rgba" "exrcompression" "rle"
|
|
//
|
|
// See function DspyImageOpen(), below, for a list of valid
|
|
// "exrpixeltype" and "exrcompression" values.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#undef NDEBUG // enable assert()
|
|
|
|
#include <ImfOutputFile.h>
|
|
#include <ImfChannelList.h>
|
|
#include <ImfIntAttribute.h>
|
|
#include <ImfFloatAttribute.h>
|
|
#include <ImfMatrixAttribute.h>
|
|
#include <ImfLut.h>
|
|
#include <ImfArray.h>
|
|
#include <ImathFun.h>
|
|
#include <Iex.h>
|
|
#include <half.h>
|
|
#include <halfFunction.h>
|
|
#include <string>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <ndspy.h>
|
|
|
|
using namespace Imath;
|
|
using namespace Imf;
|
|
using namespace std;
|
|
|
|
namespace {
|
|
|
|
typedef map <string, int> ChannelOffsetMap;
|
|
typedef vector <halfFunction <half> *> ChannelLuts;
|
|
|
|
//
|
|
// Define halfFunctions for the identity and piz12
|
|
//
|
|
half halfID( half x ) { return x; }
|
|
|
|
halfFunction <half> id( halfID );
|
|
halfFunction <half> piz12( round12log );
|
|
|
|
class Image
|
|
{
|
|
public:
|
|
|
|
Image (const char filename[],
|
|
const Header &header,
|
|
ChannelOffsetMap &rmanChannelOffsets,
|
|
int rmanPixelSize,
|
|
ChannelLuts &channelLuts);
|
|
|
|
const Header & header () const;
|
|
|
|
void writePixels (int xMin, int xMaxPlusone,
|
|
int yMin, int yMaxPlusone,
|
|
int entrySize,
|
|
const unsigned char *data);
|
|
private:
|
|
|
|
OutputFile _file;
|
|
Array <char> _buffer;
|
|
vector <int> _rmanChannelOffsets;
|
|
vector <int> _bufferChannelOffsets;
|
|
int _rmanPixelSize;
|
|
int _bufferPixelSize;
|
|
int _bufferXMin;
|
|
int _bufferNumPixels;
|
|
int _numPixelsReceived;
|
|
ChannelLuts _channelLuts;
|
|
};
|
|
|
|
|
|
Image::Image (const char filename[],
|
|
const Header &header,
|
|
ChannelOffsetMap &rmanChannelOffsets,
|
|
int rmanPixelSize,
|
|
ChannelLuts &channelLuts
|
|
)
|
|
:
|
|
_file (filename, header),
|
|
_rmanPixelSize (rmanPixelSize),
|
|
_bufferPixelSize (0),
|
|
_bufferXMin (header.dataWindow().min.x),
|
|
_bufferNumPixels (header.dataWindow().max.x - _bufferXMin + 1),
|
|
_numPixelsReceived (0),
|
|
_channelLuts (channelLuts)
|
|
{
|
|
|
|
V2i dwSize = header.dataWindow().size();
|
|
|
|
for (ChannelList::ConstIterator i = header.channels().begin();
|
|
i != header.channels().end();
|
|
++i)
|
|
{
|
|
switch (i.channel().type)
|
|
{
|
|
case HALF:
|
|
|
|
_rmanChannelOffsets.push_back (rmanChannelOffsets[i.name()]);
|
|
_bufferChannelOffsets.push_back (_bufferPixelSize);
|
|
_bufferPixelSize += sizeof (float); // Note: to avoid alignment
|
|
break; // problems when float and half
|
|
// channels are mixed, halfs
|
|
case FLOAT: // are not packed densely.
|
|
|
|
_rmanChannelOffsets.push_back (rmanChannelOffsets[i.name()]);
|
|
_bufferChannelOffsets.push_back (_bufferPixelSize);
|
|
_bufferPixelSize += sizeof (float);
|
|
break;
|
|
|
|
default:
|
|
|
|
assert (false); // unsupported channel type
|
|
break;
|
|
}
|
|
}
|
|
|
|
_buffer.resizeErase (_bufferNumPixels * _bufferPixelSize);
|
|
|
|
FrameBuffer fb;
|
|
int j = 0;
|
|
int yStride = 0;
|
|
char *base = &_buffer[0] -
|
|
_bufferXMin * _bufferPixelSize;
|
|
|
|
|
|
for (ChannelList::ConstIterator i = header.channels().begin();
|
|
i != header.channels().end();
|
|
++i)
|
|
{
|
|
fb.insert (i.name(),
|
|
Slice (i.channel().type, // type
|
|
base + _bufferChannelOffsets[j], // base
|
|
_bufferPixelSize, // xStride
|
|
yStride, // yStride
|
|
1, // xSampling
|
|
1)); // ySampling
|
|
++j;
|
|
}
|
|
|
|
_file.setFrameBuffer (fb);
|
|
}
|
|
|
|
|
|
const Header &
|
|
Image::header () const
|
|
{
|
|
return _file.header();
|
|
}
|
|
|
|
|
|
void
|
|
Image::writePixels (int xMin, int xMaxPlusone,
|
|
int yMin, int yMaxPlusone,
|
|
int entrySize,
|
|
const unsigned char *data)
|
|
{
|
|
//
|
|
// We can only deal with one scan line at a time.
|
|
//
|
|
|
|
assert (yMin == yMaxPlusone - 1);
|
|
|
|
const ChannelList &channels = _file.header().channels();
|
|
int numPixels = xMaxPlusone - xMin;
|
|
int j = 0;
|
|
|
|
char *toBase;
|
|
int toInc;
|
|
|
|
//
|
|
// Copy the pixels into our internal one-line frame buffer.
|
|
//
|
|
|
|
toBase = _buffer + _bufferPixelSize * xMin;
|
|
toInc = _bufferPixelSize;
|
|
|
|
for (ChannelList::ConstIterator i = channels.begin();
|
|
i != channels.end();
|
|
++i)
|
|
{
|
|
const unsigned char *from = data + _rmanChannelOffsets[j];
|
|
const unsigned char *end = from + numPixels * entrySize;
|
|
|
|
char *to = toBase + _bufferChannelOffsets[j];
|
|
|
|
switch (i.channel().type)
|
|
{
|
|
case HALF:
|
|
{
|
|
halfFunction <half> &lut = *_channelLuts[j];
|
|
|
|
while (from < end)
|
|
{
|
|
*(half *) to = lut( ( half )( *(float *) from ) );
|
|
from += entrySize;
|
|
to += toInc;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case FLOAT:
|
|
|
|
while (from < end)
|
|
{
|
|
*(float *) to = *(float *) from;
|
|
from += entrySize;
|
|
to += toInc;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert (false); // channel type is not currently supported
|
|
break;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
_numPixelsReceived += numPixels;
|
|
assert (_numPixelsReceived <= _bufferNumPixels);
|
|
|
|
if (_numPixelsReceived == _bufferNumPixels)
|
|
{
|
|
//
|
|
// If our one-line frame buffer is full, then write it to
|
|
// the output file.
|
|
//
|
|
|
|
_file.writePixels();
|
|
_numPixelsReceived = 0;
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
|
|
PtDspyError
|
|
DspyImageOpen (PtDspyImageHandle *pvImage,
|
|
const char *drivername,
|
|
const char *filename,
|
|
int width,
|
|
int height,
|
|
int paramCount,
|
|
const UserParameter *parameters,
|
|
int formatCount,
|
|
PtDspyDevFormat *format,
|
|
PtFlagStuff *flagstuff)
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// Build an output file header
|
|
//
|
|
|
|
Header header;
|
|
ChannelOffsetMap channelOffsets;
|
|
ChannelLuts channelLuts;
|
|
int pixelSize = 0;
|
|
|
|
halfFunction <half> *rgbLUT = &id;
|
|
halfFunction <half> *otherLUT = &id;
|
|
|
|
|
|
//
|
|
// Data window
|
|
//
|
|
|
|
{
|
|
Box2i &dw = header.dataWindow();
|
|
int n = 2;
|
|
|
|
DspyFindIntsInParamList ("origin", &n, &dw.min.x,
|
|
paramCount, parameters);
|
|
assert (n == 2);
|
|
|
|
dw.max.x = dw.min.x + width - 1;
|
|
dw.max.y = dw.min.y + height - 1;
|
|
}
|
|
|
|
//
|
|
// Display window
|
|
//
|
|
|
|
{
|
|
Box2i &dw = header.displayWindow();
|
|
int n = 2;
|
|
|
|
DspyFindIntsInParamList ("OriginalSize", &n, &dw.max.x,
|
|
paramCount, parameters);
|
|
assert (n == 2);
|
|
|
|
dw.min.x = 0;
|
|
dw.min.y = 0;
|
|
dw.max.x -= 1;
|
|
dw.max.y -= 1;
|
|
}
|
|
|
|
//
|
|
// Camera parameters
|
|
//
|
|
|
|
{
|
|
//
|
|
// World-to-NDC matrix, world-to-camera matrix,
|
|
// near and far clipping plane distances
|
|
//
|
|
|
|
M44f NP, Nl;
|
|
float near = 0, far = 0;
|
|
|
|
DspyFindMatrixInParamList ("NP", &NP[0][0], paramCount, parameters);
|
|
DspyFindMatrixInParamList ("Nl", &Nl[0][0], paramCount, parameters);
|
|
DspyFindFloatInParamList ("near", &near, paramCount, parameters);
|
|
DspyFindFloatInParamList ("far", &far, paramCount, parameters);
|
|
|
|
//
|
|
// The matrices reflect the orientation of the camera at
|
|
// render time.
|
|
//
|
|
|
|
header.insert ("worldToNDC", M44fAttribute (NP));
|
|
header.insert ("worldToCamera", M44fAttribute (Nl));
|
|
header.insert ("clipNear", FloatAttribute (near));
|
|
header.insert ("clipFar", FloatAttribute (far));
|
|
|
|
//
|
|
// Projection matrix
|
|
//
|
|
|
|
M44f P = Nl.inverse() * NP;
|
|
|
|
//
|
|
// Derive pixel aspect ratio, screen window width, screen
|
|
// window center from projection matrix.
|
|
//
|
|
|
|
Box2f sw (V2f ((-1 - P[3][0] - P[2][0]) / P[0][0],
|
|
(-1 - P[3][1] - P[2][1]) / P[1][1]),
|
|
V2f (( 1 - P[3][0] - P[2][0]) / P[0][0],
|
|
( 1 - P[3][1] - P[2][1]) / P[1][1]));
|
|
|
|
header.screenWindowWidth() = sw.max.x - sw.min.x;
|
|
header.screenWindowCenter() = (sw.max + sw.min) / 2;
|
|
|
|
const Box2i &dw = header.displayWindow();
|
|
|
|
header.pixelAspectRatio() = (sw.max.x - sw.min.x) /
|
|
(sw.max.y - sw.min.y) *
|
|
(dw.max.y - dw.min.y + 1) /
|
|
(dw.max.x - dw.min.x + 1);
|
|
}
|
|
|
|
//
|
|
// Line order
|
|
//
|
|
|
|
header.lineOrder() = INCREASING_Y;
|
|
flagstuff->flags |= PkDspyFlagsWantsScanLineOrder;
|
|
|
|
//
|
|
// Compression
|
|
//
|
|
|
|
{
|
|
char *comp = 0;
|
|
|
|
DspyFindStringInParamList ("exrcompression", &comp,
|
|
paramCount, parameters);
|
|
|
|
if (comp)
|
|
{
|
|
if (!strcmp (comp, "none"))
|
|
header.compression() = NO_COMPRESSION;
|
|
else if (!strcmp (comp, "rle"))
|
|
header.compression() = RLE_COMPRESSION;
|
|
else if (!strcmp (comp, "zips"))
|
|
header.compression() = ZIPS_COMPRESSION;
|
|
else if (!strcmp (comp, "zip"))
|
|
header.compression() = ZIP_COMPRESSION;
|
|
else if (!strcmp (comp, "piz"))
|
|
header.compression() = PIZ_COMPRESSION;
|
|
|
|
else if (!strcmp (comp, "piz12"))
|
|
{
|
|
header.compression() = PIZ_COMPRESSION;
|
|
rgbLUT = &piz12;
|
|
}
|
|
|
|
else
|
|
THROW (Iex::ArgExc,
|
|
"Invalid exrcompression \"" << comp << "\" "
|
|
"for image file " << filename << ".");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Channel list
|
|
//
|
|
|
|
{
|
|
PixelType pixelType = HALF;
|
|
char *ptype = 0;
|
|
|
|
DspyFindStringInParamList ("exrpixeltype", &ptype,
|
|
paramCount, parameters);
|
|
|
|
if (ptype)
|
|
{
|
|
if (!strcmp (ptype, "float"))
|
|
pixelType = FLOAT;
|
|
else if (!strcmp (ptype, "half"))
|
|
pixelType = HALF;
|
|
else
|
|
THROW (Iex::ArgExc,
|
|
"Invalid exrpixeltype \"" << ptype << "\" "
|
|
"for image file " << filename << ".");
|
|
}
|
|
|
|
ChannelList &channels = header.channels();
|
|
|
|
for (int i = 0; i < formatCount; ++i)
|
|
{
|
|
if (!strcmp (format[i].name, "r"))
|
|
{
|
|
channels.insert ("R", Channel (HALF));
|
|
channelOffsets["R"] = pixelSize;
|
|
channelLuts.push_back( rgbLUT );
|
|
}
|
|
else if (!strcmp (format[i].name, "g"))
|
|
{
|
|
channels.insert ("G", Channel (HALF));
|
|
channelOffsets["G"] = pixelSize;
|
|
channelLuts.push_back( rgbLUT );
|
|
}
|
|
else if (!strcmp (format[i].name, "b"))
|
|
{
|
|
channels.insert ("B", Channel (HALF));
|
|
channelOffsets["B"] = pixelSize;
|
|
channelLuts.push_back( rgbLUT );
|
|
}
|
|
else if (!strcmp (format[i].name, "a"))
|
|
{
|
|
channels.insert ("A", Channel (HALF));
|
|
channelOffsets["A"] = pixelSize;
|
|
channelLuts.push_back( otherLUT );
|
|
}
|
|
else if (!strcmp (format[i].name, "z"))
|
|
{
|
|
channels.insert ("Z", Channel (FLOAT));
|
|
channelOffsets["Z"] = pixelSize;
|
|
channelLuts.push_back( otherLUT );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unknown channel name; keep its name and store
|
|
// the channel, unless the name conflicts with
|
|
// another channel's name.
|
|
//
|
|
|
|
if (!channels.findChannel (format[i].name))
|
|
{
|
|
channels.insert (format[i].name, Channel (pixelType));
|
|
channelOffsets[format[i].name] = pixelSize;
|
|
channelLuts.push_back( otherLUT );
|
|
}
|
|
}
|
|
|
|
format[i].type = PkDspyFloat32 | PkDspyByteOrderNative;
|
|
pixelSize += sizeof (float);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open the output file
|
|
//
|
|
|
|
Image *image = new Image (filename,
|
|
header,
|
|
channelOffsets,
|
|
pixelSize,
|
|
channelLuts);
|
|
|
|
*pvImage = (PtDspyImageHandle) image;
|
|
}
|
|
catch (const exception &e)
|
|
{
|
|
DspyError ("OpenEXR display driver", "%s\n", e.what());
|
|
return PkDspyErrorUndefined;
|
|
}
|
|
|
|
return PkDspyErrorNone;
|
|
}
|
|
|
|
|
|
PtDspyError
|
|
DspyImageData (PtDspyImageHandle pvImage,
|
|
int xmin,
|
|
int xmax_plusone,
|
|
int ymin,
|
|
int ymax_plusone,
|
|
int entrysize,
|
|
const unsigned char *data)
|
|
{
|
|
try
|
|
{
|
|
Image *image = (Image *) pvImage;
|
|
|
|
image->writePixels (xmin, xmax_plusone,
|
|
ymin, ymax_plusone,
|
|
entrysize, data);
|
|
}
|
|
catch (const exception &e)
|
|
{
|
|
DspyError ("OpenEXR display driver", "%s\n", e.what());
|
|
return PkDspyErrorUndefined;
|
|
}
|
|
|
|
return PkDspyErrorNone;
|
|
}
|
|
|
|
|
|
PtDspyError
|
|
DspyImageClose (PtDspyImageHandle pvImage)
|
|
{
|
|
try
|
|
{
|
|
delete (Image *) pvImage;
|
|
}
|
|
catch (const exception &e)
|
|
{
|
|
DspyError ("OpenEXR display driver", "%s\n", e.what());
|
|
return PkDspyErrorUndefined;
|
|
}
|
|
|
|
return PkDspyErrorNone;
|
|
}
|
|
|
|
|
|
PtDspyError
|
|
DspyImageQuery (PtDspyImageHandle pvImage,
|
|
PtDspyQueryType querytype,
|
|
int datalen,
|
|
void *data)
|
|
{
|
|
if (datalen > 0 && data)
|
|
{
|
|
switch (querytype)
|
|
{
|
|
case PkOverwriteQuery:
|
|
{
|
|
PtDspyOverwriteInfo overwriteInfo;
|
|
|
|
if (datalen > sizeof(overwriteInfo))
|
|
datalen = sizeof(overwriteInfo);
|
|
|
|
overwriteInfo.overwrite = 1;
|
|
overwriteInfo.interactive = 0;
|
|
|
|
memcpy(data, &overwriteInfo, datalen);
|
|
}
|
|
break;
|
|
|
|
case PkSizeQuery:
|
|
{
|
|
PtDspySizeInfo sizeInfo;
|
|
|
|
if (datalen > sizeof(sizeInfo))
|
|
datalen = sizeof(sizeInfo);
|
|
|
|
const Image *image = (const Image *) pvImage;
|
|
|
|
if (image)
|
|
{
|
|
const Box2i &dw = image->header().dataWindow();
|
|
|
|
sizeInfo.width = dw.max.x - dw.min.x + 1;
|
|
sizeInfo.height = dw.max.y - dw.min.y + 1;
|
|
|
|
//
|
|
// Renderman documentation does not specify if
|
|
// sizeInfo.aspectRatio refers to the pixel or
|
|
// the image aspect ratio, but sample code in
|
|
// the documentation suggests pixel aspect ratio.
|
|
//
|
|
|
|
sizeInfo.aspectRatio = image->header().pixelAspectRatio();
|
|
}
|
|
else
|
|
{
|
|
sizeInfo.width = 640;
|
|
sizeInfo.height = 480;
|
|
sizeInfo.aspectRatio = 1.0f;
|
|
}
|
|
|
|
memcpy(data, &sizeInfo, datalen);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
return PkDspyErrorUnsupported;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return PkDspyErrorBadParams;
|
|
}
|
|
|
|
return PkDspyErrorNone;
|
|
}
|
|
|
|
|
|
} // extern C
|