672 lines
15 KiB
C++
672 lines
15 KiB
C++
|
///////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Copyright (c) 2014, 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.
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// class Image
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#include "ImfImage.h"
|
||
|
#include <ImfChannelList.h>
|
||
|
#include <Iex.h>
|
||
|
#include <cassert>
|
||
|
#include <algorithm>
|
||
|
|
||
|
using namespace IMATH_NAMESPACE;
|
||
|
using namespace IEX_NAMESPACE;
|
||
|
using namespace std;
|
||
|
|
||
|
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
int
|
||
|
levelSize (int min, int max, int l, LevelRoundingMode levelRoundingMode)
|
||
|
{
|
||
|
assert (l >= 0);
|
||
|
|
||
|
if (max < min)
|
||
|
return 0;
|
||
|
|
||
|
int a = max - min + 1;
|
||
|
int b = (1 << l);
|
||
|
int size = a / b;
|
||
|
|
||
|
if (levelRoundingMode == ROUND_UP && size * b < a)
|
||
|
size += 1;
|
||
|
|
||
|
return std::max (size, 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
Box2i
|
||
|
computeDataWindowForLevel
|
||
|
(const Box2i& dataWindow,
|
||
|
int lx, int ly,
|
||
|
LevelRoundingMode lrMode)
|
||
|
{
|
||
|
V2i levelMax =
|
||
|
dataWindow.min +
|
||
|
V2i (levelSize (dataWindow.min.x, dataWindow.max.x, lx, lrMode) - 1,
|
||
|
levelSize (dataWindow.min.y, dataWindow.max.y, ly, lrMode) - 1);
|
||
|
|
||
|
return Box2i (dataWindow.min, levelMax);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
floorLog2 (int x)
|
||
|
{
|
||
|
//
|
||
|
// For x > 0, floorLog2(y) returns floor(log(x)/log(2)).
|
||
|
//
|
||
|
|
||
|
int y = 0;
|
||
|
|
||
|
while (x > 1)
|
||
|
{
|
||
|
y += 1;
|
||
|
x >>= 1;
|
||
|
}
|
||
|
|
||
|
return y;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
ceilLog2 (int x)
|
||
|
{
|
||
|
//
|
||
|
// For x > 0, ceilLog2(y) returns ceil(log(x)/log(2)).
|
||
|
//
|
||
|
|
||
|
int y = 0;
|
||
|
int r = 0;
|
||
|
|
||
|
while (x > 1)
|
||
|
{
|
||
|
if (x & 1)
|
||
|
r = 1;
|
||
|
|
||
|
y += 1;
|
||
|
x >>= 1;
|
||
|
}
|
||
|
|
||
|
return y + r;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
roundLog2 (int x, LevelRoundingMode levelRoundingMode)
|
||
|
{
|
||
|
if (x < 1)
|
||
|
return 1;
|
||
|
|
||
|
return (levelRoundingMode == ROUND_DOWN)? floorLog2 (x): ceilLog2 (x);
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
computeNumXLevels
|
||
|
(const Box2i& dataWindow,
|
||
|
LevelMode levelMode,
|
||
|
LevelRoundingMode levelRoundingMode)
|
||
|
{
|
||
|
int n = 0;
|
||
|
|
||
|
switch (levelMode)
|
||
|
{
|
||
|
case ONE_LEVEL:
|
||
|
|
||
|
n = 1;
|
||
|
break;
|
||
|
|
||
|
case MIPMAP_LEVELS:
|
||
|
|
||
|
{
|
||
|
int w = dataWindow.max.x - dataWindow.min.x + 1;
|
||
|
int h = dataWindow.max.y - dataWindow.min.y + 1;
|
||
|
n = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case RIPMAP_LEVELS:
|
||
|
|
||
|
{
|
||
|
int w = dataWindow.max.x - dataWindow.min.x + 1;
|
||
|
n = roundLog2 (w, levelRoundingMode) + 1;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
|
||
|
assert (false);
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
computeNumYLevels
|
||
|
(const Box2i& dataWindow,
|
||
|
LevelMode levelMode,
|
||
|
LevelRoundingMode levelRoundingMode)
|
||
|
{
|
||
|
int n = 0;
|
||
|
|
||
|
switch (levelMode)
|
||
|
{
|
||
|
case ONE_LEVEL:
|
||
|
|
||
|
n = 1;
|
||
|
break;
|
||
|
|
||
|
case MIPMAP_LEVELS:
|
||
|
|
||
|
{
|
||
|
int w = dataWindow.max.x - dataWindow.min.x + 1;
|
||
|
int h = dataWindow.max.y - dataWindow.min.y + 1;
|
||
|
n = roundLog2 (std::max (w, h), levelRoundingMode) + 1;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case RIPMAP_LEVELS:
|
||
|
|
||
|
{
|
||
|
int h = dataWindow.max.y - dataWindow.min.y + 1;
|
||
|
n = roundLog2 (h, levelRoundingMode) + 1;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
|
||
|
assert (false);
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
Image::Image ():
|
||
|
_dataWindow (Box2i (V2i (0, 0), V2i (-1, -1))),
|
||
|
_levelMode (ONE_LEVEL),
|
||
|
_levelRoundingMode (ROUND_DOWN),
|
||
|
_channels(),
|
||
|
_levels()
|
||
|
{
|
||
|
// empty
|
||
|
}
|
||
|
|
||
|
|
||
|
Image::~Image ()
|
||
|
{
|
||
|
clearLevels();
|
||
|
clearChannels();
|
||
|
}
|
||
|
|
||
|
|
||
|
LevelMode
|
||
|
Image::levelMode () const
|
||
|
{
|
||
|
return _levelMode;
|
||
|
}
|
||
|
|
||
|
|
||
|
LevelRoundingMode
|
||
|
Image::levelRoundingMode () const
|
||
|
{
|
||
|
return _levelRoundingMode;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
Image::numLevels () const
|
||
|
{
|
||
|
if (_levelMode == ONE_LEVEL || _levelMode == MIPMAP_LEVELS)
|
||
|
return numXLevels();
|
||
|
else
|
||
|
throw LogicExc ("Number of levels query for image "
|
||
|
"must specify x or y direction.");
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
Image::numXLevels () const
|
||
|
{
|
||
|
return _levels.width();
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
Image::numYLevels () const
|
||
|
{
|
||
|
return _levels.height();
|
||
|
}
|
||
|
|
||
|
|
||
|
const Box2i &
|
||
|
Image::dataWindow () const
|
||
|
{
|
||
|
return _dataWindow;
|
||
|
}
|
||
|
|
||
|
|
||
|
const Box2i &
|
||
|
Image::dataWindowForLevel (int l) const
|
||
|
{
|
||
|
return dataWindowForLevel (l, l);
|
||
|
}
|
||
|
|
||
|
|
||
|
const Box2i &
|
||
|
Image::dataWindowForLevel (int lx, int ly) const
|
||
|
{
|
||
|
if (!levelNumberIsValid (lx, ly))
|
||
|
{
|
||
|
THROW (ArgExc,
|
||
|
"Cannot get data window for invalid image "
|
||
|
"level (" << lx << ", " << ly << ").");
|
||
|
}
|
||
|
|
||
|
return _levels[ly][lx]->dataWindow();
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
Image::levelWidth (int lx) const
|
||
|
{
|
||
|
if (lx < 0 || lx >= numXLevels())
|
||
|
{
|
||
|
THROW (ArgExc,
|
||
|
"Cannot get level width for invalid "
|
||
|
"image level number " << lx << ".");
|
||
|
}
|
||
|
|
||
|
return levelSize (_dataWindow.min.x, _dataWindow.max.x,
|
||
|
lx, _levelRoundingMode);
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
Image::levelHeight (int ly) const
|
||
|
{
|
||
|
if (ly < 0 || ly >= numYLevels())
|
||
|
{
|
||
|
THROW (ArgExc,
|
||
|
"Cannot get level height for invalid "
|
||
|
"image level number " << ly << ".");
|
||
|
}
|
||
|
|
||
|
return levelSize (_dataWindow.min.y, _dataWindow.max.y,
|
||
|
ly, _levelRoundingMode);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::resize (const Imath::Box2i &dataWindow)
|
||
|
{
|
||
|
resize (dataWindow, _levelMode, _levelRoundingMode);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::resize
|
||
|
(const Imath::Box2i &dataWindow,
|
||
|
LevelMode levelMode,
|
||
|
LevelRoundingMode levelRoundingMode)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
clearLevels();
|
||
|
|
||
|
int nx = computeNumXLevels (dataWindow, levelMode, levelRoundingMode);
|
||
|
int ny = computeNumYLevels (dataWindow, levelMode, levelRoundingMode);
|
||
|
|
||
|
_levels.resizeErase (ny, nx);
|
||
|
|
||
|
for (int y = 0; y < ny; ++y)
|
||
|
{
|
||
|
for (int x = 0; x < nx; ++x)
|
||
|
{
|
||
|
if (levelMode == MIPMAP_LEVELS && x != y)
|
||
|
{
|
||
|
_levels[y][x] = 0;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Box2i levelDataWindow =
|
||
|
computeDataWindowForLevel (dataWindow,
|
||
|
x, y,
|
||
|
levelRoundingMode);
|
||
|
|
||
|
_levels[y][x] = newLevel (x, y, levelDataWindow);
|
||
|
|
||
|
for (ChannelMap::iterator i = _channels.begin();
|
||
|
i != _channels.end();
|
||
|
++i)
|
||
|
{
|
||
|
_levels[y][x]->insertChannel (i->first,
|
||
|
i->second.type,
|
||
|
i->second.xSampling,
|
||
|
i->second.ySampling,
|
||
|
i->second.pLinear);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_dataWindow = dataWindow;
|
||
|
_levelMode = levelMode;
|
||
|
_levelRoundingMode = levelRoundingMode;
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
clearLevels();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::shiftPixels (int dx, int dy)
|
||
|
{
|
||
|
for (ChannelMap::iterator i = _channels.begin(); i != _channels.end(); ++i)
|
||
|
{
|
||
|
if (dx % i->second.xSampling != 0)
|
||
|
{
|
||
|
THROW (ArgExc, "Cannot shift image horizontally by " << dx << " "
|
||
|
"pixels. The shift distance must be a multiple "
|
||
|
"of the x sampling rate of all channels, but the "
|
||
|
"x sampling rate channel " << i->first << " "
|
||
|
"is " << i->second.xSampling << ".");
|
||
|
}
|
||
|
|
||
|
if (dy % i->second.ySampling != 0)
|
||
|
{
|
||
|
THROW (ArgExc, "Cannot shift image vertically by " << dy << " "
|
||
|
"pixels. The shift distance must be a multiple "
|
||
|
"of the y sampling rate of all channels, but the "
|
||
|
"y sampling rate channel " << i->first << " "
|
||
|
"is " << i->second.ySampling << ".");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_dataWindow.min.x += dx;
|
||
|
_dataWindow.min.y += dy;
|
||
|
_dataWindow.max.x += dx;
|
||
|
_dataWindow.max.y += dy;
|
||
|
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->shiftPixels (dx, dy);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::insertChannel
|
||
|
(const std::string &name,
|
||
|
PixelType type,
|
||
|
int xSampling,
|
||
|
int ySampling,
|
||
|
bool pLinear)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
_channels[name] = ChannelInfo (type, xSampling, ySampling, pLinear);
|
||
|
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->insertChannel
|
||
|
(name, type, xSampling, ySampling, pLinear);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
eraseChannel (name);
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::insertChannel (const string &name, const Channel &channel)
|
||
|
{
|
||
|
insertChannel (name,
|
||
|
channel.type,
|
||
|
channel.xSampling,
|
||
|
channel.ySampling,
|
||
|
channel.pLinear);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::eraseChannel (const std::string &name)
|
||
|
{
|
||
|
//
|
||
|
// Note: eraseChannel() is called to clean up if an exception is
|
||
|
// thrown during a call during insertChannel(), so eraseChannel()
|
||
|
// must work correctly even after an incomplete insertChannel()
|
||
|
// operation.
|
||
|
//
|
||
|
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->eraseChannel (name);
|
||
|
|
||
|
ChannelMap::iterator i = _channels.find (name);
|
||
|
|
||
|
if (i != _channels.end())
|
||
|
_channels.erase (i);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::clearChannels ()
|
||
|
{
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->clearChannels();
|
||
|
|
||
|
_channels.clear();
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::renameChannel (const string &oldName, const string &newName)
|
||
|
{
|
||
|
if (oldName == newName)
|
||
|
return;
|
||
|
|
||
|
ChannelMap::iterator oldChannel = _channels.find (oldName);
|
||
|
|
||
|
if (oldChannel == _channels.end())
|
||
|
{
|
||
|
THROW (ArgExc, "Cannot rename image channel " << oldName << " "
|
||
|
"to " << newName << ". The image does not have "
|
||
|
"a channel called " << oldName << ".");
|
||
|
}
|
||
|
|
||
|
if (_channels.find (newName) != _channels.end())
|
||
|
{
|
||
|
THROW (ArgExc, "Cannot rename image channel " << oldName << " "
|
||
|
"to " << newName << ". The image already has "
|
||
|
"a channel called " << newName << ".");
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->renameChannel (oldName, newName);
|
||
|
|
||
|
_channels[newName] = oldChannel->second;
|
||
|
_channels.erase (oldChannel);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
eraseChannel (oldName);
|
||
|
eraseChannel (newName);
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::renameChannels (const RenamingMap &oldToNewNames)
|
||
|
{
|
||
|
set <string> newNames;
|
||
|
|
||
|
for (ChannelMap::const_iterator i = _channels.begin();
|
||
|
i != _channels.end();
|
||
|
++i)
|
||
|
{
|
||
|
RenamingMap::const_iterator j = oldToNewNames.find (i->first);
|
||
|
std::string newName = (j == oldToNewNames.end())? i->first: j->second;
|
||
|
|
||
|
if (newNames.find (newName) != newNames.end())
|
||
|
{
|
||
|
THROW (ArgExc, "Cannot rename image channels. More than one "
|
||
|
"channel would be named \"" << newName << "\".");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
newNames.insert (newName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
renameChannelsInMap (oldToNewNames, _channels);
|
||
|
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
if (_levels[y][x])
|
||
|
_levels[y][x]->renameChannels (oldToNewNames);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
clearChannels();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
ImageLevel &
|
||
|
Image::level (int l)
|
||
|
{
|
||
|
return level (l, l);
|
||
|
}
|
||
|
|
||
|
|
||
|
const ImageLevel &
|
||
|
Image::level (int l) const
|
||
|
{
|
||
|
return level (l, l);
|
||
|
}
|
||
|
|
||
|
|
||
|
ImageLevel &
|
||
|
Image::level (int lx, int ly)
|
||
|
{
|
||
|
if (!levelNumberIsValid (lx, ly))
|
||
|
{
|
||
|
THROW (ArgExc,
|
||
|
"Cannot access image level with invalid "
|
||
|
"level number (" << lx << ", " << ly << ").");
|
||
|
}
|
||
|
|
||
|
return *_levels[ly][lx];
|
||
|
}
|
||
|
|
||
|
|
||
|
const ImageLevel &
|
||
|
Image::level (int lx, int ly) const
|
||
|
{
|
||
|
if (!levelNumberIsValid (lx, ly))
|
||
|
{
|
||
|
THROW (ArgExc,
|
||
|
"Cannot access image level with invalid "
|
||
|
"level number (" << lx << ", " << ly << ").");
|
||
|
}
|
||
|
|
||
|
return *_levels[ly][lx];
|
||
|
}
|
||
|
|
||
|
|
||
|
bool
|
||
|
Image::levelNumberIsValid (int lx, int ly) const
|
||
|
{
|
||
|
return lx >= 0 && lx < _levels.width() &&
|
||
|
ly >= 0 && ly < _levels.height() &&
|
||
|
_levels[ly][lx] != 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
Image::clearLevels ()
|
||
|
{
|
||
|
_dataWindow = Box2i (V2i (0, 0), V2i (-1, -1));
|
||
|
|
||
|
for (int y = 0; y < _levels.height(); ++y)
|
||
|
for (int x = 0; x < _levels.width(); ++x)
|
||
|
delete _levels[y][x];
|
||
|
|
||
|
_levels.resizeErase (0, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
Image::ChannelInfo::ChannelInfo
|
||
|
(PixelType type,
|
||
|
int xSampling,
|
||
|
int ySampling,
|
||
|
bool pLinear)
|
||
|
:
|
||
|
type (type),
|
||
|
xSampling (xSampling),
|
||
|
ySampling (ySampling),
|
||
|
pLinear (pLinear)
|
||
|
{
|
||
|
// empty
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
|