diff options
Diffstat (limited to 'indra')
-rw-r--r-- | indra/llimage/llimage.cpp | 4 | ||||
-rw-r--r-- | indra/llimage/llimage.h | 8 | ||||
-rw-r--r-- | indra/llimage/llimagej2c.cpp | 73 | ||||
-rw-r--r-- | indra/llimage/llimagej2c.h | 6 | ||||
-rw-r--r-- | indra/llkdu/llimagej2ckdu.cpp | 125 | ||||
-rw-r--r-- | indra/llkdu/tests/llimagej2ckdu_test.cpp | 3 | ||||
-rwxr-xr-x | indra/newview/app_settings/settings.xml | 11 | ||||
-rw-r--r-- | indra/newview/llappviewer.cpp | 2 | ||||
-rw-r--r-- | indra/newview/lltexlayer.cpp | 1 | ||||
-rw-r--r-- | indra/newview/llviewertexturelist.cpp | 1 |
10 files changed, 121 insertions, 113 deletions
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 937655a22d..7f95441075 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -48,11 +48,13 @@ //static std::string LLImage::sLastErrorMessage; LLMutex* LLImage::sMutex = NULL; +bool LLImage::sUseNewByteRange = false; LLPrivateMemoryPool* LLImageBase::sPrivatePoolp = NULL ; //static -void LLImage::initClass() +void LLImage::initClass(bool use_new_byte_range) { + sUseNewByteRange = use_new_byte_range; sMutex = new LLMutex(NULL); LLImageBase::createPrivatePool() ; diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index eba8362f1c..a643e4d9f5 100644 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -48,6 +48,8 @@ const S32 MAX_PRECINCT_SIZE = 2048; // No reason to be bigger than MAX_IMAGE_S const S32 MIN_PRECINCT_SIZE = 4; // Can't be smaller than MIN_BLOCK_SIZE const S32 MAX_BLOCK_SIZE = 64; // Max total block size is 4096, hence 64x64 when using square blocks const S32 MIN_BLOCK_SIZE = 4; // Min block dim is 4 according to jpeg2000 spec +const S32 MIN_LAYER_SIZE = 2000; // Size of the first quality layer (after header). Must be > to FIRST_PACKET_SIZE!! +const S32 MAX_NB_LAYERS = 64; // Max number of layers we'll entertain in SL (practical limit) const S32 MIN_IMAGE_SIZE = (1<<MIN_IMAGE_MIP); // 4, only used for expand/contract power of 2 const S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 2048 @@ -60,6 +62,7 @@ const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; //2048 * // *TODO: change both to 1024 when SIM texture fetching is deprecated const S32 FIRST_PACKET_SIZE = 600; const S32 MAX_IMG_PACKET_SIZE = 1000; +const S32 HTTP_PACKET_SIZE = 1496; // Base classes for images. // There are two major parts for the image: @@ -89,15 +92,18 @@ typedef enum e_image_codec class LLImage { public: - static void initClass(); + static void initClass(bool use_new_byte_range = false); static void cleanupClass(); static const std::string& getLastError(); static void setLastError(const std::string& message); + static bool useNewByteRange() { return sUseNewByteRange; } + protected: static LLMutex* sMutex; static std::string sLastErrorMessage; + static bool sUseNewByteRange; }; //============================================================================ diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index fbf4b769e1..69d261c9a6 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -56,7 +56,7 @@ std::string LLImageJ2C::getEngineInfo() LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), mMaxBytes(0), mRawDiscardLevel(-1), - mRate(0.0f), + mRate(DEFAULT_COMPRESSION_RATE), mReversible(FALSE), mAreaUsedForDataSizeCalcs(0) { @@ -262,19 +262,35 @@ S32 LLImageJ2C::calcHeaderSizeJ2C() //static S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) { - // Note: this only provides an *estimate* of the size in bytes of an image level - // *TODO: find a way to read the true size (when available) and convey the fact - // that the result is an estimate in the other cases - if (rate <= 0.f) rate = .125f; - while (discard_level > 0) + // Note: This provides an estimation for the first to last quality layer of a given discard level + // This is however an efficient approximation, as the true discard level boundary would be + // in general too big for fast fetching. + // For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study + + // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). + S32 nb_layers = 1; + S32 surface = w*h; + S32 s = 64*64; + while (surface > s) { - if (w < 1 || h < 1) - break; - w >>= 1; - h >>= 1; - discard_level--; + nb_layers++; + s *= 4; } - S32 bytes = (S32)((F32)(w*h*comp)*rate); + F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6)); + + // Compute w/pow(2,discard_level) and h/pow(2,discard_level) + w >>= discard_level; + h >>= discard_level; + w = llmax(w, 1); + h = llmax(h, 1); + + // Temporary: compute both new and old range and pick one according to the settings TextureNewByteRange + // *TODO: Take the old code out once we have enough tests done + S32 bytes; + S32 new_bytes = sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor; + S32 old_bytes = (S32)((F32)(w*h*comp)*rate); + //llinfos << "Merov debug : w = " << w << ", h = " << h << ", c = " << comp << ", r = " << rate << ", d = " << discard_level << ", l = " << nb_layers << ", old = " << old_bytes << ", new = " << new_bytes << llendl; + bytes = (LLImage::useNewByteRange() ? new_bytes : old_bytes); bytes = llmax(bytes, calcHeaderSizeJ2C()); return bytes; } @@ -284,17 +300,12 @@ S32 LLImageJ2C::calcHeaderSize() return calcHeaderSizeJ2C(); } - -// calcDataSize() returns how many bytes to read -// to load discard_level (including header and higher discard levels) -// *TODO: This is deeply wrong. That size should be taken from the image file header or other -// relevant infos. In any case, this is only an approximation. +// calcDataSize() returns how many bytes to read to load discard_level (including header) S32 LLImageJ2C::calcDataSize(S32 discard_level) { discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL); - if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth()) - || mDataSizes[0] == 0) + || (mDataSizes[0] == 0)) { mAreaUsedForDataSizeCalcs = getHeight() * getWidth(); @@ -304,25 +315,6 @@ S32 LLImageJ2C::calcDataSize(S32 discard_level) mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); level--; } - - /* This is technically a more correct way to calculate the size required - for each discard level, since they should include the size needed for - lower levels. Unfortunately, this doesn't work well and will lead to - download stalls. The true correct way is to parse the header. This will - all go away with http textures at some point. - - // Calculate the size for each discard level. Lower levels (higher quality) - // contain the cumulative size of higher levels - S32 total_size = calcHeaderSizeJ2C(); - - S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard - while ( level >= 0 ) - { // Add in this discard level and all before it - total_size += calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); - mDataSizes[level] = total_size; - level--; - } - */ } return mDataSizes[discard_level]; } @@ -351,11 +343,6 @@ S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) return discard_level; } -void LLImageJ2C::setRate(F32 rate) -{ - mRate = rate; -} - void LLImageJ2C::setMaxBytes(S32 max_bytes) { mMaxBytes = max_bytes; diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index 914174fc57..91c344d12f 100644 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h @@ -31,6 +31,9 @@ #include "llassettype.h" #include "llmetricperformancetester.h" +// JPEG2000 : compression rate used in j2c conversion. +const F32 DEFAULT_COMPRESSION_RATE = 1.f/8.f; + class LLImageJ2CImpl; class LLImageCompressionTester ; @@ -67,12 +70,11 @@ public: // Encode accessors void setReversible(const BOOL reversible); // Use non-lossy? - void setRate(F32 rate); void setMaxBytes(S32 max_bytes); S32 getMaxBytes() const { return mMaxBytes; } static S32 calcHeaderSizeJ2C(); - static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = 0.f); + static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = DEFAULT_COMPRESSION_RATE); static std::string getEngineInfo(); diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 8b170a3206..cbfc34ebb8 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -245,6 +245,8 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECod mCodeStreamp->create(mInputp); // 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 @@ -294,7 +296,7 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECod // 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); @@ -357,7 +359,8 @@ BOOL LLImageJ2CKDU::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int bloc mLevels = levels; if (mLevels != 0) { - mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MIN_DECOMPOSITION_LEVELS); + mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MAX_DECOMPOSITION_LEVELS); + base.setLevels(mLevels); } return TRUE; } @@ -390,7 +393,7 @@ BOOL LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco region_kdu->size.y = region[3] - region[1]; } int discard = (discard_level != -1 ? discard_level : base.getRawDiscardLevel()); - + //llinfos << "Merov debug : initDecode, discard used = " << discard << ", asked = " << discard_level << llendl; // Apply loading restrictions mCodeStreamp->apply_input_restrictions( first_channel, max_channel_count, discard, 0, region_kdu); @@ -589,12 +592,6 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co comment.put_text(comment_text); } - // Set codestream options - int num_layer_specs = 0; - - kdu_long layer_bytes[64]; - U32 max_bytes = 0; - if (num_components >= 3) { // Note that we always use YCC and not YUV @@ -602,67 +599,63 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co 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); + + // If the image is very small, code it in a lossless way. + // Note: it'll also have only 1 layer which is fine as there's no point reordering blocks in that case. + if (max_bytes < FIRST_PACKET_SIZE) + { + reversible = true; + } + + // 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 + U32 i = FIRST_PACKET_SIZE; + while ((i < max_bytes) && (nb_layers < (MAX_NB_LAYERS-1))) + { + if (i == FIRST_PACKET_SIZE * 4) + { + // That really just means that the first layer is FIRST_PACKET_SIZE and the second is MIN_LAYER_SIZE + i = MIN_LAYER_SIZE; + } + layer_bytes[nb_layers] = i; + nb_layers++; + i *= 4; + } + if (reversible) { codestream.access_siz()->parse_string("Creversible=yes"); - // *TODO: we should use yuv in reversible mode and one level since those images are small. + // *TODO: we should use yuv in reversible mode and one res level since those images are small. // Don't turn this on now though as both create problems on decoding for the moment //codestream.access_siz()->parse_string("Clevels=1"); //codestream.access_siz()->parse_string("Cycc=no"); - // If we're doing reversible (i.e. lossless compression), assumes we're not using quality layers. - // *TODO: this is incorrect and unecessary. Try using the regular layer setting. - codestream.access_siz()->parse_string("Clayers=1"); - num_layer_specs = 1; - layer_bytes[0] = 0; + // In the reversible case, set the last entry of that table to 0 so that all generated bits will + // indeed be output by the time the last quality layer is encountered. + layer_bytes[nb_layers] = 0; } else { - // Rate is the argument passed into the LLImageJ2C which - // specifies the target compression rate. The default is 8:1. - // Possibly if max_bytes < 500, we should just use the default setting? - // *TODO: mRate is actually always 8:1 in the viewer. Test different values. Also force to reversible for small (< 500 bytes) textures. - if (base.mRate != 0.f) - { - max_bytes = (U32)(base.mRate*base.getWidth()*base.getHeight()*base.getComponents()); - } - else - { - max_bytes = (U32)(base.getWidth()*base.getHeight()*base.getComponents()*0.125); - } - - const U32 min_bytes = FIRST_PACKET_SIZE; - if (max_bytes > min_bytes) - { - U32 i; - // This code is where we specify the target number of bytes for - // each layer. Not sure if we should do this for small images - // or not. The goal is to have this roughly align with - // different quality levels that we decode at. - for (i = min_bytes; i < max_bytes; i*=4) - { - if (i == min_bytes * 4) - { - i = 2000; - } - layer_bytes[num_layer_specs] = i; - num_layer_specs++; - } - layer_bytes[num_layer_specs] = max_bytes; - num_layer_specs++; - - std::string layer_string = llformat("Clayers=%d",num_layer_specs); - codestream.access_siz()->parse_string(layer_string.c_str()); - } - else - { - layer_bytes[0] = min_bytes; - num_layer_specs = 1; - std::string layer_string = llformat("Clayers=%d",num_layer_specs); - codestream.access_siz()->parse_string(layer_string.c_str()); - } + // Truncate the last quality layer if necessary so to fit the set compression ratio + layer_bytes[nb_layers] = max_bytes; } + nb_layers++; + + 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 + // Note: This code is *not* used in the encoding made by the viewer. It is currently used only + // by llimage_libtest to create various j2c and test alternative compression schemes. if ((mBlocksSize != -1) || (mPrecinctsSize != -1)) { if (mPrecinctsSize != -1) @@ -675,23 +668,26 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co 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=RPCL"); + 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. + // Now we are ready for sample data processing kdc_flow_control *tile = new kdc_flow_control(&mem_in,codestream); bool done = false; while (!done) @@ -708,7 +704,7 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co } // Produce the compressed output - codestream.flush(layer_bytes,num_layer_specs); + codestream.flush(layer_bytes,nb_layers); // Cleanup delete tile; @@ -807,12 +803,14 @@ copy_tile(kdu_tile tile_in, kdu_tile tile_out, int tnum_in, int tnum_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); @@ -826,6 +824,7 @@ copy_tile(kdu_tile tile_in, kdu_tile tile_out, int tnum_in, int tnum_out, 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++) @@ -857,10 +856,11 @@ void LLImageJ2CKDU::findDiscardLevelsBoundaries(LLImageJ2C &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(max,true); + mCodeStreamp->set_max_bytes(KDU_LONG_MAX,true); siz_params *siz_in = mCodeStreamp->access_siz(); // Create the output codestream object. @@ -898,6 +898,7 @@ void LLImageJ2CKDU::findDiscardLevelsBoundaries(LLImageJ2C &base) 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++) diff --git a/indra/llkdu/tests/llimagej2ckdu_test.cpp b/indra/llkdu/tests/llimagej2ckdu_test.cpp index feb6671e40..beee99a522 100644 --- a/indra/llkdu/tests/llimagej2ckdu_test.cpp +++ b/indra/llkdu/tests/llimagej2ckdu_test.cpp @@ -87,7 +87,7 @@ void LLImageFormatted::resetLastError() { } void LLImageFormatted::sanityCheck() { } void LLImageFormatted::setLastError(const std::string& , const std::string& ) { } -LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C) { } +LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), mRate(DEFAULT_COMPRESSION_RATE) { } LLImageJ2C::~LLImageJ2C() { } S32 LLImageJ2C::calcDataSize(S32 ) { return 0; } S32 LLImageJ2C::calcDiscardLevelBytes(S32 ) { return 0; } @@ -146,6 +146,7 @@ void kdu_codestream::set_fast() { } void kdu_codestream::set_fussy() { } void kdu_codestream::get_dims(int, kdu_dims&, bool ) { } int kdu_codestream::get_min_dwt_levels() { return 5; } +int kdu_codestream::get_max_tile_layers() { return 1; } void kdu_codestream::change_appearance(bool, bool, bool) { } void kdu_codestream::get_tile_dims(kdu_coords, int, kdu_dims&, bool ) { } void kdu_codestream::destroy() { } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9fff543b13..5fc9c5d863 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10686,6 +10686,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>TextureNewByteRange</key> + <map> + <key>Comment</key> + <string>Use the new more accurate byte range computation for j2c discard levels</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>TexturePickerShowFolders</key> <map> <key>Comment</key> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 971b096a15..93ad72a147 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1945,7 +1945,7 @@ bool LLAppViewer::initThreads() static const bool enable_threads = true; #endif - LLImage::initClass(); + LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange")); LLVFSThread::initClass(enable_threads && false); LLLFSThread::initClass(enable_threads && false); diff --git a/indra/newview/lltexlayer.cpp b/indra/newview/lltexlayer.cpp index 1693cfc9e2..467115c928 100644 --- a/indra/newview/lltexlayer.cpp +++ b/indra/newview/lltexlayer.cpp @@ -497,7 +497,6 @@ void LLTexLayerSetBuffer::doUpload() } LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C; - compressedImage->setRate(0.f); const char* comment_text = LINDEN_J2C_COMMENT_PREFIX "RGBHM"; // writes into baked_color_data. 5 channels (rgb, heightfield/alpha, mask) if (compressedImage->encode(baked_image, comment_text)) { diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 089f45ca89..54ae519422 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1030,7 +1030,6 @@ LLPointer<LLImageJ2C> LLViewerTextureList::convertToUploadFile(LLPointer<LLImage { raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C(); - compressedImage->setRate(0.f); if (gSavedSettings.getBOOL("LosslessJ2CUpload") && (raw_image->getWidth() * raw_image->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF)) |