diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
---|---|---|
committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/llkdu | |
parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/llkdu')
-rw-r--r-- | indra/llkdu/include_kdu_xxxx.h | 4 | ||||
-rw-r--r-- | indra/llkdu/llimagej2ckdu.cpp | 2976 | ||||
-rw-r--r-- | indra/llkdu/llimagej2ckdu.h | 158 | ||||
-rw-r--r-- | indra/llkdu/llkdumem.cpp | 306 | ||||
-rw-r--r-- | indra/llkdu/llkdumem.h | 180 | ||||
-rw-r--r-- | indra/llkdu/tests/llimagej2ckdu_test.cpp | 164 |
6 files changed, 1894 insertions, 1894 deletions
diff --git a/indra/llkdu/include_kdu_xxxx.h b/indra/llkdu/include_kdu_xxxx.h index 61204b5689..3db417a6cb 100644 --- a/indra/llkdu/include_kdu_xxxx.h +++ b/indra/llkdu/include_kdu_xxxx.h @@ -2,8 +2,8 @@ * @file include_kdu_xxxx.h * @author Nat Goodspeed * @date 2016-04-25 - * @brief - * + * @brief + * * $LicenseInfo:firstyear=2016&license=viewerlgpl$ * Copyright (c) 2016, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 8b34592535..1b4d76a035 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -1,1488 +1,1488 @@ -/** - * @file llimagej2ckdu.cpp - * @brief This is an implementation of JPEG2000 encode/decode using Kakadu - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llimagej2ckdu.h" - -#include "lltimer.h" -#include "llpointer.h" -#include "llmath.h" -#include "llkdumem.h" - -#define kdu_xxxx "kdu_block_coding.h" -#include "include_kdu_xxxx.h" - -// Avoid ubiquitous necessity of kdu_core:: qualification -using namespace kdu_core; - -#include "llexception.h" -#include <boost/exception/diagnostic_information.hpp> -#include <sstream> -#include <iomanip> - -// Turns out this must NOT be in the anonymous namespace! -namespace kdu_core -{ -// stream kdu_dims to std::ostream -inline -std::ostream& operator<<(std::ostream& out, const kdu_dims& dims) -{ - return out << "(" << dims.pos.x << "," << dims.pos.y << ")," - "[" << dims.size.x << "x" << dims.size.y << "]"; -} -} // namespace kdu_core - -// operator<<(std::ostream&, const kdu_dims&) must precede #include "stringize.h" -#include "stringize.h" - -namespace { -// Failure to load an image shouldn't crash the whole viewer. -struct KDUError: public LLContinueError -{ - KDUError(const std::string& msg): LLContinueError(msg) {} -}; - -// KDU defines int error codes as hex values, so we should log them in hex -// so we can grep KDU headers for the hex. However those hex values -// generally "happen" to encode big-endian multibyte character sequences, -// e.g. KDU_ERROR_EXCEPTION is 0x6b647545: 'kduE' -// But beware because KDU_NULL_EXCEPTION is simply 0 -- which doesn't -// preclude somebody from throwing it. -std::string report_kdu_exception(kdu_exception mb) -{ - std::ostringstream out; - // always report mb in hex - out << "kdu_exception " << std::hex << mb; - - // Also display as many chars as are encoded in the kdu_exception - // value. Make a char array; reserve 1 extra byte for nul terminator. - char bytes[sizeof(kdu_exception) + 1]; - // Back up through 'bytes' - char *bptr = bytes + sizeof(bytes); - *(--bptr) = '\0'; - while (mb) - { - // store low-order byte of mb in next-left char - *(--bptr) = char(mb & 0xFF); - // then shift mb right by one byte - mb >>= 8; - } - // did that produce any characters? - if (*bptr) - { - out << " (" << bptr << ')'; - } - - return out.str(); -} -} // anonymous namespace - - -class kdc_flow_control { - -public: - kdc_flow_control(kdu_supp::kdu_image_in_base *img_in, kdu_codestream codestream); - ~kdc_flow_control(); - bool advance_components(); - void process_components(); - -private: - - struct kdc_component_flow_control { - public: - kdu_supp::kdu_image_in_base *reader; - int vert_subsampling; - int ratio_counter; /* Initialized to 0, decremented by `count_delta'; - when < 0, a new line must be processed, after - which it is incremented by `vert_subsampling'. */ - int initial_lines; - int remaining_lines; - kdu_line_buf *line; - }; - - kdu_codestream codestream; - kdu_dims valid_tile_indices; - kdu_coords tile_idx; - kdu_tile tile; - int num_components; - kdc_component_flow_control *components; - int count_delta; // Holds the minimum of the `vert_subsampling' fields - kdu_multi_analysis engine; - kdu_long max_buffer_memory; -}; - -// -// Kakadu specific implementation -// -void set_default_colour_weights(kdu_params *siz); - -// Factory function: see declaration in llimagej2c.cpp -LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl() -{ - return new LLImageJ2CKDU(); -} - -std::string LLImageJ2CKDU::getEngineInfo() const -{ - return llformat("KDU %s", KDU_CORE_VERSION); -} - -class LLKDUDecodeState -{ -public: - LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, - kdu_codestream* codestreamp); - ~LLKDUDecodeState(); - bool processTileDecode(F32 decode_time, bool limit_time = true); - -private: - S32 mNumComponents; - bool mUseYCC; - kdu_dims mDims; - kdu_sample_allocator mAllocator; - kdu_tile_comp mComps[4]; - kdu_line_buf mLines[4]; - kdu_pull_ifc mEngines[4]; - bool mReversible[4]; // Some components may be reversible and others not - int mBitDepths[4]; // Original bit-depth may be quite different from 8 - - kdu_tile mTile; - kdu_byte *mBuf; - S32 mRowGap; -}; - -// Stuff for new kdu error handling -class LLKDUMessage: public kdu_message -{ -public: - LLKDUMessage(const std::string& type): - mType(type) - {} - - virtual void put_text(const char *s) - { - LL_INFOS() << "KDU " << mType << ": " << s << LL_ENDL; - } - - virtual void put_text(const kdu_uint16 *s) - { - // The previous implementation simply streamed 's' to the log. So - // either this put_text() override was never called -- or it produced - // some baffling log messages -- because I assert that streaming a - // const kdu_uint16* to a std::ostream will display only the hex value - // of the pointer. - LL_INFOS() << "KDU " << mType << ": " - << utf16str_to_utf8str(llutf16string(s)) << LL_ENDL; - } - -private: - std::string mType; -}; - -struct LLKDUMessageWarning : public LLKDUMessage -{ - LLKDUMessageWarning(): - LLKDUMessage("Warning") - { - kdu_customize_warnings(this); - } -}; -// Instantiating LLKDUMessageWarning calls kdu_customize_warnings() with the -// new instance. Make it static so this only happens once. -static LLKDUMessageWarning sWarningHandler; - -struct LLKDUMessageError : public LLKDUMessage -{ - LLKDUMessageError(): - LLKDUMessage("Error") - { - kdu_customize_errors(this); - } - - virtual void flush(bool end_of_message = false) - { - // According to the documentation nat found: - // http://pirlwww.lpl.arizona.edu/resources/guide/software/Kakadu/html_pages/globals__kdu$mize_errors.html - // "If a kdu_error object is destroyed, handler→flush will be called with - // an end_of_message argument equal to true and the process will - // subsequently be terminated through exit. The termination may be - // avoided, however, by throwing an exception from within the message - // terminating handler→flush call." - // So throwing an exception here isn't arbitrary: we MUST throw an - // exception if we want to recover from a KDU error. - // Because this confused me: the above quote specifically refers to - // the kdu_error class, which is constructed internally within KDU at - // the point where a fatal error is discovered and reported. It is NOT - // talking about the kdu_message subclass passed to - // kdu_customize_errors(). Destroying this static object at program - // shutdown will NOT engage the behavior described above. - if (end_of_message) - { - LLTHROW(KDUError("LLKDUMessageError::flush()")); - } - } -}; -// Instantiating LLKDUMessageError calls kdu_customize_errors() with the new -// instance. Make it static so this only happens once. -static LLKDUMessageError sErrorHandler; - -LLImageJ2CKDU::LLImageJ2CKDU() : LLImageJ2CImpl(), - mInputp(), - mCodeStreamp(), - mTPosp(), - mTileIndicesp(), - mRawImagep(NULL), - mDecodeState(), - mBlocksSize(-1), - mPrecinctsSize(-1), - mLevels(0) -{ -} - -LLImageJ2CKDU::~LLImageJ2CKDU() -{ - cleanupCodeStream(); // in case destroyed before decode completed -} - -// Stuff for new simple decode -void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision); - -// This is called by the real (private) initDecode() (keep_codestream true) -// and getMetadata() methods (keep_codestream false). As far as nat can tell, -// mode is always MODE_FAST. It was called by findDiscardLevelsBoundaries() -// as well, when that still existed, with keep_codestream true and MODE_FAST. -void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECodeStreamMode mode) -{ - LLImageDataLock lock(&base); - - S32 data_size = base.getDataSize(); - S32 max_bytes = (base.getMaxBytes() ? base.getMaxBytes() : data_size); - - // - // Initialization - // - mCodeStreamp.reset(); - - // It's not clear to nat under what circumstances we would reuse a - // pre-existing LLKDUMemSource instance. As of 2016-08-05, it consists of - // two U32s and a pointer, so it's not as if it would be a huge overhead - // to allocate a new one every time. - // Also -- why is base.getData() tested specifically here? If that returns - // NULL, shouldn't we bail out of the whole method? - if (!mInputp && base.getData()) - { - // The compressed data has been loaded - // Setup the source for the codestream - mInputp.reset(new LLKDUMemSource(base.getData(), data_size)); - } - - if (mInputp) - { - // This is LLKDUMemSource::reset(), not boost::scoped_ptr::reset(). - mInputp->reset(); - } - - mCodeStreamp->create(mInputp.get()); - - // Set the maximum number of bytes to use from the codestream - // *TODO: This seems to be wrong. The base class should have no idea of - // how j2c compression works so no good way of computing what's the byte - // range to be used. - mCodeStreamp->set_max_bytes(max_bytes,true); - - // If you want to flip or rotate the image for some reason, change - // the resolution, or identify a restricted region of interest, this is - // the place to do it. You may use "kdu_codestream::change_appearance" - // and "kdu_codestream::apply_input_restrictions" for this purpose. - // If you wish to truncate the code-stream prior to decompression, you - // may use "kdu_codestream::set_max_bytes". - // If you wish to retain all compressed data so that the material - // can be decompressed multiple times, possibly with different appearance - // parameters, you should call "kdu_codestream::set_persistent" here. - // There are a variety of other features which must be enabled at - // this point if you want to take advantage of them. See the - // descriptions appearing with the "kdu_codestream" interface functions - // in "kdu_compressed.h" for an itemized account of these capabilities. - - switch (mode) - { - case MODE_FAST: - mCodeStreamp->set_fast(); - break; - case MODE_RESILIENT: - mCodeStreamp->set_resilient(); - break; - case MODE_FUSSY: - mCodeStreamp->set_fussy(); - break; - default: - llassert(0); - mCodeStreamp->set_fast(); - } - - kdu_dims dims; - mCodeStreamp->get_dims(0,dims); - - S32 components = mCodeStreamp->get_num_components(); - - // Check that components have consistent dimensions (for PPM file) - for (int idx = 1; idx < components; ++idx) - { - kdu_dims other_dims; - mCodeStreamp->get_dims(idx, other_dims); - if (other_dims != dims) - { - // This method is only called from methods that catch KDUError. - // We want to fail the image load, not crash the viewer. - LLTHROW(KDUError(STRINGIZE("Component " << idx << " dimensions " - << stringize(other_dims) - << " do not match component 0 dimensions " - << stringize(dims) << "!"))); - } - } - - // Get the number of resolution levels in that image - mLevels = mCodeStreamp->get_min_dwt_levels(); - - // Set the base dimensions - base.setSize(dims.size.x, dims.size.y, components); - base.setLevels(mLevels); - - if (!keep_codestream) - { - mCodeStreamp.reset(); - mInputp.reset(); - } -} - -void LLImageJ2CKDU::cleanupCodeStream() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - mInputp.reset(); - mDecodeState.reset(); - mCodeStreamp.reset(); - mTPosp.reset(); - mTileIndicesp.reset(); -} - -// This is the protected virtual method called by LLImageJ2C::initDecode(). -// However, as far as nat can tell, LLImageJ2C::initDecode() is called only by -// llimage_libtest.cpp's load_image() function. No detectable production use. -bool LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level, int* region) -{ - return initDecode(base,raw_image,0.0f,MODE_FAST,0,4,discard_level,region); -} - -bool LLImageJ2CKDU::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels) -{ - mPrecinctsSize = precincts_size; - if (mPrecinctsSize != -1) - { - mPrecinctsSize = get_lower_power_two(mPrecinctsSize,MAX_PRECINCT_SIZE); - mPrecinctsSize = llmax(mPrecinctsSize,MIN_PRECINCT_SIZE); - } - mBlocksSize = blocks_size; - if (mBlocksSize != -1) - { - mBlocksSize = get_lower_power_two(mBlocksSize,MAX_BLOCK_SIZE); - mBlocksSize = llmax(mBlocksSize,MIN_BLOCK_SIZE); - if (mPrecinctsSize != -1) - { - mBlocksSize = llmin(mBlocksSize,mPrecinctsSize); // blocks *must* be smaller than precincts - } - } - mLevels = levels; - if (mLevels != 0) - { - mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MAX_DECOMPOSITION_LEVELS); - base.setLevels(mLevels); - } - return true; -} - -// This is the real (private) initDecode() called both by the protected -// initDecode() method and by decodeImpl(). As far as nat can tell, only the -// decodeImpl() usage matters for production. -bool LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level, int* region) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - base.resetLastError(); - - // *FIX: kdu calls our callback function if there's an error, and then bombs. - // To regain control, we throw an exception, and catch it here. - try - { - // Merov : Test!! DO NOT COMMIT!! - //findDiscardLevelsBoundaries(base); - - base.updateRawDiscardLevel(); - setupCodeStream(base, true, mode); - - mRawImagep = &raw_image; - mCodeStreamp->change_appearance(false, true, false); - - // Apply loading discard level and cropping if required - kdu_dims* region_kdu = NULL; - if (region != NULL) - { - region_kdu = new kdu_dims; - region_kdu->pos.x = region[0]; - region_kdu->pos.y = region[1]; - region_kdu->size.x = region[2] - region[0]; - region_kdu->size.y = region[3] - region[1]; - } - int discard = (discard_level != -1 ? discard_level : base.getRawDiscardLevel()); - //LL_INFOS() << "Merov debug : initDecode, discard used = " << discard << ", asked = " << discard_level << LL_ENDL; - // Apply loading restrictions - mCodeStreamp->apply_input_restrictions( first_channel, max_channel_count, discard, 0, region_kdu); - - // Clean-up - if (region_kdu) - { - delete region_kdu; - region_kdu = NULL; - } - - // Resize raw_image according to the image to be decoded - kdu_dims dims; mCodeStreamp->get_dims(0,dims); - S32 channels = base.getComponents() - first_channel; - channels = llmin(channels,max_channel_count); - raw_image.resize(dims.size.x, dims.size.y, channels); - - if (!mTileIndicesp) - { - mTileIndicesp.reset(new kdu_dims); - } - mCodeStreamp->get_valid_tiles(*mTileIndicesp); - if (!mTPosp) - { - mTPosp.reset(new kdu_coords); - mTPosp->y = 0; - mTPosp->x = 0; - } - } - catch (const KDUError& msg) - { - base.setLastError(msg.what()); - return false; - } - catch (kdu_exception kdu_value) - { - // KDU internally throws kdu_exception. It's possible that such an - // exception might leak out into our code. Catch kdu_exception - // specially because boost::current_exception_diagnostic_information() - // could do nothing with it. - base.setLastError(report_kdu_exception(kdu_value)); - return false; - } - catch (...) - { - base.setLastError("Unknown J2C error: " + - boost::current_exception_diagnostic_information()); - return false; - } - - return true; -} - - -// Returns true to mean done, whether successful or not. -bool LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - LLImageDataLock lockIn(&base); - LLImageDataLock lockOut(&raw_image); - - ECodeStreamMode mode = MODE_FAST; - - bool limit_time = decode_time > 0.0f; - LLTimer decode_timer; - - if (!mCodeStreamp->exists()) - { - if (!initDecode(base, raw_image, decode_time, mode, first_channel, max_channel_count)) - { - // Initializing the J2C decode failed, bail out. - cleanupCodeStream(); - return true; // done - } - } - - // These can probably be grabbed from what's saved in the class. - kdu_dims dims; - mCodeStreamp->get_dims(0, dims); - - // Now we are ready to walk through the tiles processing them one-by-one. - kdu_byte *buffer = raw_image.getData(); - if (!buffer) - { - base.setLastError("Memory error"); - base.decodeFailed(); - cleanupCodeStream(); - return true; // done - } - - while (mTPosp->y < mTileIndicesp->size.y) - { - while (mTPosp->x < mTileIndicesp->size.x) - { - try - { - if (!mDecodeState) - { - kdu_tile tile = mCodeStreamp->open_tile(*(mTPosp)+mTileIndicesp->pos); - - // Find the region of the buffer occupied by this - // tile. Note that we have no control over - // sub-sampling factors which might have been used - // during compression and so it can happen that tiles - // (at the image component level) actually have - // different dimensions. For this reason, we cannot - // figure out the buffer region occupied by a tile - // directly from the tile indices. Instead, we query - // the highest resolution of the first tile-component - // concerning its location and size on the canvas -- - // the `dims' object already holds the location and - // size of the entire image component on the same - // canvas coordinate system. Comparing the two tells - // us where the current tile is in the buffer. - S32 channels = base.getComponents() - first_channel; - if (channels > max_channel_count) - { - channels = max_channel_count; - } - kdu_resolution res = tile.access_component(0).access_resolution(); - kdu_dims tile_dims; res.get_dims(tile_dims); - kdu_coords offset = tile_dims.pos - dims.pos; - int row_gap = channels*dims.size.x; // inter-row separation - kdu_byte *buf = buffer + offset.y*row_gap + offset.x*channels; - mDecodeState.reset(new LLKDUDecodeState(tile, buf, row_gap, - mCodeStreamp.get())); - } - // Do the actual processing - F32 remaining_time = limit_time ? decode_time - decode_timer.getElapsedTimeF32().value() : 0.0f; - // This is where we do the actual decode. If we run out of time, return false. - if (mDecodeState->processTileDecode(remaining_time, limit_time)) - { - mDecodeState.reset(); - } - else - { - // Not finished decoding yet. - base.setLastError("Ran out of time while decoding"); - base.decodeFailed(); - cleanupCodeStream(); - return false; - } - } - catch (const KDUError& msg) - { - base.setLastError(msg.what()); - base.decodeFailed(); - cleanupCodeStream(); - return true; // done - } - catch (kdu_exception kdu_value) - { - // KDU internally throws kdu_exception. It's possible that such an - // exception might leak out into our code. Catch kdu_exception - // specially because boost::current_exception_diagnostic_information() - // could do nothing with it. - base.setLastError(report_kdu_exception(kdu_value)); - base.decodeFailed(); - cleanupCodeStream(); - return true; // done - } - catch (...) - { - base.setLastError("Unknown J2C error: " + - boost::current_exception_diagnostic_information()); - base.decodeFailed(); - cleanupCodeStream(); - return true; // done - } - - - mTPosp->x++; - } - mTPosp->y++; - mTPosp->x = 0; - } - - cleanupCodeStream(); - - return true; -} - - -bool LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) -{ - // Declare and set simple arguments - bool transpose = false; - bool vflip = true; - bool hflip = false; - - try - { - // Set up input image files - siz_params siz; - - // Should set rate someplace here - LLKDUMemIn mem_in(raw_image.getData(), - raw_image.getDataSize(), - raw_image.getWidth(), - raw_image.getHeight(), - raw_image.getComponents(), - &siz); - - base.setSize(raw_image.getWidth(), raw_image.getHeight(), raw_image.getComponents()); - - int num_components = raw_image.getComponents(); - - siz.set(Scomponents,0,0,num_components); - siz.set(Sdims,0,0,base.getHeight()); // Height of first image component - siz.set(Sdims,0,1,base.getWidth()); // Width of first image component - siz.set(Sprecision,0,0,8); // Image samples have original bit-depth of 8 - siz.set(Ssigned,0,0,false); // Image samples are originally unsigned - - kdu_params *siz_ref = &siz; - siz_ref->finalize(); - siz_params transformed_siz; // Use this one to construct code-stream - transformed_siz.copy_from(&siz,-1,-1,-1,0,transpose,false,false); - - // Construct the `kdu_codestream' object and parse all remaining arguments - U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents(); - max_output_size = (max_output_size < 1000 ? 1000 : max_output_size); - U8 *output_buffer = new U8[max_output_size]; - U32 output_size = 0; // Address updated by LLKDUMemTarget to give the final compressed buffer size - LLKDUMemTarget output(output_buffer, output_size, max_output_size); - - kdu_codestream codestream; - codestream.create(&transformed_siz,&output); - - if (comment_text) - { - // Set the comments for the codestream - kdu_codestream_comment comment = codestream.add_comment(); - comment.put_text(comment_text); - } - - if (num_components >= 3) - { - // Note that we always use YCC and not YUV - // *TODO: Verify this doesn't screws up reversible textures (like sculpties) as YCC is not reversible but YUV is... - set_default_colour_weights(codestream.access_siz()); - } - - // Set codestream options - int nb_layers = 0; - kdu_long layer_bytes[MAX_NB_LAYERS]; - U32 max_bytes = (U32)(base.getWidth() * base.getHeight() * base.getComponents()); - - // Rate is the argument passed into the LLImageJ2C which specifies the target compression rate. The default is 8:1. - // *TODO: mRate is actually always 8:1 in the viewer. Test different values. - llassert (base.mRate > 0.f); - max_bytes = (U32)((F32)(max_bytes) * base.mRate); - - // This code is where we specify the target number of bytes for each quality layer. - // We're using a logarithmic spacing rule that fits with our way of fetching texture data. - // Note: For more info on this layers business, read kdu_codestream::flush() doc in kdu_compressed.h - layer_bytes[nb_layers++] = FIRST_PACKET_SIZE; - U32 i = MIN_LAYER_SIZE; - while ((i < max_bytes) && (nb_layers < (MAX_NB_LAYERS-1))) - { - layer_bytes[nb_layers++] = i; - i *= 4; - } - // Note: for small images, we can have (max_bytes < FIRST_PACKET_SIZE), hence the test - if (layer_bytes[nb_layers-1] < max_bytes) - { - // Set the last quality layer so to fit the preset compression ratio - layer_bytes[nb_layers++] = max_bytes; - } - - if (reversible) - { - // Use 0 for a last quality layer for reversible images so all remaining code blocks will be flushed - // Hack: KDU encoding for reversible images has a bug for small images that leads to j2c images that - // cannot be open or are very blurry. Avoiding that last layer prevents the problem to happen. - if ((base.getWidth() >= 32) || (base.getHeight() >= 32)) - { - layer_bytes[nb_layers++] = 0; - } - codestream.access_siz()->parse_string("Creversible=yes"); - // *TODO: we should use yuv in reversible mode - // Don't turn this on now though as it creates problems on decoding for the moment - //codestream.access_siz()->parse_string("Cycc=no"); - } - - std::string layer_string = llformat("Clayers=%d",nb_layers); - codestream.access_siz()->parse_string(layer_string.c_str()); - - // Set up data ordering, markers, etc... if precincts or blocks specified - if ((mBlocksSize != -1) || (mPrecinctsSize != -1)) - { - if (mPrecinctsSize != -1) - { - std::string precincts_string = llformat("Cprecincts={%d,%d}",mPrecinctsSize,mPrecinctsSize); - codestream.access_siz()->parse_string(precincts_string.c_str()); - } - if (mBlocksSize != -1) - { - std::string blocks_string = llformat("Cblk={%d,%d}",mBlocksSize,mBlocksSize); - codestream.access_siz()->parse_string(blocks_string.c_str()); - } - std::string ordering_string = llformat("Corder=LRCP"); - codestream.access_siz()->parse_string(ordering_string.c_str()); - std::string PLT_string = llformat("ORGgen_plt=yes"); - codestream.access_siz()->parse_string(PLT_string.c_str()); - std::string Parts_string = llformat("ORGtparts=R"); - codestream.access_siz()->parse_string(Parts_string.c_str()); - } - - // Set the number of wavelets subresolutions (aka levels) - if (mLevels != 0) - { - std::string levels_string = llformat("Clevels=%d",mLevels); - codestream.access_siz()->parse_string(levels_string.c_str()); - } - - // Complete the encode settings - codestream.access_siz()->finalize_all(); - codestream.change_appearance(transpose,vflip,hflip); - - // Now we are ready for sample data processing - kdc_flow_control *tile = new kdc_flow_control(&mem_in,codestream); - bool done = false; - while (!done) - { - // Process line by line - if (tile->advance_components()) - { - tile->process_components(); - } - else - { - done = true; - } - } - - // Produce the compressed output - codestream.flush(layer_bytes,nb_layers); - - // Cleanup - delete tile; - codestream.destroy(); - - // Now that we're done encoding, create the new data buffer for the compressed - // image and stick it there. - base.copyData(output_buffer, output_size); - base.updateData(); // set width, height - delete[] output_buffer; - } - catch(const KDUError& msg) - { - base.setLastError(msg.what()); - return false; - } - catch (kdu_exception kdu_value) - { - // KDU internally throws kdu_exception. It's possible that such an - // exception might leak out into our code. Catch kdu_exception - // specially because boost::current_exception_diagnostic_information() - // could do nothing with it. - base.setLastError(report_kdu_exception(kdu_value)); - return false; - } - catch( ... ) - { - base.setLastError("Unknown J2C error: " + - boost::current_exception_diagnostic_information()); - return false; - } - - return true; -} - -bool LLImageJ2CKDU::getMetadata(LLImageJ2C &base) -{ - // *FIX: kdu calls our callback function if there's an error, and - // then bombs. To regain control, we throw an exception, and - // catch it here. - try - { - setupCodeStream(base, false, MODE_FAST); - return true; - } - catch (const KDUError& msg) - { - base.setLastError(msg.what()); - return false; - } - catch (kdu_exception kdu_value) - { - // KDU internally throws kdu_exception. It's possible that such an - // exception might leak out into our code. Catch kdu_exception - // specially because boost::current_exception_diagnostic_information() - // could do nothing with it. - base.setLastError(report_kdu_exception(kdu_value)); - return false; - } - catch (...) - { - base.setLastError("Unknown J2C error: " + - boost::current_exception_diagnostic_information()); - return false; - } -} - -/*****************************************************************************/ -/* STATIC copy_block */ -/*****************************************************************************/ - -/*==========================================================================*| -// Only called by copy_tile(), which is itself commented out -static void copy_block(kdu_block *in, kdu_block *out) -{ - if (in->K_max_prime != out->K_max_prime) - { - std::cout << "Cannot copy blocks belonging to subbands with different quantization parameters." << std::endl; - return; - } - if ((in->size.x != out->size.x) || (in->size.y != out->size.y)) - { - std::cout << "Cannot copy code-blocks with different dimensions." << std::endl; - return; - } - out->missing_msbs = in->missing_msbs; - if (out->max_passes < (in->num_passes+2)) // Gives us enough to round up - out->set_max_passes(in->num_passes+2,false); // to the next whole bit-plane - out->num_passes = in->num_passes; - int num_bytes = 0; - for (int z=0; z < in->num_passes; z++) - { - num_bytes += (out->pass_lengths[z] = in->pass_lengths[z]); - out->pass_slopes[z] = in->pass_slopes[z]; - } - - // Just copy compressed code-bytes. Block transcoding not supported. - if (out->max_bytes < num_bytes) - out->set_max_bytes(num_bytes,false); - memcpy(out->byte_buffer,in->byte_buffer,(size_t) num_bytes); -} -|*==========================================================================*/ - -/*****************************************************************************/ -/* STATIC copy_tile */ -/*****************************************************************************/ - -/*==========================================================================*| -// Only called by findDiscardLevelsBoundaries(), which is itself commented out -static void -copy_tile(kdu_tile tile_in, kdu_tile tile_out, int tnum_in, int tnum_out, - kdu_params *siz_in, kdu_params *siz_out, int skip_components, - int &num_blocks) -{ - int num_components = tile_out.get_num_components(); - int new_tpart=0, next_tpart = 1; - - for (int c=0; c < num_components; c++) - { - kdu_tile_comp comp_in, comp_out; - comp_in = tile_in.access_component(c); - comp_out = tile_out.access_component(c); - int num_resolutions = comp_out.get_num_resolutions(); - //std::cout << " Copying tile : num_resolutions = " << num_resolutions << std::endl; - for (int r=0; r < num_resolutions; r++) - { - kdu_resolution res_in; res_in = comp_in.access_resolution(r); - kdu_resolution res_out; res_out = comp_out.access_resolution(r); - int b, min_band; - int num_bands = res_in.get_valid_band_indices(min_band); - std::cout << " Copying tile : num_bands = " << num_bands << std::endl; - for (b=min_band; num_bands > 0; num_bands--, b++) - { - kdu_subband band_in; band_in = res_in.access_subband(b); - kdu_subband band_out; band_out = res_out.access_subband(b); - kdu_dims blocks_in; band_in.get_valid_blocks(blocks_in); - kdu_dims blocks_out; band_out.get_valid_blocks(blocks_out); - if ((blocks_in.size.x != blocks_out.size.x) || - (blocks_in.size.y != blocks_out.size.y)) - { - std::cout << "Transcoding operation cannot proceed: Code-block partitions for the input and output code-streams do not agree." << std::endl; - return; - } - kdu_coords idx; - //std::cout << " Copying tile : block indices, x = " << blocks_out.size.x << " and y = " << blocks_out.size.y << std::endl; - for (idx.y=0; idx.y < blocks_out.size.y; idx.y++) - { - for (idx.x=0; idx.x < blocks_out.size.x; idx.x++) - { - kdu_block *in = - band_in.open_block(idx+blocks_in.pos,&new_tpart); - for (; next_tpart <= new_tpart; next_tpart++) - siz_out->copy_from(siz_in,tnum_in,tnum_out,next_tpart, - skip_components); - kdu_block *out = band_out.open_block(idx+blocks_out.pos); - copy_block(in,out); - band_in.close_block(in); - band_out.close_block(out); - num_blocks++; - } - } - } - } - } -} -|*==========================================================================*/ - -// Find the block boundary for each discard level in the input image. -// We parse the input blocks and copy them in a temporary output stream. -// For the moment, we do nothing more that parsing the raw list of blocks and outputing result. -/*==========================================================================*| -// See comments in header file for why this is commented out. -void LLImageJ2CKDU::findDiscardLevelsBoundaries(LLImageJ2C &base) -{ - // We need the number of levels in that image before starting. - getMetadata(base); - - for (int discard_level = 0; discard_level < mLevels; discard_level++) - { - //std::cout << "Parsing discard level = " << discard_level << std::endl; - // Create the input codestream object. - setupCodeStream(base, true, MODE_FAST); - mCodeStreamp->apply_input_restrictions(0, 4, discard_level, 0, NULL); - mCodeStreamp->set_max_bytes(KDU_LONG_MAX,true); - siz_params *siz_in = mCodeStreamp->access_siz(); - - // Create the output codestream object. - siz_params siz; - siz.copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false); - siz.set(Scomponents,0,0,mCodeStreamp->get_num_components()); - - U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents(); - max_output_size = (max_output_size < 1000 ? 1000 : max_output_size); - U8 *output_buffer = new U8[max_output_size]; - U32 output_size = 0; // Address updated by LLKDUMemTarget to give the final compressed buffer size - LLKDUMemTarget output(output_buffer, output_size, max_output_size); - kdu_codestream codestream_out; - codestream_out.create(&siz,&output); - //codestream_out.share_buffering(*mCodeStreamp); - siz_params *siz_out = codestream_out.access_siz(); - siz_out->copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false); - codestream_out.access_siz()->finalize_all(-1); - - // Set up rate control variables - kdu_long max_bytes = KDU_LONG_MAX; - kdu_params *cod = siz_out->access_cluster(COD_params); - int total_layers; cod->get(Clayers,0,0,total_layers); - kdu_long *layer_bytes = new kdu_long[total_layers]; - int nel, non_empty_layers = 0; - - // Now ready to perform the transfer of compressed data between streams - int flush_counter = INT_MAX; - kdu_dims tile_indices_in; - mCodeStreamp->get_valid_tiles(tile_indices_in); - kdu_dims tile_indices_out; - codestream_out.get_valid_tiles(tile_indices_out); - assert((tile_indices_in.size.x == tile_indices_out.size.x) && - (tile_indices_in.size.y == tile_indices_out.size.y)); - int num_blocks=0; - - kdu_coords idx; - //std::cout << "Parsing tiles : x = " << tile_indices_out.size.x << " to y = " << tile_indices_out.size.y << std::endl; - for (idx.y=0; idx.y < tile_indices_out.size.y; idx.y++) - { - for (idx.x=0; idx.x < tile_indices_out.size.x; idx.x++) - { - kdu_tile tile_in = mCodeStreamp->open_tile(idx+tile_indices_in.pos); - int tnum_in = tile_in.get_tnum(); - int tnum_out = idx.x + idx.y*tile_indices_out.size.x; - siz_out->copy_from(siz_in,tnum_in,tnum_out,0,0,discard_level,false,false,false); - siz_out->finalize_all(tnum_out); - // Note: do not open the output tile without first copying any tile-specific code-stream parameters - kdu_tile tile_out = codestream_out.open_tile(idx+tile_indices_out.pos); - assert(tnum_out == tile_out.get_tnum()); - copy_tile(tile_in,tile_out,tnum_in,tnum_out,siz_in,siz_out,0,num_blocks); - tile_in.close(); - tile_out.close(); - flush_counter--; - if ((flush_counter <= 0) && codestream_out.ready_for_flush()) - { - flush_counter = INT_MAX; - nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers); - non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers; - } - } - } - - // Generate the output code-stream - if (codestream_out.ready_for_flush()) - { - nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers); - non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers; - } - if (non_empty_layers > total_layers) - non_empty_layers = total_layers; // Can happen if a tile has more layers - - // Print out stats - std::cout << "Code stream parsing for discard level = " << discard_level << std::endl; - std::cout << " Total compressed memory in = " << mCodeStreamp->get_compressed_data_memory() << " bytes" << std::endl; - std::cout << " Total compressed memory out = " << codestream_out.get_compressed_data_memory() << " bytes" << std::endl; - //std::cout << " Output contains " << total_layers << " quality layers" << std::endl; - std::cout << " Transferred " << num_blocks << " code-blocks from in to out" << std::endl; - //std::cout << " Read " << mCodeStreamp->get_num_tparts() << " tile-part(s) from a total of " << (int) tile_indices_in.area() << " tile(s)" << std::endl; - std::cout << " Total bytes read = " << mCodeStreamp->get_total_bytes() << std::endl; - //std::cout << " Wrote " << codestream_out.get_num_tparts() << " tile-part(s) in a total of " << (int) tile_indices_out.area() << " tile(s)" << std::endl; - std::cout << " Total bytes written = " << codestream_out.get_total_bytes() << std::endl; - std::cout << "-------------" << std::endl; - - // Clean-up - cleanupCodeStream(); - codestream_out.destroy(); - delete[] output_buffer; - } - return; -} -|*==========================================================================*/ - -void set_default_colour_weights(kdu_params *siz) -{ - kdu_params *cod = siz->access_cluster(COD_params); - assert(cod != NULL); - - bool can_use_ycc = true; - bool rev0 = false; - int depth0 = 0, sub_x0 = 1, sub_y0 = 1; - for (int c = 0; c < 3; c++) - { - int depth = 0; siz->get(Sprecision,c,0,depth); - int sub_y = 1; siz->get(Ssampling,c,0,sub_y); - int sub_x = 1; siz->get(Ssampling,c,1,sub_x); - kdu_params *coc = cod->access_relation(-1,c); - bool rev = false; coc->get(Creversible,0,0,rev); - if (c == 0) - { - rev0 = rev; depth0 = depth; sub_x0 = sub_x; sub_y0 = sub_y; - } - else if ((rev != rev0) || (depth != depth0) || - (sub_x != sub_x0) || (sub_y != sub_y0)) - { - can_use_ycc = false; - } - } - if (!can_use_ycc) - { - return; - } - - bool use_ycc; - if (!cod->get(Cycc,0,0,use_ycc)) - { - cod->set(Cycc,0,0,use_ycc=true); - } - if (!use_ycc) - { - return; - } - float weight; - if (cod->get(Clev_weights,0,0,weight) || cod->get(Cband_weights,0,0,weight)) - { - // Weights already specified explicitly -> nothing to do - return; - } - - // These example weights are adapted from numbers generated by Marcus Nadenau - // at EPFL, for a viewing distance of 15 cm and a display resolution of - // 300 DPI. - - cod->parse_string("Cband_weights:C0=" - "{0.0901},{0.2758},{0.2758}," - "{0.7018},{0.8378},{0.8378},{1}"); - cod->parse_string("Cband_weights:C1=" - "{0.0263},{0.0863},{0.0863}," - "{0.1362},{0.2564},{0.2564}," - "{0.3346},{0.4691},{0.4691}," - "{0.5444},{0.6523},{0.6523}," - "{0.7078},{0.7797},{0.7797},{1}"); - cod->parse_string("Cband_weights:C2=" - "{0.0773},{0.1835},{0.1835}," - "{0.2598},{0.4130},{0.4130}," - "{0.5040},{0.6464},{0.6464}," - "{0.7220},{0.8254},{0.8254}," - "{0.8769},{0.9424},{0.9424},{1}"); -} - -/******************************************************************************/ -/* transfer_bytes */ -/******************************************************************************/ - -void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision) -/* Transfers source samples from the supplied line buffer into the output -byte buffer, spacing successive output samples apart by `gap' bytes -(to allow for interleaving of colour components). The function performs -all necessary level shifting, type conversion, rounding and truncation. */ -{ - int width = src.get_width(); - if (src.get_buf32() != NULL) - { // Decompressed samples have a 32-bit representation (integer or float) - assert(precision >= 8); // Else would have used 16 bit representation - kdu_sample32 *sp = src.get_buf32(); - if (!src.is_absolute()) - { // Transferring normalized floating point data. - float scale16 = (float)(1<<16); - kdu_int32 val; - - for (; width > 0; width--, sp++, dest+=gap) - { - val = (kdu_int32)(sp->fval*scale16); - val = (val+128)>>8; // May be faster than true rounding - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 255); - } - *dest = (kdu_byte) val; - } - } - else - { // Transferring 32-bit absolute integers. - kdu_int32 val; - kdu_int32 downshift = precision-8; - kdu_int32 offset = (1<<downshift)>>1; - - for (; width > 0; width--, sp++, dest+=gap) - { - val = sp->ival; - val = (val+offset)>>downshift; - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 255); - } - *dest = (kdu_byte) val; - } - } - } - else - { // Source data is 16 bits. - kdu_sample16 *sp = src.get_buf16(); - if (!src.is_absolute()) - { // Transferring 16-bit fixed point quantities - kdu_int16 val; - - if (precision >= 8) - { // Can essentially ignore the bit-depth. - for (; width > 0; width--, sp++, dest+=gap) - { - val = sp->ival; - val += (1<<(KDU_FIX_POINT-8))>>1; - val >>= (KDU_FIX_POINT-8); - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 255); - } - *dest = (kdu_byte) val; - } - } - else - { // Need to force zeros into one or more least significant bits. - kdu_int16 downshift = KDU_FIX_POINT-precision; - kdu_int16 upshift = 8-precision; - kdu_int16 offset = 1<<(downshift-1); - - for (; width > 0; width--, sp++, dest+=gap) - { - val = sp->ival; - val = (val+offset)>>downshift; - val <<= upshift; - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 256 - (1<<upshift)); - } - *dest = (kdu_byte) val; - } - } - } - else - { // Transferring 16-bit absolute integers. - kdu_int16 val; - - if (precision >= 8) - { - kdu_int16 downshift = precision-8; - kdu_int16 offset = (1<<downshift)>>1; - - for (; width > 0; width--, sp++, dest+=gap) - { - val = sp->ival; - val = (val+offset)>>downshift; - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 255); - } - *dest = (kdu_byte) val; - } - } - else - { - kdu_int16 upshift = 8-precision; - - for (; width > 0; width--, sp++, dest+=gap) - { - val = sp->ival; - val <<= upshift; - val += 128; - if (val & ((0xffffffffU)<<8)) - { - val = (val < 0 ? 0 : 256 - (1<<upshift)); - } - *dest = (kdu_byte) val; - } - } - } - } -} - -LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, - kdu_codestream* codestreamp) -{ - S32 c; - - mTile = tile; - mBuf = buf; - mRowGap = row_gap; - - mNumComponents = tile.get_num_components(); - - llassert(mNumComponents <= 4); - mUseYCC = tile.get_ycc(); - - for (c = 0; c < 4; ++c) - { - mReversible[c] = false; - mBitDepths[c] = 0; - } - - // Open tile-components and create processing engines and resources - for (c = 0; c < mNumComponents; c++) - { - mComps[c] = mTile.access_component(c); - mReversible[c] = mComps[c].get_reversible(); - mBitDepths[c] = mComps[c].get_bit_depth(); - kdu_resolution res = mComps[c].access_resolution(); // Get top resolution - kdu_dims comp_dims; res.get_dims(comp_dims); - if (c == 0) - { - mDims = comp_dims; - } - else - { - llassert(mDims == comp_dims); // Safety check; the caller has ensured this - } - bool use_shorts = (mComps[c].get_bit_depth(true) <= 16); - mLines[c].pre_create(&mAllocator,mDims.size.x,mReversible[c],use_shorts,0,0); - if (res.which() == 0) // No DWT levels used - { - mEngines[c] = kdu_decoder(res.access_subband(LL_BAND),&mAllocator,use_shorts); - } - else - { - mEngines[c] = kdu_synthesis(res,&mAllocator,use_shorts); - } - } - mAllocator.finalize(*codestreamp); // Actually creates buffering resources - for (c = 0; c < mNumComponents; c++) - { - mLines[c].create(); // Grabs resources from the allocator. - } -} - -LLKDUDecodeState::~LLKDUDecodeState() -{ - // Cleanup - for (S32 c = 0; c < mNumComponents; c++) - { - mEngines[c].destroy(); // engines are interfaces; no default destructors - } - mTile.close(); -} - -bool LLKDUDecodeState::processTileDecode(F32 decode_time, bool limit_time) -/* Decompresses a tile, writing the data into the supplied byte buffer. -The buffer contains interleaved image components, if there are any. -Although you may think of the buffer as belonging entirely to this tile, -the `buf' pointer may actually point into a larger buffer representing -multiple tiles. For this reason, `row_gap' is needed to identify the -separation between consecutive rows in the real buffer. */ -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - S32 c; - // Now walk through the lines of the buffer, recovering them from the - // relevant tile-component processing engines. - - LLTimer decode_timer; - while (mDims.size.y--) - { - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - pull"); - for (c = 0; c < mNumComponents; c++) - { - mEngines[c].pull(mLines[c]); - } - } - - if ((mNumComponents >= 3) && mUseYCC) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - convert"); - kdu_convert_ycc_to_rgb(mLines[0],mLines[1],mLines[2]); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - transfer"); - for (c = 0; c < mNumComponents; c++) - { - transfer_bytes(mBuf + c, mLines[c], mNumComponents, mBitDepths[c]); - } - } - mBuf += mRowGap; - if (mDims.size.y % 10) - { - if (limit_time && decode_timer.getElapsedTimeF32() > decode_time) - { - return false; - } - } - } - return true; -} - -// kdc_flow_control - -kdc_flow_control::kdc_flow_control (kdu_supp::kdu_image_in_base *img_in, kdu_codestream codestream) -{ - int n; - - this->codestream = codestream; - codestream.get_valid_tiles(valid_tile_indices); - tile_idx = valid_tile_indices.pos; - tile = codestream.open_tile(tile_idx,NULL); - - // Set up the individual components - num_components = codestream.get_num_components(true); - components = new kdc_component_flow_control[num_components]; - count_delta = 0; - kdc_component_flow_control *comp = components; - for (n = 0; n < num_components; n++, comp++) - { - comp->line = NULL; - comp->reader = img_in; - kdu_coords subsampling; - codestream.get_subsampling(n,subsampling,true); - kdu_dims dims; - codestream.get_tile_dims(tile_idx,n,dims,true); - comp->vert_subsampling = subsampling.y; - if ((n == 0) || (comp->vert_subsampling < count_delta)) - { - count_delta = comp->vert_subsampling; - } - comp->ratio_counter = 0; - comp->remaining_lines = comp->initial_lines = dims.size.y; - } - assert(num_components >= 0); - - tile.set_components_of_interest(num_components); - max_buffer_memory = engine.create(codestream,tile,false,NULL,false,1,NULL,NULL,false); -} - -kdc_flow_control::~kdc_flow_control() -{ - if (components != NULL) - { - delete[] components; - } - if (engine.exists()) - { - engine.destroy(); - } -} - -bool kdc_flow_control::advance_components() -{ - bool found_line = false; - while (!found_line) - { - bool all_done = true; - kdc_component_flow_control *comp = components; - for (int n = 0; n < num_components; n++, comp++) - { - assert(comp->ratio_counter >= 0); - if (comp->remaining_lines > 0) - { - all_done = false; - comp->ratio_counter -= count_delta; - if (comp->ratio_counter < 0) - { - found_line = true; - comp->line = engine.exchange_line(n,NULL,NULL); - assert(comp->line != NULL); - if (comp->line->get_width()) - { - comp->reader->get(n,*(comp->line),0); - } - } - } - } - if (all_done) - { - return false; - } - } - return true; -} - -void kdc_flow_control::process_components() -{ - kdc_component_flow_control *comp = components; - for (int n = 0; n < num_components; n++, comp++) - { - if (comp->ratio_counter < 0) - { - comp->ratio_counter += comp->vert_subsampling; - assert(comp->ratio_counter >= 0); - assert(comp->remaining_lines > 0); - comp->remaining_lines--; - assert(comp->line != NULL); - engine.exchange_line(n,comp->line,NULL); - comp->line = NULL; - } - } -} +/**
+ * @file llimagej2ckdu.cpp
+ * @brief This is an implementation of JPEG2000 encode/decode using Kakadu
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llimagej2ckdu.h"
+
+#include "lltimer.h"
+#include "llpointer.h"
+#include "llmath.h"
+#include "llkdumem.h"
+
+#define kdu_xxxx "kdu_block_coding.h"
+#include "include_kdu_xxxx.h"
+
+// Avoid ubiquitous necessity of kdu_core:: qualification
+using namespace kdu_core;
+
+#include "llexception.h"
+#include <boost/exception/diagnostic_information.hpp>
+#include <sstream>
+#include <iomanip>
+
+// Turns out this must NOT be in the anonymous namespace!
+namespace kdu_core
+{
+// stream kdu_dims to std::ostream
+inline
+std::ostream& operator<<(std::ostream& out, const kdu_dims& dims)
+{
+ return out << "(" << dims.pos.x << "," << dims.pos.y << "),"
+ "[" << dims.size.x << "x" << dims.size.y << "]";
+}
+} // namespace kdu_core
+
+// operator<<(std::ostream&, const kdu_dims&) must precede #include "stringize.h"
+#include "stringize.h"
+
+namespace {
+// Failure to load an image shouldn't crash the whole viewer.
+struct KDUError: public LLContinueError
+{
+ KDUError(const std::string& msg): LLContinueError(msg) {}
+};
+
+// KDU defines int error codes as hex values, so we should log them in hex
+// so we can grep KDU headers for the hex. However those hex values
+// generally "happen" to encode big-endian multibyte character sequences,
+// e.g. KDU_ERROR_EXCEPTION is 0x6b647545: 'kduE'
+// But beware because KDU_NULL_EXCEPTION is simply 0 -- which doesn't
+// preclude somebody from throwing it.
+std::string report_kdu_exception(kdu_exception mb)
+{
+ std::ostringstream out;
+ // always report mb in hex
+ out << "kdu_exception " << std::hex << mb;
+
+ // Also display as many chars as are encoded in the kdu_exception
+ // value. Make a char array; reserve 1 extra byte for nul terminator.
+ char bytes[sizeof(kdu_exception) + 1];
+ // Back up through 'bytes'
+ char *bptr = bytes + sizeof(bytes);
+ *(--bptr) = '\0';
+ while (mb)
+ {
+ // store low-order byte of mb in next-left char
+ *(--bptr) = char(mb & 0xFF);
+ // then shift mb right by one byte
+ mb >>= 8;
+ }
+ // did that produce any characters?
+ if (*bptr)
+ {
+ out << " (" << bptr << ')';
+ }
+
+ return out.str();
+}
+} // anonymous namespace
+
+
+class kdc_flow_control {
+
+public:
+ kdc_flow_control(kdu_supp::kdu_image_in_base *img_in, kdu_codestream codestream);
+ ~kdc_flow_control();
+ bool advance_components();
+ void process_components();
+
+private:
+
+ struct kdc_component_flow_control {
+ public:
+ kdu_supp::kdu_image_in_base *reader;
+ int vert_subsampling;
+ int ratio_counter; /* Initialized to 0, decremented by `count_delta';
+ when < 0, a new line must be processed, after
+ which it is incremented by `vert_subsampling'. */
+ int initial_lines;
+ int remaining_lines;
+ kdu_line_buf *line;
+ };
+
+ kdu_codestream codestream;
+ kdu_dims valid_tile_indices;
+ kdu_coords tile_idx;
+ kdu_tile tile;
+ int num_components;
+ kdc_component_flow_control *components;
+ int count_delta; // Holds the minimum of the `vert_subsampling' fields
+ kdu_multi_analysis engine;
+ kdu_long max_buffer_memory;
+};
+
+//
+// Kakadu specific implementation
+//
+void set_default_colour_weights(kdu_params *siz);
+
+// Factory function: see declaration in llimagej2c.cpp
+LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl()
+{
+ return new LLImageJ2CKDU();
+}
+
+std::string LLImageJ2CKDU::getEngineInfo() const
+{
+ return llformat("KDU %s", KDU_CORE_VERSION);
+}
+
+class LLKDUDecodeState
+{
+public:
+ LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap,
+ kdu_codestream* codestreamp);
+ ~LLKDUDecodeState();
+ bool processTileDecode(F32 decode_time, bool limit_time = true);
+
+private:
+ S32 mNumComponents;
+ bool mUseYCC;
+ kdu_dims mDims;
+ kdu_sample_allocator mAllocator;
+ kdu_tile_comp mComps[4];
+ kdu_line_buf mLines[4];
+ kdu_pull_ifc mEngines[4];
+ bool mReversible[4]; // Some components may be reversible and others not
+ int mBitDepths[4]; // Original bit-depth may be quite different from 8
+
+ kdu_tile mTile;
+ kdu_byte *mBuf;
+ S32 mRowGap;
+};
+
+// Stuff for new kdu error handling
+class LLKDUMessage: public kdu_message
+{
+public:
+ LLKDUMessage(const std::string& type):
+ mType(type)
+ {}
+
+ virtual void put_text(const char *s)
+ {
+ LL_INFOS() << "KDU " << mType << ": " << s << LL_ENDL;
+ }
+
+ virtual void put_text(const kdu_uint16 *s)
+ {
+ // The previous implementation simply streamed 's' to the log. So
+ // either this put_text() override was never called -- or it produced
+ // some baffling log messages -- because I assert that streaming a
+ // const kdu_uint16* to a std::ostream will display only the hex value
+ // of the pointer.
+ LL_INFOS() << "KDU " << mType << ": "
+ << utf16str_to_utf8str(llutf16string(s)) << LL_ENDL;
+ }
+
+private:
+ std::string mType;
+};
+
+struct LLKDUMessageWarning : public LLKDUMessage
+{
+ LLKDUMessageWarning():
+ LLKDUMessage("Warning")
+ {
+ kdu_customize_warnings(this);
+ }
+};
+// Instantiating LLKDUMessageWarning calls kdu_customize_warnings() with the
+// new instance. Make it static so this only happens once.
+static LLKDUMessageWarning sWarningHandler;
+
+struct LLKDUMessageError : public LLKDUMessage
+{
+ LLKDUMessageError():
+ LLKDUMessage("Error")
+ {
+ kdu_customize_errors(this);
+ }
+
+ virtual void flush(bool end_of_message = false)
+ {
+ // According to the documentation nat found:
+ // http://pirlwww.lpl.arizona.edu/resources/guide/software/Kakadu/html_pages/globals__kdu$mize_errors.html
+ // "If a kdu_error object is destroyed, handler→flush will be called with
+ // an end_of_message argument equal to true and the process will
+ // subsequently be terminated through exit. The termination may be
+ // avoided, however, by throwing an exception from within the message
+ // terminating handler→flush call."
+ // So throwing an exception here isn't arbitrary: we MUST throw an
+ // exception if we want to recover from a KDU error.
+ // Because this confused me: the above quote specifically refers to
+ // the kdu_error class, which is constructed internally within KDU at
+ // the point where a fatal error is discovered and reported. It is NOT
+ // talking about the kdu_message subclass passed to
+ // kdu_customize_errors(). Destroying this static object at program
+ // shutdown will NOT engage the behavior described above.
+ if (end_of_message)
+ {
+ LLTHROW(KDUError("LLKDUMessageError::flush()"));
+ }
+ }
+};
+// Instantiating LLKDUMessageError calls kdu_customize_errors() with the new
+// instance. Make it static so this only happens once.
+static LLKDUMessageError sErrorHandler;
+
+LLImageJ2CKDU::LLImageJ2CKDU() : LLImageJ2CImpl(),
+ mInputp(),
+ mCodeStreamp(),
+ mTPosp(),
+ mTileIndicesp(),
+ mRawImagep(NULL),
+ mDecodeState(),
+ mBlocksSize(-1),
+ mPrecinctsSize(-1),
+ mLevels(0)
+{
+}
+
+LLImageJ2CKDU::~LLImageJ2CKDU()
+{
+ cleanupCodeStream(); // in case destroyed before decode completed
+}
+
+// Stuff for new simple decode
+void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision);
+
+// This is called by the real (private) initDecode() (keep_codestream true)
+// and getMetadata() methods (keep_codestream false). As far as nat can tell,
+// mode is always MODE_FAST. It was called by findDiscardLevelsBoundaries()
+// as well, when that still existed, with keep_codestream true and MODE_FAST.
+void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECodeStreamMode mode)
+{
+ LLImageDataLock lock(&base);
+
+ S32 data_size = base.getDataSize();
+ S32 max_bytes = (base.getMaxBytes() ? base.getMaxBytes() : data_size);
+
+ //
+ // Initialization
+ //
+ mCodeStreamp.reset();
+
+ // It's not clear to nat under what circumstances we would reuse a
+ // pre-existing LLKDUMemSource instance. As of 2016-08-05, it consists of
+ // two U32s and a pointer, so it's not as if it would be a huge overhead
+ // to allocate a new one every time.
+ // Also -- why is base.getData() tested specifically here? If that returns
+ // NULL, shouldn't we bail out of the whole method?
+ if (!mInputp && base.getData())
+ {
+ // The compressed data has been loaded
+ // Setup the source for the codestream
+ mInputp.reset(new LLKDUMemSource(base.getData(), data_size));
+ }
+
+ if (mInputp)
+ {
+ // This is LLKDUMemSource::reset(), not boost::scoped_ptr::reset().
+ mInputp->reset();
+ }
+
+ mCodeStreamp->create(mInputp.get());
+
+ // Set the maximum number of bytes to use from the codestream
+ // *TODO: This seems to be wrong. The base class should have no idea of
+ // how j2c compression works so no good way of computing what's the byte
+ // range to be used.
+ mCodeStreamp->set_max_bytes(max_bytes,true);
+
+ // If you want to flip or rotate the image for some reason, change
+ // the resolution, or identify a restricted region of interest, this is
+ // the place to do it. You may use "kdu_codestream::change_appearance"
+ // and "kdu_codestream::apply_input_restrictions" for this purpose.
+ // If you wish to truncate the code-stream prior to decompression, you
+ // may use "kdu_codestream::set_max_bytes".
+ // If you wish to retain all compressed data so that the material
+ // can be decompressed multiple times, possibly with different appearance
+ // parameters, you should call "kdu_codestream::set_persistent" here.
+ // There are a variety of other features which must be enabled at
+ // this point if you want to take advantage of them. See the
+ // descriptions appearing with the "kdu_codestream" interface functions
+ // in "kdu_compressed.h" for an itemized account of these capabilities.
+
+ switch (mode)
+ {
+ case MODE_FAST:
+ mCodeStreamp->set_fast();
+ break;
+ case MODE_RESILIENT:
+ mCodeStreamp->set_resilient();
+ break;
+ case MODE_FUSSY:
+ mCodeStreamp->set_fussy();
+ break;
+ default:
+ llassert(0);
+ mCodeStreamp->set_fast();
+ }
+
+ kdu_dims dims;
+ mCodeStreamp->get_dims(0,dims);
+
+ S32 components = mCodeStreamp->get_num_components();
+
+ // Check that components have consistent dimensions (for PPM file)
+ for (int idx = 1; idx < components; ++idx)
+ {
+ kdu_dims other_dims;
+ mCodeStreamp->get_dims(idx, other_dims);
+ if (other_dims != dims)
+ {
+ // This method is only called from methods that catch KDUError.
+ // We want to fail the image load, not crash the viewer.
+ LLTHROW(KDUError(STRINGIZE("Component " << idx << " dimensions "
+ << stringize(other_dims)
+ << " do not match component 0 dimensions "
+ << stringize(dims) << "!")));
+ }
+ }
+
+ // Get the number of resolution levels in that image
+ mLevels = mCodeStreamp->get_min_dwt_levels();
+
+ // Set the base dimensions
+ base.setSize(dims.size.x, dims.size.y, components);
+ base.setLevels(mLevels);
+
+ if (!keep_codestream)
+ {
+ mCodeStreamp.reset();
+ mInputp.reset();
+ }
+}
+
+void LLImageJ2CKDU::cleanupCodeStream()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ mInputp.reset();
+ mDecodeState.reset();
+ mCodeStreamp.reset();
+ mTPosp.reset();
+ mTileIndicesp.reset();
+}
+
+// This is the protected virtual method called by LLImageJ2C::initDecode().
+// However, as far as nat can tell, LLImageJ2C::initDecode() is called only by
+// llimage_libtest.cpp's load_image() function. No detectable production use.
+bool LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level, int* region)
+{
+ return initDecode(base,raw_image,0.0f,MODE_FAST,0,4,discard_level,region);
+}
+
+bool LLImageJ2CKDU::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels)
+{
+ mPrecinctsSize = precincts_size;
+ if (mPrecinctsSize != -1)
+ {
+ mPrecinctsSize = get_lower_power_two(mPrecinctsSize,MAX_PRECINCT_SIZE);
+ mPrecinctsSize = llmax(mPrecinctsSize,MIN_PRECINCT_SIZE);
+ }
+ mBlocksSize = blocks_size;
+ if (mBlocksSize != -1)
+ {
+ mBlocksSize = get_lower_power_two(mBlocksSize,MAX_BLOCK_SIZE);
+ mBlocksSize = llmax(mBlocksSize,MIN_BLOCK_SIZE);
+ if (mPrecinctsSize != -1)
+ {
+ mBlocksSize = llmin(mBlocksSize,mPrecinctsSize); // blocks *must* be smaller than precincts
+ }
+ }
+ mLevels = levels;
+ if (mLevels != 0)
+ {
+ mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MAX_DECOMPOSITION_LEVELS);
+ base.setLevels(mLevels);
+ }
+ return true;
+}
+
+// This is the real (private) initDecode() called both by the protected
+// initDecode() method and by decodeImpl(). As far as nat can tell, only the
+// decodeImpl() usage matters for production.
+bool LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level, int* region)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ base.resetLastError();
+
+ // *FIX: kdu calls our callback function if there's an error, and then bombs.
+ // To regain control, we throw an exception, and catch it here.
+ try
+ {
+ // Merov : Test!! DO NOT COMMIT!!
+ //findDiscardLevelsBoundaries(base);
+
+ base.updateRawDiscardLevel();
+ setupCodeStream(base, true, mode);
+
+ mRawImagep = &raw_image;
+ mCodeStreamp->change_appearance(false, true, false);
+
+ // Apply loading discard level and cropping if required
+ kdu_dims* region_kdu = NULL;
+ if (region != NULL)
+ {
+ region_kdu = new kdu_dims;
+ region_kdu->pos.x = region[0];
+ region_kdu->pos.y = region[1];
+ region_kdu->size.x = region[2] - region[0];
+ region_kdu->size.y = region[3] - region[1];
+ }
+ int discard = (discard_level != -1 ? discard_level : base.getRawDiscardLevel());
+ //LL_INFOS() << "Merov debug : initDecode, discard used = " << discard << ", asked = " << discard_level << LL_ENDL;
+ // Apply loading restrictions
+ mCodeStreamp->apply_input_restrictions( first_channel, max_channel_count, discard, 0, region_kdu);
+
+ // Clean-up
+ if (region_kdu)
+ {
+ delete region_kdu;
+ region_kdu = NULL;
+ }
+
+ // Resize raw_image according to the image to be decoded
+ kdu_dims dims; mCodeStreamp->get_dims(0,dims);
+ S32 channels = base.getComponents() - first_channel;
+ channels = llmin(channels,max_channel_count);
+ raw_image.resize(dims.size.x, dims.size.y, channels);
+
+ if (!mTileIndicesp)
+ {
+ mTileIndicesp.reset(new kdu_dims);
+ }
+ mCodeStreamp->get_valid_tiles(*mTileIndicesp);
+ if (!mTPosp)
+ {
+ mTPosp.reset(new kdu_coords);
+ mTPosp->y = 0;
+ mTPosp->x = 0;
+ }
+ }
+ catch (const KDUError& msg)
+ {
+ base.setLastError(msg.what());
+ return false;
+ }
+ catch (kdu_exception kdu_value)
+ {
+ // KDU internally throws kdu_exception. It's possible that such an
+ // exception might leak out into our code. Catch kdu_exception
+ // specially because boost::current_exception_diagnostic_information()
+ // could do nothing with it.
+ base.setLastError(report_kdu_exception(kdu_value));
+ return false;
+ }
+ catch (...)
+ {
+ base.setLastError("Unknown J2C error: " +
+ boost::current_exception_diagnostic_information());
+ return false;
+ }
+
+ return true;
+}
+
+
+// Returns true to mean done, whether successful or not.
+bool LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+
+ LLImageDataLock lockIn(&base);
+ LLImageDataLock lockOut(&raw_image);
+
+ ECodeStreamMode mode = MODE_FAST;
+
+ bool limit_time = decode_time > 0.0f;
+ LLTimer decode_timer;
+
+ if (!mCodeStreamp->exists())
+ {
+ if (!initDecode(base, raw_image, decode_time, mode, first_channel, max_channel_count))
+ {
+ // Initializing the J2C decode failed, bail out.
+ cleanupCodeStream();
+ return true; // done
+ }
+ }
+
+ // These can probably be grabbed from what's saved in the class.
+ kdu_dims dims;
+ mCodeStreamp->get_dims(0, dims);
+
+ // Now we are ready to walk through the tiles processing them one-by-one.
+ kdu_byte *buffer = raw_image.getData();
+ if (!buffer)
+ {
+ base.setLastError("Memory error");
+ base.decodeFailed();
+ cleanupCodeStream();
+ return true; // done
+ }
+
+ while (mTPosp->y < mTileIndicesp->size.y)
+ {
+ while (mTPosp->x < mTileIndicesp->size.x)
+ {
+ try
+ {
+ if (!mDecodeState)
+ {
+ kdu_tile tile = mCodeStreamp->open_tile(*(mTPosp)+mTileIndicesp->pos);
+
+ // Find the region of the buffer occupied by this
+ // tile. Note that we have no control over
+ // sub-sampling factors which might have been used
+ // during compression and so it can happen that tiles
+ // (at the image component level) actually have
+ // different dimensions. For this reason, we cannot
+ // figure out the buffer region occupied by a tile
+ // directly from the tile indices. Instead, we query
+ // the highest resolution of the first tile-component
+ // concerning its location and size on the canvas --
+ // the `dims' object already holds the location and
+ // size of the entire image component on the same
+ // canvas coordinate system. Comparing the two tells
+ // us where the current tile is in the buffer.
+ S32 channels = base.getComponents() - first_channel;
+ if (channels > max_channel_count)
+ {
+ channels = max_channel_count;
+ }
+ kdu_resolution res = tile.access_component(0).access_resolution();
+ kdu_dims tile_dims; res.get_dims(tile_dims);
+ kdu_coords offset = tile_dims.pos - dims.pos;
+ int row_gap = channels*dims.size.x; // inter-row separation
+ kdu_byte *buf = buffer + offset.y*row_gap + offset.x*channels;
+ mDecodeState.reset(new LLKDUDecodeState(tile, buf, row_gap,
+ mCodeStreamp.get()));
+ }
+ // Do the actual processing
+ F32 remaining_time = limit_time ? decode_time - decode_timer.getElapsedTimeF32().value() : 0.0f;
+ // This is where we do the actual decode. If we run out of time, return false.
+ if (mDecodeState->processTileDecode(remaining_time, limit_time))
+ {
+ mDecodeState.reset();
+ }
+ else
+ {
+ // Not finished decoding yet.
+ base.setLastError("Ran out of time while decoding");
+ base.decodeFailed();
+ cleanupCodeStream();
+ return false;
+ }
+ }
+ catch (const KDUError& msg)
+ {
+ base.setLastError(msg.what());
+ base.decodeFailed();
+ cleanupCodeStream();
+ return true; // done
+ }
+ catch (kdu_exception kdu_value)
+ {
+ // KDU internally throws kdu_exception. It's possible that such an
+ // exception might leak out into our code. Catch kdu_exception
+ // specially because boost::current_exception_diagnostic_information()
+ // could do nothing with it.
+ base.setLastError(report_kdu_exception(kdu_value));
+ base.decodeFailed();
+ cleanupCodeStream();
+ return true; // done
+ }
+ catch (...)
+ {
+ base.setLastError("Unknown J2C error: " +
+ boost::current_exception_diagnostic_information());
+ base.decodeFailed();
+ cleanupCodeStream();
+ return true; // done
+ }
+
+
+ mTPosp->x++;
+ }
+ mTPosp->y++;
+ mTPosp->x = 0;
+ }
+
+ cleanupCodeStream();
+
+ return true;
+}
+
+
+bool LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible)
+{
+ // Declare and set simple arguments
+ bool transpose = false;
+ bool vflip = true;
+ bool hflip = false;
+
+ try
+ {
+ // Set up input image files
+ siz_params siz;
+
+ // Should set rate someplace here
+ LLKDUMemIn mem_in(raw_image.getData(),
+ raw_image.getDataSize(),
+ raw_image.getWidth(),
+ raw_image.getHeight(),
+ raw_image.getComponents(),
+ &siz);
+
+ base.setSize(raw_image.getWidth(), raw_image.getHeight(), raw_image.getComponents());
+
+ int num_components = raw_image.getComponents();
+
+ siz.set(Scomponents,0,0,num_components);
+ siz.set(Sdims,0,0,base.getHeight()); // Height of first image component
+ siz.set(Sdims,0,1,base.getWidth()); // Width of first image component
+ siz.set(Sprecision,0,0,8); // Image samples have original bit-depth of 8
+ siz.set(Ssigned,0,0,false); // Image samples are originally unsigned
+
+ kdu_params *siz_ref = &siz;
+ siz_ref->finalize();
+ siz_params transformed_siz; // Use this one to construct code-stream
+ transformed_siz.copy_from(&siz,-1,-1,-1,0,transpose,false,false);
+
+ // Construct the `kdu_codestream' object and parse all remaining arguments
+ U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents();
+ max_output_size = (max_output_size < 1000 ? 1000 : max_output_size);
+ U8 *output_buffer = new U8[max_output_size];
+ U32 output_size = 0; // Address updated by LLKDUMemTarget to give the final compressed buffer size
+ LLKDUMemTarget output(output_buffer, output_size, max_output_size);
+
+ kdu_codestream codestream;
+ codestream.create(&transformed_siz,&output);
+
+ if (comment_text)
+ {
+ // Set the comments for the codestream
+ kdu_codestream_comment comment = codestream.add_comment();
+ comment.put_text(comment_text);
+ }
+
+ if (num_components >= 3)
+ {
+ // Note that we always use YCC and not YUV
+ // *TODO: Verify this doesn't screws up reversible textures (like sculpties) as YCC is not reversible but YUV is...
+ set_default_colour_weights(codestream.access_siz());
+ }
+
+ // Set codestream options
+ int nb_layers = 0;
+ kdu_long layer_bytes[MAX_NB_LAYERS];
+ U32 max_bytes = (U32)(base.getWidth() * base.getHeight() * base.getComponents());
+
+ // Rate is the argument passed into the LLImageJ2C which specifies the target compression rate. The default is 8:1.
+ // *TODO: mRate is actually always 8:1 in the viewer. Test different values.
+ llassert (base.mRate > 0.f);
+ max_bytes = (U32)((F32)(max_bytes) * base.mRate);
+
+ // This code is where we specify the target number of bytes for each quality layer.
+ // We're using a logarithmic spacing rule that fits with our way of fetching texture data.
+ // Note: For more info on this layers business, read kdu_codestream::flush() doc in kdu_compressed.h
+ layer_bytes[nb_layers++] = FIRST_PACKET_SIZE;
+ U32 i = MIN_LAYER_SIZE;
+ while ((i < max_bytes) && (nb_layers < (MAX_NB_LAYERS-1)))
+ {
+ layer_bytes[nb_layers++] = i;
+ i *= 4;
+ }
+ // Note: for small images, we can have (max_bytes < FIRST_PACKET_SIZE), hence the test
+ if (layer_bytes[nb_layers-1] < max_bytes)
+ {
+ // Set the last quality layer so to fit the preset compression ratio
+ layer_bytes[nb_layers++] = max_bytes;
+ }
+
+ if (reversible)
+ {
+ // Use 0 for a last quality layer for reversible images so all remaining code blocks will be flushed
+ // Hack: KDU encoding for reversible images has a bug for small images that leads to j2c images that
+ // cannot be open or are very blurry. Avoiding that last layer prevents the problem to happen.
+ if ((base.getWidth() >= 32) || (base.getHeight() >= 32))
+ {
+ layer_bytes[nb_layers++] = 0;
+ }
+ codestream.access_siz()->parse_string("Creversible=yes");
+ // *TODO: we should use yuv in reversible mode
+ // Don't turn this on now though as it creates problems on decoding for the moment
+ //codestream.access_siz()->parse_string("Cycc=no");
+ }
+
+ std::string layer_string = llformat("Clayers=%d",nb_layers);
+ codestream.access_siz()->parse_string(layer_string.c_str());
+
+ // Set up data ordering, markers, etc... if precincts or blocks specified
+ if ((mBlocksSize != -1) || (mPrecinctsSize != -1))
+ {
+ if (mPrecinctsSize != -1)
+ {
+ std::string precincts_string = llformat("Cprecincts={%d,%d}",mPrecinctsSize,mPrecinctsSize);
+ codestream.access_siz()->parse_string(precincts_string.c_str());
+ }
+ if (mBlocksSize != -1)
+ {
+ std::string blocks_string = llformat("Cblk={%d,%d}",mBlocksSize,mBlocksSize);
+ codestream.access_siz()->parse_string(blocks_string.c_str());
+ }
+ std::string ordering_string = llformat("Corder=LRCP");
+ codestream.access_siz()->parse_string(ordering_string.c_str());
+ std::string PLT_string = llformat("ORGgen_plt=yes");
+ codestream.access_siz()->parse_string(PLT_string.c_str());
+ std::string Parts_string = llformat("ORGtparts=R");
+ codestream.access_siz()->parse_string(Parts_string.c_str());
+ }
+
+ // Set the number of wavelets subresolutions (aka levels)
+ if (mLevels != 0)
+ {
+ std::string levels_string = llformat("Clevels=%d",mLevels);
+ codestream.access_siz()->parse_string(levels_string.c_str());
+ }
+
+ // Complete the encode settings
+ codestream.access_siz()->finalize_all();
+ codestream.change_appearance(transpose,vflip,hflip);
+
+ // Now we are ready for sample data processing
+ kdc_flow_control *tile = new kdc_flow_control(&mem_in,codestream);
+ bool done = false;
+ while (!done)
+ {
+ // Process line by line
+ if (tile->advance_components())
+ {
+ tile->process_components();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+
+ // Produce the compressed output
+ codestream.flush(layer_bytes,nb_layers);
+
+ // Cleanup
+ delete tile;
+ codestream.destroy();
+
+ // Now that we're done encoding, create the new data buffer for the compressed
+ // image and stick it there.
+ base.copyData(output_buffer, output_size);
+ base.updateData(); // set width, height
+ delete[] output_buffer;
+ }
+ catch(const KDUError& msg)
+ {
+ base.setLastError(msg.what());
+ return false;
+ }
+ catch (kdu_exception kdu_value)
+ {
+ // KDU internally throws kdu_exception. It's possible that such an
+ // exception might leak out into our code. Catch kdu_exception
+ // specially because boost::current_exception_diagnostic_information()
+ // could do nothing with it.
+ base.setLastError(report_kdu_exception(kdu_value));
+ return false;
+ }
+ catch( ... )
+ {
+ base.setLastError("Unknown J2C error: " +
+ boost::current_exception_diagnostic_information());
+ return false;
+ }
+
+ return true;
+}
+
+bool LLImageJ2CKDU::getMetadata(LLImageJ2C &base)
+{
+ // *FIX: kdu calls our callback function if there's an error, and
+ // then bombs. To regain control, we throw an exception, and
+ // catch it here.
+ try
+ {
+ setupCodeStream(base, false, MODE_FAST);
+ return true;
+ }
+ catch (const KDUError& msg)
+ {
+ base.setLastError(msg.what());
+ return false;
+ }
+ catch (kdu_exception kdu_value)
+ {
+ // KDU internally throws kdu_exception. It's possible that such an
+ // exception might leak out into our code. Catch kdu_exception
+ // specially because boost::current_exception_diagnostic_information()
+ // could do nothing with it.
+ base.setLastError(report_kdu_exception(kdu_value));
+ return false;
+ }
+ catch (...)
+ {
+ base.setLastError("Unknown J2C error: " +
+ boost::current_exception_diagnostic_information());
+ return false;
+ }
+}
+
+/*****************************************************************************/
+/* STATIC copy_block */
+/*****************************************************************************/
+
+/*==========================================================================*|
+// Only called by copy_tile(), which is itself commented out
+static void copy_block(kdu_block *in, kdu_block *out)
+{
+ if (in->K_max_prime != out->K_max_prime)
+ {
+ std::cout << "Cannot copy blocks belonging to subbands with different quantization parameters." << std::endl;
+ return;
+ }
+ if ((in->size.x != out->size.x) || (in->size.y != out->size.y))
+ {
+ std::cout << "Cannot copy code-blocks with different dimensions." << std::endl;
+ return;
+ }
+ out->missing_msbs = in->missing_msbs;
+ if (out->max_passes < (in->num_passes+2)) // Gives us enough to round up
+ out->set_max_passes(in->num_passes+2,false); // to the next whole bit-plane
+ out->num_passes = in->num_passes;
+ int num_bytes = 0;
+ for (int z=0; z < in->num_passes; z++)
+ {
+ num_bytes += (out->pass_lengths[z] = in->pass_lengths[z]);
+ out->pass_slopes[z] = in->pass_slopes[z];
+ }
+
+ // Just copy compressed code-bytes. Block transcoding not supported.
+ if (out->max_bytes < num_bytes)
+ out->set_max_bytes(num_bytes,false);
+ memcpy(out->byte_buffer,in->byte_buffer,(size_t) num_bytes);
+}
+|*==========================================================================*/
+
+/*****************************************************************************/
+/* STATIC copy_tile */
+/*****************************************************************************/
+
+/*==========================================================================*|
+// Only called by findDiscardLevelsBoundaries(), which is itself commented out
+static void
+copy_tile(kdu_tile tile_in, kdu_tile tile_out, int tnum_in, int tnum_out,
+ kdu_params *siz_in, kdu_params *siz_out, int skip_components,
+ int &num_blocks)
+{
+ int num_components = tile_out.get_num_components();
+ int new_tpart=0, next_tpart = 1;
+
+ for (int c=0; c < num_components; c++)
+ {
+ kdu_tile_comp comp_in, comp_out;
+ comp_in = tile_in.access_component(c);
+ comp_out = tile_out.access_component(c);
+ int num_resolutions = comp_out.get_num_resolutions();
+ //std::cout << " Copying tile : num_resolutions = " << num_resolutions << std::endl;
+ for (int r=0; r < num_resolutions; r++)
+ {
+ kdu_resolution res_in; res_in = comp_in.access_resolution(r);
+ kdu_resolution res_out; res_out = comp_out.access_resolution(r);
+ int b, min_band;
+ int num_bands = res_in.get_valid_band_indices(min_band);
+ std::cout << " Copying tile : num_bands = " << num_bands << std::endl;
+ for (b=min_band; num_bands > 0; num_bands--, b++)
+ {
+ kdu_subband band_in; band_in = res_in.access_subband(b);
+ kdu_subband band_out; band_out = res_out.access_subband(b);
+ kdu_dims blocks_in; band_in.get_valid_blocks(blocks_in);
+ kdu_dims blocks_out; band_out.get_valid_blocks(blocks_out);
+ if ((blocks_in.size.x != blocks_out.size.x) ||
+ (blocks_in.size.y != blocks_out.size.y))
+ {
+ std::cout << "Transcoding operation cannot proceed: Code-block partitions for the input and output code-streams do not agree." << std::endl;
+ return;
+ }
+ kdu_coords idx;
+ //std::cout << " Copying tile : block indices, x = " << blocks_out.size.x << " and y = " << blocks_out.size.y << std::endl;
+ for (idx.y=0; idx.y < blocks_out.size.y; idx.y++)
+ {
+ for (idx.x=0; idx.x < blocks_out.size.x; idx.x++)
+ {
+ kdu_block *in =
+ band_in.open_block(idx+blocks_in.pos,&new_tpart);
+ for (; next_tpart <= new_tpart; next_tpart++)
+ siz_out->copy_from(siz_in,tnum_in,tnum_out,next_tpart,
+ skip_components);
+ kdu_block *out = band_out.open_block(idx+blocks_out.pos);
+ copy_block(in,out);
+ band_in.close_block(in);
+ band_out.close_block(out);
+ num_blocks++;
+ }
+ }
+ }
+ }
+ }
+}
+|*==========================================================================*/
+
+// Find the block boundary for each discard level in the input image.
+// We parse the input blocks and copy them in a temporary output stream.
+// For the moment, we do nothing more that parsing the raw list of blocks and outputing result.
+/*==========================================================================*|
+// See comments in header file for why this is commented out.
+void LLImageJ2CKDU::findDiscardLevelsBoundaries(LLImageJ2C &base)
+{
+ // We need the number of levels in that image before starting.
+ getMetadata(base);
+
+ for (int discard_level = 0; discard_level < mLevels; discard_level++)
+ {
+ //std::cout << "Parsing discard level = " << discard_level << std::endl;
+ // Create the input codestream object.
+ setupCodeStream(base, true, MODE_FAST);
+ mCodeStreamp->apply_input_restrictions(0, 4, discard_level, 0, NULL);
+ mCodeStreamp->set_max_bytes(KDU_LONG_MAX,true);
+ siz_params *siz_in = mCodeStreamp->access_siz();
+
+ // Create the output codestream object.
+ siz_params siz;
+ siz.copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false);
+ siz.set(Scomponents,0,0,mCodeStreamp->get_num_components());
+
+ U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents();
+ max_output_size = (max_output_size < 1000 ? 1000 : max_output_size);
+ U8 *output_buffer = new U8[max_output_size];
+ U32 output_size = 0; // Address updated by LLKDUMemTarget to give the final compressed buffer size
+ LLKDUMemTarget output(output_buffer, output_size, max_output_size);
+ kdu_codestream codestream_out;
+ codestream_out.create(&siz,&output);
+ //codestream_out.share_buffering(*mCodeStreamp);
+ siz_params *siz_out = codestream_out.access_siz();
+ siz_out->copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false);
+ codestream_out.access_siz()->finalize_all(-1);
+
+ // Set up rate control variables
+ kdu_long max_bytes = KDU_LONG_MAX;
+ kdu_params *cod = siz_out->access_cluster(COD_params);
+ int total_layers; cod->get(Clayers,0,0,total_layers);
+ kdu_long *layer_bytes = new kdu_long[total_layers];
+ int nel, non_empty_layers = 0;
+
+ // Now ready to perform the transfer of compressed data between streams
+ int flush_counter = INT_MAX;
+ kdu_dims tile_indices_in;
+ mCodeStreamp->get_valid_tiles(tile_indices_in);
+ kdu_dims tile_indices_out;
+ codestream_out.get_valid_tiles(tile_indices_out);
+ assert((tile_indices_in.size.x == tile_indices_out.size.x) &&
+ (tile_indices_in.size.y == tile_indices_out.size.y));
+ int num_blocks=0;
+
+ kdu_coords idx;
+ //std::cout << "Parsing tiles : x = " << tile_indices_out.size.x << " to y = " << tile_indices_out.size.y << std::endl;
+ for (idx.y=0; idx.y < tile_indices_out.size.y; idx.y++)
+ {
+ for (idx.x=0; idx.x < tile_indices_out.size.x; idx.x++)
+ {
+ kdu_tile tile_in = mCodeStreamp->open_tile(idx+tile_indices_in.pos);
+ int tnum_in = tile_in.get_tnum();
+ int tnum_out = idx.x + idx.y*tile_indices_out.size.x;
+ siz_out->copy_from(siz_in,tnum_in,tnum_out,0,0,discard_level,false,false,false);
+ siz_out->finalize_all(tnum_out);
+ // Note: do not open the output tile without first copying any tile-specific code-stream parameters
+ kdu_tile tile_out = codestream_out.open_tile(idx+tile_indices_out.pos);
+ assert(tnum_out == tile_out.get_tnum());
+ copy_tile(tile_in,tile_out,tnum_in,tnum_out,siz_in,siz_out,0,num_blocks);
+ tile_in.close();
+ tile_out.close();
+ flush_counter--;
+ if ((flush_counter <= 0) && codestream_out.ready_for_flush())
+ {
+ flush_counter = INT_MAX;
+ nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers);
+ non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers;
+ }
+ }
+ }
+
+ // Generate the output code-stream
+ if (codestream_out.ready_for_flush())
+ {
+ nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers);
+ non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers;
+ }
+ if (non_empty_layers > total_layers)
+ non_empty_layers = total_layers; // Can happen if a tile has more layers
+
+ // Print out stats
+ std::cout << "Code stream parsing for discard level = " << discard_level << std::endl;
+ std::cout << " Total compressed memory in = " << mCodeStreamp->get_compressed_data_memory() << " bytes" << std::endl;
+ std::cout << " Total compressed memory out = " << codestream_out.get_compressed_data_memory() << " bytes" << std::endl;
+ //std::cout << " Output contains " << total_layers << " quality layers" << std::endl;
+ std::cout << " Transferred " << num_blocks << " code-blocks from in to out" << std::endl;
+ //std::cout << " Read " << mCodeStreamp->get_num_tparts() << " tile-part(s) from a total of " << (int) tile_indices_in.area() << " tile(s)" << std::endl;
+ std::cout << " Total bytes read = " << mCodeStreamp->get_total_bytes() << std::endl;
+ //std::cout << " Wrote " << codestream_out.get_num_tparts() << " tile-part(s) in a total of " << (int) tile_indices_out.area() << " tile(s)" << std::endl;
+ std::cout << " Total bytes written = " << codestream_out.get_total_bytes() << std::endl;
+ std::cout << "-------------" << std::endl;
+
+ // Clean-up
+ cleanupCodeStream();
+ codestream_out.destroy();
+ delete[] output_buffer;
+ }
+ return;
+}
+|*==========================================================================*/
+
+void set_default_colour_weights(kdu_params *siz)
+{
+ kdu_params *cod = siz->access_cluster(COD_params);
+ assert(cod != NULL);
+
+ bool can_use_ycc = true;
+ bool rev0 = false;
+ int depth0 = 0, sub_x0 = 1, sub_y0 = 1;
+ for (int c = 0; c < 3; c++)
+ {
+ int depth = 0; siz->get(Sprecision,c,0,depth);
+ int sub_y = 1; siz->get(Ssampling,c,0,sub_y);
+ int sub_x = 1; siz->get(Ssampling,c,1,sub_x);
+ kdu_params *coc = cod->access_relation(-1,c);
+ bool rev = false; coc->get(Creversible,0,0,rev);
+ if (c == 0)
+ {
+ rev0 = rev; depth0 = depth; sub_x0 = sub_x; sub_y0 = sub_y;
+ }
+ else if ((rev != rev0) || (depth != depth0) ||
+ (sub_x != sub_x0) || (sub_y != sub_y0))
+ {
+ can_use_ycc = false;
+ }
+ }
+ if (!can_use_ycc)
+ {
+ return;
+ }
+
+ bool use_ycc;
+ if (!cod->get(Cycc,0,0,use_ycc))
+ {
+ cod->set(Cycc,0,0,use_ycc=true);
+ }
+ if (!use_ycc)
+ {
+ return;
+ }
+ float weight;
+ if (cod->get(Clev_weights,0,0,weight) || cod->get(Cband_weights,0,0,weight))
+ {
+ // Weights already specified explicitly -> nothing to do
+ return;
+ }
+
+ // These example weights are adapted from numbers generated by Marcus Nadenau
+ // at EPFL, for a viewing distance of 15 cm and a display resolution of
+ // 300 DPI.
+
+ cod->parse_string("Cband_weights:C0="
+ "{0.0901},{0.2758},{0.2758},"
+ "{0.7018},{0.8378},{0.8378},{1}");
+ cod->parse_string("Cband_weights:C1="
+ "{0.0263},{0.0863},{0.0863},"
+ "{0.1362},{0.2564},{0.2564},"
+ "{0.3346},{0.4691},{0.4691},"
+ "{0.5444},{0.6523},{0.6523},"
+ "{0.7078},{0.7797},{0.7797},{1}");
+ cod->parse_string("Cband_weights:C2="
+ "{0.0773},{0.1835},{0.1835},"
+ "{0.2598},{0.4130},{0.4130},"
+ "{0.5040},{0.6464},{0.6464},"
+ "{0.7220},{0.8254},{0.8254},"
+ "{0.8769},{0.9424},{0.9424},{1}");
+}
+
+/******************************************************************************/
+/* transfer_bytes */
+/******************************************************************************/
+
+void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision)
+/* Transfers source samples from the supplied line buffer into the output
+byte buffer, spacing successive output samples apart by `gap' bytes
+(to allow for interleaving of colour components). The function performs
+all necessary level shifting, type conversion, rounding and truncation. */
+{
+ int width = src.get_width();
+ if (src.get_buf32() != NULL)
+ { // Decompressed samples have a 32-bit representation (integer or float)
+ assert(precision >= 8); // Else would have used 16 bit representation
+ kdu_sample32 *sp = src.get_buf32();
+ if (!src.is_absolute())
+ { // Transferring normalized floating point data.
+ float scale16 = (float)(1<<16);
+ kdu_int32 val;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = (kdu_int32)(sp->fval*scale16);
+ val = (val+128)>>8; // May be faster than true rounding
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 255);
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ { // Transferring 32-bit absolute integers.
+ kdu_int32 val;
+ kdu_int32 downshift = precision-8;
+ kdu_int32 offset = (1<<downshift)>>1;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 255);
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ else
+ { // Source data is 16 bits.
+ kdu_sample16 *sp = src.get_buf16();
+ if (!src.is_absolute())
+ { // Transferring 16-bit fixed point quantities
+ kdu_int16 val;
+
+ if (precision >= 8)
+ { // Can essentially ignore the bit-depth.
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val += (1<<(KDU_FIX_POINT-8))>>1;
+ val >>= (KDU_FIX_POINT-8);
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 255);
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ { // Need to force zeros into one or more least significant bits.
+ kdu_int16 downshift = KDU_FIX_POINT-precision;
+ kdu_int16 upshift = 8-precision;
+ kdu_int16 offset = 1<<(downshift-1);
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val <<= upshift;
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 256 - (1<<upshift));
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ else
+ { // Transferring 16-bit absolute integers.
+ kdu_int16 val;
+
+ if (precision >= 8)
+ {
+ kdu_int16 downshift = precision-8;
+ kdu_int16 offset = (1<<downshift)>>1;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 255);
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ {
+ kdu_int16 upshift = 8-precision;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val <<= upshift;
+ val += 128;
+ if (val & ((0xffffffffU)<<8))
+ {
+ val = (val < 0 ? 0 : 256 - (1<<upshift));
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ }
+}
+
+LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap,
+ kdu_codestream* codestreamp)
+{
+ S32 c;
+
+ mTile = tile;
+ mBuf = buf;
+ mRowGap = row_gap;
+
+ mNumComponents = tile.get_num_components();
+
+ llassert(mNumComponents <= 4);
+ mUseYCC = tile.get_ycc();
+
+ for (c = 0; c < 4; ++c)
+ {
+ mReversible[c] = false;
+ mBitDepths[c] = 0;
+ }
+
+ // Open tile-components and create processing engines and resources
+ for (c = 0; c < mNumComponents; c++)
+ {
+ mComps[c] = mTile.access_component(c);
+ mReversible[c] = mComps[c].get_reversible();
+ mBitDepths[c] = mComps[c].get_bit_depth();
+ kdu_resolution res = mComps[c].access_resolution(); // Get top resolution
+ kdu_dims comp_dims; res.get_dims(comp_dims);
+ if (c == 0)
+ {
+ mDims = comp_dims;
+ }
+ else
+ {
+ llassert(mDims == comp_dims); // Safety check; the caller has ensured this
+ }
+ bool use_shorts = (mComps[c].get_bit_depth(true) <= 16);
+ mLines[c].pre_create(&mAllocator,mDims.size.x,mReversible[c],use_shorts,0,0);
+ if (res.which() == 0) // No DWT levels used
+ {
+ mEngines[c] = kdu_decoder(res.access_subband(LL_BAND),&mAllocator,use_shorts);
+ }
+ else
+ {
+ mEngines[c] = kdu_synthesis(res,&mAllocator,use_shorts);
+ }
+ }
+ mAllocator.finalize(*codestreamp); // Actually creates buffering resources
+ for (c = 0; c < mNumComponents; c++)
+ {
+ mLines[c].create(); // Grabs resources from the allocator.
+ }
+}
+
+LLKDUDecodeState::~LLKDUDecodeState()
+{
+ // Cleanup
+ for (S32 c = 0; c < mNumComponents; c++)
+ {
+ mEngines[c].destroy(); // engines are interfaces; no default destructors
+ }
+ mTile.close();
+}
+
+bool LLKDUDecodeState::processTileDecode(F32 decode_time, bool limit_time)
+/* Decompresses a tile, writing the data into the supplied byte buffer.
+The buffer contains interleaved image components, if there are any.
+Although you may think of the buffer as belonging entirely to this tile,
+the `buf' pointer may actually point into a larger buffer representing
+multiple tiles. For this reason, `row_gap' is needed to identify the
+separation between consecutive rows in the real buffer. */
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ S32 c;
+ // Now walk through the lines of the buffer, recovering them from the
+ // relevant tile-component processing engines.
+
+ LLTimer decode_timer;
+ while (mDims.size.y--)
+ {
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - pull");
+ for (c = 0; c < mNumComponents; c++)
+ {
+ mEngines[c].pull(mLines[c]);
+ }
+ }
+
+ if ((mNumComponents >= 3) && mUseYCC)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - convert");
+ kdu_convert_ycc_to_rgb(mLines[0],mLines[1],mLines[2]);
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("kduptc - transfer");
+ for (c = 0; c < mNumComponents; c++)
+ {
+ transfer_bytes(mBuf + c, mLines[c], mNumComponents, mBitDepths[c]);
+ }
+ }
+ mBuf += mRowGap;
+ if (mDims.size.y % 10)
+ {
+ if (limit_time && decode_timer.getElapsedTimeF32() > decode_time)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// kdc_flow_control
+
+kdc_flow_control::kdc_flow_control (kdu_supp::kdu_image_in_base *img_in, kdu_codestream codestream)
+{
+ int n;
+
+ this->codestream = codestream;
+ codestream.get_valid_tiles(valid_tile_indices);
+ tile_idx = valid_tile_indices.pos;
+ tile = codestream.open_tile(tile_idx,NULL);
+
+ // Set up the individual components
+ num_components = codestream.get_num_components(true);
+ components = new kdc_component_flow_control[num_components];
+ count_delta = 0;
+ kdc_component_flow_control *comp = components;
+ for (n = 0; n < num_components; n++, comp++)
+ {
+ comp->line = NULL;
+ comp->reader = img_in;
+ kdu_coords subsampling;
+ codestream.get_subsampling(n,subsampling,true);
+ kdu_dims dims;
+ codestream.get_tile_dims(tile_idx,n,dims,true);
+ comp->vert_subsampling = subsampling.y;
+ if ((n == 0) || (comp->vert_subsampling < count_delta))
+ {
+ count_delta = comp->vert_subsampling;
+ }
+ comp->ratio_counter = 0;
+ comp->remaining_lines = comp->initial_lines = dims.size.y;
+ }
+ assert(num_components >= 0);
+
+ tile.set_components_of_interest(num_components);
+ max_buffer_memory = engine.create(codestream,tile,false,NULL,false,1,NULL,NULL,false);
+}
+
+kdc_flow_control::~kdc_flow_control()
+{
+ if (components != NULL)
+ {
+ delete[] components;
+ }
+ if (engine.exists())
+ {
+ engine.destroy();
+ }
+}
+
+bool kdc_flow_control::advance_components()
+{
+ bool found_line = false;
+ while (!found_line)
+ {
+ bool all_done = true;
+ kdc_component_flow_control *comp = components;
+ for (int n = 0; n < num_components; n++, comp++)
+ {
+ assert(comp->ratio_counter >= 0);
+ if (comp->remaining_lines > 0)
+ {
+ all_done = false;
+ comp->ratio_counter -= count_delta;
+ if (comp->ratio_counter < 0)
+ {
+ found_line = true;
+ comp->line = engine.exchange_line(n,NULL,NULL);
+ assert(comp->line != NULL);
+ if (comp->line->get_width())
+ {
+ comp->reader->get(n,*(comp->line),0);
+ }
+ }
+ }
+ }
+ if (all_done)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void kdc_flow_control::process_components()
+{
+ kdc_component_flow_control *comp = components;
+ for (int n = 0; n < num_components; n++, comp++)
+ {
+ if (comp->ratio_counter < 0)
+ {
+ comp->ratio_counter += comp->vert_subsampling;
+ assert(comp->ratio_counter >= 0);
+ assert(comp->remaining_lines > 0);
+ comp->remaining_lines--;
+ assert(comp->line != NULL);
+ engine.exchange_line(n,comp->line,NULL);
+ comp->line = NULL;
+ }
+ }
+}
diff --git a/indra/llkdu/llimagej2ckdu.h b/indra/llkdu/llimagej2ckdu.h index fe3902380c..8037c81868 100644 --- a/indra/llkdu/llimagej2ckdu.h +++ b/indra/llkdu/llimagej2ckdu.h @@ -1,25 +1,25 @@ -/** +/** * @file llimagej2ckdu.h * @brief This is an implementation of JPEG2000 encode/decode using Kakadu * * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -48,84 +48,84 @@ class LLKDUDecodeState; class LLKDUMemSource; class LLImageJ2CKDU : public LLImageJ2CImpl -{ +{ public: - enum ECodeStreamMode - { - MODE_FAST = 0, - MODE_RESILIENT = 1, - MODE_FUSSY = 2 - }; - LLImageJ2CKDU(); - virtual ~LLImageJ2CKDU(); - + enum ECodeStreamMode + { + MODE_FAST = 0, + MODE_RESILIENT = 1, + MODE_FUSSY = 2 + }; + LLImageJ2CKDU(); + virtual ~LLImageJ2CKDU(); + protected: - virtual bool getMetadata(LLImageJ2C &base); - virtual bool decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count); - virtual bool encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0, - bool reversible=false); - virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL); - virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0); - virtual std::string getEngineInfo() const; + virtual bool getMetadata(LLImageJ2C &base); + virtual bool decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count); + virtual bool encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0, + bool reversible=false); + virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL); + virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0); + virtual std::string getEngineInfo() const; private: - bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level = -1, int* region = NULL); - void setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECodeStreamMode mode); - void cleanupCodeStream(); - - // This method was public, but the only call to it is commented out in our - // own initDecode() method. I (nat 2016-08-04) don't know what it does or - // why. Even if it should be uncommented, it should probably still be - // private. -// void findDiscardLevelsBoundaries(LLImageJ2C &base); - - // Helper class to hold a kdu_codestream, which is a handle to the - // underlying implementation object. When CodeStreamHolder is reset() or - // destroyed, it calls kdu_codestream::destroy() -- which kdu_codestream - // itself does not. - // - // Call through it like a smart pointer using operator->(). - // - // Every RAII class must be noncopyable. For this we don't need move - // support. - class CodeStreamHolder: public boost::noncopyable - { - public: - ~CodeStreamHolder() - { - reset(); - } - - void reset() - { - if (mCodeStream.exists()) - { - mCodeStream.destroy(); - } - } - - // for those few times when you need a raw kdu_codestream* - kdu_core::kdu_codestream* get() { return &mCodeStream; } - kdu_core::kdu_codestream* operator->() { return &mCodeStream; } - - private: - kdu_core::kdu_codestream mCodeStream; - }; - - // Encode variable - std::unique_ptr<LLKDUMemSource> mInputp; - CodeStreamHolder mCodeStreamp; - std::unique_ptr<kdu_core::kdu_coords> mTPosp; // tile position - std::unique_ptr<kdu_core::kdu_dims> mTileIndicesp; - int mBlocksSize; - int mPrecinctsSize; - int mLevels; - - // Temporary variables for in-progress decodes... - // We don't own this LLImageRaw. We're simply pointing to an instance - // passed into initDecode(). - LLImageRaw *mRawImagep; - std::unique_ptr<LLKDUDecodeState> mDecodeState; + bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level = -1, int* region = NULL); + void setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECodeStreamMode mode); + void cleanupCodeStream(); + + // This method was public, but the only call to it is commented out in our + // own initDecode() method. I (nat 2016-08-04) don't know what it does or + // why. Even if it should be uncommented, it should probably still be + // private. +// void findDiscardLevelsBoundaries(LLImageJ2C &base); + + // Helper class to hold a kdu_codestream, which is a handle to the + // underlying implementation object. When CodeStreamHolder is reset() or + // destroyed, it calls kdu_codestream::destroy() -- which kdu_codestream + // itself does not. + // + // Call through it like a smart pointer using operator->(). + // + // Every RAII class must be noncopyable. For this we don't need move + // support. + class CodeStreamHolder: public boost::noncopyable + { + public: + ~CodeStreamHolder() + { + reset(); + } + + void reset() + { + if (mCodeStream.exists()) + { + mCodeStream.destroy(); + } + } + + // for those few times when you need a raw kdu_codestream* + kdu_core::kdu_codestream* get() { return &mCodeStream; } + kdu_core::kdu_codestream* operator->() { return &mCodeStream; } + + private: + kdu_core::kdu_codestream mCodeStream; + }; + + // Encode variable + std::unique_ptr<LLKDUMemSource> mInputp; + CodeStreamHolder mCodeStreamp; + std::unique_ptr<kdu_core::kdu_coords> mTPosp; // tile position + std::unique_ptr<kdu_core::kdu_dims> mTileIndicesp; + int mBlocksSize; + int mPrecinctsSize; + int mLevels; + + // Temporary variables for in-progress decodes... + // We don't own this LLImageRaw. We're simply pointing to an instance + // passed into initDecode(). + LLImageRaw *mRawImagep; + std::unique_ptr<LLKDUDecodeState> mDecodeState; }; #endif diff --git a/indra/llkdu/llkdumem.cpp b/indra/llkdu/llkdumem.cpp index 96e9da25d8..04ab033093 100644 --- a/indra/llkdu/llkdumem.cpp +++ b/indra/llkdu/llkdumem.cpp @@ -1,25 +1,25 @@ - /** + /** * @file llkdumem.cpp * @brief Helper class for kdu memory management * * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -36,164 +36,164 @@ using kd_supp_image_local::image_line_buf; #endif LLKDUMemIn::LLKDUMemIn(const U8 *data, - const U32 size, - const U16 width, - const U16 height, - const U8 in_num_components, - siz_params *siz) + const U32 size, + const U16 width, + const U16 height, + const U8 in_num_components, + siz_params *siz) { - U8 n; - - first_comp_idx = 0; - rows = height; - cols = width; - num_components = in_num_components; - alignment_bytes = 0; - - for (n = 0; n < 3; ++n) - { - precision[n] = 0; - } - - for (n = 0; n < num_components; ++n) - { - siz->set(Sdims,n,0,rows); - siz->set(Sdims,n,1,cols); - siz->set(Ssigned,n,0,false); - siz->set(Sprecision,n,0,8); - } - incomplete_lines = NULL; - free_lines = NULL; - num_unread_rows = rows; - - mData = data; - mDataSize = size; - mCurPos = 0; + U8 n; + + first_comp_idx = 0; + rows = height; + cols = width; + num_components = in_num_components; + alignment_bytes = 0; + + for (n = 0; n < 3; ++n) + { + precision[n] = 0; + } + + for (n = 0; n < num_components; ++n) + { + siz->set(Sdims,n,0,rows); + siz->set(Sdims,n,1,cols); + siz->set(Ssigned,n,0,false); + siz->set(Sprecision,n,0,8); + } + incomplete_lines = NULL; + free_lines = NULL; + num_unread_rows = rows; + + mData = data; + mDataSize = size; + mCurPos = 0; } LLKDUMemIn::~LLKDUMemIn() { - if ((num_unread_rows > 0) || (incomplete_lines != NULL)) - { - kdu_warning w; - w << "Not all rows of image components " - << first_comp_idx << " through " - << first_comp_idx+num_components-1 - << " were consumed!"; - } - image_line_buf *tmp; - while ((tmp=incomplete_lines) != NULL) - { - incomplete_lines = tmp->next; - delete tmp; - } - while ((tmp=free_lines) != NULL) - { - free_lines = tmp->next; - delete tmp; - } + if ((num_unread_rows > 0) || (incomplete_lines != NULL)) + { + kdu_warning w; + w << "Not all rows of image components " + << first_comp_idx << " through " + << first_comp_idx+num_components-1 + << " were consumed!"; + } + image_line_buf *tmp; + while ((tmp=incomplete_lines) != NULL) + { + incomplete_lines = tmp->next; + delete tmp; + } + while ((tmp=free_lines) != NULL) + { + free_lines = tmp->next; + delete tmp; + } } bool LLKDUMemIn::get(int comp_idx, kdu_line_buf &line, int x_tnum) { - int idx = comp_idx - this->first_comp_idx; - assert((idx >= 0) && (idx < num_components)); - x_tnum = x_tnum*num_components+idx; - image_line_buf *scan, *prev=NULL; - for (scan = incomplete_lines; scan != NULL; prev = scan, scan = scan->next) - { - assert(scan->next_x_tnum >= x_tnum); - if (scan->next_x_tnum == x_tnum) - { - break; - } - } - if (scan == NULL) - { // Need to read a new image line. - assert(x_tnum == 0); // Must consume in very specific order. - if (num_unread_rows == 0) - { - return false; - } - if ((scan = free_lines) == NULL) - { - scan = new image_line_buf(cols+3,num_components); - } - free_lines = scan->next; - if (prev == NULL) - { - incomplete_lines = scan; - } - else - { - prev->next = scan; - } - - // Copy from image buffer into scan. - memcpy(scan->buf, mData+mCurPos, cols*num_components); - mCurPos += cols*num_components; - - num_unread_rows--; - scan->accessed_samples = 0; - scan->next_x_tnum = 0; - } - - assert((cols-scan->accessed_samples) >= line.get_width()); - - int comp_offset = idx; - kdu_byte *sp = scan->buf+num_components*scan->accessed_samples + comp_offset; - int n=line.get_width(); - - if (line.get_buf32() != NULL) - { - kdu_sample32 *dp = line.get_buf32(); - if (line.is_absolute()) - { // 32-bit absolute integers - for (; n > 0; n--, sp+=num_components, dp++) - { - dp->ival = ((kdu_int32)(*sp)) - 128; - } - } - else - { // true 32-bit floats - for (; n > 0; n--, sp+=num_components, dp++) - { - dp->fval = (((float)(*sp)) / 256.0F) - 0.5F; - } - } - } - else - { - kdu_sample16 *dp = line.get_buf16(); - if (line.is_absolute()) - { // 16-bit absolute integers - for (; n > 0; n--, sp+=num_components, dp++) - { - dp->ival = ((kdu_int16)(*sp)) - 128; - } - } - else - { // 16-bit normalized representation. - for (; n > 0; n--, sp+=num_components, dp++) - { - dp->ival = (((kdu_int16)(*sp)) - 128) << (KDU_FIX_POINT-8); - } - } - } - - scan->next_x_tnum++; - if (idx == (num_components-1)) - { - scan->accessed_samples += line.get_width(); - } - if (scan->accessed_samples == cols) - { // Send empty line to free list. - assert(scan == incomplete_lines); - incomplete_lines = scan->next; - scan->next = free_lines; - free_lines = scan; - } + int idx = comp_idx - this->first_comp_idx; + assert((idx >= 0) && (idx < num_components)); + x_tnum = x_tnum*num_components+idx; + image_line_buf *scan, *prev=NULL; + for (scan = incomplete_lines; scan != NULL; prev = scan, scan = scan->next) + { + assert(scan->next_x_tnum >= x_tnum); + if (scan->next_x_tnum == x_tnum) + { + break; + } + } + if (scan == NULL) + { // Need to read a new image line. + assert(x_tnum == 0); // Must consume in very specific order. + if (num_unread_rows == 0) + { + return false; + } + if ((scan = free_lines) == NULL) + { + scan = new image_line_buf(cols+3,num_components); + } + free_lines = scan->next; + if (prev == NULL) + { + incomplete_lines = scan; + } + else + { + prev->next = scan; + } + + // Copy from image buffer into scan. + memcpy(scan->buf, mData+mCurPos, cols*num_components); + mCurPos += cols*num_components; + + num_unread_rows--; + scan->accessed_samples = 0; + scan->next_x_tnum = 0; + } + + assert((cols-scan->accessed_samples) >= line.get_width()); + + int comp_offset = idx; + kdu_byte *sp = scan->buf+num_components*scan->accessed_samples + comp_offset; + int n=line.get_width(); + + if (line.get_buf32() != NULL) + { + kdu_sample32 *dp = line.get_buf32(); + if (line.is_absolute()) + { // 32-bit absolute integers + for (; n > 0; n--, sp+=num_components, dp++) + { + dp->ival = ((kdu_int32)(*sp)) - 128; + } + } + else + { // true 32-bit floats + for (; n > 0; n--, sp+=num_components, dp++) + { + dp->fval = (((float)(*sp)) / 256.0F) - 0.5F; + } + } + } + else + { + kdu_sample16 *dp = line.get_buf16(); + if (line.is_absolute()) + { // 16-bit absolute integers + for (; n > 0; n--, sp+=num_components, dp++) + { + dp->ival = ((kdu_int16)(*sp)) - 128; + } + } + else + { // 16-bit normalized representation. + for (; n > 0; n--, sp+=num_components, dp++) + { + dp->ival = (((kdu_int16)(*sp)) - 128) << (KDU_FIX_POINT-8); + } + } + } + + scan->next_x_tnum++; + if (idx == (num_components-1)) + { + scan->accessed_samples += line.get_width(); + } + if (scan->accessed_samples == cols) + { // Send empty line to free list. + assert(scan == incomplete_lines); + incomplete_lines = scan->next; + scan->next = free_lines; + free_lines = scan; + } return true; } diff --git a/indra/llkdu/llkdumem.h b/indra/llkdu/llkdumem.h index 09d81f38de..177a8ea976 100644 --- a/indra/llkdu/llkdumem.h +++ b/indra/llkdu/llkdumem.h @@ -1,25 +1,25 @@ -/** +/** * @file llkdumem.h * @brief Helper class for kdu memory management * * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -47,106 +47,106 @@ class LLKDUMemSource: public kdu_core::kdu_compressed_source { public: - LLKDUMemSource(U8 *input_buffer, U32 size) - { - mData = input_buffer; - mSize = size; - mCurPos = 0; - } - - ~LLKDUMemSource() - { - } - - int read(kdu_core::kdu_byte *buf, int num_bytes) - { - U32 num_out; - num_out = num_bytes; - - if ((mSize - mCurPos) < (U32)num_bytes) - { - num_out = mSize -mCurPos; - } - memcpy(buf, mData + mCurPos, num_out); - mCurPos += num_out; - return num_out; - } - - void reset() - { - mCurPos = 0; - } + LLKDUMemSource(U8 *input_buffer, U32 size) + { + mData = input_buffer; + mSize = size; + mCurPos = 0; + } + + ~LLKDUMemSource() + { + } + + int read(kdu_core::kdu_byte *buf, int num_bytes) + { + U32 num_out; + num_out = num_bytes; + + if ((mSize - mCurPos) < (U32)num_bytes) + { + num_out = mSize -mCurPos; + } + memcpy(buf, mData + mCurPos, num_out); + mCurPos += num_out; + return num_out; + } + + void reset() + { + mCurPos = 0; + } private: - U8 *mData; - U32 mSize; - U32 mCurPos; + U8 *mData; + U32 mSize; + U32 mCurPos; }; class LLKDUMemTarget: public kdu_core::kdu_compressed_target { public: - LLKDUMemTarget(U8 *output_buffer, U32 &output_size, const U32 buffer_size) - { - mData = output_buffer; - mSize = buffer_size; - mCurPos = 0; - mOutputSize = &output_size; - } - - ~LLKDUMemTarget() - { - } - - bool write(const kdu_core::kdu_byte *buf, int num_bytes) - { - U32 num_out; - num_out = num_bytes; - - if ((mSize - mCurPos) < (U32)num_bytes) - { - num_out = mSize - mCurPos; - memcpy(mData + mCurPos, buf, num_out); - return false; - } - memcpy(mData + mCurPos, buf, num_out); - mCurPos += num_out; - *mOutputSize = mCurPos; - return true; - } - + LLKDUMemTarget(U8 *output_buffer, U32 &output_size, const U32 buffer_size) + { + mData = output_buffer; + mSize = buffer_size; + mCurPos = 0; + mOutputSize = &output_size; + } + + ~LLKDUMemTarget() + { + } + + bool write(const kdu_core::kdu_byte *buf, int num_bytes) + { + U32 num_out; + num_out = num_bytes; + + if ((mSize - mCurPos) < (U32)num_bytes) + { + num_out = mSize - mCurPos; + memcpy(mData + mCurPos, buf, num_out); + return false; + } + memcpy(mData + mCurPos, buf, num_out); + mCurPos += num_out; + *mOutputSize = mCurPos; + return true; + } + private: - U8 *mData; - U32 mSize; - U32 mCurPos; - U32 *mOutputSize; + U8 *mData; + U32 mSize; + U32 mCurPos; + U32 *mOutputSize; }; class LLKDUMemIn : public kdu_supp::kdu_image_in_base { public: - LLKDUMemIn(const U8 *data, - const U32 size, - const U16 rows, - const U16 cols, - U8 in_num_components, - kdu_core::siz_params *siz); - ~LLKDUMemIn(); + LLKDUMemIn(const U8 *data, + const U32 size, + const U16 rows, + const U16 cols, + U8 in_num_components, + kdu_core::siz_params *siz); + ~LLKDUMemIn(); - bool get(int comp_idx, kdu_core::kdu_line_buf &line, int x_tnum); + bool get(int comp_idx, kdu_core::kdu_line_buf &line, int x_tnum); private: - const U8 *mData; - int first_comp_idx; - int num_components; - int rows, cols; - int alignment_bytes; // Number of 0's at end of each line. - int precision[3]; - kd_supp_image_local::image_line_buf *incomplete_lines; // Each "sample" represents a full pixel - kd_supp_image_local::image_line_buf *free_lines; - int num_unread_rows; - - U32 mCurPos; - U32 mDataSize; + const U8 *mData; + int first_comp_idx; + int num_components; + int rows, cols; + int alignment_bytes; // Number of 0's at end of each line. + int precision[3]; + kd_supp_image_local::image_line_buf *incomplete_lines; // Each "sample" represents a full pixel + kd_supp_image_local::image_line_buf *free_lines; + int num_unread_rows; + + U32 mCurPos; + U32 mDataSize; }; #endif diff --git a/indra/llkdu/tests/llimagej2ckdu_test.cpp b/indra/llkdu/tests/llimagej2ckdu_test.cpp index 16213b7f45..db81d60d9e 100644 --- a/indra/llkdu/tests/llimagej2ckdu_test.cpp +++ b/indra/llkdu/tests/llimagej2ckdu_test.cpp @@ -1,4 +1,4 @@ -/** +/** * @file llimagej2ckdu_test.cpp * @author Merov Linden * @date 2010-12-17 @@ -6,27 +6,27 @@ * $LicenseInfo:firstyear=2006&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" -// Class to test +// Class to test #include "llimagej2ckdu.h" #if __clang__ @@ -44,7 +44,7 @@ // ------------------------------------------------------------------------------------------- // Stubbing: Declarations required to link and run the class being tested -// Notes: +// Notes: // * Add here stubbed implementation of the few classes and methods used in the class to be tested // * Add as little as possible (let the link errors guide you) // * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) @@ -53,7 +53,7 @@ // End Stubbing // ------------------------------------------------------------------------------------------- // Stub the LL Image Classes -//LLTrace::MemStatHandle LLImageBase::sMemStat("LLImage"); +//LLTrace::MemStatHandle LLImageBase::sMemStat("LLImage"); LLImageRaw::LLImageRaw() { } LLImageRaw::~LLImageRaw() { } @@ -241,81 +241,81 @@ void kdu_params::operator delete(void *) {} namespace tut { - // Test wrapper declarations - struct llimagej2ckdu_test - { - // Derived test class - class LLTestImageJ2CKDU : public LLImageJ2CKDU - { - public: - // Provides public access to some protected methods for testing - bool callGetMetadata(LLImageJ2C &base) { return getMetadata(base); } - bool callDecodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) - { - return decodeImpl(base, raw_image, decode_time, first_channel, max_channel_count); - } - bool callEncodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text) - { - return encodeImpl(base, raw_image, comment_text); - } - }; - // Instance to be tested - LLTestImageJ2CKDU* mImage; - - // Constructor and destructor of the test wrapper - llimagej2ckdu_test() - { - mImage = new LLTestImageJ2CKDU; - } - ~llimagej2ckdu_test() - { - delete mImage; - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group<llimagej2ckdu_test> llimagej2ckdu_t; - typedef llimagej2ckdu_t::object llimagej2ckdu_object_t; - tut::llimagej2ckdu_t tut_llimagej2ckdu("LLImageJ2CKDU"); - - // --------------------------------------------------------------------------------------- - // Test functions - // Notes: - // * Test as many as you possibly can without requiring a full blown simulation of everything - // * The tests are executed in sequence so the test instance state may change between calls - // * Remember that you cannot test private methods with tut - // --------------------------------------------------------------------------------------- + // Test wrapper declarations + struct llimagej2ckdu_test + { + // Derived test class + class LLTestImageJ2CKDU : public LLImageJ2CKDU + { + public: + // Provides public access to some protected methods for testing + bool callGetMetadata(LLImageJ2C &base) { return getMetadata(base); } + bool callDecodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) + { + return decodeImpl(base, raw_image, decode_time, first_channel, max_channel_count); + } + bool callEncodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text) + { + return encodeImpl(base, raw_image, comment_text); + } + }; + // Instance to be tested + LLTestImageJ2CKDU* mImage; + + // Constructor and destructor of the test wrapper + llimagej2ckdu_test() + { + mImage = new LLTestImageJ2CKDU; + } + ~llimagej2ckdu_test() + { + delete mImage; + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group<llimagej2ckdu_test> llimagej2ckdu_t; + typedef llimagej2ckdu_t::object llimagej2ckdu_object_t; + tut::llimagej2ckdu_t tut_llimagej2ckdu("LLImageJ2CKDU"); + + // --------------------------------------------------------------------------------------- + // Test functions + // Notes: + // * Test as many as you possibly can without requiring a full blown simulation of everything + // * The tests are executed in sequence so the test instance state may change between calls + // * Remember that you cannot test private methods with tut + // --------------------------------------------------------------------------------------- - // Test 1 : test getMetadata() - template<> template<> - void llimagej2ckdu_object_t::test<1>() - { - LLImageJ2C* image = new LLImageJ2C(); - bool res = mImage->callGetMetadata(*image); - // Trying to set up a data stream with all NIL values and stubbed KDU will "work" and return true - // Note that is linking with KDU, that call will throw an exception and fail, returning false - ensure("getMetadata() test failed", res); - } + // Test 1 : test getMetadata() + template<> template<> + void llimagej2ckdu_object_t::test<1>() + { + LLImageJ2C* image = new LLImageJ2C(); + bool res = mImage->callGetMetadata(*image); + // Trying to set up a data stream with all NIL values and stubbed KDU will "work" and return true + // Note that is linking with KDU, that call will throw an exception and fail, returning false + ensure("getMetadata() test failed", res); + } - // Test 2 : test decodeImpl() - template<> template<> - void llimagej2ckdu_object_t::test<2>() - { - LLImageJ2C* image = new LLImageJ2C(); - LLImageRaw* raw = new LLImageRaw(); - bool res = mImage->callDecodeImpl(*image, *raw, 0.0, 0, 0); - // Decoding returns true whenever there's nothing else to do, including if decoding failed, so we'll get true here - ensure("decodeImpl() test failed", res); - } + // Test 2 : test decodeImpl() + template<> template<> + void llimagej2ckdu_object_t::test<2>() + { + LLImageJ2C* image = new LLImageJ2C(); + LLImageRaw* raw = new LLImageRaw(); + bool res = mImage->callDecodeImpl(*image, *raw, 0.0, 0, 0); + // Decoding returns true whenever there's nothing else to do, including if decoding failed, so we'll get true here + ensure("decodeImpl() test failed", res); + } - // Test 3 : test encodeImpl() - template<> template<> - void llimagej2ckdu_object_t::test<3>() - { - LLImageJ2C* image = new LLImageJ2C(); - LLImageRaw* raw = new LLImageRaw(); - bool res = mImage->callEncodeImpl(*image, *raw, NULL); - // Encoding returns true unless an exception was raised, so we'll get true here though nothing really was done - ensure("encodeImpl() test failed", res); - } + // Test 3 : test encodeImpl() + template<> template<> + void llimagej2ckdu_object_t::test<3>() + { + LLImageJ2C* image = new LLImageJ2C(); + LLImageRaw* raw = new LLImageRaw(); + bool res = mImage->callEncodeImpl(*image, *raw, NULL); + // Encoding returns true unless an exception was raised, so we'll get true here though nothing really was done + ensure("encodeImpl() test failed", res); + } } |