blob: fb2134f9547d4e42bd8528bc1487af86f65faa6e [file] [log] [blame]
Hal Canaryd7b38452017-12-11 17:46:26 -05001/*
2 * Copyright 2017 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 "gm_knowledge.h"
9
10#include <cfloat>
Hal Canary2a7f0aa2017-12-18 16:59:56 -050011#include <cstdlib>
Hal Canaryd7b38452017-12-11 17:46:26 -050012#include <fstream>
Hal Canary2a7f0aa2017-12-18 16:59:56 -050013#include <mutex>
Hal Canaryd7b38452017-12-11 17:46:26 -050014#include <sstream>
15#include <string>
16#include <vector>
17
18#include "../../src/core/SkStreamPriv.h"
Hal Canary2a7f0aa2017-12-18 16:59:56 -050019#include "../../src/core/SkTSort.h"
Hal Canaryd7b38452017-12-11 17:46:26 -050020#include "SkBitmap.h"
21#include "SkCodec.h"
22#include "SkOSFile.h"
Hal Canary2a7f0aa2017-12-18 16:59:56 -050023#include "SkOSPath.h"
Hal Canaryd7b38452017-12-11 17:46:26 -050024#include "SkPngEncoder.h"
25#include "SkStream.h"
26
Hal Canary2a7f0aa2017-12-18 16:59:56 -050027
Hal Canaryd7b38452017-12-11 17:46:26 -050028#include "skqp_asset_manager.h"
29
30#define PATH_MAX_PNG "max.png"
31#define PATH_MIN_PNG "min.png"
32#define PATH_IMG_PNG "image.png"
33#define PATH_ERR_PNG "errors.png"
34#define PATH_REPORT "report.html"
35
36////////////////////////////////////////////////////////////////////////////////
Hal Canaryd7b38452017-12-11 17:46:26 -050037
38static int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) {
39 int error = 0;
40 for (int j : {0, 8, 16, 24}) {
41 uint8_t v = (value >> j) & 0xFF,
42 vmin = (value_min >> j) & 0xFF,
43 vmax = (value_max >> j) & 0xFF;
44 if (v > vmax) {
45 error = std::max(v - vmax, error);
46 } else if (v < vmin) {
47 error = std::max(vmin - v, error);
48 }
49 }
50 return error;
51}
52
53static float set_error_code(gmkb::Error* error_out, gmkb::Error error) {
54 SkASSERT(error != gmkb::Error::kNone);
55 if (error_out) {
56 *error_out = error;
57 }
58 return FLT_MAX;
59}
60
61static SkPixmap to_pixmap(const SkBitmap& bitmap) {
62 SkPixmap pixmap;
63 SkAssertResult(bitmap.peekPixels(&pixmap));
64 return pixmap;
65}
66
67
68static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) {
69 SkFILEWStream wStream(path);
70 SkPngEncoder::Options options;
71 options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore;
72 return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, options);
73}
74
75constexpr SkColorType kColorType = kRGBA_8888_SkColorType;
76constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType;
77
78static SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) {
79 SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType);
80 return SkPixmap(info, pixels, width * sizeof(uint32_t));
81}
82
83static bool asset_exists(skqp::AssetManager* mgr, const char* path) {
84 return mgr && nullptr != mgr->open(path);
85}
86
87static bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) {
88 if (mgr) {
89 if (auto stream = mgr->open(path)) {
90 SkFILEWStream wStream(dst);
91 return wStream.isValid() && SkStreamCopy(&wStream, stream.get());
92 }
93 }
94 return false;
95}
96
97static SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) {
98 SkBitmap bitmap;
99 if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) {
100 SkISize size = codec->getInfo().dimensions();
101 SkASSERT(!size.isEmpty());
102 SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType);
103 bitmap.allocPixels(info);
104 SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t));
105 if (SkCodec::kSuccess != codec->getPixels(to_pixmap(bitmap))) {
106 bitmap.reset();
107 }
108 }
109 return bitmap;
110}
111
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500112namespace {
113struct Run {
114 SkString fBackend;
115 SkString fGM;
116 int fMaxerror;
117 int fBadpixels;
118};
119} // namespace
120
121static std::vector<Run> gErrors;
122static std::mutex gMutex;
123
Hal Canaryd7b38452017-12-11 17:46:26 -0500124namespace gmkb {
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500125bool IsGoodGM(const char* name, skqp::AssetManager* assetManager) {
126 return asset_exists(assetManager, SkOSPath::Join(name, PATH_MAX_PNG).c_str())
127 && asset_exists(assetManager, SkOSPath::Join(name, PATH_MIN_PNG).c_str());
128}
129
Hal Canaryd7b38452017-12-11 17:46:26 -0500130// Assumes that for each GM foo, asset_manager has files foo/{max,min}.png
131float Check(const uint32_t* pixels,
132 int width,
133 int height,
134 const char* name,
135 const char* backend,
136 skqp::AssetManager* assetManager,
137 const char* report_directory_path,
138 Error* error_out) {
Hal Canaryd7b38452017-12-11 17:46:26 -0500139 if (width <= 0 || height <= 0) {
140 return set_error_code(error_out, Error::kBadInput);
141 }
142 size_t N = (unsigned)width * (unsigned)height;
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500143 SkString max_path = SkOSPath::Join(name, PATH_MAX_PNG);
144 SkString min_path = SkOSPath::Join(name, PATH_MIN_PNG);
Hal Canaryd7b38452017-12-11 17:46:26 -0500145 SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str());
146 if (max_image.isNull()) {
147 return set_error_code(error_out, Error::kBadData);
148 }
149 SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str());
150 if (min_image.isNull()) {
151 return set_error_code(error_out, Error::kBadData);
152 }
153 if (max_image.width() != min_image.width() ||
154 max_image.height() != min_image.height())
155 {
156 return set_error_code(error_out, Error::kBadData);
157 }
158 if (max_image.width() != width || max_image.height() != height) {
159 return set_error_code(error_out, Error::kBadInput);
160 }
161 int badness = 0;
162 int badPixelCount = 0;
163 const uint32_t* max_pixels = (uint32_t*)max_image.getPixels();
164 const uint32_t* min_pixels = (uint32_t*)min_image.getPixels();
165
166 for (size_t i = 0; i < N; ++i) {
167 int error = get_error(pixels[i], max_pixels[i], min_pixels[i]);
168 if (error > 0) {
169 badness = SkTMax(error, badness);
170 ++badPixelCount;
171 }
172 }
173 if (report_directory_path && badness > 0 && report_directory_path[0] != '\0') {
174 sk_mkdir(report_directory_path);
175 if (!backend) {
176 backend = "skia";
177 }
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500178 SkString report_directory = SkOSPath::Join(report_directory_path, backend);
Hal Canaryd7b38452017-12-11 17:46:26 -0500179 sk_mkdir(report_directory.c_str());
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500180 SkString report_subdirectory = SkOSPath::Join(report_directory.c_str(), name);
Hal Canaryd7b38452017-12-11 17:46:26 -0500181 sk_mkdir(report_subdirectory.c_str());
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500182 SkString error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_IMG_PNG);
Hal Canaryd7b38452017-12-11 17:46:26 -0500183 SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height),
184 error_path.c_str()));
185 SkBitmap errorBitmap;
186 errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType));
187 uint32_t* errors = (uint32_t*)errorBitmap.getPixels();
188 for (size_t i = 0; i < N; ++i) {
189 int error = get_error(pixels[i], max_pixels[i], min_pixels[i]);
190 errors[i] = error > 0 ? 0xFF000000 + (unsigned)error : 0x00000000;
191 }
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500192 error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_ERR_PNG);
Hal Canaryd7b38452017-12-11 17:46:26 -0500193 SkAssertResult(WritePixmapToFile(to_pixmap(errorBitmap), error_path.c_str()));
194
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500195 SkString report_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_REPORT);
Hal Canaryd7b38452017-12-11 17:46:26 -0500196
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500197 SkString max_path_out = SkOSPath::Join(report_subdirectory.c_str(), PATH_MAX_PNG);
198 SkString min_path_out = SkOSPath::Join(report_subdirectory.c_str(), PATH_MIN_PNG);
Hal Canaryd7b38452017-12-11 17:46:26 -0500199 (void)copy(assetManager, max_path.c_str(), max_path_out.c_str());
200 (void)copy(assetManager, min_path.c_str(), min_path_out.c_str());
201
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500202 std::lock_guard<std::mutex> lock(gMutex);
203 gErrors.push_back(Run{SkString(backend), SkString(name), badness, badPixelCount});
204 }
205 if (error_out) {
206 *error_out = Error::kNone;
207 }
208 return (float)badness;
209}
210
211bool MakeReport(const char* report_directory_path) {
212 SkFILEWStream out(SkOSPath::Join(report_directory_path, PATH_REPORT).c_str());
213 if (!out.isValid()) {
214 return false;
215 }
216 out.writeText(
217 "<!doctype html>\n"
218 "<html lang=\"en\">\n"
219 "<head>\n"
220 "<meta charset=\"UTF-8\">\n"
221 "<title>SkQP Report</title>\n"
222 "<style>\n"
223 "img { max-width:48%; border:1px green solid; }\n"
224 "</style>\n"
225 "</head>\n"
226 "<body>\n"
227 "<h1>SkQP Report</h1>\n"
228 "<hr>\n");
229 std::lock_guard<std::mutex> lock(gMutex);
230 for (const Run& run : gErrors) {
231 const SkString& backend = run.fBackend;
232 const SkString& gm = run.fGM;
233 int maxerror = run.fMaxerror;
234 int badpixels = run.fBadpixels;
235 SkString rdir = SkOSPath::Join(backend.c_str(), gm.c_str());
Hal Canaryd7b38452017-12-11 17:46:26 -0500236 SkString text = SkStringPrintf(
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500237 "<h2>%s</h2>\n"
Hal Canaryd7b38452017-12-11 17:46:26 -0500238 "backend: %s\n<br>\n"
239 "gm name: %s\n<br>\n"
240 "maximum error: %d\n<br>\n"
241 "bad pixel counts: %d\n<br>\n"
242 "<a href=\"%s/" PATH_IMG_PNG "\">"
243 "<img src=\"%s/" PATH_IMG_PNG "\" alt='img'></a>\n"
244 "<a href=\"%s/" PATH_ERR_PNG "\">"
245 "<img src=\"%s/" PATH_ERR_PNG "\" alt='err'></a>\n<br>\n"
246 "<a href=\"%s/" PATH_MAX_PNG "\">max</a>\n<br>\n"
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500247 "<a href=\"%s/" PATH_MIN_PNG "\">min</a>\n<hr>\n\n",
248 rdir.c_str(), backend.c_str(), gm.c_str(), maxerror, badpixels,
249 rdir.c_str(), rdir.c_str(), rdir.c_str(),
250 rdir.c_str(), rdir.c_str(), rdir.c_str());
251 out.write(text.c_str(), text.size());
Hal Canaryd7b38452017-12-11 17:46:26 -0500252 }
Hal Canary2a7f0aa2017-12-18 16:59:56 -0500253 out.writeText("</body>\n</html>\n");
254 return true;
Hal Canaryd7b38452017-12-11 17:46:26 -0500255}
256} // namespace gmkb