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