From 6bf3cb875cef4da7c35850ebbea5100dc5244601 Mon Sep 17 00:00:00 2001
From: Merov Linden <merov@lindenlab.com>
Date: Tue, 28 Jan 2014 10:19:57 -0800
Subject: [PATCH] ACME-1195 : WIP : Lazy evaluate intermediate images in
 snapshot preview so perf is better in common cases, allow thumbnail to be
 computed from grabed frame (for SL Share), thumbnail display still buggy in
 SL Share

---
 indra/llimage/llimage.cpp               |  80 ++---
 indra/llimage/llimage.h                 |   3 +
 indra/newview/llfloaterfacebook.cpp     |   3 +-
 indra/newview/llfloaterflickr.cpp       |   4 +-
 indra/newview/llfloatersnapshot.cpp     |   5 +-
 indra/newview/llfloatertwitter.cpp      |   6 +-
 indra/newview/llsnapshotlivepreview.cpp | 443 ++++++++++++++----------
 indra/newview/llsnapshotlivepreview.h   |  14 +-
 8 files changed, 318 insertions(+), 240 deletions(-)

diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index c8a05e1faea..18e08b94a63 100755
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -449,18 +449,8 @@ void LLImageRaw::verticalFlip()
 void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image)
 {
 	// Find new sizes
-	S32 new_width = MIN_IMAGE_SIZE;
-	S32 new_height = MIN_IMAGE_SIZE;
-
-	while( (new_width < getWidth()) && (new_width < max_dim) )
-	{
-		new_width <<= 1;
-	}
-
-	while( (new_height < getHeight()) && (new_height < max_dim) )
-	{
-		new_height <<= 1;
-	}
+	S32 new_width  = expandDimToPowerOfTwo(getWidth(), max_dim);
+	S32 new_height = expandDimToPowerOfTwo(getHeight(), max_dim);
 
 	scale( new_width, new_height, scale_image );
 }
@@ -468,55 +458,61 @@ void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image)
 void LLImageRaw::contractToPowerOfTwo(S32 max_dim, BOOL scale_image)
 {
 	// Find new sizes
-	S32 new_width = max_dim;
-	S32 new_height = max_dim;
-
-	while( (new_width > getWidth()) && (new_width > MIN_IMAGE_SIZE) )
-	{
-		new_width >>= 1;
-	}
-
-	while( (new_height > getHeight()) && (new_height > MIN_IMAGE_SIZE) )
-	{
-		new_height >>= 1;
-	}
+	S32 new_width  = contractDimToPowerOfTwo(getWidth(), MIN_IMAGE_SIZE);
+	S32 new_height = contractDimToPowerOfTwo(getHeight(), MIN_IMAGE_SIZE);
 
 	scale( new_width, new_height, scale_image );
 }
 
-void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim)
+// static
+S32 LLImageRaw::biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim)
 {
 	// Strong bias towards rounding down (to save bandwidth)
 	// No bias would mean THRESHOLD == 1.5f;
-	const F32 THRESHOLD = 1.75f; 
-
+	const F32 THRESHOLD = 1.75f;
+    
 	// Find new sizes
-	S32 larger_w = max_dim;	// 2^n >= mWidth
-	S32 smaller_w = max_dim;	// 2^(n-1) <= mWidth
-	while( (smaller_w > getWidth()) && (smaller_w > MIN_IMAGE_SIZE) )
+	S32 larger_dim  = max_dim;	// 2^n >= curr_dim
+	S32 smaller_dim = max_dim;	// 2^(n-1) <= curr_dim
+	while( (smaller_dim > curr_dim) && (smaller_dim > MIN_IMAGE_SIZE) )
 	{
-		larger_w = smaller_w;
-		smaller_w >>= 1;
+		larger_dim = smaller_dim;
+		smaller_dim >>= 1;
 	}
-	S32 new_width = ( (F32)getWidth() / smaller_w > THRESHOLD ) ? larger_w : smaller_w;
+	return ( ((F32)curr_dim / (F32)smaller_dim) > THRESHOLD ) ? larger_dim : smaller_dim;
+}
 
+// static
+S32 LLImageRaw::expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim)
+{
+	S32 new_dim = MIN_IMAGE_SIZE;
+	while( (new_dim < curr_dim) && (new_dim < max_dim) )
+	{
+		new_dim <<= 1;
+	}
+    return new_dim;
+}
 
