Open Source Computer Vision Library https://opencv.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

633 lines
13 KiB

///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007, 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.
//
///////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// ACES image file I/O.
//
//-----------------------------------------------------------------------------
#include <ImfAcesFile.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#include <Iex.h>
#include <algorithm> // for std::max()
using namespace std;
using namespace Imath;
using namespace Iex;
namespace Imf {
const Chromaticities &
acesChromaticities ()
{
static const Chromaticities acesChr
(V2f (0.73470, 0.26530), // red
V2f (0.00000, 1.00000), // green
V2f (0.00010, -0.07700), // blue
V2f (0.32168, 0.33767)); // white
return acesChr;
}
class AcesOutputFile::Data
{
public:
Data();
~Data();
RgbaOutputFile * rgbaFile;
};
AcesOutputFile::Data::Data ():
rgbaFile (0)
{
// empty
}
AcesOutputFile::Data::~Data ()
{
delete rgbaFile;
}
namespace {
void
checkCompression (Compression compression)
{
//
// Not all compression methods are allowed in ACES files.
//
switch (compression)
{
case NO_COMPRESSION:
case PIZ_COMPRESSION:
case B44A_COMPRESSION:
break;
default:
throw ArgExc ("Invalid compression type for ACES file.");
}
}
} // namespace
AcesOutputFile::AcesOutputFile
(const std::string &name,
const Header &header,
RgbaChannels rgbaChannels,
int numThreads)
:
_data (new Data)
{
checkCompression (header.compression());
Header newHeader = header;
addChromaticities (newHeader, acesChromaticities());
addAdoptedNeutral (newHeader, acesChromaticities().white);
_data->rgbaFile = new RgbaOutputFile (name.c_str(),
newHeader,
rgbaChannels,
numThreads);
_data->rgbaFile->setYCRounding (7, 6);
}
AcesOutputFile::AcesOutputFile
(OStream &os,
const Header &header,
RgbaChannels rgbaChannels,
int numThreads)
:
_data (new Data)
{
checkCompression (header.compression());
Header newHeader = header;
addChromaticities (newHeader, acesChromaticities());
addAdoptedNeutral (newHeader, acesChromaticities().white);
_data->rgbaFile = new RgbaOutputFile (os,
header,
rgbaChannels,
numThreads);
_data->rgbaFile->setYCRounding (7, 6);
}
AcesOutputFile::AcesOutputFile
(const std::string &name,
const Imath::Box2i &displayWindow,
const Imath::Box2i &dataWindow,
RgbaChannels rgbaChannels,
float pixelAspectRatio,
const Imath::V2f screenWindowCenter,
float screenWindowWidth,
LineOrder lineOrder,
Compression compression,
int numThreads)
:
_data (new Data)
{
checkCompression (compression);
Header newHeader (displayWindow,
dataWindow.isEmpty()? displayWindow: dataWindow,
pixelAspectRatio,
screenWindowCenter,
screenWindowWidth,
lineOrder,
compression);
addChromaticities (newHeader, acesChromaticities());
addAdoptedNeutral (newHeader, acesChromaticities().white);
_data->rgbaFile = new RgbaOutputFile (name.c_str(),
newHeader,
rgbaChannels,
numThreads);
_data->rgbaFile->setYCRounding (7, 6);
}
AcesOutputFile::AcesOutputFile
(const std::string &name,
int width,
int height,
RgbaChannels rgbaChannels,
float pixelAspectRatio,
const Imath::V2f screenWindowCenter,
float screenWindowWidth,
LineOrder lineOrder,
Compression compression,
int numThreads)
:
_data (new Data)
{
checkCompression (compression);
Header newHeader (width,
height,
pixelAspectRatio,
screenWindowCenter,
screenWindowWidth,
lineOrder,
compression);
addChromaticities (newHeader, acesChromaticities());
addAdoptedNeutral (newHeader, acesChromaticities().white);
_data->rgbaFile = new RgbaOutputFile (name.c_str(),
newHeader,
rgbaChannels,
numThreads);
_data->rgbaFile->setYCRounding (7, 6);
}
AcesOutputFile::~AcesOutputFile ()
{
delete _data;
}
void
AcesOutputFile::setFrameBuffer
(const Rgba *base,
size_t xStride,
size_t yStride)
{
_data->rgbaFile->setFrameBuffer (base, xStride, yStride);
}
void
AcesOutputFile::writePixels (int numScanLines)
{
_data->rgbaFile->writePixels (numScanLines);
}
int
AcesOutputFile::currentScanLine () const
{
return _data->rgbaFile->currentScanLine();
}
const Header &
AcesOutputFile::header () const
{
return _data->rgbaFile->header();
}
const Imath::Box2i &
AcesOutputFile::displayWindow () const
{
return _data->rgbaFile->displayWindow();
}
const Imath::Box2i &
AcesOutputFile::dataWindow () const
{
return _data->rgbaFile->dataWindow();
}
float
AcesOutputFile::pixelAspectRatio () const
{
return _data->rgbaFile->pixelAspectRatio();
}
const Imath::V2f
AcesOutputFile::screenWindowCenter () const
{
return _data->rgbaFile->screenWindowCenter();
}
float
AcesOutputFile::screenWindowWidth () const
{
return _data->rgbaFile->screenWindowWidth();
}
LineOrder
AcesOutputFile::lineOrder () const
{
return _data->rgbaFile->lineOrder();
}
Compression
AcesOutputFile::compression () const
{
return _data->rgbaFile->compression();
}
RgbaChannels
AcesOutputFile::channels () const
{
return _data->rgbaFile->channels();
}
void
AcesOutputFile::updatePreviewImage (const PreviewRgba pixels[])
{
_data->rgbaFile->updatePreviewImage (pixels);
}
class AcesInputFile::Data
{
public:
Data();
~Data();
void initColorConversion ();
RgbaInputFile * rgbaFile;
Rgba * fbBase;
size_t fbXStride;
size_t fbYStride;
int minX;
int maxX;
bool mustConvertColor;
M44f fileToAces;
};
AcesInputFile::Data::Data ():
rgbaFile (0),
fbBase (0),
fbXStride (0),
fbYStride (0),
minX (0),
maxX (0),
mustConvertColor (false)
{
// empty
}
AcesInputFile::Data::~Data ()
{
delete rgbaFile;
}
void
AcesInputFile::Data::initColorConversion ()
{
const Header &header = rgbaFile->header();
Chromaticities fileChr;
if (hasChromaticities (header))
fileChr = chromaticities (header);
V2f fileNeutral = fileChr.white;
if (hasAdoptedNeutral (header))
fileNeutral = adoptedNeutral (header);
const Chromaticities acesChr = acesChromaticities();
V2f acesNeutral = acesChr.white;
if (fileChr.red == acesChr.red &&
fileChr.green == acesChr.green &&
fileChr.blue == acesChr.blue &&
fileChr.white == acesChr.white &&
fileNeutral == acesNeutral)
{
//
// The file already contains ACES data,
// color conversion is not necessary.
return;
}
mustConvertColor = true;
minX = header.dataWindow().min.x;
maxX = header.dataWindow().max.x;
//
// Create a matrix that transforms colors from the
// RGB space of the input file into the ACES space
// using a color adaptation transform to move the
// white point.
//
//
// We'll need the Bradford cone primary matrix and its inverse
//
static const M44f bradfordCPM
(0.895100, -0.750200, 0.038900, 0.000000,
0.266400, 1.713500, -0.068500, 0.000000,
-0.161400, 0.036700, 1.029600, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000);
const static M44f inverseBradfordCPM
(0.986993, 0.432305, -0.008529, 0.000000,
-0.147054, 0.518360, 0.040043, 0.000000,
0.159963, 0.049291, 0.968487, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000);
//
// Convert the white points of the two RGB spaces to XYZ
//
float fx = fileNeutral.x;
float fy = fileNeutral.y;
V3f fileNeutralXYZ (fx / fy, 1, (1 - fx - fy) / fy);
float ax = acesNeutral.x;
float ay = acesNeutral.y;
V3f acesNeutralXYZ (ax / ay, 1, (1 - ax - ay) / ay);
//
// Compute the Bradford transformation matrix
//
V3f ratio ((acesNeutralXYZ * bradfordCPM) /
(fileNeutralXYZ * bradfordCPM));
M44f ratioMat (ratio[0], 0, 0, 0,
0, ratio[1], 0, 0,
0, 0, ratio[2], 0,
0, 0, 0, 1);
M44f bradfordTrans = bradfordCPM *
ratioMat *
inverseBradfordCPM;
//
// Build a combined file-RGB-to-ACES-RGB conversion matrix
//
fileToAces = RGBtoXYZ (fileChr, 1) * bradfordTrans * XYZtoRGB (acesChr, 1);
}
AcesInputFile::AcesInputFile (const std::string &name, int numThreads):
_data (new Data)
{
_data->rgbaFile = new RgbaInputFile (name.c_str(), numThreads);
_data->initColorConversion();
}
AcesInputFile::AcesInputFile (IStream &is, int numThreads):
_data (new Data)
{
_data->rgbaFile = new RgbaInputFile (is, numThreads);
_data->initColorConversion();
}
AcesInputFile::~AcesInputFile ()
{
delete _data;
}
void
AcesInputFile::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride)
{
_data->rgbaFile->setFrameBuffer (base, xStride, yStride);
_data->fbBase = base;
_data->fbXStride = xStride;
_data->fbYStride = yStride;
}
void
AcesInputFile::readPixels (int scanLine1, int scanLine2)
{
//
// Copy the pixels from the RgbaInputFile into the frame buffer.
//
_data->rgbaFile->readPixels (scanLine1, scanLine2);
//
// If the RGB space of the input file is not the same as the ACES
// RGB space, then the pixels in the frame buffer must be transformed
// into the ACES RGB space.
//
if (!_data->mustConvertColor)
return;
int minY = min (scanLine1, scanLine2);
int maxY = max (scanLine1, scanLine2);
for (int y = minY; y <= maxY; ++y)
{
Rgba *base = _data->fbBase +
_data->fbXStride * _data->minX +
_data->fbYStride * y;
for (int x = _data->minX; x <= _data->maxX; ++x)
{
V3f aces = V3f (base->r, base->g, base->b) * _data->fileToAces;
base->r = aces[0];
base->g = aces[1];
base->b = aces[2];
base += _data->fbXStride;
}
}
}
void
AcesInputFile::readPixels (int scanLine)
{
readPixels (scanLine, scanLine);
}
const Header &
AcesInputFile::header () const
{
return _data->rgbaFile->header();
}
const Imath::Box2i &
AcesInputFile::displayWindow () const
{
return _data->rgbaFile->displayWindow();
}
const Imath::Box2i &
AcesInputFile::dataWindow () const
{
return _data->rgbaFile->dataWindow();
}
float
AcesInputFile::pixelAspectRatio () const
{
return _data->rgbaFile->pixelAspectRatio();
}
const Imath::V2f
AcesInputFile::screenWindowCenter () const
{
return _data->rgbaFile->screenWindowCenter();
}
float
AcesInputFile::screenWindowWidth () const
{
return _data->rgbaFile->screenWindowWidth();
}
LineOrder
AcesInputFile::lineOrder () const
{
return _data->rgbaFile->lineOrder();
}
Compression
AcesInputFile::compression () const
{
return _data->rgbaFile->compression();
}
RgbaChannels
AcesInputFile::channels () const
{
return _data->rgbaFile->channels();
}
const char *
AcesInputFile::fileName () const
{
return _data->rgbaFile->fileName();
}
bool
AcesInputFile::isComplete () const
{
return _data->rgbaFile->isComplete();
}
int
AcesInputFile::version () const
{
return _data->rgbaFile->version();
}
} // namespace Imf