/* * 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 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" //////////////////////////////////////////////////////////////////////////////// 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 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 SkPixmap to_pixmap(const SkBitmap& bitmap) { SkPixmap pixmap; SkAssertResult(bitmap.peekPixels(&pixmap)); return pixmap; } static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) { SkFILEWStream wStream(path); SkPngEncoder::Options options; options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore; return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, 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 asset_exists(skqp::AssetManager* mgr, const char* path) { return mgr && nullptr != mgr->open(path); } 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(to_pixmap(bitmap))) { bitmap.reset(); } } return bitmap; } namespace { struct Run { SkString fBackend; SkString fGM; int fMaxerror; int fBadpixels; }; } // namespace static std::vector gErrors; static std::mutex gMutex; namespace gmkb { bool IsGoodGM(const char* name, skqp::AssetManager* assetManager) { return asset_exists(assetManager, SkOSPath::Join(name, PATH_MAX_PNG).c_str()) && asset_exists(assetManager, SkOSPath::Join(name, PATH_MIN_PNG).c_str()); } // Assumes that for each GM foo, asset_manager has files foo/{max,min}.png 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 (width <= 0 || height <= 0) { return set_error_code(error_out, Error::kBadInput); } size_t N = (unsigned)width * (unsigned)height; SkString max_path = SkOSPath::Join(name, PATH_MAX_PNG); SkString min_path = SkOSPath::Join(name, PATH_MIN_PNG); SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str()); if (max_image.isNull()) { return set_error_code(error_out, Error::kBadData); } SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str()); if (min_image.isNull()) { return set_error_code(error_out, Error::kBadData); } 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; const uint32_t* max_pixels = (uint32_t*)max_image.getPixels(); const uint32_t* min_pixels = (uint32_t*)min_image.getPixels(); for (size_t i = 0; i < N; ++i) { int error = get_error(pixels[i], max_pixels[i], min_pixels[i]); 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') { sk_mkdir(report_directory_path); if (!backend) { backend = "skia"; } SkString report_directory = SkOSPath::Join(report_directory_path, backend); sk_mkdir(report_directory.c_str()); SkString report_subdirectory = SkOSPath::Join(report_directory.c_str(), name); sk_mkdir(report_subdirectory.c_str()); SkString error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_IMG_PNG); SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height), error_path.c_str())); SkBitmap errorBitmap; errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType)); uint32_t* errors = (uint32_t*)errorBitmap.getPixels(); for (size_t i = 0; i < N; ++i) { int error = get_error(pixels[i], max_pixels[i], min_pixels[i]); errors[i] = error > 0 ? 0xFF000000 + (unsigned)error : 0x00000000; } error_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_ERR_PNG); SkAssertResult(WritePixmapToFile(to_pixmap(errorBitmap), error_path.c_str())); SkString report_path = SkOSPath::Join(report_subdirectory.c_str(), PATH_REPORT); SkString max_path_out = SkOSPath::Join(report_subdirectory.c_str(), PATH_MAX_PNG); SkString min_path_out = SkOSPath::Join(report_subdirectory.c_str(), PATH_MIN_PNG); (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; } bool MakeReport(const char* report_directory_path) { std::lock_guard lock(gMutex); { SkFILEWStream csvOut(SkOSPath::Join(report_directory_path, "out.csv").c_str()); if (!csvOut.isValid()) { return false; } for (const Run& run : gErrors) { SkString line = SkStringPrintf("\"%s\",\"%s\",%d,%d\n", run.fBackend.c_str(), run.fGM.c_str(), run.fMaxerror, run.fBadpixels); csvOut.write(line.c_str(), line.size()); } } SkFILEWStream out(SkOSPath::Join(report_directory_path, PATH_REPORT).c_str()); if (!out.isValid()) { return false; } out.writeText( "\n" "\n" "\n" "\n" "SkQP Report\n" "\n" "\n" "\n" "

SkQP Report

\n" "
\n"); for (const Run& run : gErrors) { const SkString& backend = run.fBackend; const SkString& gm = run.fGM; int maxerror = run.fMaxerror; int badpixels = run.fBadpixels; if (maxerror == 0 && badpixels == 0) { continue; } SkString rdir = SkOSPath::Join(backend.c_str(), gm.c_str()); SkString text = SkStringPrintf( "

%s

\n" "backend: %s\n
\n" "gm name: %s\n
\n" "maximum error: %d\n
\n" "bad pixel counts: %d\n
\n" "" "img\n" "" "err\n
\n" "max\n
\n" "min\n
\n\n", rdir.c_str(), backend.c_str(), gm.c_str(), maxerror, badpixels, rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str()); out.write(text.c_str(), text.size()); } out.writeText("\n\n"); return true; } } // namespace gmkb