/* * 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 "skqp_model.h" #include "skqp.h" #include "SkBitmap.h" #include "SkCodec.h" #include "SkOSPath.h" #include "SkStream.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 = SkMax32(err, SkMax32((int)v - (int)vmax, (int)vmin - (int)v)); } error = SkMin32(error, err); } } if (error > (int)tolerance) { ++result.fBadPixelCount; result.fTotalError += error; result.fMaxError = SkMax32(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 data) { SkBitmap bitmap; if (auto codec = SkCodec::MakeFromData(std::move(data))) { SkISize size = codec->getInfo().dimensions(); SkASSERT(!size.isEmpty()); SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), 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; }