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
color(const SkPixmap & pm,SkIPoint p)22 static inline uint32_t color(const SkPixmap& pm, SkIPoint p) {
23 return *pm.addr32(p.x(), p.y());
24 }
25
inside(SkIPoint point,SkISize dimensions)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
Check(const SkPixmap & minImg,const SkPixmap & maxImg,const SkPixmap & img,unsigned tolerance,SkBitmap * errorOut)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
decode(sk_sp<SkData> data)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
CheckAgainstModel(const char * name,const SkPixmap & pm,SkQPAssetManager * mgr)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 }
145