diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index 7f95441075295981f9bbc52edc084e1e57823b5a..cce03ff6e66307d4c3dcfabf4eba64d3a58d9f7f 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -1337,7 +1337,8 @@ LLImageFormatted::LLImageFormatted(S8 codec)
 	  mDecoding(0),
 	  mDecoded(0),
 	  mDiscardLevel(-1),
-	  mLevels(0)
+	  mLevels(0),
+      mLayers(0)
 {
 	mMemType = LLMemType::MTYPE_IMAGEFORMATTED;
 }
diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h
index 18f8ae2fbb4e02338d156dbdaa015064ed4c87d4..c5a7d6262e1134036ed85a3ee1a9c700b85aba4f 100644
--- a/indra/llimage/llimage.h
+++ b/indra/llimage/llimage.h
@@ -320,6 +320,8 @@ class LLImageFormatted : public LLImageBase
 	S8 getDiscardLevel() const { return mDiscardLevel; }
 	S8 getLevels() const { return mLevels; }
 	void setLevels(S8 nlevels) { mLevels = nlevels; }
+	S32 getLayers() const { return mLayers; }
+	void setLayers(S32 nlayers) { mLayers = nlayers; }
 
 	// setLastError needs to be deferred for J2C images since it may be called from a DLL
 	virtual void resetLastError();
@@ -334,6 +336,7 @@ class LLImageFormatted : public LLImageBase
 	S8 mDecoded;  // unused, but changing LLImage layout requires recompiling static Mac/Linux libs. 2009-01-30 JC
 	S8 mDiscardLevel;	// Current resolution level worked on. 0 = full res, 1 = half res, 2 = quarter res, etc...
 	S8 mLevels;			// Number of resolution levels in that image. Min is 1. 0 means unknown.
+	S32 mLayers;		// Number of quality layers in that image. Min is 1. 0 means unknown.
 	
 public:
 	static S32 sGlobalFormattedMemory;
diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp
index cbb6f75b437a2c82df3627572732739eb7c57d51..dc5bd8b5d5b7bf1446e96fff4b076f5fc892cf1a 100644
--- a/indra/llimage/llimagej2c.cpp
+++ b/indra/llimage/llimagej2c.cpp
@@ -58,7 +58,8 @@ LLImageJ2C::LLImageJ2C() : 	LLImageFormatted(IMG_CODEC_J2C),
 							mRawDiscardLevel(-1),
 							mRate(0.0f),
 							mReversible(FALSE),
-							mAreaUsedForDataSizeCalcs(0)
+							mAreaUsedForDataSizeCalcs(0),
+							mLayersUsedForDataSizeCalcs(0)
 {
 	mImpl = fallbackCreateLLImageJ2CImpl();
 
@@ -260,7 +261,7 @@ S32 LLImageJ2C::calcHeaderSizeJ2C()
 }
 
 //static
-S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate)
+S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, S32 nb_layers, F32 rate)
 {
 	// Note: This provides an estimation for the first quality layer of a given discard level
 	// This is however an efficient approximation, as the true discard level boundary would be
@@ -278,10 +279,11 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r
 	}
 	// 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
-	// *TODO: Replace the magic "7" by the number of quality layers in the j2c image
 	S32 bytes;
-	S32 new_bytes = sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/7.f;
+	F32 layer_factor = ((nb_layers > 0) && (nb_layers < 7) ? 3.0f * (7 - nb_layers): 3.0f);
+	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 : calcDataSizeJ2C, layers = " << nb_layers << ", old = " << old_bytes << ", new = " << new_bytes << llendl;
 	bytes = (LLImage::useNewByteRange() ? new_bytes : old_bytes);
 	bytes = llmax(bytes, calcHeaderSizeJ2C());
 	return bytes;
@@ -298,14 +300,20 @@ S32 LLImageJ2C::calcDataSize(S32 discard_level)
 	discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL);
 
 	if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth()) 
-		|| mDataSizes[0] == 0)
+		|| (mLayersUsedForDataSizeCalcs != getLayers())
+		|| (mDataSizes[0] == 0))
 	{
+		if (mLayersUsedForDataSizeCalcs != getLayers())
+		{
+			llinfos << "Merov debug : recomputing data size because " << mLayersUsedForDataSizeCalcs << " != " << getLayers() << llendl;
+		}
 		mAreaUsedForDataSizeCalcs = getHeight() * getWidth();
+		mLayersUsedForDataSizeCalcs = getLayers();
 		
 		S32 level = MAX_DISCARD_LEVEL;	// Start at the highest discard
 		while ( level >= 0 )
 		{
-			mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate);
+			mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, getLayers(), mRate);
 			level--;
 		}
 	}
diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h
index 914174fc57fd092ddfdd2e436e79ebdebd87b4fa..28e3026aac22b1a6eeef37d67925a82b0c13ec24 100644
--- a/indra/llimage/llimagej2c.h
+++ b/indra/llimage/llimagej2c.h
@@ -72,7 +72,7 @@ class LLImageJ2C : public LLImageFormatted
 	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, S32 nb_layers = 0, F32 rate = 0.f);
 
 	static std::string getEngineInfo();
 
