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
color(const SkPixmap & pm,SkIPoint p)24 static inline uint32_t color(const SkPixmap& pm, SkIPoint p) {
25 return *pm.addr32(p.x(), p.y());
26 }
27
inside(SkIPoint point,SkISize dimensions)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
Check(const SkPixmap & minImg,const SkPixmap & maxImg,const SkPixmap & img,unsigned tolerance,SkBitmap * errorOut)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;
63 err = SkMax32(err, SkMax32((int)v - (int)vmax, (int)vmin - (int)v));
64 }
65 error = SkMin32(error, err);
66 }
67 }
68 if (error > (int)tolerance) {
69 ++result.fBadPixelCount;
70 result.fTotalError += error;
71 result.fMaxError = SkMax32(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
decode(sk_sp<SkData> data)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());
94 SkImageInfo info = SkImageInfo::Make(size.width(), size.height(),
95 skqp::kColorType, skqp::kAlphaType);
96 bitmap.allocPixels(info);
97 if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) {
98 bitmap.reset();
99 }
100 }
101 return bitmap;
102 }
103
CheckAgainstModel(const char * name,const SkPixmap & pm,SkQPAssetManager * mgr)104 skqp::ModelResult skqp::CheckAgainstModel(const char* name,
105 const SkPixmap& pm,
106 SkQPAssetManager* mgr) {
107 skqp::ModelResult result;
108 if (pm.colorType() != kColorType || pm.alphaType() != kAlphaType) {
109 result.fErrorString = "Model failed: source image format.";
110 return result;
111 }
112 if (pm.info().isEmpty()) {
113 result.fErrorString = "Model failed: empty source image";
114 return result;
115 }
116 constexpr char PATH_ROOT[] = "gmkb";
117 SkString img_path = SkOSPath::Join(PATH_ROOT, name);
118 SkString max_path = SkOSPath::Join(img_path.c_str(), kMaxPngPath);
119 SkString min_path = SkOSPath::Join(img_path.c_str(), kMinPngPath);
120
121 result.fMaxPng = mgr->open(max_path.c_str());
122 result.fMinPng = mgr->open(min_path.c_str());
123
124 SkBitmap max_image = decode(result.fMaxPng);
125 SkBitmap min_image = decode(result.fMinPng);
126
127 if (max_image.isNull() || min_image.isNull()) {
128 result.fErrorString = "Model missing";
129 return result;
130 }
131 if (max_image.info().dimensions() != min_image.info().dimensions()) {
132 result.fErrorString = "Model has mismatched data.";
133 return result;
134 }
135
136 if (max_image.info().dimensions() != pm.info().dimensions()) {
137 result.fErrorString = "Model data does not match source size.";
138 return result;
139 }
140 result.fOutcome = Check(min_image.pixmap(),
141 max_image.pixmap(),
142 pm,
143 SK_SKQP_GLOBAL_ERROR_TOLERANCE,
144 &result.fErrors);
145 return result;
146 }
147