Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 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 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 8 | #include "tools/skqp/src/skqp.h" |
| 9 | #include "tools/skqp/src/skqp_model.h" |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 10 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 11 | #include "include/codec/SkCodec.h" |
| 12 | #include "include/core/SkBitmap.h" |
| 13 | #include "include/core/SkStream.h" |
| 14 | #include "src/utils/SkOSPath.h" |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 15 | |
Ben Wagner | 0b9b1f1 | 2019-04-04 18:00:05 -0400 | [diff] [blame] | 16 | #include <limits.h> |
| 17 | |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 18 | #ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE |
| 19 | #define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0 |
| 20 | #endif |
| 21 | |
| 22 | //////////////////////////////////////////////////////////////////////////////// |
| 23 | |
| 24 | static inline uint32_t color(const SkPixmap& pm, SkIPoint p) { |
| 25 | return *pm.addr32(p.x(), p.y()); |
| 26 | } |
| 27 | |
| 28 | static inline bool inside(SkIPoint point, SkISize dimensions) { |
| 29 | return (unsigned)point.x() < (unsigned)dimensions.width() && |
| 30 | (unsigned)point.y() < (unsigned)dimensions.height(); |
| 31 | } |
| 32 | |
| 33 | SkQP::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; |
Brian Osman | 7f36405 | 2020-02-06 11:25:43 -0500 | [diff] [blame] | 63 | err = std::max(err, std::max((int)v - (int)vmax, (int)vmin - (int)v)); |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 64 | } |
Brian Osman | 7f36405 | 2020-02-06 11:25:43 -0500 | [diff] [blame] | 65 | error = std::min(error, err); |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 66 | } |
| 67 | } |
| 68 | if (error > (int)tolerance) { |
| 69 | ++result.fBadPixelCount; |
| 70 | result.fTotalError += error; |
Brian Osman | 7f36405 | 2020-02-06 11:25:43 -0500 | [diff] [blame] | 71 | result.fMaxError = std::max(error, result.fMaxError); |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 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 | |
| 89 | static 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()); |
Brian Salomon | 4bc0c1f | 2019-09-30 15:12:27 -0400 | [diff] [blame] | 94 | SkImageInfo info = SkImageInfo::Make(size, skqp::kColorType, skqp::kAlphaType); |
Hal Canary | ac7f23c | 2018-11-26 14:07:41 -0500 | [diff] [blame] | 95 | bitmap.allocPixels(info); |
| 96 | if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) { |
| 97 | bitmap.reset(); |
| 98 | } |
| 99 | } |
| 100 | return bitmap; |
| 101 | } |
| 102 | |
| 103 | skqp::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 | } |