-	S32 larger_h = max_dim;	// 2^m >= mHeight
-	S32 smaller_h = max_dim;	// 2^(m-1) <= mHeight
-	while( (smaller_h > getHeight()) && (smaller_h > MIN_IMAGE_SIZE) )
+// static
+S32 LLImageRaw::contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim)
+{
+	S32 new_dim = MAX_IMAGE_SIZE;
+	while( (new_dim > curr_dim) && (new_dim > min_dim) )
 	{
-		larger_h = smaller_h;
-		smaller_h >>= 1;
+		new_dim >>= 1;
 	}
-	S32 new_height = ( (F32)getHeight() / smaller_h > THRESHOLD ) ? larger_h : smaller_h;
+    return new_dim;
+}
 
+void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim)
+{
+	// Find new sizes
+	S32 new_width  = biasedDimToPowerOfTwo(getWidth(),max_dim);
+	S32 new_height = biasedDimToPowerOfTwo(getHeight(),max_dim);
 
 	scale( new_width, new_height );
 }
 
-
-
-
 // Calculates (U8)(255*(a/255.f)*(b/255.f) + 0.5f).  Thanks, Jim Blinn!
 inline U8 LLImageRaw::fastFractionalMult( U8 a, U8 b )
 {
diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h
index 2277afc5852..c1ba1e3c217 100755
--- a/indra/llimage/llimage.h
+++ b/indra/llimage/llimage.h
@@ -205,6 +205,9 @@ class LLImageRaw : public LLImageBase
 
 	void verticalFlip();
 
+    static S32 biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE);
+    static S32 expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE);
+    static S32 contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim = MIN_IMAGE_SIZE);
 	void expandToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE);
 	void contractToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE);
 	void biasedScaleToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE);
diff --git a/indra/newview/llfloaterfacebook.cpp b/indra/newview/llfloaterfacebook.cpp
index 7bf74ef6ba8..9676dfaf936 100644
--- a/indra/newview/llfloaterfacebook.cpp
+++ b/indra/newview/llfloaterfacebook.cpp
@@ -257,8 +257,6 @@ void LLFacebookPhotoPanel::draw()
 		gl_draw_scaled_image(offset_x, offset_y, 
 			thumbnail_w, thumbnail_h,
 			previewp->getThumbnailImage(), color % alpha);
-
-		previewp->drawPreviewRect(offset_x, offset_y) ;
 	}
 
     // Update the visibility of the working (computing preview) label
@@ -303,6 +301,7 @@ void LLFacebookPhotoPanel::onVisibilityChange(const LLSD& new_visibility)
 			previewp->setSnapshotType(previewp->SNAPSHOT_WEB);
 			previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG);
 			previewp->setSnapshotQuality(mQuality, false);
+            previewp->setThumbnailSubsampled(TRUE);     // We want the preview to reflect the *saved* image
 			previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect());
 
 			updateControls();
diff --git a/indra/newview/llfloaterflickr.cpp b/indra/newview/llfloaterflickr.cpp
index c34124caf57..fbf4d758bef 100644
--- a/indra/newview/llfloaterflickr.cpp
+++ b/indra/newview/llfloaterflickr.cpp
@@ -160,8 +160,6 @@ void LLFlickrPhotoPanel::draw()
 		gl_draw_scaled_image(offset_x, offset_y, 
 			thumbnail_w, thumbnail_h,
 			previewp->getThumbnailImage(), color % alpha);
-
-		previewp->drawPreviewRect(offset_x, offset_y) ;
 	}
 
     // Update the visibility of the working (computing preview) label
@@ -204,7 +202,7 @@ void LLFlickrPhotoPanel::onVisibilityChange(const LLSD& new_visibility)
 
 			previewp->setSnapshotType(previewp->SNAPSHOT_WEB);
 			previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG);
-			//previewp->setSnapshotQuality(98);
+            previewp->setThumbnailSubsampled(TRUE);     // We want the preview to reflect the *saved* image
 			previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect());
 
 			updateControls();
diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp
index c3efc26991c..7ba5fc7b872 100755
--- a/indra/newview/llfloatersnapshot.cpp
+++ b/indra/newview/llfloatersnapshot.cpp
@@ -436,9 +436,8 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshot* floater)
 	image_res_tb->setVisible(got_snap);
 	if (got_snap)
 	{
-		LLPointer<LLImageRaw> img = previewp->getEncodedImage();
-		image_res_tb->setTextArg("[WIDTH]", llformat("%d", img->getWidth()));
-		image_res_tb->setTextArg("[HEIGHT]", llformat("%d", img->getHeight()));
+		image_res_tb->setTextArg("[WIDTH]", llformat("%d", previewp->getEncodedImageWidth()));
+		image_res_tb->setTextArg("[HEIGHT]", llformat("%d", previewp->getEncodedImageHeight()));
 	}
 
 	floater->getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : floater->getString("unknown"));
