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

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