blob: 62560e8755af4681a3e19ed7135b5cb6c8561fc4 [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_test_util.h"
#include <algorithm>
#include <cmath>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "cc/test/pixel_test_utils.h"
#include "components/viz/test/paths.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/gfx/geometry/rect.h"
namespace viz {
using ColorBar = GLScalerTestUtil::ColorBar;
// static
SkBitmap GLScalerTestUtil::AllocateRGBABitmap(const gfx::Size& size) {
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::Make(size.width(), size.height(),
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType));
return bitmap;
}
// static
uint8_t GLScalerTestUtil::ToClamped255(float value) {
value = std::fma(value, 255.0f, 0.5f /* rounding */);
return base::saturated_cast<uint8_t>(value);
}
// static
std::vector<ColorBar> GLScalerTestUtil::GetScaledSMPTEColorBars(
const gfx::Size& size) {
std::vector<ColorBar> rects;
const SkScalar scale_x =
static_cast<SkScalar>(size.width()) / kSMPTEFullSize.width();
const SkScalar scale_y =
static_cast<SkScalar>(size.height()) / kSMPTEFullSize.height();
for (const auto& cr : kSMPTEColorBars) {
const SkRect rect =
SkRect{cr.rect.fLeft * scale_x, cr.rect.fTop * scale_y,
cr.rect.fRight * scale_x, cr.rect.fBottom * scale_y};
rects.push_back(ColorBar{rect.round(), cr.color});
}
return rects;
}
// static
SkBitmap GLScalerTestUtil::CreateSMPTETestImage(const gfx::Size& size) {
SkBitmap result = AllocateRGBABitmap(size);
// Set all pixels to a color that should not exist in the result. Later, a
// sanity-check will ensure all pixels have been overwritten.
constexpr SkColor kDeadColor = SkColorSetARGB(0xde, 0xad, 0xbe, 0xef);
result.eraseColor(kDeadColor);
// Set the pixels corresponding to each color bar.
for (const auto& cr : GetScaledSMPTEColorBars(size)) {
result.erase(cr.color, cr.rect);
}
// Validate that every pixel in the result bitmap has been touched by one of
// the color bars.
for (int y = 0; y < result.height(); ++y) {
for (int x = 0; x < result.width(); ++x) {
if (result.getColor(x, y) == kDeadColor) {
NOTREACHED() << "TEST BUG: Error creating SMPTE test image. Bad size ("
<< size.ToString() << ")?";
return result;
}
}
}
return result;
}
// static
bool GLScalerTestUtil::LooksLikeSMPTETestImage(const SkBitmap& image,
const gfx::Size& src_size,
const gfx::Rect& src_rect,
int fuzzy_pixels,
int* max_color_diff) {
if (image.width() <= 0 || image.height() <= 0) {
return false;
}
const SkScalar offset_x = static_cast<SkScalar>(src_rect.x());
const SkScalar offset_y = static_cast<SkScalar>(src_rect.y());
const SkScalar scale_x =
static_cast<SkScalar>(image.width()) / src_rect.width();
const SkScalar scale_y =
static_cast<SkScalar>(image.height()) / src_rect.height();
int measured_max_diff = 0;
for (const auto& cr : GetScaledSMPTEColorBars(src_size)) {
const SkIRect offset_rect = cr.rect.makeOffset(-offset_x, -offset_y);
const SkIRect rect =
SkRect{offset_rect.fLeft * scale_x, offset_rect.fTop * scale_y,
offset_rect.fRight * scale_x, offset_rect.fBottom * scale_y}
.round()
.makeInset(fuzzy_pixels, fuzzy_pixels);
for (int y = std::max(0, rect.fTop),
y_end = std::min(image.height(), rect.fBottom);
y < y_end; ++y) {
for (int x = std::max(0, rect.fLeft),
x_end = std::min(image.width(), rect.fRight);
x < x_end; ++x) {
const SkColor actual = image.getColor(x, y);
measured_max_diff =
std::max({measured_max_diff,
std::abs(static_cast<int>(SkColorGetR(cr.color)) -
static_cast<int>(SkColorGetR(actual))),
std::abs(static_cast<int>(SkColorGetG(cr.color)) -
static_cast<int>(SkColorGetG(actual))),
std::abs(static_cast<int>(SkColorGetB(cr.color)) -
static_cast<int>(SkColorGetB(actual))),
std::abs(static_cast<int>(SkColorGetA(cr.color)) -
static_cast<int>(SkColorGetA(actual)))});
}
}
}
if (max_color_diff) {
const int threshold = *max_color_diff;
*max_color_diff = measured_max_diff;
return measured_max_diff <= threshold;
}
return measured_max_diff == 0;
}
// static
SkBitmap GLScalerTestUtil::CreateCyclicalTestImage(
const gfx::Size& size,
CyclicalPattern pattern,
const std::vector<SkColor>& cycle,
size_t rotation) {
CHECK(!cycle.empty());
// Map SkColors to RGBA data. Also, applies the cycle |rotation| to simplify
// the rest of the code below.
std::vector<uint32_t> cycle_as_rgba(cycle.size());
for (size_t i = 0; i < cycle.size(); ++i) {
const SkColor color = cycle[(i + rotation) % cycle.size()];
cycle_as_rgba[i] = ((SkColorGetR(color) << kRedShift) |
(SkColorGetG(color) << kGreenShift) |
(SkColorGetB(color) << kBlueShift) |
(SkColorGetA(color) << kAlphaShift));
}
SkBitmap result = AllocateRGBABitmap(size);
switch (pattern) {
case HORIZONTAL_STRIPES:
for (int y = 0; y < size.height(); ++y) {
uint32_t* const pixels = result.getAddr32(0, y);
const uint32_t stripe_rgba = cycle_as_rgba[y % cycle_as_rgba.size()];
for (int x = 0; x < size.width(); ++x) {
pixels[x] = stripe_rgba;
}
}
break;
case VERTICAL_STRIPES:
for (int y = 0; y < size.height(); ++y) {
uint32_t* const pixels = result.getAddr32(0, y);
for (int x = 0; x < size.width(); ++x) {
pixels[x] = cycle_as_rgba[x % cycle_as_rgba.size()];
}
}
break;
case STAGGERED:
for (int y = 0; y < size.height(); ++y) {
uint32_t* const pixels = result.getAddr32(0, y);
for (int x = 0; x < size.width(); ++x) {
pixels[x] = cycle_as_rgba[(x + y) % cycle_as_rgba.size()];
}
}
break;
}
return result;
}
// static
gfx::ColorSpace GLScalerTestUtil::DefaultRGBColorSpace() {
return gfx::ColorSpace::CreateSRGB();
}
// static
gfx::ColorSpace GLScalerTestUtil::DefaultYUVColorSpace() {
return gfx::ColorSpace::CreateREC709();
}
// static
void GLScalerTestUtil::ConvertBitmapToYUV(SkBitmap* image) {
const auto transform = gfx::ColorTransform::NewColorTransform(
DefaultRGBColorSpace(), DefaultYUVColorSpace(),
gfx::ColorTransform::Intent::INTENT_ABSOLUTE);
// Loop, transforming one row of pixels at a time.
std::vector<gfx::ColorTransform::TriStim> stims(image->width());
for (int y = 0; y < image->height(); ++y) {
uint32_t* const pixels = image->getAddr32(0, y);
for (int x = 0; x < image->width(); ++x) {
stims[x].set_x(((pixels[x] >> kRedShift) & 0xff) / 255.0f);
stims[x].set_y(((pixels[x] >> kGreenShift) & 0xff) / 255.0f);
stims[x].set_z(((pixels[x] >> kBlueShift) & 0xff) / 255.0f);
}
transform->Transform(stims.data(), stims.size());
for (int x = 0; x < image->width(); ++x) {
pixels[x] = ((ToClamped255(stims[x].x()) << kRedShift) |
(ToClamped255(stims[x].y()) << kGreenShift) |
(ToClamped255(stims[x].z()) << kBlueShift) |
(((pixels[x] >> kAlphaShift) & 0xff) << kAlphaShift));
}
}
}
// static
void GLScalerTestUtil::SwizzleBitmap(SkBitmap* image) {
for (int y = 0; y < image->height(); ++y) {
uint32_t* const pixels = image->getAddr32(0, y);
for (int x = 0; x < image->width(); ++x) {
pixels[x] = ((((pixels[x] >> kBlueShift) & 0xff) << kRedShift) |
(((pixels[x] >> kGreenShift) & 0xff) << kGreenShift) |
(((pixels[x] >> kRedShift) & 0xff) << kBlueShift) |
(((pixels[x] >> kAlphaShift) & 0xff) << kAlphaShift));
}
}
}
// static
SkBitmap GLScalerTestUtil::CreatePackedPlanarBitmap(const SkBitmap& source,
int channel) {
CHECK_EQ(source.width() % 4, 0);
SkBitmap result =
AllocateRGBABitmap(gfx::Size(source.width() / 4, source.height()));
constexpr int kShiftForChannel[4] = {kRedShift, kGreenShift, kBlueShift,
kAlphaShift};
const int shift = kShiftForChannel[channel];
for (int y = 0; y < result.height(); ++y) {
const uint32_t* const src = source.getAddr32(0, y);
uint32_t* const dst = result.getAddr32(0, y);
for (int x = 0; x < result.width(); ++x) {
// (src[0..3]) (dst)
// RGBA RGBA RGBA RGBA --> RRRR (if channel is 0)
dst[x] = ((((src[x * 4 + 0] >> shift) & 0xff) << kRedShift) |
(((src[x * 4 + 1] >> shift) & 0xff) << kGreenShift) |
(((src[x * 4 + 2] >> shift) & 0xff) << kBlueShift) |
(((src[x * 4 + 3] >> shift) & 0xff) << kAlphaShift));
}
}
return result;
}
// static
void GLScalerTestUtil::UnpackPlanarBitmap(const SkBitmap& plane,
int channel,
SkBitmap* out) {
// The heuristic below auto-adapts to subsampled plane sizes. However, there
// are two cricital requirements: 1) |plane| cannot be empty; 2) |plane| must
// have a size that cleanly unpacks to |out|'s size.
CHECK_GT(plane.width(), 0);
CHECK_GT(plane.height(), 0);
const int col_sampling_ratio = out->width() / plane.width();
CHECK_EQ(out->width() % plane.width(), 0);
CHECK_GT(col_sampling_ratio, 0);
const int row_sampling_ratio = out->height() / plane.height();
CHECK_EQ(out->height() % plane.height(), 0);
CHECK_GT(row_sampling_ratio, 0);
const int ch_sampling_ratio = col_sampling_ratio / 4;
CHECK_GT(ch_sampling_ratio, 0);
// These determine which single byte in each of |out|'s uint32_t-valued pixels
// will be modified.
constexpr int kShiftForChannel[4] = {kRedShift, kGreenShift, kBlueShift,
kAlphaShift};
const int output_shift = kShiftForChannel[channel];
const uint32_t output_retain_mask = ~(UINT32_C(0xff) << output_shift);
// Iterate over the pixels of |out|, sampling each of the 4 components of each
// of |plane|'s pixels.
for (int y = 0; y < out->height(); ++y) {
const uint32_t* const src = plane.getAddr32(0, y / row_sampling_ratio);
uint32_t* const dst = out->getAddr32(0, y);
for (int x = 0; x < out->width(); ++x) {
// Zero-out the existing byte (e.g., if channel==1, then "RGBA" → "R0BA").
dst[x] &= output_retain_mask;
// From |src|, grab one of "XYZW". Then, copy it to the target byte in
// |dst| (e.g., if x_src_ch=3, then grab "W" from |src|, and |dst| changes
// from "R0BA" to "RWBA").
const int x_src = x / col_sampling_ratio;
const int x_src_ch = (x / ch_sampling_ratio) % 4;
dst[x] |= ((src[x_src] >> kShiftForChannel[x_src_ch]) & 0xff)
<< output_shift;
}
}
}
// static
SkBitmap GLScalerTestUtil::CreateVerticallyFlippedBitmap(
const SkBitmap& source) {
SkBitmap bitmap;
bitmap.allocPixels(source.info());
CHECK_EQ(bitmap.rowBytes(), source.rowBytes());
for (int y = 0; y < bitmap.height(); ++y) {
const int src_y = bitmap.height() - y - 1;
memcpy(bitmap.getAddr32(0, y), source.getAddr32(0, src_y),
bitmap.rowBytes());
}
return bitmap;
}
// static
SkBitmap GLScalerTestUtil::LoadPNGTestImage(const std::string& basename) {
base::FilePath test_dir;
if (!base::PathService::Get(Paths::DIR_TEST_DATA, &test_dir)) {
LOG(ERROR) << "Unable to get Paths::DIR_TEST_DATA from base::PathService.";
return SkBitmap();
}
const auto source_file = test_dir.AppendASCII(basename);
SkBitmap as_n32;
if (!cc::ReadPNGFile(source_file, &as_n32)) {
return SkBitmap();
}
SkBitmap as_rgba =
AllocateRGBABitmap(gfx::Size(as_n32.width(), as_n32.height()));
if (!as_n32.readPixels(SkPixmap(as_rgba.info(), as_rgba.getAddr(0, 0),
as_rgba.rowBytes()))) {
return SkBitmap();
}
return as_rgba;
}
// The area and color of the bars in a 1920x1080 HD SMPTE color bars test image
// (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg). The gray
// linear gradient bar is defined as half solid 0-level black and half solid
// full-intensity white).
const ColorBar GLScalerTestUtil::kSMPTEColorBars[30] = {
{{0, 0, 240, 630}, SkColorSetRGB(0x66, 0x66, 0x66)},
{{240, 0, 445, 630}, SkColorSetRGB(0xbf, 0xbf, 0xbf)},
{{445, 0, 651, 630}, SkColorSetRGB(0xbf, 0xbf, 0x00)},
{{651, 0, 857, 630}, SkColorSetRGB(0x00, 0xbf, 0xbf)},
{{857, 0, 1063, 630}, SkColorSetRGB(0x00, 0xbf, 0x00)},
{{1063, 0, 1269, 630}, SkColorSetRGB(0xbf, 0x00, 0xbf)},
{{1269, 0, 1475, 630}, SkColorSetRGB(0xbf, 0x00, 0x00)},
{{1475, 0, 1680, 630}, SkColorSetRGB(0x00, 0x00, 0xbf)},
{{1680, 0, 1920, 630}, SkColorSetRGB(0x66, 0x66, 0x66)},
{{0, 630, 240, 720}, SkColorSetRGB(0x00, 0xff, 0xff)},
{{240, 630, 445, 720}, SkColorSetRGB(0x00, 0x21, 0x4c)},
{{445, 630, 1680, 720}, SkColorSetRGB(0xbf, 0xbf, 0xbf)},
{{1680, 630, 1920, 720}, SkColorSetRGB(0x00, 0x00, 0xff)},
{{0, 720, 240, 810}, SkColorSetRGB(0xff, 0xff, 0x00)},
{{240, 720, 445, 810}, SkColorSetRGB(0x32, 0x00, 0x6a)},
{{445, 720, 1063, 810}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1063, 720, 1680, 810}, SkColorSetRGB(0xff, 0xff, 0xff)},
{{1680, 720, 1920, 810}, SkColorSetRGB(0xff, 0x00, 0x00)},
{{0, 810, 240, 1080}, SkColorSetRGB(0x26, 0x26, 0x26)},
{{240, 810, 549, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{549, 810, 960, 1080}, SkColorSetRGB(0xff, 0xff, 0xff)},
{{960, 810, 1131, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1131, 810, 1200, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1200, 810, 1268, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1268, 810, 1337, 1080}, SkColorSetRGB(0x05, 0x05, 0x05)},
{{1337, 810, 1405, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1405, 810, 1474, 1080}, SkColorSetRGB(0x0a, 0x0a, 0x0a)},
{{1474, 810, 1680, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
{{1680, 810, 1920, 1080}, SkColorSetRGB(0x26, 0x26, 0x26)},
};
constexpr gfx::Size GLScalerTestUtil::kSMPTEFullSize;
GLScalerTestTextureHelper::GLScalerTestTextureHelper(
gpu::gles2::GLES2Interface* gl)
: gl_(gl) {
CHECK(gl_);
}
GLScalerTestTextureHelper::~GLScalerTestTextureHelper() {
gl_->DeleteTextures(textures_to_delete_.size(), textures_to_delete_.data());
textures_to_delete_.clear();
}
GLuint GLScalerTestTextureHelper::CreateTexture(const gfx::Size& size) {
GLuint texture = 0;
gl_->GenTextures(1, &texture);
gl_->BindTexture(GL_TEXTURE_2D, texture);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
gl_->BindTexture(GL_TEXTURE_2D, 0);
if (texture) {
textures_to_delete_.push_back(texture);
}
return texture;
}
GLuint GLScalerTestTextureHelper::UploadTexture(const SkBitmap& bitmap) {
CHECK_EQ(bitmap.colorType(), kRGBA_8888_SkColorType);
GLuint texture = 0;
gl_->GenTextures(1, &texture);
gl_->BindTexture(GL_TEXTURE_2D, texture);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getAddr32(0, 0));
gl_->BindTexture(GL_TEXTURE_2D, 0);
if (texture) {
textures_to_delete_.push_back(texture);
}
return texture;
}
SkBitmap GLScalerTestTextureHelper::DownloadTexture(GLuint texture,
const gfx::Size& size) {
GLuint framebuffer = 0;
gl_->GenFramebuffers(1, &framebuffer);
gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
texture, 0);
SkBitmap result = GLScalerTestUtil::AllocateRGBABitmap(size);
gl_->ReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_BYTE,
result.getAddr32(0, 0));
gl_->DeleteFramebuffers(1, &framebuffer);
return result;
}
} // namespace viz