diff --git a/indra/newview/llfloatertwitter.cpp b/indra/newview/llfloatertwitter.cpp
index ea263566a63..68bc4f1c3cf 100644
--- a/indra/newview/llfloatertwitter.cpp
+++ b/indra/newview/llfloatertwitter.cpp
@@ -150,8 +150,6 @@ void LLTwitterPhotoPanel::draw()
 		gl_draw_scaled_image(offset_x, offset_y, 
 			thumbnail_w, thumbnail_h,
 			previewp->getThumbnailImage(), color % alpha);
-
-		previewp->drawPreviewRect(offset_x, offset_y) ;
 	}
 
     // Update the visibility of the working (computing preview) label
@@ -190,11 +188,11 @@ void LLTwitterPhotoPanel::onVisibilityChange(const LLSD& new_visibility)
 			LLSnapshotLivePreview::Params p;
 			p.rect(full_screen_rect);
 			LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p);
-			mPreviewHandle = previewp->getHandle();	
+			mPreviewHandle = previewp->getHandle();
 
 			previewp->setSnapshotType(previewp->SNAPSHOT_WEB);
 			previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG);
-			//previewp->setSnapshotQuality(98);
+            previewp->setThumbnailSubsampled(TRUE);     // We want the preview to reflect the *saved* image
 			previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect());
 
 			updateControls();
diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp
index ee74dbdb0f2..4463dfdc389 100644
--- a/indra/newview/llsnapshotlivepreview.cpp
+++ b/indra/newview/llsnapshotlivepreview.cpp
@@ -78,6 +78,7 @@ LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Param
 	mThumbnailImage(NULL) ,
 	mThumbnailWidth(0),
 	mThumbnailHeight(0),
+    mThumbnailSubsampled(FALSE),
 	mPreviewImageEncoded(NULL),
 	mFormattedImage(NULL),
 	mShineCountdown(0),
@@ -126,14 +127,7 @@ LLSnapshotLivePreview::~LLSnapshotLivePreview()
 
 void LLSnapshotLivePreview::setMaxImageSize(S32 size) 
 {
-	if(size < MAX_SNAPSHOT_IMAGE_SIZE)
-	{
-		mMaxImageSize = size;
-	}
-	else
-	{
-		mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ;
-	}
+    mMaxImageSize = llmin(size,(S32)(MAX_SNAPSHOT_IMAGE_SIZE));
 }
 
 LLViewerTexture* LLSnapshotLivePreview::getCurrentImage()
@@ -141,32 +135,17 @@ LLViewerTexture* LLSnapshotLivePreview::getCurrentImage()
 	return mViewerImage[mCurImageIndex];
 }
 
-F32 LLSnapshotLivePreview::getAspect()
-{
-	F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight());
-	F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight());
-
-	if (!mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot"))
-	{
-		return image_aspect_ratio;
-	}
-	else
-	{
-		return window_aspect_ratio;
-	}
-}
-
 F32 LLSnapshotLivePreview::getImageAspect()
 {
 	if (!getCurrentImage())
 	{
 		return 0.f;
 	}
-
-	return getAspect() ;	
+	// mKeepAspectRatio) == gSavedSettings.getBOOL("KeepAspectForSnapshot"))
+    return (mKeepAspectRatio ? ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()) : ((F32)getWidth()) / ((F32)getHeight()));
 }
 
