Commit eac07688 authored by Wei-Yin Chen (陳威尹)'s avatar Wei-Yin Chen (陳威尹) Committed by Commit Bot

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.

Bug: 971939
Change-Id: Iaf21ea2170afbb7b0385570459f4566715786ca0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1648963Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Commit-Queue: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#667302}
parent 9bce35e5
......@@ -8,6 +8,7 @@ import static java.lang.Math.min;
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;
......@@ -16,8 +17,10 @@ import android.view.ViewGroup.MarginLayoutParams;
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.FrozenNativePage;
......@@ -29,6 +32,7 @@ import org.chromium.chrome.browser.util.FeatureUtilities;
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 @@ public class TabContentManager {
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 @@ public class TabContentManager {
mPriorityTabIds = new int[mFullResThumbnailsMaxSize];
mNativeTabContentManager = nativeInit(defaultCacheSize,
approximationCacheSize, compressionQueueMaxSize, writeQueueMaxSize,
useApproximationThumbnails);
mNativeTabContentManager =
nativeInit(defaultCacheSize, approximationCacheSize, compressionQueueMaxSize,
writeQueueMaxSize, useApproximationThumbnails, saveJpegThumbnails);
}
/**
......@@ -277,13 +282,13 @@ public class TabContentManager {
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);
captureDownsampledThumbnail(tab, (bitmap) -> {
// Null check to avoid having a Bitmap from nativeGetTabThumbnailWithCallback() but
......@@ -297,6 +302,28 @@ public class TabContentManager {
});
}
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.
......@@ -421,7 +448,8 @@ public class TabContentManager {
// 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);
......
......@@ -115,13 +115,15 @@ TabContentManager::TabContentManager(JNIEnv* env,
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);
}
......@@ -435,11 +437,12 @@ jlong JNI_TabContentManager_Init(JNIEnv* env,
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);
}
......
......@@ -45,7 +45,8 @@ class TabContentManager : public ThumbnailCacheObserver {
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();
......
......@@ -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 @@ ThumbnailCache::ThumbnailCache(size_t default_cache_size,
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),
......@@ -263,6 +267,10 @@ base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) {
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();
......@@ -351,6 +359,9 @@ void ThumbnailCache::RemoveFromDiskTask(TabId tab_id) {
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(
......@@ -371,6 +382,23 @@ void ThumbnailCache::WriteThumbnailIfNecessary(
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,
......@@ -400,6 +428,19 @@ void ThumbnailCache::CompressThumbnailIfNecessary(
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() {
......@@ -569,6 +610,32 @@ void ThumbnailCache::WriteTask(TabId tab_id,
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_--;
}
......@@ -619,6 +686,33 @@ void ThumbnailCache::CompressionTask(
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,
......
......@@ -47,7 +47,8 @@ class ThumbnailCache : ThumbnailDelegate {
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 @@ class ThumbnailCache : ThumbnailDelegate {
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 @@ class ThumbnailCache : ThumbnailDelegate {
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 @@ class ThumbnailCache : ThumbnailDelegate {
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 @@ class ThumbnailCache : ThumbnailDelegate {
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_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment