mirror of https://github.com/opencv/opencv.git
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.
632 lines
13 KiB
632 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
|
|
|