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