/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "gm_knowledge.h" #include #include #include #include #include #include #include #include "../../src/core/SkStreamPriv.h" #include "../../src/core/SkTSort.h" #include "SkBitmap.h" #include "SkCodec.h" #include "SkOSFile.h" #include "SkOSPath.h" #include "SkPngEncoder.h" #include "SkStream.h" #include "skqp_asset_manager.h" #define IMAGES_DIRECTORY_PATH "images" #define PATH_MAX_PNG "max.png" #define PATH_MIN_PNG "min.png" #define PATH_IMG_PNG "image.png" #define PATH_ERR_PNG "errors.png" #define PATH_REPORT "report.html" #define PATH_CSV "out.csv" #ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE #define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0 #endif //////////////////////////////////////////////////////////////////////////////// static int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) { int error = 0; for (int j : {0, 8, 16, 24}) { uint8_t v = (value >> j) & 0xFF, vmin = (value_min >> j) & 0xFF, vmax = (value_max >> j) & 0xFF; if (v > vmax) { error = std::max(v - vmax, error); } else if (v < vmin) { error = std::max(vmin - v, error); } } return std::max(0, error - SK_SKQP_GLOBAL_ERROR_TOLERANCE); } static int get_error_with_nearby(int x, int y, const SkPixmap& pm, const SkPixmap& pm_max, const SkPixmap& pm_min) { struct NearbyPixels { const int x, y, w, h; struct Iter { const int x, y, w, h; int8_t curr; SkIPoint operator*() const { return this->get(); } SkIPoint get() const { switch (curr) { case 0: return {x - 1, y - 1}; case 1: return {x , y - 1}; case 2: return {x + 1, y - 1}; case 3: return {x - 1, y }; case 4: return {x + 1, y }; case 5: return {x - 1, y + 1}; case 6: return {x , y + 1}; case 7: return {x + 1, y + 1}; default: SkASSERT(false); return {0, 0}; } } void skipBad() { while (curr < 8) { SkIPoint p = this->get(); if (p.x() >= 0 && p.y() >= 0 && p.x() < w && p.y() < h) { return; } ++curr; } curr = -1; } void operator++() { if (-1 == curr) { return; } ++curr; this->skipBad(); } bool operator!=(const Iter& other) const { return curr != other.curr; } }; Iter begin() const { Iter i{x, y, w, h, 0}; i.skipBad(); return i; } Iter end() const { return Iter{x, y, w, h, -1}; } }; uint32_t c = *pm.addr32(x, y); int error = get_error(c, *pm_max.addr32(x, y), *pm_min.addr32(x, y)); for (SkIPoint p : NearbyPixels{x, y, pm.width(), pm.height()}) { if (error == 0) { return 0; } error = SkTMin(error, get_error( c, *pm_max.addr32(p.x(), p.y()), *pm_min.addr32(p.x(), p.y()))); } return error; } static float set_error_code(gmkb::Error* error_out, gmkb::Error error) { SkASSERT(error != gmkb::Error::kNone); if (error_out) { *error_out = error; } return FLT_MAX; } static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) { SkFILEWStream wStream(path); return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, SkPngEncoder::Options()); } constexpr SkColorType kColorType = kRGBA_8888_SkColorType; constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType; static SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) { SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType); return SkPixmap(info, pixels, width * sizeof(uint32_t)); } static bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) { if (mgr) { if (auto stream = mgr->open(path)) { SkFILEWStream wStream(dst); return wStream.isValid() && SkStreamCopy(&wStream, stream.get()); } } return false; } static SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) { SkBitmap bitmap; if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) { SkISize size = codec->getInfo().dimensions(); SkASSERT(!size.isEmpty()); SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType); bitmap.allocPixels(info); SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t)); if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) { bitmap.reset(); } } return bitmap; } namespace { struct Run { SkString fBackend; SkString fGM; int fMaxerror; int fBadpixels; }; } // namespace static std::vector gErrors; static std::mutex gMutex; static SkString make_path(const SkString& images_directory, const char* backend, const char* gm_name, const char* thing) { auto path = SkStringPrintf("%s_%s_%s", backend, gm_name, thing); return SkOSPath::Join(images_directory.c_str(), path.c_str()); } namespace gmkb { float Check(const uint32_t* pixels, int width, int height, const char* name, const char* backend, skqp::AssetManager* assetManager, const char* report_directory_path, Error* error_out) { if (report_directory_path && report_directory_path[0]) { SkASSERT_RELEASE(sk_isdir(report_directory_path)); } if (width <= 0 || height <= 0) { return set_error_code(error_out, Error::kBadInput); } constexpr char PATH_ROOT[] = "gmkb"; SkString img_path = SkOSPath::Join(PATH_ROOT, name); SkString max_path = SkOSPath::Join(img_path.c_str(), PATH_MAX_PNG); SkString min_path = SkOSPath::Join(img_path.c_str(), PATH_MIN_PNG); SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str()); SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str()); if (max_image.isNull() || min_image.isNull()) { // No data. if (error_out) { *error_out = Error::kNone; } return 0; } if (max_image.width() != min_image.width() || max_image.height() != min_image.height()) { return set_error_code(error_out, Error::kBadData); } if (max_image.width() != width || max_image.height() != height) { return set_error_code(error_out, Error::kBadInput); } int badness = 0; int badPixelCount = 0; SkPixmap pm(SkImageInfo::Make(width, height, kColorType, kAlphaType), pixels, width * sizeof(uint32_t)); SkPixmap pm_max = max_image.pixmap(); SkPixmap pm_min = min_image.pixmap(); for (int y = 0; y < pm.height(); ++y) { for (int x = 0; x < pm.width(); ++x) { int error = get_error_with_nearby(x, y, pm, pm_max, pm_min) ; if (error > 0) { badness = SkTMax(error, badness); ++badPixelCount; } } } if (badness == 0) { std::lock_guard lock(gMutex); gErrors.push_back(Run{SkString(backend), SkString(name), 0, 0}); } if (report_directory_path && badness > 0 && report_directory_path[0] != '\0') { if (!backend) { backend = "skia"; } SkString images_directory = SkOSPath::Join(report_directory_path, IMAGES_DIRECTORY_PATH); sk_mkdir(images_directory.c_str()); SkString image_path = make_path(images_directory, backend, name, PATH_IMG_PNG); SkString error_path = make_path(images_directory, backend, name, PATH_ERR_PNG); SkString max_path_out = make_path(images_directory, backend, name, PATH_MAX_PNG); SkString min_path_out = make_path(images_directory, backend, name, PATH_MIN_PNG); SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height), image_path.c_str())); SkBitmap errorBitmap; errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType)); for (int y = 0; y < pm.height(); ++y) { for (int x = 0; x < pm.width(); ++x) { int error = get_error_with_nearby(x, y, pm, pm_max, pm_min); *errorBitmap.getAddr32(x, y) = error > 0 ? 0xFF000000 + (unsigned)error : 0xFFFFFFFF; } } SkAssertResult(WritePixmapToFile(errorBitmap.pixmap(), error_path.c_str())); (void)copy(assetManager, max_path.c_str(), max_path_out.c_str()); (void)copy(assetManager, min_path.c_str(), min_path_out.c_str()); std::lock_guard lock(gMutex); gErrors.push_back(Run{SkString(backend), SkString(name), badness, badPixelCount}); } if (error_out) { *error_out = Error::kNone; } return (float)badness; } static constexpr char kDocHead[] = "\n" "\n" "\n" "\n" "SkQP Report\n" "\n" "\n" "\n" "\n" "

