1/* 2 * Copyright 2018 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "tools/skqp/src/skqp.h" 9#include "tools/skqp/src/skqp_model.h" 10 11#include "include/codec/SkCodec.h" 12#include "include/core/SkBitmap.h" 13#include "include/core/SkStream.h" 14#include "src/utils/SkOSPath.h" 15 16#include <limits.h> 17 18#ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE 19#define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0 20#endif 21 22//////////////////////////////////////////////////////////////////////////////// 23 24static inline uint32_t color(const SkPixmap& pm, SkIPoint p) { 25 return *pm.addr32(p.x(), p.y()); 26} 27 28static inline bool inside(SkIPoint point, SkISize dimensions) { 29 return (unsigned)point.x() < (unsigned)dimensions.width() && 30 (unsigned)point.y() < (unsigned)dimensions.height(); 31} 32 33SkQP::RenderOutcome skqp::Check(const SkPixmap& minImg, 34 const SkPixmap& maxImg, 35 const SkPixmap& img, 36 unsigned tolerance, 37 SkBitmap* errorOut) { 38 SkQP::RenderOutcome result; 39 SkISize dim = img.info().dimensions(); 40 SkASSERT(minImg.info().dimensions() == dim); 41 SkASSERT(maxImg.info().dimensions() == dim); 42 static const SkIPoint kNeighborhood[9] = { 43 { 0, 0}, // ordered by closest pixels first. 44 {-1, 0}, { 1, 0}, { 0, -1}, { 0, 1}, 45 {-1, -1}, { 1, -1}, {-1, 1}, { 1, 1}, 46 }; 47 for (int y = 0; y < dim.height(); ++y) { 48 for (int x = 0; x < dim.width(); ++x) { 49 const SkIPoint xy{x, y}; 50 const uint32_t c = color(img, xy); 51 int error = INT_MAX; 52 // loop over neighborhood (halo); 53 for (SkIPoint delta : kNeighborhood) { 54 SkIPoint point = xy + delta; 55 if (inside(point, dim)) { // skip out of pixmap bounds. 56 int err = 0; 57 // loop over four color channels. 58 // Return Manhattan distance in channel-space. 59 for (int component : {0, 8, 16, 24}) { 60 uint8_t v = (c >> component) & 0xFF, 61 vmin = (color(minImg, point) >> component) & 0xFF, 62 vmax = (color(maxImg, point) >> component) & 0xFF; 63 err = std::max(err, std::max((int)v - (int)vmax, (int)vmin - (int)v)); 64 } 65 error = std::min(error, err); 66 } 67 } 68 if (error > (int)tolerance) { 69 ++result.fBadPixelCount; 70 result.fTotalError += error; 71 result.fMaxError = std::max(error, result.fMaxError); 72 if (errorOut) { 73 if (!errorOut->getPixels()) { 74 errorOut->allocPixels(SkImageInfo::Make( 75 dim.width(), dim.height(), 76 kBGRA_8888_SkColorType, 77 kOpaque_SkAlphaType)); 78 errorOut->eraseColor(SK_ColorWHITE); 79 } 80 SkASSERT((unsigned)error < 256); 81 *(errorOut->getAddr32(x, y)) = SkColorSetARGB(0xFF, (uint8_t)error, 0, 0); 82 } 83 } 84 } 85 } 86 return result; 87} 88 89static SkBitmap decode(sk_sp<SkData> data) { 90 SkBitmap bitmap; 91 if (auto codec = SkCodec::MakeFromData(std::move(data))) { 92 SkISize size = codec->getInfo().dimensions(); 93 SkASSERT(!size.isEmpty()); 94 SkImageInfo info = SkImageInfo::Make(size, skqp::kColorType, skqp::kAlphaType); 95 bitmap.allocPixels(info); 96 if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) { 97 bitmap.reset(); 98 } 99 } 100 return bitmap; 101} 102 103skqp::ModelResult skqp::CheckAgainstModel(const char* name, 104 const SkPixmap& pm, 105 SkQPAssetManager* mgr) { 106 skqp::ModelResult result; 107 if (pm.colorType() != kColorType || pm.alphaType() != kAlphaType) { 108 result.fErrorString = "Model failed: source image format."; 109 return result; 110 } 111 if (pm.info().isEmpty()) { 112 result.fErrorString = "Model failed: empty source image"; 113 return result; 114 } 115 constexpr char PATH_ROOT[] = "gmkb"; 116 SkString img_path = SkOSPath::Join(PATH_ROOT, name); 117 SkString max_path = SkOSPath::Join(img_path.c_str(), kMaxPngPath); 118 SkString min_path = SkOSPath::Join(img_path.c_str(), kMinPngPath); 119 120 result.fMaxPng = mgr->open(max_path.c_str()); 121 result.fMinPng = mgr->open(min_path.c_str()); 122 123 SkBitmap max_image = decode(result.fMaxPng); 124 SkBitmap min_image = decode(result.fMinPng); 125 126 if (max_image.isNull() || min_image.isNull()) { 127 result.fErrorString = "Model missing"; 128 return result; 129 } 130 if (max_image.info().dimensions() != min_image.info().dimensions()) { 131 result.fErrorString = "Model has mismatched data."; 132 return result; 133 } 134 135 if (max_image.info().dimensions() != pm.info().dimensions()) { 136 result.fErrorString = "Model data does not match source size."; 137 return result; 138 } 139 result.fOutcome = Check(min_image.pixmap(), 140 max_image.pixmap(), 141 pm, 142 SK_SKQP_GLOBAL_ERROR_TOLERANCE, 143 &result.fErrors); 144 return result; 145} 146