[go: up one dir, main page]

Introduce JPEG thumbnails for Grid Tab Switcher

The thumbnails shown in the Grid Tab Switcher (GTS) were decoded from
the ETC1 format, downsampled, and then transferred through the JNI
boundary. This is not the most efficient way.

This CL introduces JPEG thumbnails, which would be saved at the same
time ETC1 thumbnails are saved, if GTS is enabled. The GTS would
directly read and decode the JPEG files from the Java side.

(cherry picked from commit eac0768882bbf2c6d0e32966beb194e374afce23)

Bug: 971939
Change-Id: Iaf21ea2170afbb7b0385570459f4566715786ca0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1648963
Reviewed-by: David Trainor <dtrainor@chromium.org>
Reviewed-by: Matthew Jones <mdjones@chromium.org>
Reviewed-by: Yusuf Ozuysal <yusufo@chromium.org>
Commit-Queue: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#667302}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1663939
Reviewed-by: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/branch-heads/3809@{#430}
Cr-Branched-From: d82dec1a818f378c464ba307ddd9c92133eac355-refs/heads/master@{#665002}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
index de886c57..5499cae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
@@ -8,6 +8,7 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.support.annotation.NonNull;
@@ -17,8 +18,10 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.CommandLine;
+import org.chromium.base.PathUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.native_page.NativePage;
@@ -29,6 +32,7 @@
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.display.DisplayAndroid;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -115,6 +119,7 @@
 
         float thumbnailScale = 1.f;
         boolean useApproximationThumbnails;
+        boolean saveJpegThumbnails = FeatureUtilities.isGridTabSwitcherEnabled();
         DisplayAndroid display = DisplayAndroid.getNonMultiDisplay(context);
         float deviceDensity = display.getDipScale();
         if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(context)) {
@@ -133,9 +138,9 @@
 
         mPriorityTabIds = new int[mFullResThumbnailsMaxSize];
 
-        mNativeTabContentManager = nativeInit(defaultCacheSize,
-                approximationCacheSize, compressionQueueMaxSize, writeQueueMaxSize,
-                useApproximationThumbnails);
+        mNativeTabContentManager =
+                nativeInit(defaultCacheSize, approximationCacheSize, compressionQueueMaxSize,
+                        writeQueueMaxSize, useApproximationThumbnails, saveJpegThumbnails);
     }
 
     /**
@@ -272,13 +277,13 @@
         if (mNativeTabContentManager == 0 || !mSnapshotsEnabled) return;
 
         if (!forceUpdate) {
-            nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), callback);
+            getTabThumbnailFromDisk(tab, callback);
             return;
         }
 
         // Reading thumbnail from disk is faster than taking screenshot from live Tab, so fetch
         // that first even if |forceUpdate|.
-        nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), (diskBitmap) -> {
+        getTabThumbnailFromDisk(tab, (diskBitmap) -> {
             callback.onResult(diskBitmap);
             cacheTabThumbnail(tab, (bitmap) -> {
                 // Null check to avoid having a Bitmap from nativeGetTabThumbnailWithCallback() but
@@ -292,6 +297,28 @@
         });
     }
 
+    private void getTabThumbnailFromDisk(@NonNull Tab tab, @NonNull Callback<Bitmap> callback) {
+        // Try JPEG thumbnail first before using the more costly nativeGetTabThumbnailWithCallback.
+        new AsyncTask<Bitmap>() {
+            @Override
+            public Bitmap doInBackground() {
+                File file = new File(PathUtils.getThumbnailCacheDirectory(), tab.getId() + ".jpeg");
+                if (!file.isFile()) return null;
+                return BitmapFactory.decodeFile(file.getPath());
+            }
+
+            @Override
+            public void onPostExecute(Bitmap bitmap) {
+                if (bitmap != null) {
+                    callback.onResult(bitmap);
+                    return;
+                }
+                if (mNativeTabContentManager == 0 || !mSnapshotsEnabled) return;
+                nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), callback);
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
     /**
      * Cache the content of a tab as a thumbnail.
      * @param tab The tab whose content we will cache.
@@ -398,7 +425,8 @@
 
     // Class Object Methods
     private native long nativeInit(int defaultCacheSize, int approximationCacheSize,
-            int compressionQueueMaxSize, int writeQueueMaxSize, boolean useApproximationThumbnail);
+            int compressionQueueMaxSize, int writeQueueMaxSize, boolean useApproximationThumbnail,
+            boolean saveJpegThumbnails);
     private native void nativeAttachTab(long nativeTabContentManager, Tab tab, int tabId);
     private native void nativeDetachTab(long nativeTabContentManager, Tab tab, int tabId);
     private native boolean nativeHasFullCachedThumbnail(long nativeTabContentManager, int tabId);
diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc
index c5d515f1..c6e8ff9 100644
--- a/chrome/browser/android/compositor/tab_content_manager.cc
+++ b/chrome/browser/android/compositor/tab_content_manager.cc
@@ -115,13 +115,15 @@
                                      jint approximation_cache_size,
                                      jint compression_queue_max_size,
                                      jint write_queue_max_size,
-                                     jboolean use_approximation_thumbnail)
+                                     jboolean use_approximation_thumbnail,
+                                     jboolean save_jpeg_thumbnails)
     : weak_java_tab_content_manager_(env, obj), weak_factory_(this) {
   thumbnail_cache_ = std::make_unique<ThumbnailCache>(
       static_cast<size_t>(default_cache_size),
       static_cast<size_t>(approximation_cache_size),
       static_cast<size_t>(compression_queue_max_size),
-      static_cast<size_t>(write_queue_max_size), use_approximation_thumbnail);
+      static_cast<size_t>(write_queue_max_size), use_approximation_thumbnail,
+      save_jpeg_thumbnails);
   thumbnail_cache_->AddThumbnailCacheObserver(this);
 }
 
@@ -402,11 +404,12 @@
                                  jint approximation_cache_size,
                                  jint compression_queue_max_size,
                                  jint write_queue_max_size,
-                                 jboolean use_approximation_thumbnail) {
+                                 jboolean use_approximation_thumbnail,
+                                 jboolean save_jpeg_thumbnails) {
   TabContentManager* manager = new TabContentManager(
       env, obj, default_cache_size, approximation_cache_size,
       compression_queue_max_size, write_queue_max_size,
-      use_approximation_thumbnail);
+      use_approximation_thumbnail, save_jpeg_thumbnails);
   return reinterpret_cast<intptr_t>(manager);
 }
 
diff --git a/chrome/browser/android/compositor/tab_content_manager.h b/chrome/browser/android/compositor/tab_content_manager.h
index abe38a2..643cbfc 100644
--- a/chrome/browser/android/compositor/tab_content_manager.h
+++ b/chrome/browser/android/compositor/tab_content_manager.h
@@ -45,7 +45,8 @@
                     jint approximation_cache_size,
                     jint compression_queue_max_size,
                     jint write_queue_max_size,
-                    jboolean use_approximation_thumbnail);
+                    jboolean use_approximation_thumbnail,
+                    jboolean save_jpeg_thumbnails);
 
   virtual ~TabContentManager();
 
diff --git a/chrome/browser/android/thumbnail/thumbnail_cache.cc b/chrome/browser/android/thumbnail/thumbnail_cache.cc
index 48061fd..04915de2 100644
--- a/chrome/browser/android/thumbnail/thumbnail_cache.cc
+++ b/chrome/browser/android/thumbnail/thumbnail_cache.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
+#include "skia/ext/image_operations.h"
 #include "third_party/android_opengl/etc1/etc1.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
@@ -32,6 +33,7 @@
 #include "ui/android/resources/ui_resource_provider.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/geometry/size_conversions.h"
 
 namespace {
@@ -123,12 +125,14 @@
                                size_t approximation_cache_size,
                                size_t compression_queue_max_size,
                                size_t write_queue_max_size,
-                               bool use_approximation_thumbnail)
+                               bool use_approximation_thumbnail,
+                               bool save_jpeg_thumbnails)
     : file_sequenced_task_runner_(
           base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})),
       compression_queue_max_size_(compression_queue_max_size),
       write_queue_max_size_(write_queue_max_size),
       use_approximation_thumbnail_(use_approximation_thumbnail),
+      save_jpeg_thumbnails_(save_jpeg_thumbnails),
       compression_tasks_count_(0),
       write_tasks_count_(0),
       read_in_progress_(false),
@@ -264,6 +268,10 @@
   return path.Append(base::NumberToString(tab_id));
 }
 
+base::FilePath ThumbnailCache::GetJpegFilePath(TabId tab_id) {
+  return GetFilePath(tab_id).AddExtension(".jpeg");
+}
+
 bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id,
                                                      const GURL& url) {
   base::Time current_time = base::Time::Now();
@@ -352,6 +360,9 @@
   base::FilePath file_path = GetFilePath(tab_id);
   if (base::PathExists(file_path))
     base::DeleteFile(file_path, false);
+  base::FilePath jpeg_file_path = GetJpegFilePath(tab_id);
+  if (base::PathExists(jpeg_file_path))
+    base::DeleteFile(jpeg_file_path, false);
 }
 
 void ThumbnailCache::WriteThumbnailIfNecessary(
@@ -372,6 +383,23 @@
                      content_size, post_write_task));
 }
 
+void ThumbnailCache::WriteJpegThumbnailIfNecessary(
+    TabId tab_id,
+    std::vector<uint8_t> compressed_data) {
+  if (compressed_data.empty())
+    return;
+  if (write_tasks_count_ >= write_queue_max_size_)
+    return;
+
+  write_tasks_count_++;
+
+  base::Callback<void()> post_write_task =
+      base::Bind(&ThumbnailCache::PostWriteTask, weak_factory_.GetWeakPtr());
+  file_sequenced_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&ThumbnailCache::WriteJpegTask, tab_id,
+                                std::move(compressed_data), post_write_task));
+}
+
 void ThumbnailCache::CompressThumbnailIfNecessary(
     TabId tab_id,
     const base::Time& time_stamp,
@@ -401,6 +429,19 @@
        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
       base::BindOnce(&ThumbnailCache::CompressionTask, bitmap, encoded_size,
                      post_compression_task));
+
+  if (save_jpeg_thumbnails_) {
+    base::Callback<void(std::vector<uint8_t>)> post_jpeg_compression_task =
+        base::Bind(&ThumbnailCache::WriteJpegThumbnailIfNecessary,
+                   weak_factory_.GetWeakPtr(), tab_id);
+
+    base::PostTaskWithTraits(
+        FROM_HERE,
+        {base::TaskPriority::BEST_EFFORT,
+         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+        base::BindOnce(&ThumbnailCache::JpegProcessingTask, bitmap,
+                       post_jpeg_compression_task));
+  }
 }
 
 void ThumbnailCache::ReadNextThumbnail() {
@@ -570,6 +611,32 @@
                            post_write_task);
 }
 
+void ThumbnailCache::WriteJpegTask(
+    TabId tab_id,
+    std::vector<uint8_t> compressed_data,
+    const base::Callback<void()>& post_write_task) {
+  DCHECK(!compressed_data.empty());
+
+  base::FilePath file_path = GetJpegFilePath(tab_id);
+  base::File file(file_path,
+                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+
+  bool success = file.IsValid();
+  if (success) {
+    int bytes_written =
+        file.Write(0, reinterpret_cast<const char*>(compressed_data.data()),
+                   compressed_data.size());
+    success &= bytes_written == static_cast<int>(compressed_data.size());
+    file.Close();
+  }
+
+  if (!success)
+    base::DeleteFile(file_path, false);
+
+  base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
+                           post_write_task);
+}
+
 void ThumbnailCache::PostWriteTask() {
   write_tasks_count_--;
 }
@@ -620,6 +687,33 @@
                      content_size));
 }
 
+void ThumbnailCache::JpegProcessingTask(
+    SkBitmap bitmap,
+    const base::Callback<void(std::vector<uint8_t>)>& post_processing_task) {
+  // In portrait mode, we want to show thumbnails in squares.
+  // Therefore, the thumbnail saved in portrait mode needs to be cropped to
+  // a square, or it would be vertically center-aligned, and the top would
+  // be hidden.
+  // It's fine to horizontally center-align thumbnail saved in landscape
+  // mode.
+  int scale = 2;
+  SkIRect dest_subset = {0, 0, bitmap.width() / scale,
+                         std::min(bitmap.width(), bitmap.height()) / scale};
+  SkBitmap result_bitmap = skia::ImageOperations::Resize(
+      bitmap, skia::ImageOperations::RESIZE_BETTER, bitmap.width() / scale,
+      bitmap.height() / scale, dest_subset);
+
+  constexpr int kCompressionQuality = 97;
+  std::vector<uint8_t> data;
+  const bool result =
+      gfx::JPEGCodec::Encode(result_bitmap, kCompressionQuality, &data);
+  DCHECK(result);
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(post_processing_task, std::move(data)));
+}
+
 void ThumbnailCache::PostCompressionTask(
     TabId tab_id,
     const base::Time& time_stamp,
diff --git a/chrome/browser/android/thumbnail/thumbnail_cache.h b/chrome/browser/android/thumbnail/thumbnail_cache.h
index 529be1b..acaa935 100644
--- a/chrome/browser/android/thumbnail/thumbnail_cache.h
+++ b/chrome/browser/android/thumbnail/thumbnail_cache.h
@@ -47,7 +47,8 @@
                  size_t approximation_cache_size,
                  size_t compression_queue_max_size,
                  size_t write_queue_max_size,
-                 bool use_approximation_thumbnail);
+                 bool use_approximation_thumbnail,
+                 bool save_jpeg_thumbnails);
 
   ~ThumbnailCache() override;
 
@@ -77,6 +78,7 @@
   void InvalidateCachedThumbnail(Thumbnail* thumbnail) override;
   static base::FilePath GetCacheDirectory();
   static base::FilePath GetFilePath(TabId tab_id);
+  static base::FilePath GetJpegFilePath(TabId tab_id);
 
  private:
   class ThumbnailMetaData {
@@ -100,6 +102,8 @@
                                  sk_sp<SkPixelRef> compressed_data,
                                  float scale,
                                  const gfx::Size& content_size);
+  void WriteJpegThumbnailIfNecessary(TabId tab_id,
+                                     std::vector<uint8_t> compressed_data);
   void CompressThumbnailIfNecessary(TabId tab_id,
                                     const base::Time& time_stamp,
                                     const SkBitmap& bitmap,
@@ -112,12 +116,18 @@
                         float scale,
                         const gfx::Size& content_size,
                         const base::Callback<void()>& post_write_task);
+  static void WriteJpegTask(TabId tab_id,
+                            std::vector<uint8_t> compressed_data,
+                            const base::Callback<void()>& post_write_task);
   void PostWriteTask();
   static void CompressionTask(
       SkBitmap raw_data,
       gfx::Size encoded_size,
       const base::Callback<void(sk_sp<SkPixelRef>, const gfx::Size&)>&
           post_compression_task);
+  static void JpegProcessingTask(
+      SkBitmap bitmap,
+      const base::Callback<void(std::vector<uint8_t>)>& post_processing_task);
   void PostCompressionTask(TabId tab_id,
                            const base::Time& time_stamp,
                            float scale,
@@ -152,6 +162,7 @@
   const size_t compression_queue_max_size_;
   const size_t write_queue_max_size_;
   const bool use_approximation_thumbnail_;
+  const bool save_jpeg_thumbnails_;
 
   size_t compression_tasks_count_;
   size_t write_tasks_count_;