SkQP Report

\n"; static constexpr char kDocTail[] = "
    \n" "
    \n" "

    Left image: test result
    \n" "Right image: errors (white = no error, black = smallest error, red = biggest error)

    \n" "
    \n" "\n" "\n"; static void write(SkWStream* wStream, const SkString& text) { wStream->write(text.c_str(), text.size()); } enum class Backend { kUnknown, kGLES, kVulkan, }; static Backend get_backend(const SkString& s) { if (s.equals("gles")) { return Backend::kGLES; } else if (s.equals("vk")) { return Backend::kVulkan; } return Backend::kUnknown; } bool MakeReport(const char* report_directory_path) { int glesErrorCount = 0, vkErrorCount = 0, gles = 0, vk = 0; SkASSERT_RELEASE(sk_isdir(report_directory_path)); std::lock_guard lock(gMutex); SkFILEWStream csvOut(SkOSPath::Join(report_directory_path, PATH_CSV).c_str()); SkFILEWStream htmOut(SkOSPath::Join(report_directory_path, PATH_REPORT).c_str()); SkASSERT_RELEASE(csvOut.isValid()); if (!csvOut.isValid() || !htmOut.isValid()) { return false; } htmOut.writeText(kDocHead); for (const Run& run : gErrors) { auto backend = get_backend(run.fBackend); switch (backend) { case Backend::kGLES: ++gles; break; case Backend::kVulkan: ++vk; break; default: break; } write(&csvOut, SkStringPrintf("\"%s\",\"%s\",%d,%d\n", run.fBackend.c_str(), run.fGM.c_str(), run.fMaxerror, run.fBadpixels)); if (run.fMaxerror == 0 && run.fBadpixels == 0) { continue; } write(&htmOut, SkStringPrintf(" f(\"%s\", \"%s\", %d, %d);\n", run.fBackend.c_str(), run.fGM.c_str(), run.fMaxerror, run.fBadpixels)); switch (backend) { case Backend::kGLES: ++glesErrorCount; break; case Backend::kVulkan: ++vkErrorCount; break; default: break; } } htmOut.writeText(kDocMiddle); write(&htmOut, SkStringPrintf("

    gles errors: %d (of %d)
    \n" "vk errors: %d (of %d)

    \n", glesErrorCount, gles, vkErrorCount, vk)); htmOut.writeText(kDocTail); return true; } } // namespace gmkb