Bug 854795. Add support for -moz-samplesize. r=seth
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Fri, 28 Feb 2014 16:43:14 -0500
changeset 171714 86d91f902667513aa0c8620b0f3b1bda93cc5960
parent 171713 8b52006f691b8b6e4dea034bb8cebc27b5b83f13
child 171715 a761b18588dab7d40db44484da373d778d7f96ff
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersseth
bugs854795
milestone30.0a1
Bug 854795. Add support for -moz-samplesize. r=seth -moz-samplesize allows decoding at a lower resolution. This is valuable for saving memory when we know that we don't need a large version of the image.
image/decoders/nsJPEGDecoder.cpp
image/src/ImageFactory.cpp
image/src/RasterImage.cpp
image/src/RasterImage.h
image/test/reftest/jpeg/reftest.list
modules/libpref/src/init/all.js
netwerk/base/src/nsMediaFragmentURIParser.cpp
netwerk/base/src/nsMediaFragmentURIParser.h
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -236,18 +236,27 @@ nsJPEGDecoder::WriteInternal(const char 
 
     /* Step 3: read file parameters with jpeg_read_header() */
     if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) {
       PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
              ("} (JPEG_SUSPENDED)"));
       return; /* I/O suspension */
     }
 
+    int sampleSize = mImage.GetRequestedSampleSize();
+    if (sampleSize > 0) {
+      mInfo.scale_num = 1;
+      mInfo.scale_denom = sampleSize;
+    }
+
+    /* Used to set up image size so arrays can be allocated */
+    jpeg_calc_output_dimensions(&mInfo);
+
     // Post our size to the superclass
-    PostSize(mInfo.image_width, mInfo.image_height, ReadOrientationFromEXIF());
+    PostSize(mInfo.output_width, mInfo.output_height, ReadOrientationFromEXIF());
     if (HasError()) {
       // Setting the size led to an error.
       mState = JPEG_ERROR;
       return;
     }
 
     /* If we're doing a size decode, we're done. */
     if (IsSizeDecode())
@@ -370,30 +379,27 @@ nsJPEGDecoder::WriteInternal(const char 
     }
 
     /*
      * Don't allocate a giant and superfluous memory buffer
      * when not doing a progressive decode.
      */
     mInfo.buffered_image = mDecodeStyle == PROGRESSIVE && jpeg_has_multiple_scans(&mInfo);
 
