///////////////////////////////////////////////////////////////////////////
//
// 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