blob: 2a97cd6f06c9649e51858c8d848af690e706eb09 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tools/skqp/src/skqp.h"
#include "tools/skqp/src/skqp_model.h"
#include "include/codec/SkCodec.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkStream.h"
#include "src/utils/SkOSPath.h"
#include <limits.h>
#ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE
#define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0
#endif
////////////////////////////////////////////////////////////////////////////////
static inline uint32_t color(const SkPixmap& pm, SkIPoint p) {
return *pm.addr32(p.x(), p.y());
}
static inline bool inside(SkIPoint point, SkISize dimensions) {
return (unsigned)point.x() < (unsigned)dimensions.width() &&
(unsigned)point.y() < (unsigned)dimensions.height();
}
SkQP::RenderOutcome skqp::Check(const SkPixmap& minImg,
const SkPixmap& maxImg,
const SkPixmap& img,
unsigned tolerance,
SkBitmap* errorOut) {
SkQP::RenderOutcome result;
SkISize dim = img.info().dimensions();
SkASSERT(minImg.info().dimensions() == dim);
SkASSERT(maxImg.info().dimensions() == dim);
static const SkIPoint kNeighborhood[9] = {
{ 0, 0}, // ordered by closest pixels first.
{-1, 0}, { 1, 0}, { 0, -1}, { 0, 1},
{-1, -1}, { 1, -1}, {-1, 1}, { 1, 1},
};
for (int y = 0; y < dim.height(); ++y) {
for (int x = 0; x < dim.width(); ++x) {
const SkIPoint xy{x, y};
const uint32_t c = color(img, xy);
int error = INT_MAX;
// loop over neighborhood (halo);
for (SkIPoint delta : kNeighborhood) {
SkIPoint point = xy + delta;
if (inside(point, dim)) { // skip out of pixmap bounds.
int err = 0;
// loop over four color channels.
// Return Manhattan distance in channel-space.
for (int component : {0, 8, 16, 24}) {
uint8_t v = (c >> component) & 0xFF,
vmin = (color(minImg, point) >> component) & 0xFF,
vmax = (color(maxImg, point) >> component) & 0xFF;
err = std::max(err, std::max((int)v - (int)vmax, (int)vmin - (int)v));
}
error = std::min(error, err);
}
}
if (error > (int)tolerance) {
++result.fBadPixelCount;
result.fTotalError += error;
result.fMaxError = std::max(error, result.fMaxError);
if (errorOut) {
if (!errorOut->getPixels()) {
errorOut->allocPixels(SkImageInfo::Make(
dim.width(), dim.height(),
kBGRA_8888_SkColorType,
kOpaque_SkAlphaType));
errorOut->eraseColor(SK_ColorWHITE);
}
SkASSERT((unsigned)error < 256);
*(errorOut->getAddr32(x, y)) = SkColorSetARGB(0xFF, (uint8_t)error, 0, 0);
}
}
}
}
return result;
}
static SkBitmap decode(sk_sp<SkData> data) {
SkBitmap bitmap;
if (auto codec = SkCodec::MakeFromData(std::move(data))) {
SkISize size = codec->getInfo().dimensions();
SkASSERT(!size.isEmpty());
SkImageInfo info = SkImageInfo::Make(size, skqp::kColorType, skqp::kAlphaType);
bitmap.allocPixels(info);
if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) {
bitmap.reset();
}
}
return bitmap;
}
skqp::ModelResult skqp::CheckAgainstModel(const char* name,
const SkPixmap& pm,
SkQPAssetManager* mgr) {
skqp::ModelResult result;
if (pm.colorType() != kColorType || pm.alphaType() != kAlphaType) {
result.fErrorString = "Model failed: source image format.";
return result;
}
if (pm.info().isEmpty()) {
result.fErrorString = "Model failed: empty source image";
return result;
}
constexpr char PATH_ROOT[] = "gmkb";
SkString img_path = SkOSPath::Join(PATH_ROOT, name);
SkString max_path = SkOSPath::Join(img_path.c_str(), kMaxPngPath);
SkString min_path = SkOSPath::Join(img_path.c_str(), kMinPngPath);
result.fMaxPng = mgr->open(max_path.c_str());
result.fMinPng = mgr->open(min_path.c_str());
SkBitmap max_image = decode(result.fMaxPng);
SkBitmap min_image = decode(result.fMinPng);
if (max_image.isNull() || min_image.isNull()) {
result.fErrorString = "Model missing";
return result;
}
if (max_image.info().dimensions() != min_image.info().dimensions()) {
result.fErrorString = "Model has mismatched data.";
return result;
}
if (max_image.info().dimensions() != pm.info().dimensions()) {
result.fErrorString = "Model data does not match source size.";
return result;
}
result.fOutcome = Check(min_image.pixmap(),
max_image.pixmap(),
pm,
SK_SKQP_GLOBAL_ERROR_TOLERANCE,
&result.fErrors);
return result;
}