-    /* Used to set up image size so arrays can be allocated */
-    jpeg_calc_output_dimensions(&mInfo);
-
     if (!mImageData) {
       mState = JPEG_ERROR;
       PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
       PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
              ("} (could not initialize image frame)"));
       return;
     }
 
     PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
            ("        JPEGDecoderAccounting: nsJPEGDecoder::Write -- created image frame with %ux%u pixels",
-            mInfo.image_width, mInfo.image_height));
+            mInfo.output_width, mInfo.output_height));
 
     mState = JPEG_START_DECOMPRESS;
   }
 
   case JPEG_START_DECOMPRESS:
   {
     LOG_SCOPE(GetJPEGLog(), "nsJPEGDecoder::Write -- entering JPEG_START_DECOMPRESS case");
     /* Step 4: set parameters for decompression */
--- a/image/src/ImageFactory.cpp
+++ b/image/src/ImageFactory.cpp
@@ -14,34 +14,38 @@
 #include "nsIFile.h"
 #include "nsMimeTypes.h"
 #include "nsIRequest.h"
 
 #include "RasterImage.h"
 #include "VectorImage.h"
 #include "Image.h"
 #include "nsMediaFragmentURIParser.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
 
 #include "ImageFactory.h"
 
 namespace mozilla {
 namespace image {
 
 // Global preferences related to image containers.
 static bool gInitializedPrefCaches = false;
 static bool gDecodeOnDraw = false;
 static bool gDiscardable = false;
+static bool gEnableMozSampleSize = false;
 
 /*static*/ void
 ImageFactory::Initialize()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!gInitializedPrefCaches) {
     Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
     Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
+    Preferences::AddBoolVarCache(&gEnableMozSampleSize, "image.mozsamplesize.enabled");
     gInitializedPrefCaches = true;
   }
 }
 
 static uint32_t
 ComputeImageFlags(ImageURL* uri, bool isMultiPart)
 {
   nsresult rv;
@@ -207,16 +211,32 @@ ImageFactory::CreateRasterImage(nsIReque
 
   nsAutoCString ref;
   aURI->GetRef(ref);
   mozilla::net::nsMediaFragmentURIParser parser(ref);
   if (parser.HasResolution()) {
     newImage->SetRequestedResolution(parser.GetResolution());
   }
 
+  if (parser.HasSampleSize()) {
+      /* Get our principal */
+      nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+      nsCOMPtr<nsIPrincipal> principal;
+      if (chan) {
+        nsContentUtils::GetSecurityManager()->GetChannelPrincipal(chan,
+                                                                  getter_AddRefs(principal));
+      }
+
+      if ((principal &&
+           principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED) ||
+          gEnableMozSampleSize) {
+        newImage->SetRequestedSampleSize(parser.GetSampleSize());
+      }
+  }
+
   return newImage.forget();
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateVectorImage(nsIRequest* aRequest,
                                 imgStatusTracker* aStatusTracker,
                                 const nsCString& aMimeType,
                                 ImageURL* aURI,
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -382,16 +382,17 @@ RasterImage::RasterImage(imgStatusTracke
                          ImageURL* aURI /* = nullptr */) :
   ImageResource(aURI), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mMultipartDecodedFrame(nullptr),
   mAnim(nullptr),
   mLockCount(0),
   mDecodeCount(0),
+  mRequestedSampleSize(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mDecodingMonitor("RasterImage Decoding Monitor"),
   mDecoder(nullptr),
   mBytesDecoded(0),
   mInDecoder(false),
   mStatusDiff(ImageStatusDiff::NoChange()),
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -271,16 +271,26 @@ public:
   /* Provide a hint for the requested resolution of the resulting image. */
   void SetRequestedResolution(const nsIntSize requestedResolution) {
     mRequestedResolution = requestedResolution;
   }
 
   nsIntSize GetRequestedResolution() {
     return mRequestedResolution;
   }
+  /* Provide a hint for the requested dimension of the resulting image. */
+  void SetRequestedSampleSize(int requestedSampleSize) {
+    mRequestedSampleSize = requestedSampleSize;
+  }
+
+  int GetRequestedSampleSize() {
+    return mRequestedSampleSize;
+  }
+
+
 
  nsCString GetURIString() {
     nsCString spec;
     if (GetURI()) {
       GetURI()->GetSpec(spec);
     }
     return spec;
   }
@@ -638,16 +648,19 @@ private: // data
 
   // How many times we've decoded this image.
   // This is currently only used for statistics
   int32_t                        mDecodeCount;
 
   // If the image contains multiple resolutions, a hint as to which one should be used
   nsIntSize                  mRequestedResolution;
 
+  // A hint for image decoder that directly scale the image to smaller buffer
+  int                        mRequestedSampleSize;
+
   // Cached value for GetImageContainer.
   nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
 
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
   // Below are the pieces of data that can be accessed on more than one thread
--- a/image/test/reftest/jpeg/reftest.list
+++ b/image/test/reftest/jpeg/reftest.list
@@ -47,8 +47,10 @@ random-if(Android) == jpg-srgb-icc.jpg j
 # <contents of blue.jpg> (no newline)
 # --BOUNDARYOMG--\r\n
 # 
 # (The boundary is arbitrary, and just has to be defined as something that
 # won't be in the text of the contents themselves. --$(boundary)\r\n means
 # "Here is the beginning of a boundary," and --$(boundary)-- means "All done
 # sending you parts.")
 HTTP == webcam-simulacrum.mjpg blue.jpg
+pref(image.mozsamplesize.enabled,true) fuzzy(21,256) == jpg-size-32x32.jpg#-moz-samplesize=2 jpg-size-16x16.png
+pref(image.mozsamplesize.enabled,true) fuzzy(92,16) == jpg-size-32x32.jpg#-moz-samplesize=8 jpg-size-4x4.png
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4331,8 +4331,11 @@ pref("urlclassifier.download_allow_table
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // Wakelock is disabled by default.
 pref("dom.wakelock.enabled", false);
 
 // The URL of the Firefox Accounts auth server backend
 pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
+
+// disable mozsample size for now
+pref("image.mozsamplesize.enabled", false);
--- a/netwerk/base/src/nsMediaFragmentURIParser.cpp
+++ b/netwerk/base/src/nsMediaFragmentURIParser.cpp
@@ -346,16 +346,29 @@ bool nsMediaFragmentURIParser::ParseMozR
       aString.Length() == 0) {
     mResolution.construct(w,h);
     return true;
   }
 
   return false;
 }
 
+bool nsMediaFragmentURIParser::ParseMozSampleSize(nsDependentSubstring aString)
+{
+  int32_t sampleSize;
+
+  // Read and validate coordinates.
+  if (ParseInteger(aString, sampleSize) && sampleSize > 0) {
+    mSampleSize.construct(sampleSize);
+    return true;
+  }
+
+  return false;
+}
+
 void nsMediaFragmentURIParser::Parse(nsACString& aRef)
 {
   // Create an array of possibly-invalid media fragments.
   nsTArray< std::pair<nsCString, nsCString> > fragments;
   nsCCharSeparatedTokenizer tokenizer(aRef, '&');
 
   while (tokenizer.hasMoreTokens()) {
     const nsCSubstring& nv = tokenizer.nextToken();
@@ -366,27 +379,31 @@ void nsMediaFragmentURIParser::Parse(nsA
       NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
       NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
                      esc_Ref | esc_AlwaysCopy, value);
       fragments.AppendElement(make_pair(name, value));
     }
   }
 
   // Parse the media fragment values.
-  bool gotTemporal = false, gotSpatial = false, gotResolution = false;
+  bool gotTemporal = false, gotSpatial = false,
+      gotResolution = false, gotSampleSize = false;
   for (int i = fragments.Length() - 1 ; i >= 0 ; --i) {
-    if (gotTemporal && gotSpatial && gotResolution) {
+    if (gotTemporal && gotSpatial && gotResolution && gotSampleSize) {
       // We've got one of each possible type. No need to look at the rest.
       break;
     } else if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
       nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
       gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
     } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
       nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
       gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
     } else if (!gotResolution && fragments[i].first.EqualsLiteral("-moz-resolution")) {
       nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
       gotResolution = ParseMozResolution(nsDependentSubstring(value, 0));
+    } else if (!gotSampleSize && fragments[i].first.EqualsLiteral("-moz-samplesize")) {
+      nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+      gotSampleSize = ParseMozSampleSize(nsDependentSubstring(value, 0));
     }
   }
 }
 
 }} // namespace mozilla::net
