Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 1 | /* |
| 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 Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 11 | #include <cstdlib> |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 12 | #include <fstream> |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 13 | #include <mutex> |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 14 | #include <sstream> |
| 15 | #include <string> |
| 16 | #include <vector> |
| 17 | |
| 18 | #include "../../src/core/SkStreamPriv.h" |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 19 | #include "../../src/core/SkTSort.h" |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 20 | #include "SkBitmap.h" |
| 21 | #include "SkCodec.h" |
| 22 | #include "SkOSFile.h" |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 23 | #include "SkOSPath.h" |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 24 | #include "SkPngEncoder.h" |
| 25 | #include "SkStream.h" |
| 26 | |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 27 | |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 28 | #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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 37 | |
| 38 | static 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 | |
| 53 | static 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 | |
| 61 | static SkPixmap to_pixmap(const SkBitmap& bitmap) { |
| 62 | SkPixmap pixmap; |
| 63 | SkAssertResult(bitmap.peekPixels(&pixmap)); |
| 64 | return pixmap; |
| 65 | } |
| 66 | |
| 67 | |
| 68 | static 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 | |
| 75 | constexpr SkColorType kColorType = kRGBA_8888_SkColorType; |
| 76 | constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType; |
| 77 | |
| 78 | static 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 | |
| 83 | static bool asset_exists(skqp::AssetManager* mgr, const char* path) { |
| 84 | return mgr && nullptr != mgr->open(path); |
| 85 | } |
| 86 | |
| 87 | static 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 | |
| 97 | static 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 Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 112 | namespace { |
| 113 | struct Run { |
| 114 | SkString fBackend; |
| 115 | SkString fGM; |
| 116 | int fMaxerror; |
| 117 | int fBadpixels; |
| 118 | }; |
| 119 | } // namespace |
| 120 | |
| 121 | static std::vector<Run> gErrors; |
| 122 | static std::mutex gMutex; |
| 123 | |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 124 | namespace gmkb { |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 125 | bool 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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 130 | // Assumes that for each GM foo, asset_manager has files foo/{max,min}.png |
| 131 | float 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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 139 | if (width <= 0 || height <= 0) { |
| 140 | return set_error_code(error_out, Error::kBadInput); |
| 141 | } |
| 142 | size_t N = (unsigned)width * (unsigned)height; |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 143 | SkString max_path = SkOSPath::Join(name, PATH_MAX_PNG); |
| 144 | SkString min_path = SkOSPath::Join(name, PATH_MIN_PNG); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 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 | } |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 178 | SkString report_directory = SkOSPath::Join(report_directory_path, backend); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 179 | sk_mkdir(report_directory.c_str()); |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 180 | SkString report_subdirectory = SkOSPath::Join(report_directory.c_str(), name); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 181 | sk_mkdir(report_subdirectory.c_str()); |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 182 | SkString error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_IMG_PNG); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 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 | } |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 192 | error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_ERR_PNG); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 193 | SkAssertResult(WritePixmapToFile(to_pixmap(errorBitmap), error_path.c_str())); |
| 194 | |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 195 | SkString report_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_REPORT); |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 196 | |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 197 | 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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 199 | (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 Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 202 | 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 | |
| 211 | bool 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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 236 | SkString text = SkStringPrintf( |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 237 | "<h2>%s</h2>\n" |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 238 | "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 Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 247 | "<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 Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 252 | } |
Hal Canary | 2a7f0aa | 2017-12-18 16:59:56 -0500 | [diff] [blame^] | 253 | out.writeText("</body>\n</html>\n"); |
| 254 | return true; |
Hal Canary | d7b3845 | 2017-12-11 17:46:26 -0500 | [diff] [blame] | 255 | } |
| 256 | } // namespace gmkb |