blob: 370f7cd0b124e7931fae7dfe5b8e995c54b1aa1f [file] [log] [blame]
// 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 "components/viz/common/gl_scaler.h"
#include <sstream>
#include "base/strings/pattern.h"
#include "build/build_config.h"
#include "cc/test/pixel_test.h"
#include "cc/test/pixel_test_utils.h"
#include "components/viz/common/gl_scaler_test_util.h"
#include "components/viz/common/gpu/context_provider.h"
#include "gpu/GLES2/gl2chromium.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/color_space.h"
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
#endif
namespace viz {
#define EXPECT_STRING_MATCHES(expected, actual) \
if (!base::MatchPattern(actual, expected)) { \
ADD_FAILURE() << "\nActual: " << (actual) \
<< "\nExpected to match pattern: " << (expected); \
}
class GLScalerPixelTest : public cc::PixelTest, public GLScalerTestUtil {
public:
GLScaler* scaler() const { return scaler_.get(); }
std::string GetScalerString() const {
std::ostringstream oss;
oss << *scaler_;
return oss.str();
}
GLuint CreateTexture(const gfx::Size& size) {
return texture_helper_->CreateTexture(size);
}
GLuint UploadTexture(const SkBitmap& bitmap) {
return texture_helper_->UploadTexture(bitmap);
}
SkBitmap DownloadTexture(GLuint texture, const gfx::Size& size) {
return texture_helper_->DownloadTexture(texture, size);
}
// Test convenience to upload |src_bitmap| to the GPU, execute the scaling,
// then download the result from the GPU and return it as a SkBitmap.
SkBitmap Scale(const SkBitmap& src_bitmap,
const gfx::Vector2d& src_offset,
const gfx::Rect& output_rect) {
const GLuint src_texture = UploadTexture(src_bitmap);
const GLuint dest_texture = CreateTexture(output_rect.size());
if (!scaler()->Scale(src_texture,
gfx::Size(src_bitmap.width(), src_bitmap.height()),
src_offset, dest_texture, output_rect)) {
return SkBitmap();
}
return DownloadTexture(dest_texture, output_rect.size());
}
// Returns the amount of color error expected due to bugs in the current
// platform's bilinear texture sampler.
int GetBaselineColorDifference() const {
#if defined(OS_ANDROID)
// Android seems to have texture sampling problems that are not at all seen
// on any of the desktop platforms. Also, versions before Marshmallow seem
// to have a much larger accuracy issues.
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_MARSHMALLOW) {
return 12;
}
return 2;
#else
return 0;
#endif
}
protected:
void SetUp() final {
cc::PixelTest::SetUpGLWithoutRenderer(false);
scaler_ = std::make_unique<GLScaler>(context_provider());
gl_ = context_provider()->ContextGL();
CHECK(gl_);
texture_helper_ = std::make_unique<GLScalerTestTextureHelper>(gl_);
}
void TearDown() final {
texture_helper_.reset();
gl_ = nullptr;
scaler_.reset();
cc::PixelTest::TearDown();
}
private:
std::unique_ptr<GLScaler> scaler_;
gpu::gles2::GLES2Interface* gl_ = nullptr;
std::unique_ptr<GLScalerTestTextureHelper> texture_helper_;
};
// Tests that the default GLScaler::Parameters produces an unscaled copy.
TEST_F(GLScalerPixelTest, CopiesByDefault) {
ASSERT_TRUE(scaler()->Configure(GLScaler::Parameters()));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 0, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a FAST quality scaling of 2→1 in X and 3→2 in Y.
TEST_F(GLScalerPixelTest, ScalesAtFastQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(2, 3);
params.scale_to = gfx::Vector2d(1, 2);
params.quality = GLScaler::Parameters::Quality::FAST;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp [2 3] to [1 2]} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
static_assert(kSMPTEFullSize.height() % 3 == 0, "Fix kSMPTEFullSize.");
const SkBitmap actual = Scale(source, gfx::Vector2d(),
gfx::Rect(0, 0, kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() * 2 / 3));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a GOOD quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest, ScalesALittleAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(1280, 720);
params.scale_to = gfx::Vector2d(1024, 700);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR2X2/lowp [1280 720] to [1024 700]} ← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a large, skewed reduction at GOOD quality: 3840x720 → 128x256.
TEST_F(GLScalerPixelTest, ScalesALotHorizontallyAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(3840, 720);
params.scale_to = gfx::Vector2d(128, 256);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BILINEAR/lowp [256 256] to [128 256]} "
u8"← {BILINEAR4/lowp [2048 512] to [256 256]} "
u8"← {BILINEAR2X2/lowp [3840 720] to [2048 512]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(3840, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 128, 256));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a large, skewed reduction at GOOD quality: 640x2160 → 256x128.
TEST_F(GLScalerPixelTest, ScalesALotVerticallyAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(640, 2160);
params.scale_to = gfx::Vector2d(256, 128);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BILINEAR/lowp [256 256] to [256 128]} "
u8"← {BILINEAR4/lowp [512 2048] to [256 256]} "
u8"← {BILINEAR2X2/lowp [640 2160] to [512 2048]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(640, 2160);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 256, 128));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a BEST quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest, ScalesAtBestQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(1280, 720);
params.scale_to = gfx::Vector2d(1024, 700);
params.quality = GLScaler::Parameters::Quality::BEST;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BICUBIC_HALF_1D/lowp [2048 700] to [1024 700]} "
u8"← {BICUBIC_UPSCALE/lowp [1280 700] to [2048 700]} "
u8"← {BICUBIC_HALF_1D/lowp [1280 1400] to [1280 700]} "
u8"← {BICUBIC_UPSCALE/lowp [1280 720] to [1280 1400]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 4, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests that a source offset can be provided to sample the source starting at a
// different location.
TEST_F(GLScalerPixelTest, TranslatesWithSourceOffset) {
GLScaler::Parameters params;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
static_assert(kSMPTEFullSize.height() % 4 == 0, "Fix kSMPTEFullSize.");
const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() / 4);
const gfx::Rect src_rect(offset.x(), offset.y(),
kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const SkBitmap actual = Scale(source, offset, output_rect);
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(actual, kSMPTEFullSize, src_rect, 0,
&max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests that the source offset works when the source content is vertically
// flipped.
TEST_F(GLScalerPixelTest, TranslatesVerticallyFlippedSourceWithSourceOffset) {
GLScaler::Parameters params;
params.is_flipped_source = true;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap flipped_source =
CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSMPTEFullSize));
const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() / 4);
const gfx::Rect src_rect(offset.x(), offset.y(),
kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const SkBitmap flipped_back_actual =
CreateVerticallyFlippedBitmap(Scale(flipped_source, offset, output_rect));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
src_rect, 0, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
}
// Tests that the correct source selection is made when both translating the
// source and then scaling. Scale "from" and "to" values are chosen such that a
// multi-stage scaler will be configured (to test that offsets are correcty
// calculated and passed between multiple stages).
TEST_F(GLScalerPixelTest, ScalesWithTranslatedSourceOffset) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(640, 2160);
params.scale_to = gfx::Vector2d(256, 128);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BILINEAR/lowp [256 256] to [256 128]} "
u8"← {BILINEAR4/lowp [512 2048] to [256 256]} "
u8"← {BILINEAR2X2/lowp [640 2160] to [512 2048]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(640, 2160);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const gfx::Vector2d offset(kSourceSize.width() / 2, kSourceSize.height() / 4);
const gfx::Rect output_rect(0, 0, 128, 64);
const SkBitmap actual = Scale(source, offset, output_rect);
const gfx::Rect expected_copy_rect(
offset.x(), offset.y(),
output_rect.width() * params.scale_from.x() / params.scale_to.x(),
output_rect.height() * params.scale_from.y() / params.scale_to.y());
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(actual, kSourceSize, expected_copy_rect,
2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nExpected crop region of source: " << expected_copy_rect.ToString()
<< "\nFull (uncropped) Source: " << cc::GetPNGDataUrl(source)
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests that the output is vertically flipped, if requested in the parameters.
TEST_F(GLScalerPixelTest, VerticallyFlipsOutput) {
GLScaler::Parameters params;
params.is_flipped_source = false;
params.flip_output = true;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp+flip_y copy} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap flipped_back_actual = CreateVerticallyFlippedBitmap(
Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize)));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
gfx::Rect(kSMPTEFullSize), 0,
&max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
}
// Tests that the single-channel export ScalerStage works by executing a red
// channel export.
TEST_F(GLScalerPixelTest, ExportsTheRedColorChannel) {
GLScaler::Parameters params;
params.is_flipped_source = false;
params.export_format = GLScaler::Parameters::ExportFormat::CHANNEL_0;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {PLANAR_CHANNEL_0/lowp [4 1] to [1 1]} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap expected = CreatePackedPlanarBitmap(source, 0);
const gfx::Size output_size(expected.width(), expected.height());
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
constexpr float kAvgAbsoluteErrorLimit = 1.f;
constexpr int kMaxAbsoluteErrorLimit = 2;
EXPECT_TRUE(cc::FuzzyPixelComparator(
false, 100.f, 0.f,
GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
.Compare(expected, actual))
<< "\nActual: " << cc::GetPNGDataUrl(actual)
<< "\Expected: " << cc::GetPNGDataUrl(expected);
}
// A test that also stands as an example for how to use the GLScaler to scale a
// screen-sized RGB source (2160x1440, 16:10 aspect ratio) to a typical video
// resolution (720p, 16:9). The end-goal is to produce three textures, which
// contain the three YUV planes in I420 format.
//
// This is a two step process: First, the source is scaled and color space
// converted, with the final result exported as NV61 format (a full size luma
// plane + a half-width interleaved UV image). Second, the interleaved UV image
// is scaled by half in the vertical direction and then separated into one U and
// one V plane.
TEST_F(GLScalerPixelTest, Example_ScaleAndExportForScreenVideoCapture) {
if (scaler()->GetMaxDrawBuffersSupported() < 2) {
LOG(WARNING) << "Skipping test due to lack of MRT support.";
return;
}
// Step 1: Produce a scaled NV61-format result.
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(2160, 1440);
params.scale_to = gfx::Vector2d(1280, 720);
params.source_color_space = DefaultRGBColorSpace();
params.output_color_space = DefaultYUVColorSpace();
params.enable_precise_color_management = true;
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = true;
params.flip_output = true;
params.export_format = GLScaler::Parameters::ExportFormat::NV61;
params.swizzle[0] = GL_BGRA_EXT; // Swizzle for readback.
params.swizzle[1] = GL_RGBA; // Don't swizzle output for Step 2.
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_STRING_MATCHES(
u8"Output "
u8"← {I422_NV61_MRT/mediump [5120 720] to [1280 720], with color x-form "
u8"to *BT709*, with swizzle(0)} "
u8"← {BILINEAR2/mediump [2160 1440] to [1280 720]} "
u8"← {BILINEAR/mediump+flip_y copy, with color x-form *BT709* to "
u8"*transfer:1.0000\\*x*} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(2160, 1440);
const GLuint src_texture = UploadTexture(
CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSourceSize)));
constexpr gfx::Size kOutputSize = gfx::Size(1280, 720);
SkBitmap expected = CreateSMPTETestImage(kOutputSize);
ConvertBitmapToYUV(&expected);
// While the output size is 1280x720, the packing of 4 pixels into one RGBA
// quad means that the texture width must be divided by 4, and that size
// passed in the output_rect argument in the call to ScaleToMultipleOutputs().
const gfx::Size y_plane_size(kOutputSize.width() / 4, kOutputSize.height());
const GLuint y_plane_texture = CreateTexture(y_plane_size);
const GLuint uv_interleaved_texture = CreateTexture(y_plane_size);
ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
src_texture, kSourceSize, gfx::Vector2d(), y_plane_texture,
uv_interleaved_texture, gfx::Rect(y_plane_size)));
// Step 2: Run the scaler again with the deinterleaver exporter, to produce
// the I420 U and V planes from the NV61 UV interleaved image.
params = GLScaler::Parameters(); // Reset params.
params.scale_from = gfx::Vector2d(1, 2);
params.scale_to = gfx::Vector2d(1, 1);
params.source_color_space = DefaultYUVColorSpace();
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false; // Output was already flipped in Step 1.
params.export_format =
GLScaler::Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE;
params.swizzle[0] = GL_BGRA_EXT; // Swizzle for readback.
params.swizzle[1] = GL_BGRA_EXT; // Swizzle for readback.
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {DEINTERLEAVE_PAIRWISE_MRT/lowp [2 2] to [1 1], with swizzle(0), "
u8"with swizzle(1)} "
u8"← Source",
GetScalerString());
const gfx::Size uv_plane_size(y_plane_size.width() / 2,
y_plane_size.height() / 2);
const GLuint u_plane_texture = CreateTexture(uv_plane_size);
const GLuint v_plane_texture = CreateTexture(uv_plane_size);
ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
uv_interleaved_texture, y_plane_size, gfx::Vector2d(), u_plane_texture,
v_plane_texture, gfx::Rect(uv_plane_size)));
// Download the textures, and unpack them into an interleaved YUV bitmap, for
// comparison against the |expected| rendition.
SkBitmap actual = AllocateRGBABitmap(kOutputSize);
actual.eraseColor(SkColorSetARGB(0xff, 0x00, 0x80, 0x80));
SkBitmap y_plane = DownloadTexture(y_plane_texture, y_plane_size);
SwizzleBitmap(&y_plane);
UnpackPlanarBitmap(y_plane, 0, &actual);
SkBitmap u_plane = DownloadTexture(u_plane_texture, uv_plane_size);
SwizzleBitmap(&u_plane);
UnpackPlanarBitmap(u_plane, 1, &actual);
SkBitmap v_plane = DownloadTexture(v_plane_texture, uv_plane_size);
SwizzleBitmap(&v_plane);
UnpackPlanarBitmap(v_plane, 2, &actual);
// Provide generous error limits to account for the chroma subsampling in the
// |actual| result when compared to the perfect |expected| rendition.
constexpr float kAvgAbsoluteErrorLimit = 16.f;
constexpr int kMaxAbsoluteErrorLimit = 0x80;
EXPECT_TRUE(cc::FuzzyPixelComparator(false, 100.f, 0.f,
kAvgAbsoluteErrorLimit,
kMaxAbsoluteErrorLimit, 0)
.Compare(expected, actual))
<< "\nActual: " << cc::GetPNGDataUrl(actual)
<< "\nExpected: " << cc::GetPNGDataUrl(expected);
}
// Performs a scaling-with-gamma-correction experiment to test GLScaler's
// "precise color management" feature. A 50% scale is executed on the same
// source image, once with color management turned on, and once with it turned
// off. The results, each of which should be different, are then examined.
TEST_F(GLScalerPixelTest, ScalesWithColorManagement) {
if (!scaler()->SupportsPreciseColorManagement()) {
LOG(WARNING) << "Skipping test due to lack of 16-bit float support.";
return;
}
// An image of a raspberry (source:
// https://commons.wikimedia.org/wiki/File:Framboise_Margy_3.jpg) has been
// transformed in such a way that scaling it by half in both directions will
// reveal whether scaling is occurring on linearized color values. When scaled
// correctly, the output image should contain a visible raspberry blended
// heavily with solid gray. However, if done naively, the output will be a
// solid 50% gray. For details, see: http://www.ericbrasseur.org/gamma.html
//
// Note that the |source| and |expected| images both use the sRGB color space.
const SkBitmap source = LoadPNGTestImage("rasp-grayator.png");
ASSERT_FALSE(source.isNull());
const SkBitmap expected = LoadPNGTestImage("rasp-grayator-half.png");
ASSERT_FALSE(expected.isNull());
const gfx::Size output_size =
gfx::Size(source.width() / 2, source.height() / 2);
ASSERT_EQ(gfx::Size(expected.width(), expected.height()), output_size);
const SkBitmap expected_naive = AllocateRGBABitmap(output_size);
expected_naive.eraseColor(SkColorSetARGB(0xff, 0x7f, 0x7f, 0x7f));
// Scale the right way: With color management enabled, the raspberry should be
// visible in the downscaled result.
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(2, 2);
params.scale_to = gfx::Vector2d(1, 1);
params.source_color_space = gfx::ColorSpace::CreateSRGB();
params.enable_precise_color_management = true;
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_STRING_MATCHES(
u8"Output "
u8"← {BILINEAR/mediump [2 2] to [1 1], with color x-form to *BT709*} "
u8"← {BILINEAR/mediump copy, with color x-form *BT709* to "
u8"*transfer:1.0000\\*x*} "
u8"← Source",
GetScalerString());
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
constexpr float kAvgAbsoluteErrorLimit = 1.f;
constexpr int kMaxAbsoluteErrorLimit = 2;
EXPECT_TRUE(cc::FuzzyPixelComparator(
false, 100.f, 0.f,
GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
.Compare(expected, actual))
<< "\nActual: " << cc::GetPNGDataUrl(actual)
<< "\nExpected (half size): " << cc::GetPNGDataUrl(expected)
<< "\nOriginal: " << cc::GetPNGDataUrl(source);
// Scale the naive way: Without color management, expect a solid gray result.
params.enable_precise_color_management = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp [2 2] to [1 1]} ← Source",
GetScalerString());
const SkBitmap actual_naive =
Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
EXPECT_TRUE(cc::FuzzyPixelComparator(
false, 100.f, 0.f,
GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
.Compare(expected_naive, actual_naive))
<< "\nActual: " << cc::GetPNGDataUrl(actual_naive)
<< "\nExpected (half size): " << cc::GetPNGDataUrl(expected_naive)
<< "\nOriginal: " << cc::GetPNGDataUrl(source);
}
#undef EXPECT_STRING_MATCHES
} // namespace viz