--- a/netwerk/base/src/nsMediaFragmentURIParser.h
+++ b/netwerk/base/src/nsMediaFragmentURIParser.h
@@ -65,16 +65,20 @@ public:
   // returns the region. If not, returns an empty region. The unit
   // used depends on the value returned by GetClipUnit().
   nsIntRect GetClip() const { return mClip.ref(); }
 
   // If a valid spatial media fragment indicated a clipping region,
   // returns the unit used.
   ClipUnit GetClipUnit() const { return mClipUnit; }
 
+  bool HasSampleSize() const { return !mSampleSize.empty(); }
+
+  int GetSampleSize() const { return mSampleSize.ref(); }
+
 private:
   // Parse the URI ref provided, looking for media fragments. This is
   // the top-level parser the invokes the others below.
   void Parse(nsACString& aRef);
 
   // The following methods parse the fragment as per the media
   // fragments specification. 'aString' contains the remaining
   // fragment data to be parsed. The method returns true
@@ -87,20 +91,22 @@ private:
   bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
   bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
   bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
   bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour);
   bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute);
   bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond);
   bool ParseXYWH(nsDependentSubstring aString);
   bool ParseMozResolution(nsDependentSubstring aString);
+  bool ParseMozSampleSize(nsDependentSubstring aString);
 
   // Media fragment information.
   Maybe<double>    mStart;
   Maybe<double>    mEnd;
   Maybe<nsIntRect> mClip;
   ClipUnit         mClipUnit;
   Maybe<nsIntSize> mResolution;
+  Maybe<int>       mSampleSize;
 };
 
 }} // namespace mozilla::net
 
 #endif