xref: /third_party/skia/tools/skqp/src/skqp_model.cpp (revision cb93a386)
1/*
2 * Copyright 2018 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 "tools/skqp/src/skqp.h"
9#include "tools/skqp/src/skqp_model.h"
10
11#include "include/codec/SkCodec.h"
12#include "include/core/SkBitmap.h"
13#include "include/core/SkStream.h"
14#include "src/utils/SkOSPath.h"
15
16#include <limits.h>
17
18#ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE
19#define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0
20#endif
21
22////////////////////////////////////////////////////////////////////////////////
23
24static inline uint32_t color(const SkPixmap& pm, SkIPoint p) {
25    return *pm.addr32(p.x(), p.y());
26}
27
28static inline bool inside(SkIPoint point, SkISize dimensions) {
29    return (unsigned)point.x() < (unsigned)dimensions.width() &&
30           (unsigned)point.y() < (unsigned)dimensions.height();
31}
32
33SkQP::RenderOutcome skqp::Check(const SkPixmap& minImg,
34                                const SkPixmap& maxImg,
35                                const SkPixmap& img,
36                                unsigned tolerance,
37                                SkBitmap* errorOut) {
38    SkQP::RenderOutcome result;
39    SkISize dim = img.info().dimensions();
40    SkASSERT(minImg.info().dimensions() == dim);
41    SkASSERT(maxImg.info().dimensions() == dim);
42    static const SkIPoint kNeighborhood[9] = {
43        { 0,  0}, // ordered by closest pixels first.
44        {-1,  0}, { 1,  0}, { 0, -1}, { 0,  1},
45        {-1, -1}, { 1, -1}, {-1,  1}, { 1,  1},
46    };
47    for (int y = 0; y < dim.height(); ++y) {
48        for (int x = 0; x < dim.width(); ++x) {
49            const SkIPoint xy{x, y};
50            const uint32_t c = color(img, xy);
51            int error = INT_MAX;
52            // loop over neighborhood (halo);
53            for (SkIPoint delta : kNeighborhood) {
54                SkIPoint point = xy + delta;
55                if (inside(point, dim)) {  // skip out of pixmap bounds.
56                    int err = 0;
57                    // loop over four color channels.
58                    // Return Manhattan distance in channel-space.
59                    for (int component : {0, 8, 16, 24}) {
60                        uint8_t v    = (c                    >> component) & 0xFF,
61                                vmin = (color(minImg, point) >> component) & 0xFF,
62                                vmax = (color(maxImg, point) >> component) & 0xFF;
63                        err = std::max(err, std::max((int)v - (int)vmax, (int)vmin - (int)v));
64                    }
65                    error = std::min(error, err);
66                }
67            }
68            if (error > (int)tolerance) {
69                ++result.fBadPixelCount;
70                result.fTotalError += error;
71                result.fMaxError = std::max(error, result.fMaxError);
72                if (errorOut) {
73                    if (!errorOut->getPixels()) {
74                        errorOut->allocPixels(SkImageInfo::Make(
75                                    dim.width(), dim.height(),
76                                    kBGRA_8888_SkColorType,
77                                    kOpaque_SkAlphaType));
78                        errorOut->eraseColor(SK_ColorWHITE);
79                    }
80                    SkASSERT((unsigned)error < 256);
81                    *(errorOut->getAddr32(x, y)) = SkColorSetARGB(0xFF, (uint8_t)error, 0, 0);
82                }
83            }
84        }
85    }
86    return result;
87}
88
89static SkBitmap decode(sk_sp<SkData> data) {
90    SkBitmap bitmap;
91    if (auto codec = SkCodec::MakeFromData(std::move(data))) {
92        SkISize size = codec->getInfo().dimensions();
93        SkASSERT(!size.isEmpty());
94        SkImageInfo info = SkImageInfo::Make(size, skqp::kColorType, skqp::kAlphaType);
95        bitmap.allocPixels(info);
96        if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) {
97            bitmap.reset();
98        }
99    }
100    return bitmap;
101}
102
103skqp::ModelResult skqp::CheckAgainstModel(const char* name,
104                                          const SkPixmap& pm,
105                                          SkQPAssetManager* mgr) {
106    skqp::ModelResult result;
107    if (pm.colorType() != kColorType || pm.alphaType() != kAlphaType) {
108        result.fErrorString = "Model failed: source image format.";
109        return result;
110    }
111    if (pm.info().isEmpty()) {
112        result.fErrorString = "Model failed: empty source image";
113        return result;
114    }
115    constexpr char PATH_ROOT[] = "gmkb";
116    SkString img_path = SkOSPath::Join(PATH_ROOT, name);
117    SkString max_path = SkOSPath::Join(img_path.c_str(), kMaxPngPath);
118    SkString min_path = SkOSPath::Join(img_path.c_str(), kMinPngPath);
119
120    result.fMaxPng = mgr->open(max_path.c_str());
121    result.fMinPng = mgr->open(min_path.c_str());
122
123    SkBitmap max_image = decode(result.fMaxPng);
124    SkBitmap min_image = decode(result.fMinPng);
125
126    if (max_image.isNull() || min_image.isNull()) {
127        result.fErrorString = "Model missing";
128        return result;
129    }
130    if (max_image.info().dimensions() != min_image.info().dimensions()) {
131        result.fErrorString = "Model has mismatched data.";
132        return result;
133    }
134
135    if (max_image.info().dimensions() != pm.info().dimensions()) {
136        result.fErrorString = "Model data does not match source size.";
137        return result;
138    }
139    result.fOutcome = Check(min_image.pixmap(),
140                            max_image.pixmap(),
141                            pm,
142                            SK_SKQP_GLOBAL_ERROR_TOLERANCE,
143                            &result.fErrors);
144    return result;
145}
146