blob: bc2ca8201af0705f0716b7970829274e57d04810 [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>
11#include <fstream>
12#include <sstream>
13#include <string>
14#include <vector>
15
16#include "../../src/core/SkStreamPriv.h"
17#include "SkBitmap.h"
18#include "SkCodec.h"
19#include "SkOSFile.h"
20#include "SkPngEncoder.h"
21#include "SkStream.h"
22
23#include "skqp_asset_manager.h"
24
25#define PATH_MAX_PNG "max.png"
26#define PATH_MIN_PNG "min.png"
27#define PATH_IMG_PNG "image.png"
28#define PATH_ERR_PNG "errors.png"
29#define PATH_REPORT "report.html"
30
31////////////////////////////////////////////////////////////////////////////////
32inline void path_join_append(std::ostringstream* o) { }
33
34template<class... Types>
35void path_join_append(std::ostringstream* o, const char* v, Types... args) {
36 constexpr char kPathSeparator[] = "/";
37 *o << kPathSeparator << v;
38 path_join_append(o, args...);
39}
40
41template<class... Types>
42std::string path_join(const char* v, Types... args) {
43 std::ostringstream o;
44 o << v;
45 path_join_append(&o, args...);
46 return o.str();
47}
48template<class... Types>
49std::string path_join(const std::string& v, Types... args) {
50 return path_join(v.c_str(), args...);
51}
52////////////////////////////////////////////////////////////////////////////////
53
54static int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) {
55 int error = 0;
56 for (int j : {0, 8, 16, 24}) {
57 uint8_t v = (value >> j) & 0xFF,
58 vmin = (value_min >> j) & 0xFF,
59 vmax = (value_max >> j) & 0xFF;
60 if (v > vmax) {
61 error = std::max(v - vmax, error);
62 } else if (v < vmin) {
63 error = std::max(vmin - v, error);
64 }
65 }
66 return error;
67}
68
69static float set_error_code(gmkb::Error* error_out, gmkb::Error error) {
70 SkASSERT(error != gmkb::Error::kNone);
71 if (error_out) {
72 *error_out = error;
73 }
74 return FLT_MAX;
75}
76
77static SkPixmap to_pixmap(const SkBitmap& bitmap) {
78 SkPixmap pixmap;
79 SkAssertResult(bitmap.peekPixels(&pixmap));
80 return pixmap;
81}
82
83
84static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) {
85 SkFILEWStream wStream(path);
86 SkPngEncoder::Options options;
87 options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore;
88 return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, options);
89}
90
91constexpr SkColorType kColorType = kRGBA_8888_SkColorType;
92constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType;
93
94static SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) {
95 SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType);
96 return SkPixmap(info, pixels, width * sizeof(uint32_t));
97}
98
99static bool asset_exists(skqp::AssetManager* mgr, const char* path) {
100 return mgr && nullptr != mgr->open(path);
101}
102
103static bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) {
104 if (mgr) {
105 if (auto stream = mgr->open(path)) {
106 SkFILEWStream wStream(dst);
107 return wStream.isValid() && SkStreamCopy(&wStream, stream.get());
108 }
109 }
110 return false;
111}
112
113static SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) {
114 SkBitmap bitmap;
115 if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) {
116 SkISize size = codec->getInfo().dimensions();
117 SkASSERT(!size.isEmpty());
118 SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType);
119 bitmap.allocPixels(info);
120 SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t));
121 if (SkCodec::kSuccess != codec->getPixels(to_pixmap(bitmap))) {
122 bitmap.reset();
123 }
124 }
125 return bitmap;
126}
127
128namespace gmkb {
129// Assumes that for each GM foo, asset_manager has files foo/{max,min}.png
130float Check(const uint32_t* pixels,
131 int width,
132 int height,
133 const char* name,
134 const char* backend,
135 skqp::AssetManager* assetManager,
136 const char* report_directory_path,
137 Error* error_out) {
138 using std::string;
139 if (width <= 0 || height <= 0) {
140 return set_error_code(error_out, Error::kBadInput);
141 }
142 size_t N = (unsigned)width * (unsigned)height;
143 string max_path = path_join(name, PATH_MAX_PNG);
144 string min_path = path_join(name, PATH_MIN_PNG);
145 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 }
178 string report_directory = path_join(report_directory_path, backend);
179 sk_mkdir(report_directory.c_str());
180 string report_subdirectory = path_join(report_directory, name);
181 sk_mkdir(report_subdirectory.c_str());
182 string error_path = path_join(report_subdirectory, PATH_IMG_PNG);
183 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 }
192 error_path = path_join(report_subdirectory, PATH_ERR_PNG);
193 SkAssertResult(WritePixmapToFile(to_pixmap(errorBitmap), error_path.c_str()));
194
195 auto report_path = path_join(report_subdirectory, PATH_REPORT);
196 auto rdir = path_join("..", "..", backend, name);
197
198 auto max_path_out = path_join(report_subdirectory, PATH_MAX_PNG);
199 auto min_path_out = path_join(report_subdirectory, PATH_MIN_PNG);
200 (void)copy(assetManager, max_path.c_str(), max_path_out.c_str());
201 (void)copy(assetManager, min_path.c_str(), min_path_out.c_str());
202
203 SkString text = SkStringPrintf(
204 "backend: %s\n<br>\n"
205 "gm name: %s\n<br>\n"
206 "maximum error: %d\n<br>\n"
207 "bad pixel counts: %d\n<br>\n"
208 "<a href=\"%s/" PATH_IMG_PNG "\">"
209 "<img src=\"%s/" PATH_IMG_PNG "\" alt='img'></a>\n"
210 "<a href=\"%s/" PATH_ERR_PNG "\">"
211 "<img src=\"%s/" PATH_ERR_PNG "\" alt='err'></a>\n<br>\n"
212 "<a href=\"%s/" PATH_MAX_PNG "\">max</a>\n<br>\n"
213 "<a href=\"%s/" PATH_MIN_PNG "\">min</a>\n<hr>\n",
214 backend, name, badness, badPixelCount,
215 rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str());
216 SkFILEWStream(report_path.c_str()).write(text.c_str(), text.size());
217 }
218 if (error_out) {
219 *error_out = Error::kNone;
220 }
221 return (float)badness;
222}
223
224bool IsGoodGM(const char* name, skqp::AssetManager* assetManager) {
225 std::string max_path = path_join(name, PATH_MAX_PNG);
226 std::string min_path = path_join(name, PATH_MIN_PNG);
227 return asset_exists(assetManager, max_path.c_str())
228 && asset_exists(assetManager, min_path.c_str());
229}
230} // namespace gmkb