-void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay) 
+void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay)
 {
 	// Invalidate current image.
 	lldebugs << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << llendl;
@@ -483,58 +462,49 @@ BOOL LLSnapshotLivePreview::setThumbnailImageSize()
 	{
 		return FALSE ;
 	}
-	S32 window_width = gViewerWindow->getWindowWidthRaw() ;
-	S32 window_height = gViewerWindow->getWindowHeightRaw() ;
+	S32 width  = (mThumbnailSubsampled ? mPreviewImage->getWidth()  : gViewerWindow->getWindowWidthRaw());
+	S32 height = (mThumbnailSubsampled ? mPreviewImage->getHeight() : gViewerWindow->getWindowHeightRaw()) ;
 
-	F32 window_aspect_ratio = ((F32)window_width) / ((F32)window_height);
+	F32 aspect_ratio = ((F32)width) / ((F32)height);
 
 	// UI size for thumbnail
 	// *FIXME: the rect does not change, so maybe there's no need to recalculate max w/h.
-	const LLRect& thumbnail_rect = mThumbnailPlaceholderRect;
-	S32 max_width = thumbnail_rect.getWidth();
-	S32 max_height = thumbnail_rect.getHeight();
+	//const LLRect& thumbnail_rect = mThumbnailPlaceholderRect;
+	S32 max_width  = mThumbnailPlaceholderRect.getWidth();
+	S32 max_height = mThumbnailPlaceholderRect.getHeight();
 
-	if (window_aspect_ratio > (F32)max_width / max_height)
+	if (aspect_ratio > (F32)max_width / (F32)max_height)
 	{
 		// image too wide, shrink to width
 		mThumbnailWidth = max_width;
-		mThumbnailHeight = llround((F32)max_width / window_aspect_ratio);
+		mThumbnailHeight = llround((F32)max_width / aspect_ratio);
 	}
 	else
 	{
 		// image too tall, shrink to height
 		mThumbnailHeight = max_height;
-		mThumbnailWidth = llround((F32)max_height * window_aspect_ratio);
+		mThumbnailWidth = llround((F32)max_height * aspect_ratio);
 	}
 
-	if(mThumbnailWidth > window_width || mThumbnailHeight > window_height)
+	if (mThumbnailWidth > width || mThumbnailHeight > height)
 	{
 		return FALSE ;//if the window is too small, ignore thumbnail updating.
 	}
 
 	S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ;
-	if(!mKeepAspectRatio)
+	if(!mKeepAspectRatio && !mThumbnailSubsampled)
 	{
-		F32 ratio_x = (F32)getWidth() / window_width ;
-		F32 ratio_y = (F32)getHeight() / window_height ;
+		F32 ratio_x = (F32)getWidth()  / width ;
+		F32 ratio_y = (F32)getHeight() / height ;
 
-		//if(getWidth() > window_width ||
-		//	getHeight() > window_height )
-		{
-			if(ratio_x > ratio_y)
-			{
-				top = (S32)(top * ratio_y / ratio_x) ;
-			}
-			else
-			{
-				right = (S32)(right * ratio_x / ratio_y) ;
-			}			
-		}
-		//else
-		//{
-		//	right = (S32)(right * ratio_x) ;
-		//	top = (S32)(top * ratio_y) ;
-		//}
+        if (ratio_x > ratio_y)
+        {
+            top = (S32)(top * ratio_y / ratio_x) ;
+        }
+        else
+        {
+            right = (S32)(right * ratio_x / ratio_y) ;
+        }
 		left = (S32)((mThumbnailWidth - right) * 0.5f) ;
 		bottom = (S32)((mThumbnailHeight - top) * 0.5f) ;
 		top += bottom ;
@@ -576,18 +546,41 @@ void LLSnapshotLivePreview::generateThumbnailImage(BOOL force_update)
 	}		
 
 	LLPointer<LLImageRaw> raw = new LLImageRaw;
-	if(!gViewerWindow->thumbnailSnapshot(raw,
-		mThumbnailWidth, mThumbnailHeight,
-		gSavedSettings.getBOOL("RenderUIInSnapshot"),
-		FALSE,
-		mSnapshotBufferType) )								
-	{
-		raw = NULL ;
-	}
-
-	if(raw)
+    
+    if (mThumbnailSubsampled)
+    {
+        // The thumbnail is be a subsampled version of the preview (used in SL Share previews, i.e. Flickr, Twitter, Facebook)
+		raw->resize( mPreviewImage->getWidth(),
+                     mPreviewImage->getHeight(),
+                     mPreviewImage->getComponents());
+        raw->copy(mPreviewImage);
+        // Scale to the thumbnal size modulo a power of 2
+        S32 width  = LLImageRaw::expandDimToPowerOfTwo(mThumbnailWidth,MAX_IMAGE_SIZE);
+        S32 height = LLImageRaw::expandDimToPowerOfTwo(mThumbnailHeight,MAX_IMAGE_SIZE);
+        if (!raw->scale(width, height))
+        {
+            raw = NULL ;
+        }
+    }
+    else
+    {
+        // The thumbnail is a screen view with screen grab positioning preview
+        if(!gViewerWindow->thumbnailSnapshot(raw,
+                                         mThumbnailWidth, mThumbnailHeight,
+                                         gSavedSettings.getBOOL("RenderUIInSnapshot"),
+                                         FALSE,
+                                         mSnapshotBufferType) )
+        {
+            raw = NULL ;
+        }
+        else
+        {
+            raw->expandToPowerOfTwo();
+        }
+    }
+    
+	if (raw)
 	{
-		raw->expandToPowerOfTwo();
         // Filter the thumbnail
         if (getFilter() != "")
         {
@@ -650,26 +643,22 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview )
 	}
 
 	// time to produce a snapshot
-	previewp->setThumbnailImageSize();
+    //previewp->setThumbnailImageSize();
 
 	lldebugs << "producing snapshot" << llendl;
+	llinfos << "Merov : producing snapshot" << llendl;
 	if (!previewp->mPreviewImage)
 	{
 		previewp->mPreviewImage = new LLImageRaw;
 	}
 
-	if (!previewp->mPreviewImageEncoded)
-	{
-		previewp->mPreviewImageEncoded = new LLImageRaw;
-	}
-
 	previewp->setVisible(FALSE);
 	previewp->setEnabled(FALSE);
 
 	previewp->getWindow()->incBusyCount();
 	previewp->setImageScaled(FALSE);
 
-	// grab the raw image and encode it into desired format
+	// grab the raw image
 	if(gViewerWindow->rawSnapshot(
 		previewp->mPreviewImage,
 		previewp->getWidth(),
@@ -681,123 +670,60 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview )
 		previewp->mSnapshotBufferType,
 		previewp->getMaxImageSize()))
 	{
-		previewp->mPreviewImageEncoded->resize(
-			previewp->mPreviewImage->getWidth(), 
-			previewp->mPreviewImage->getHeight(), 
-			previewp->mPreviewImage->getComponents());
+        // Invalidate/delete any existing encoded image
+        previewp->mPreviewImageEncoded = NULL;
+        // Invalidate/delete any existing formatted image
+        previewp->mFormattedImage = NULL;
+        // Update the data size
+        previewp->estimateDataSize();
 
-		if(previewp->getSnapshotType() == SNAPSHOT_TEXTURE)
-		{
-			lldebugs << "Encoding new image of format J2C" << llendl;
-			LLPointer<LLImageJ2C> formatted = new LLImageJ2C;
-			LLPointer<LLImageRaw> scaled = new LLImageRaw(
-				previewp->mPreviewImage->getData(),
-				previewp->mPreviewImage->getWidth(),
-				previewp->mPreviewImage->getHeight(),
-				previewp->mPreviewImage->getComponents());
-
-			scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE);
-			previewp->setImageScaled(TRUE);
-			if (formatted->encode(scaled, 0.f))
-			{
-				previewp->mDataSize = formatted->getDataSize();
-				formatted->decode(previewp->mPreviewImageEncoded, 0);
-			}
-		}
-		else
-		{
-            // Apply the filter to mPreviewImage
-            if (previewp->getFilter() != "")
+        // Full size preview is set: get the decoded image result and save it for animation
+        if (gSavedSettings.getBOOL("UseFreezeFrame"))
+        {
+            // Get the decoded version of the formatted image
+            previewp->getEncodedImage();
+            
+            // We need to scale that a bit for display...
+            LLPointer<LLImageRaw> scaled = new LLImageRaw(
+                previewp->mPreviewImageEncoded->getData(),
+                previewp->mPreviewImageEncoded->getWidth(),
+			    previewp->mPreviewImageEncoded->getHeight(),
+			    previewp->mPreviewImageEncoded->getComponents());
+
+            if (!scaled->isBufferInvalid())
             {
-                std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(previewp->getFilter());
-                if (filter_path != "")
+                // leave original image dimensions, just scale up texture buffer
+                if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024)
                 {
-                    LLImageFilter filter(filter_path);
-                    filter.executeFilter(previewp->mPreviewImage);
+                    // go ahead and shrink image to appropriate power of 2 for display
+                    scaled->biasedScaleToPowerOfTwo(1024);
+                    previewp->setImageScaled(TRUE);
                 }
                 else
                 {
-                    llwarns << "Couldn't find a path to the following filter : " << previewp->getFilter() << llendl;
+                    // expand image but keep original image data intact
+                    scaled->expandToPowerOfTwo(1024, FALSE);
                 }
-            }
-            
-			// delete any existing image
-			previewp->mFormattedImage = NULL;
-			// now create the new one of the appropriate format.
-			LLFloaterSnapshot::ESnapshotFormat format = previewp->getSnapshotFormat();
-			lldebugs << "Encoding new image of format " << format << llendl;
 
-			switch(format)
-			{
-			case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG:
-				previewp->mFormattedImage = new LLImagePNG(); 
-				break;
-			case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG:
-				previewp->mFormattedImage = new LLImageJPEG(previewp->mSnapshotQuality); 
-				break;
-			case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP:
-				previewp->mFormattedImage = new LLImageBMP(); 
-				break;
-			}
-			if (previewp->mFormattedImage->encode(previewp->mPreviewImage, 0))
-			{
-				previewp->mDataSize = previewp->mFormattedImage->getDataSize();
-				// special case BMP to copy instead of decode otherwise decode will crash.
-				if(format == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP)
-				{
-					previewp->mPreviewImageEncoded->copy(previewp->mPreviewImage);
-				}
-				else
-				{
-					previewp->mFormattedImage->decode(previewp->mPreviewImageEncoded, 0);
-				}
-			}
-		}
+                previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE);
+                LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex];
+                gGL.getTexUnit(0)->bind(curr_preview_image);
+                curr_preview_image->setFilteringOption(previewp->getSnapshotType() == SNAPSHOT_TEXTURE ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT);
+                curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP);
 
