#include "services/image_annotation/public/cpp/image_processor.h"
#include <cmath>
#include <limits>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "services/image_annotation/image_annotation_metrics.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/jpeg_codec.h"
namespace image_annotation {
namespace {
using testing::Eq;
using testing::Lt;
constexpr double kMaxError = 1e-6;
SkBitmap GenCheckerboardBitmap(const int dim) {
const int check_dim = dim / 8;
SkBitmap out;
out.setInfo(SkImageInfo::Make(dim, dim, kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType));
out.allocPixels();
uint8_t* const pixels = reinterpret_cast<uint8_t*>(out.getPixels());
for (int row = 0; row < dim; ++row) {
for (int col = 0; col < dim; ++col) {
const bool black = ((row / check_dim + col / check_dim) % 2) == 1;
uint8_t* const byte_pos = UNSAFE_TODO(pixels + row * out.rowBytes() +
col * out.bytesPerPixel());
*reinterpret_cast<uint32_t*>(byte_pos) =
black ? SK_ColorBLACK : SK_ColorWHITE;
}
}
return out;
}
double CalcImageError(const SkBitmap& orig, const SkBitmap& comp) {
CHECK(orig.width() == comp.width() && orig.height() == comp.height());
double sum = 0;
for (int row = 0; row < orig.width(); ++row) {
for (int col = 0; col < orig.height(); ++col) {
const auto orig_col = SkColor4f::FromColor(orig.getColor(col, row));
const auto comp_col = SkColor4f::FromColor(comp.getColor(col, row));
for (int i = 0; i < 4; ++i) {
sum += std::pow(
UNSAFE_TODO(orig_col.vec()[i]) - UNSAFE_TODO(comp_col.vec()[i]), 2);
}
}
}
return sum / (4 * orig.width() * orig.height());
}
void OutputImageError(double* const error,
const SkBitmap& expected,
const std::vector<uint8_t>& result,
const int32_t width,
const int32_t height) {
SkBitmap comp = gfx::JPEGCodec::Decode(result);
CHECK(!comp.isNull());
*error = width == expected.width() && height == expected.height()
? CalcImageError(expected, comp)
: std::numeric_limits<double>::infinity();
}
}
TEST(ImageProcessorTest, NullImage) {
base::test::TaskEnvironment test_task_env;
base::HistogramTester histogram_tester;
bool empty_bytes = false;
ImageProcessor(base::BindRepeating([]() { return SkBitmap(); }))
.GetJpgImageData(base::BindOnce(
[](bool* const empty_bytes, const std::vector<uint8_t>& bytes,
const int32_t w, const int32_t h) {
*empty_bytes = bytes.empty() && w == 0 && h == 0;
},
&empty_bytes));
test_task_env.RunUntilIdle();
EXPECT_THAT(empty_bytes, Eq(true));
histogram_tester.ExpectUniqueSample(metrics_internal::kSourcePixelCount,
0 , 1 );
}
TEST(ImageProcessorTest, ImageContent) {
base::test::TaskEnvironment test_task_env;
base::HistogramTester histogram_tester;
const int max_dim = static_cast<int>(std::sqrt(ImageProcessor::kMaxPixels));
const SkBitmap small_orig = GenCheckerboardBitmap(max_dim);
const SkBitmap large_orig = GenCheckerboardBitmap(max_dim * 2);
double comp_error = kMaxError;
ImageProcessor(
base::BindRepeating([](const SkBitmap& b) { return b; }, small_orig))
.GetJpgImageData(
base::BindOnce(&OutputImageError, &comp_error, small_orig));
test_task_env.RunUntilIdle();
EXPECT_THAT(comp_error, Lt(kMaxError));
double scale_error = kMaxError;
ImageProcessor(
base::BindRepeating([](const SkBitmap& b) { return b; }, large_orig))
.GetJpgImageData(
base::BindOnce(&OutputImageError, &scale_error, small_orig));
test_task_env.RunUntilIdle();
EXPECT_THAT(scale_error, Lt(kMaxError));
histogram_tester.ExpectBucketCount(metrics_internal::kSourcePixelCount,
max_dim * max_dim ,
1 );
histogram_tester.ExpectBucketCount(metrics_internal::kSourcePixelCount,
4 * max_dim * max_dim ,
1 );
histogram_tester.ExpectTotalCount(metrics_internal::kSourcePixelCount, 2);
}
}