Commit ad3dc556 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Notification] Add MediaImage scoring code

Choose the best MediaImage based on the a score
using type and size.

BUG=897836

Change-Id: Ieab6eefd9e1071b23ffe3be3ca46b35501480e4a
Reviewed-on: https://chromium-review.googlesource.com/c/1338405
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#612891}
parent 8332115e
...@@ -29,6 +29,7 @@ test("services_unittests") { ...@@ -29,6 +29,7 @@ test("services_unittests") {
"//services/data_decoder:tests", "//services/data_decoder:tests",
"//services/device:tests", "//services/device:tests",
"//services/media_session:tests", "//services/media_session:tests",
"//services/media_session/public/cpp:tests",
"//services/preferences:tests", "//services/preferences:tests",
"//services/proxy_resolver:tests", "//services/proxy_resolver:tests",
"//services/resource_coordinator:tests", "//services/resource_coordinator:tests",
......
...@@ -6,6 +6,8 @@ component("cpp") { ...@@ -6,6 +6,8 @@ component("cpp") {
output_name = "media_session_cpp" output_name = "media_session_cpp"
sources = [ sources = [
"media_image_manager.cc",
"media_image_manager.h",
"media_metadata.cc", "media_metadata.cc",
"media_metadata.h", "media_metadata.h",
"switches.cc", "switches.cc",
...@@ -27,3 +29,17 @@ component("cpp") { ...@@ -27,3 +29,17 @@ component("cpp") {
defines = [ "IS_MEDIA_SESSION_CPP_IMPL" ] defines = [ "IS_MEDIA_SESSION_CPP_IMPL" ]
} }
source_set("tests") {
testonly = true
sources = [
"media_image_manager_unittest.cc",
]
deps = [
":cpp",
"//base",
"//testing/gtest",
]
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/media_session/public/cpp/media_image_manager.h"
#include <algorithm>
#include "base/hash.h"
#include "ui/gfx/geometry/size.h"
namespace media_session {
namespace {
// The default score of unknown image size.
const double kDefaultImageSizeScore = 0.4;
// The scores for different image types. Keep them sorted by value.
const double kDefaultTypeScore = 0.6;
const double kPNGTypeScore = 1.0;
const double kJPEGTypeScore = 0.7;
const double kBMPTypeScore = 0.5;
const double kXIconTypeScore = 0.4;
const double kGIFTypeScore = 0.3;
double GetImageAspectRatioScore(const gfx::Size& size) {
double long_edge = std::max(size.width(), size.height());
double short_edge = std::min(size.width(), size.height());
return short_edge / long_edge;
}
std::string GetExtension(const std::string& path) {
auto const pos = path.find_last_of('.');
if (pos == std::string::npos)
return std::string();
return base::ToLowerASCII(path.substr(pos));
}
} // namespace
MediaImageManager::MediaImageManager(int min_size, int ideal_size)
: min_size_(min_size), ideal_size_(ideal_size) {}
MediaImageManager::~MediaImageManager() = default;
base::Optional<MediaMetadata::MediaImage> MediaImageManager::SelectImage(
const std::vector<MediaMetadata::MediaImage>& images) {
base::Optional<MediaMetadata::MediaImage> selected;
double best_score = 0;
for (auto& image : images) {
double score = GetImageScore(image);
if (score > best_score) {
best_score = score;
selected = image;
}
}
return selected;
}
double MediaImageManager::GetImageScore(
const MediaMetadata::MediaImage& image) const {
double best_size_score = 0;
if (image.sizes.empty()) {
best_size_score = kDefaultImageSizeScore;
} else {
for (auto& size : image.sizes)
best_size_score = std::max(best_size_score, GetImageSizeScore(size));
}
double type_score = kDefaultTypeScore;
if (base::Optional<double> ext_score = GetImageExtensionScore(image.src)) {
type_score = *ext_score;
} else if (base::Optional<double> mime_score =
GetImageTypeScore(image.type)) {
type_score = *mime_score;
}
return best_size_score * type_score;
}
double MediaImageManager::GetImageSizeScore(const gfx::Size& size) const {
return GetImageDominantSizeScore(size) * GetImageAspectRatioScore(size);
}
double MediaImageManager::GetImageDominantSizeScore(
const gfx::Size& size) const {
int dominant_size = std::max(size.width(), size.height());
// If the size is "any".
if (dominant_size == 0)
return 0.8;
// Ignore images that are too small.
if (dominant_size < min_size_)
return 0;
if (dominant_size <= ideal_size_)
return 0.8 * (dominant_size - min_size_) / (ideal_size_ - min_size_) + 0.2;
return 1.0 * ideal_size_ / dominant_size;
}
// static
base::Optional<double> MediaImageManager::GetImageExtensionScore(
const GURL& url) {
if (!url.has_path())
return base::nullopt;
std::string extension = GetExtension(url.path());
// These hashes are calculated in
// MediaImageManagerTest_CheckExpectedImageExtensionHashes
switch (base::PersistentHash(extension)) {
case 0x17f3f565: // .png
return kPNGTypeScore;
case 0x32937444: // .jpeg
return kJPEGTypeScore;
case 0x384e2a41: // .jpg
return kJPEGTypeScore;
case 0x9e9716f8: // .bmp
return kBMPTypeScore;
case 0xa123ca62: // .icon
return kXIconTypeScore;
case 0x97112590: // .gif
return kGIFTypeScore;
}
return base::nullopt;
}
// static
base::Optional<double> MediaImageManager::GetImageTypeScore(
const base::string16& type) {
// These hashes are calculated in
// MediaImageManagerTest_CheckExpectedImageTypeHashes
switch (
base::PersistentHash(type.data(), type.size() * sizeof(base::char16))) {
case 0xfd295465: // image/bmp
return kBMPTypeScore;
case 0xce81e113: // image/gif
return kGIFTypeScore;
case 0xb1b44900: // image/jpeg
return kJPEGTypeScore;
case 0x466b4956: // image/png
return kPNGTypeScore;
case 0x5668ffa3: // image/x-icon
return kXIconTypeScore;
}
return base::nullopt;
}
} // namespace media_session
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_MEDIA_SESSION_PUBLIC_CPP_MEDIA_IMAGE_MANAGER_H_
#define SERVICES_MEDIA_SESSION_PUBLIC_CPP_MEDIA_IMAGE_MANAGER_H_
#include <vector>
#include "base/component_export.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/optional.h"
#include "services/media_session/public/cpp/media_metadata.h"
namespace gfx {
class Size;
} // namespace gfx
class GURL;
namespace media_session {
// MediaImageManager manages the MediaImage selection process.
//
// The scoring works as follows:
// - An image score is computed by multiplying the type score with the size
// score.
// - The type score is between 0 and 1 and is based on the image MIME type
// and/or file extension.
// - PNG and JPEG are prefered than others.
// - If unspecified, use the default type score (0.6).
// - The size score is between 0 and 1 and is computed by multiplying the
// dominant size score and aspect ratio score:
// - The dominant size score is between 0 and 1 and is computed using
// |min_size| and |ideal_size|.
// - If size < |min_size| (too small), the size score is 0.
// - If |min_size| <= size <= |ideal_size|, the score increases
// linearly from 0.2 to 1.
// - If size > |ideal_size|, the score is |ideal_size| / size which
// drops from 1 to 0.
// - When size is "any", the size score is 0.8.
// - If unspecified, use the default size score (0.4).
// - The aspect ratio score is between 0 and 1 and is computed by dividing
// the short edge length by the long edge.
class COMPONENT_EXPORT(MEDIA_SESSION_CPP) MediaImageManager {
public:
// The |min_size| is the min size of the images to select in px. The
// |ideal_size| is the ideal size of the images to select in px.
MediaImageManager(int min_size, int ideal_size);
~MediaImageManager();
// Select the best image from the |images|. If an image could not be selected
// then will return null.
base::Optional<MediaMetadata::MediaImage> SelectImage(
const std::vector<MediaMetadata::MediaImage>& images);
private:
FRIEND_TEST_ALL_PREFIXES(MediaImageManagerTest,
CheckExpectedImageExtensionHashes);
FRIEND_TEST_ALL_PREFIXES(MediaImageManagerTest, CheckExpectedImageTypeHashes);
double GetImageScore(const MediaMetadata::MediaImage& image) const;
double GetImageSizeScore(const gfx::Size& size) const;
double GetImageDominantSizeScore(const gfx::Size& size) const;
static base::Optional<double> GetImageExtensionScore(const GURL& url);
static base::Optional<double> GetImageTypeScore(const base::string16& type);
const int min_size_;
const int ideal_size_;
DISALLOW_COPY_AND_ASSIGN(MediaImageManager);
};
} // namespace media_session
#endif // SERVICES_MEDIA_SESSION_PUBLIC_CPP_MEDIA_IMAGE_MANAGER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/media_session/public/cpp/media_image_manager.h"
#include "base/hash.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_session {
namespace {
const int kMinSize = 10;
const int kIdealSize = 40;
} // namespace
class MediaImageManagerTest : public testing::Test {
public:
MediaImageManagerTest() = default;
void SetUp() override {
manager_ = std::make_unique<MediaImageManager>(kMinSize, kIdealSize);
}
MediaImageManager* manager() const { return manager_.get(); }
private:
std::unique_ptr<MediaImageManager> manager_;
DISALLOW_COPY_AND_ASSIGN(MediaImageManagerTest);
};
TEST_F(MediaImageManagerTest, CheckExpectedImageExtensionHashes) {
const std::string extensions[] = {".png", ".jpeg", ".jpg",
".bmp", ".icon", ".gif"};
for (const auto& extension : extensions) {
// Uncomment this line to print the hashes if new ones need to be added.
// printf("0x%x %s\n", base::PersistentHash(extension), extension.c_str());
GURL url("https://www.example.com/test" + extension);
EXPECT_TRUE(MediaImageManager::GetImageExtensionScore(url));
}
}
TEST_F(MediaImageManagerTest, CheckExpectedImageTypeHashes) {
const std::string types[] = {"image/bmp", "image/gif", "image/jpeg",
"image/png", "image/x-icon"};
for (const auto& type : types) {
base::string16 type16 = base::ASCIIToUTF16(type);
// Uncomment these lines to print the hashes if new ones need to be added.
// printf("0x%x %s\n",
// base::PersistentHash(type16.data(),
// type16.size() * sizeof(base::char16)),
// type.c_str());
EXPECT_TRUE(MediaImageManager::GetImageTypeScore(type16));
}
}
TEST_F(MediaImageManagerTest, PickImageFromMimeType) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.type = base::ASCIIToUTF16("image/bmp");
image1.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.type = base::ASCIIToUTF16("image/png");
image2.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image2);
EXPECT_EQ(image2, manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, PickImageFromExtension) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.src = GURL("https://www.example.com/test.bmp");
image1.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.src = GURL("https://www.example.com/test.PNG");
image2.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image2);
media_session::MediaMetadata::MediaImage image3;
image3.src = GURL("https://www.example.com/test");
image3.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image3);
EXPECT_EQ(image2, manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, IgnoreImageTooSmall) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image;
image.sizes.push_back(gfx::Size(1, 1));
images.push_back(image);
EXPECT_FALSE(manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, PickImageUseDefaultScoreIfNoSize) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.src = GURL("https://www.example.com/test.bmp");
image1.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.src = GURL("https://www.example.com/test.PNG");
images.push_back(image2);
EXPECT_EQ(image1, manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, PickImageCloserToIdeal) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.sizes.push_back(gfx::Size(kMinSize, kMinSize));
images.push_back(image2);
EXPECT_EQ(image1, manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, PickImageWithMultipleSizes) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.sizes.push_back(gfx::Size(kIdealSize - 5, kIdealSize - 5));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.sizes.push_back(gfx::Size(kMinSize, kMinSize));
image2.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image2);
EXPECT_EQ(image2, manager()->SelectImage(images));
}
TEST_F(MediaImageManagerTest, PickImageWithBetterAspectRatio) {
std::vector<MediaMetadata::MediaImage> images;
media_session::MediaMetadata::MediaImage image1;
image1.sizes.push_back(gfx::Size(kIdealSize, kIdealSize));
images.push_back(image1);
media_session::MediaMetadata::MediaImage image2;
image2.sizes.push_back(gfx::Size(kIdealSize, kMinSize));
images.push_back(image2);
EXPECT_EQ(image1, manager()->SelectImage(images));
}
} // namespace media_session
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