-		LLPointer<LLImageRaw> scaled = new LLImageRaw(
-			previewp->mPreviewImageEncoded->getData(),
-			previewp->mPreviewImageEncoded->getWidth(),
-			previewp->mPreviewImageEncoded->getHeight(),
-			previewp->mPreviewImageEncoded->getComponents());
-
-		if(!scaled->isBufferInvalid())
-		{
-			// leave original image dimensions, just scale up texture buffer
-			if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024)
-			{
-				// go ahead and shrink image to appropriate power of 2 for display
-				scaled->biasedScaleToPowerOfTwo(1024);
-				previewp->setImageScaled(TRUE);
-			}
-			else
-			{
-				// expand image but keep original image data intact
-				scaled->expandToPowerOfTwo(1024, FALSE);
-			}
-
-			previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE);
-			LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex];
-			gGL.getTexUnit(0)->bind(curr_preview_image);
-			if (previewp->getSnapshotType() != SNAPSHOT_TEXTURE)
-			{
-				curr_preview_image->setFilteringOption(LLTexUnit::TFO_POINT);
-			}
-			else
-			{
-				curr_preview_image->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC);
-			}
-			curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP);
-
-			previewp->mSnapshotUpToDate = TRUE;
-			previewp->generateThumbnailImage(TRUE) ;
-
-			previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal();
-			previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame
-		}
+                previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal();
+                previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame
+            }
+        }
+        // K, the snapshot is updated...
+        previewp->mSnapshotUpToDate = TRUE;
+        
+        // We need to update the thumbnail though
+        previewp->setThumbnailImageSize();
+        previewp->generateThumbnailImage(TRUE) ;
 	}
 	previewp->getWindow()->decBusyCount();
-	// only show fullscreen preview when in freeze frame mode
-	previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame"));
+	previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame")); // only show fullscreen preview when in freeze frame mode
 	previewp->mSnapshotDelayTimer.stop();
 	previewp->mSnapshotActive = FALSE;
 
@@ -806,6 +732,7 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview )
 		previewp->generateThumbnailImage() ;
 	}
 	lldebugs << "done creating snapshot" << llendl;
+	llinfos << "Merov : Done creating snapshot" << llendl;
 	LLFloaterSnapshot::postUpdate();
 	LLFloaterFacebook::postUpdate();
 	LLFloaterFlickr::postUpdate();
@@ -814,6 +741,153 @@ BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview )
 	return TRUE;
 }
 
+S32 LLSnapshotLivePreview::getEncodedImageWidth() const
+{
+    S32 width = getWidth();
+    if (getSnapshotType() == SNAPSHOT_TEXTURE)
+    {
+        width = LLImageRaw::biasedDimToPowerOfTwo(width,MAX_TEXTURE_SIZE);
+    }
+    return width;
+}
+S32 LLSnapshotLivePreview::getEncodedImageHeight() const
+{
+    S32 height = getHeight();
+    if (getSnapshotType() == SNAPSHOT_TEXTURE)
+    {
+        height = LLImageRaw::biasedDimToPowerOfTwo(height,MAX_TEXTURE_SIZE);
+    }
+    return height;
+}
+
+LLPointer<LLImageRaw> LLSnapshotLivePreview::getEncodedImage()
+{
+	if (!mPreviewImageEncoded)
+	{
+		mPreviewImageEncoded = new LLImageRaw;
+    
+		mPreviewImageEncoded->resize(
+            mPreviewImage->getWidth(),
+            mPreviewImage->getHeight(),
+            mPreviewImage->getComponents());
+        
+		if (getSnapshotType() == SNAPSHOT_TEXTURE)
+		{
+            // We don't store the intermediate formatted image in mFormattedImage in the J2C case 
+			lldebugs << "Encoding new image of format J2C" << llendl;
+			LLPointer<LLImageJ2C> formatted = new LLImageJ2C;
+            // Copy the preview
+			LLPointer<LLImageRaw> scaled = new LLImageRaw(
+                                                          mPreviewImage->getData(),
+                                                          mPreviewImage->getWidth(),
+                                                          mPreviewImage->getHeight(),
+                                                          mPreviewImage->getComponents());
+            // Scale it as required by J2C
+			scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE);
+			setImageScaled(TRUE);
+            // Compress to J2C
+			if (formatted->encode(scaled, 0.f))
+			{
+                // We can update the data size precisely at that point
+				mDataSize = formatted->getDataSize();
+                // Decompress back
+				formatted->decode(mPreviewImageEncoded, 0);
+			}
+		}
+		else
+		{
+            // Update mFormattedImage if necessary
+            getFormattedImage();
+            if (getSnapshotFormat() == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP)
+            {
+                // BMP hack : copy instead of decode otherwise decode will crash.
+                mPreviewImageEncoded->copy(mPreviewImage);
+            }
+            else
+            {
+                // Decode back
+                mFormattedImage->decode(mPreviewImageEncoded, 0);
+            }
+		}
+	}
+    return mPreviewImageEncoded;
+}
+
+// We actually estimate the data size so that we do not require actual compression when showing the preview
+// Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size
+void LLSnapshotLivePreview::estimateDataSize()
+{
+    // Compression ratio
+    F32 ratio = 1.0;
+    
+    if (getSnapshotType() == SNAPSHOT_TEXTURE)
+    {
+        ratio = 8.0;    // This is what we shoot for when compressing to J2C
+    }
+    else
+    {
+        LLFloaterSnapshot::ESnapshotFormat format = getSnapshotFormat();
+        switch (format)
+        {
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG:
+                ratio = 3.0;    // Average observed PNG compression ratio
+                break;
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG:
+                // Observed from JPG compression tests
+                ratio = (110 - mSnapshotQuality) / 2;
+                break;
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP:
+                ratio = 1.0;    // No compression with BMP
+                break;
+        }
+    }
+    mDataSize = (S32)((F32)mPreviewImage->getDataSize() / ratio);
+}
+
+LLPointer<LLImageFormatted>	LLSnapshotLivePreview::getFormattedImage()
+{
+    if (!mFormattedImage)
+    {
+        // Apply the filter to mPreviewImage
+        if (getFilter() != "")
+        {
+            std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter());
+            if (filter_path != "")
+            {
+                LLImageFilter filter(filter_path);
+                filter.executeFilter(mPreviewImage);
+            }
+            else
+            {
+                llwarns << "Couldn't find a path to the following filter : " << getFilter() << llendl;
+            }
+        }
+        
+        // Create the new formatted image of the appropriate format.
+        LLFloaterSnapshot::ESnapshotFormat format = getSnapshotFormat();
+        lldebugs << "Encoding new image of format " << format << llendl;
+            
+        switch (format)
+        {
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG:
+                mFormattedImage = new LLImagePNG();
+                break;
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG:
+                mFormattedImage = new LLImageJPEG(mSnapshotQuality);
+                break;
+            case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP:
+                mFormattedImage = new LLImageBMP();
+                break;
+        }
+        if (mFormattedImage->encode(mPreviewImage, 0))
+        {
+            // We can update the data size precisely at that point
+            mDataSize = mFormattedImage->getDataSize();
+        }
+    }
+    return mFormattedImage;
+}
+
 void LLSnapshotLivePreview::setSize(S32 w, S32 h)
 {
 	lldebugs << "setSize(" << w << ", " << h << ")" << llendl;
@@ -875,12 +949,14 @@ void LLSnapshotLivePreview::saveTexture()
 	}
 
 	LLViewerStats::getInstance()->incStat(LLViewerStats::ST_SNAPSHOT_COUNT );