@@ -88,6 +88,7 @@ class LLImageJ2C : public LLImageFormatted
 	
 	S32 mDataSizes[MAX_DISCARD_LEVEL+1];		// Size of data required to reach a given level
 	U32 mAreaUsedForDataSizeCalcs;				// Height * width used to calculate mDataSizes
+	S32 mLayersUsedForDataSizeCalcs;			// Numbers of layers used to calculate mDataSizes
 
 	S8  mRawDiscardLevel;
 	F32 mRate;
diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp
index 78c9be7bfd6f4938e986ea61ed997b21d6ab5f72..08d840917df13ba83595cefcecad0bbe4bc19cf0 100644
--- a/indra/llkdu/llimagej2ckdu.cpp
+++ b/indra/llkdu/llimagej2ckdu.cpp
@@ -195,7 +195,8 @@ mRawImagep(NULL),
 mDecodeState(NULL),
 mBlocksSize(-1),
 mPrecinctsSize(-1),
-mLevels(0)
+mLevels(0),
+mLayers(0)
 {
 }
 
@@ -245,6 +246,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
@@ -295,13 +298,6 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECod
 	// Get the number of resolution levels in that image
 	mLevels = mCodeStreamp->get_min_dwt_levels();
 	
-	//kdu_coords idx; idx.x = 0; idx.y = 0;
-	//kdu_dims tile_indices_in;  
-	//mCodeStreamp->get_valid_tiles(tile_indices_in);
-	//mCodeStreamp->create_tile(idx+tile_indices_in.pos);
-	//int layers = mCodeStreamp->get_max_tile_layers();
-	//llinfos << "Merov debug : setupCodeStream, levels = " << mLevels << ", layers = " << layers << llendl;
-
 	// Set the base dimensions
 	base.setSize(dims.size.x, dims.size.y, components);
 	base.setLevels(mLevels);
@@ -364,7 +360,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;
 }
@@ -476,7 +473,11 @@ BOOL LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco
 					kdu_tile tile = mCodeStreamp->open_tile(*(mTPosp)+mTileIndicesp->pos);
 
 					int layers = mCodeStreamp->get_max_tile_layers();
-					llinfos << "Merov debug : decodeImpl, levels = " << mLevels << ", layers = " << layers << llendl;
+					if (layers > mLayers)
+					{
+						mLayers = layers;
+						base.setLayers(mLayers);
+					}
 
 					// Find the region of the buffer occupied by this
 					// tile.  Note that we have no control over
@@ -607,7 +608,7 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
 		}
 
 		// Set codestream options
-		int num_layer_specs = 0;
+		mLayers = 0;
 		kdu_long layer_bytes[MAX_NB_LAYERS];
 		U32 max_bytes = (U32)(base.getWidth() * base.getHeight() * base.getComponents());
 
@@ -634,15 +635,15 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
 		// 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) && (num_layer_specs < (MAX_NB_LAYERS-1)))
+		while ((i < max_bytes) && (mLayers < (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[num_layer_specs] = i;
-			num_layer_specs++;
+			layer_bytes[mLayers] = i;
+			mLayers++;
 			i *= 4;
 		}
 
@@ -655,17 +656,18 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
 			//codestream.access_siz()->parse_string("Cycc=no");
 			// 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[num_layer_specs] = 0;
+			layer_bytes[mLayers] = 0;
 		}
 		else
 		{
 			// Truncate the last quality layer if necessary so to fit the set compression ratio
-			layer_bytes[num_layer_specs] = max_bytes;
+			layer_bytes[mLayers] = max_bytes;
 		}
-		num_layer_specs++;
+		mLayers++;
 
-		std::string layer_string = llformat("Clayers=%d",num_layer_specs);
+		std::string layer_string = llformat("Clayers=%d",mLayers);
 		codestream.access_siz()->parse_string(layer_string.c_str());
+		base.setLayers(mLayers);
 		
 		// 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
@@ -718,7 +720,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,mLayers);
 
 		// Cleanup
 		delete tile;
diff --git a/indra/llkdu/llimagej2ckdu.h b/indra/llkdu/llimagej2ckdu.h
index 9ab0b9e4a7bcfe9402ebb81956c3cb9b8e50a83a..fab97326d4c3713022a5fd91caa03d16a9723713 100644
--- a/indra/llkdu/llimagej2ckdu.h
+++ b/indra/llkdu/llimagej2ckdu.h
@@ -75,6 +75,7 @@ class LLImageJ2CKDU : public LLImageJ2CImpl
 	int mBlocksSize;
 	int mPrecinctsSize;
 	int mLevels;
+	int mLayers;
 
 	// Temporary variables for in-progress decodes...
 	LLImageRaw *mRawImagep;
diff --git a/indra/llkdu/tests/llimagej2ckdu_test.cpp b/indra/llkdu/tests/llimagej2ckdu_test.cpp
index feb6671e404a65abe3b7d32119d558110f315a81..405f7c938818d9994aba2baf87191e60fe75aca1 100644
--- a/indra/llkdu/tests/llimagej2ckdu_test.cpp
+++ b/indra/llkdu/tests/llimagej2ckdu_test.cpp
@@ -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() { }