-
-	mDataSize = 0;
 }
 
 BOOL LLSnapshotLivePreview::saveLocal()
 {
+    // Update mFormattedImage if necessary
+    getFormattedImage();
+    
+    // Save the formatted image
 	BOOL success = gViewerWindow->saveImageNumbered(mFormattedImage);
 
 	if(success)
@@ -892,6 +968,9 @@ BOOL LLSnapshotLivePreview::saveLocal()
 
 void LLSnapshotLivePreview::saveWeb()
 {
+    // Update mFormattedImage if necessary
+    getFormattedImage();
+    
 	// *FIX: Will break if the window closes because of CloseSnapshotOnKeep!
 	// Needs to pass on ownership of the image.
 	LLImageJPEG* jpg = dynamic_cast<LLImageJPEG*>(mFormattedImage.get());
diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h
index 6addc87de2e..4fd6dedeed1 100644
--- a/indra/newview/llsnapshotlivepreview.h
+++ b/indra/newview/llsnapshotlivepreview.h
@@ -70,6 +70,9 @@ class LLSnapshotLivePreview : public LLView
 	void getSize(S32& w, S32& h) const;
 	S32 getWidth() const { return mWidth[mCurImageIndex]; }
 	S32 getHeight() const { return mHeight[mCurImageIndex]; }
+    S32 getEncodedImageWidth() const;
+    S32 getEncodedImageHeight() const;
+    void estimateDataSize();
 	S32 getDataSize() const { return mDataSize; }
 	void setMaxImageSize(S32 size) ;
 	S32  getMaxImageSize() {return mMaxImageSize ;}
@@ -83,9 +86,10 @@ class LLSnapshotLivePreview : public LLView
 	S32  getThumbnailHeight() const { return mThumbnailHeight ; }
 	BOOL getThumbnailLock() const { return mThumbnailUpdateLock ; }
 	BOOL getThumbnailUpToDate() const { return mThumbnailUpToDate ;}
+    void setThumbnailSubsampled(BOOL subsampled) { mThumbnailSubsampled = subsampled; }
+
 	LLViewerTexture* getCurrentImage();
 	F32 getImageAspect();
-	F32 getAspect() ;
 	const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; }
 	BOOL isImageScaled() const { return mImageScaled[mCurImageIndex]; }
 	void setImageScaled(BOOL scaled) { mImageScaled[mCurImageIndex] = scaled; }
@@ -96,14 +100,14 @@ class LLSnapshotLivePreview : public LLView
 	bool setSnapshotQuality(S32 quality, bool set_by_user = true);
 	void setSnapshotBufferType(LLViewerWindow::ESnapshotType type) { mSnapshotBufferType = type; }
     void setFilter(std::string filter_name) { mFilterName = filter_name; }
-    std::string  getFilter() { return mFilterName; }
+    std::string  getFilter() const { return mFilterName; }
 	void updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail = FALSE, F32 delay = 0.f);
 	void saveWeb();
 	void saveTexture();
 	BOOL saveLocal();
 
-	LLPointer<LLImageFormatted>	getFormattedImage() const { return mFormattedImage; }
-	LLPointer<LLImageRaw>		getEncodedImage() const { return mPreviewImageEncoded; }
+	LLPointer<LLImageFormatted>	getFormattedImage();
+	LLPointer<LLImageRaw>		getEncodedImage();
 
 	/// Sets size of preview thumbnail image and thhe surrounding rect.
 	void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; }
@@ -135,8 +139,10 @@ class LLSnapshotLivePreview : public LLView
 	BOOL                        mThumbnailUpdateLock ;
 	BOOL                        mThumbnailUpToDate ;
 	LLRect                      mThumbnailPlaceholderRect;
+    BOOL                        mThumbnailSubsampled; // TRUE is the thumbnail is a subsampled version of the mPreviewImage
 
 	S32							mCurImageIndex;
+    // The logic is mPreviewImage (raw frame) -> mFormattedImage (formatted / filtered) -> mPreviewImageEncoded (decoded back, to show artifacts)
 	LLPointer<LLImageRaw>		mPreviewImage;
 	LLPointer<LLImageRaw>		mPreviewImageEncoded;
 	LLPointer<LLImageFormatted>	mFormattedImage;
-- 
GitLab