aboutsummaryrefslogtreecommitdiff
path: root/gm/vias/SimpleVias.cpp
blob: b75ce2194c36baad5b708438ccc7cc44a117f9e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "gm/gm.h"
#include "gm/vias/Draw.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/encode/SkPngEncoder.h"
#include "include/utils/SkBase64.h"

#include <sstream>
#include <string>

// Implements the "direct" via. It draws the GM directly on the surface under test without any
// additional behaviors. Equivalent to running a GM with DM without using any vias.
static GMOutput draw_direct(skiagm::GM* gm, SkSurface* surface) {
    // Run the GM.
    SkString msg;
    skiagm::GM::DrawResult result = gm->draw(surface->getCanvas(), &msg);
    if (result != skiagm::DrawResult::kOk) {
        return {result, msg.c_str()};
    }

    // Extract a bitmap from the surface.
    SkBitmap bitmap;
    bitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(), SkBitmap::kZeroPixels_AllocFlag);
    if (!surface->readPixels(bitmap, 0, 0)) {
        return {skiagm::DrawResult::kFail, "Could not read pixels from surface"};
    }
    return {result, msg.c_str(), bitmap};
}

// Encodes a bitmap as a base-64 image/png URI. In the presence of errors, the returned string will
// contain a human-friendly error message. Otherwise the string will start with "data:image/png".
//
// Based on
// https://skia.googlesource.com/skia/+/650c980daa72d887602e701db8f84072e26d4d48/tests/TestUtils.cpp#127.
static std::string bitmap_to_base64_data_uri(const SkBitmap& bitmap) {
    SkPixmap pm;
    if (!bitmap.peekPixels(&pm)) {
        return "peekPixels failed";
    }

    // We're going to embed this PNG in a data URI, so make it as small as possible.
    SkPngEncoder::Options options;
    options.fFilterFlags = SkPngEncoder::FilterFlag::kAll;
    options.fZLibLevel = 9;

    SkDynamicMemoryWStream wStream;
    if (!SkPngEncoder::Encode(&wStream, pm, options)) {
        return "SkPngEncoder::Encode failed";
    }

    sk_sp<SkData> pngData = wStream.detachAsData();
    size_t len = SkBase64::EncodedSize(pngData->size());

    // The PNG can be almost arbitrarily large. We don't want to fill our logs with enormous URLs
    // and should only output them on failure.
    static const size_t kMaxBase64Length = 1024 * 1024;
    if (len > kMaxBase64Length) {
        return SkStringPrintf("Encoded image too large (%lu bytes)", len).c_str();
    }

    std::string out;
    out.resize(len);
    SkBase64::Encode(pngData->data(), pngData->size(), out.data());
    return "data:image/png;base64," + out;
}

// Implements the "picture" and "picture_serialization" vias. The "serialize" argument determines
// which of the two vias is used.
//
// The "picture" via is based on DM's ViaPicture class here:
// https://skia.googlesource.com/skia/+/dcc56df202cca129edda3f6f8bae04ec306b264e/dm/DMSrcSink.cpp#2310.
//
// The "picture_serialization" via is based on DM's ViaSerialization class here:
// https://skia.googlesource.com/skia/+/dcc56df202cca129edda3f6f8bae04ec306b264e/dm/DMSrcSink.cpp#2281.
static GMOutput draw_via_picture(skiagm::GM* gm, SkSurface* surface, bool serialize) {
    // Draw GM on a recording canvas.
    SkPictureRecorder recorder;
    SkCanvas* recordingCanvas =
            recorder.beginRecording(gm->getISize().width(), gm->getISize().height());
    SkString msg;
    skiagm::DrawResult result = gm->draw(recordingCanvas, &msg);
    if (result != skiagm::DrawResult::kOk) {
        return {result, msg.c_str()};
    }

    // Finish recording, and optionally serialize and then deserialize the resulting picture. Note
    // that the pic->serialize() call uses the default behavior from SkSerialProcs, which implies a
    // dependency on libpng.
    sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
    if (serialize) {
        pic = SkPicture::MakeFromData(pic->serialize().get());
    }

    // Draw the recorded picture on the surface under test and extract it as a bitmap.
    surface->getCanvas()->drawPicture(pic);
    SkBitmap recordedBitmap;
    recordedBitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(),
                                    SkBitmap::kZeroPixels_AllocFlag);
    if (!surface->readPixels(recordedBitmap, 0, 0)) {
        return {skiagm::DrawResult::kFail, "Could not read recorded picture pixels from surface"};
    }

    // Draw GM on the surface under test and extract the reference bitmap.
    result = gm->draw(surface->getCanvas(), &msg);
    if (result != skiagm::DrawResult::kOk) {
        return {result, msg.c_str()};
    }
    SkBitmap referenceBitmap;
    referenceBitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(),
                                     SkBitmap::kZeroPixels_AllocFlag);
    if (!surface->readPixels(referenceBitmap, 0, 0)) {
        return {skiagm::DrawResult::kFail, "Could not read reference picture pixels from surface"};
    }

    // The recorded and reference bitmaps should be identical.
    if (recordedBitmap.computeByteSize() != referenceBitmap.computeByteSize()) {
        return {skiagm::DrawResult::kFail,
                SkStringPrintf("Recorded and reference bitmap dimensions do not match: "
                               "expected byte size %lu, width %d and height %d; "
                               "got %lu, %d and %d",
                               referenceBitmap.computeByteSize(),
                               referenceBitmap.bounds().width(),
                               referenceBitmap.bounds().height(),
                               recordedBitmap.computeByteSize(),
                               recordedBitmap.bounds().width(),
                               recordedBitmap.bounds().height())
                        .c_str()};
    }
    if (0 != memcmp(recordedBitmap.getPixels(),
                    referenceBitmap.getPixels(),
                    referenceBitmap.computeByteSize())) {
        return {skiagm::DrawResult::kFail,
                SkStringPrintf("Recorded and reference bitmap pixels do not match.\n"
                               "Recorded image:\n%s\nReference image:\n%s",
                               bitmap_to_base64_data_uri(recordedBitmap).c_str(),
                               bitmap_to_base64_data_uri(referenceBitmap).c_str())
                        .c_str()};
    }

    return {result, msg.c_str(), referenceBitmap};
}

// This draw() implementation supports the "direct", "picture" and "picture_serialization" vias.
//
// The "direct" via draws the GM directly on the surface under test with no additional behaviors.
// It is equivalent to running a GM with DM without using a via.
//
// The "picture" via tests that if we record a GM using an SkPictureRecorder, the bitmap produced
// by drawing the recorded picture on the surface under test is the same as the bitmap obtained by
// drawing the GM directly on the surface under test.
//
// The "picture_serialization" via is identical to the "picture" via, except that the recorded
// picture is serialized and then deserialized before being drawn on the surface under test.
GMOutput draw(skiagm::GM* gm, SkSurface* surface, std::string via) {
    if (via == "direct") {
        return draw_direct(gm, surface);
    } else if (via == "picture") {
        return draw_via_picture(gm, surface, /* serialize= */ false);
    } else if (via == "picture_serialization") {
        return draw_via_picture(gm, surface, /* serialize= */ true);
    }
    SK_ABORT("unknown --via flag value: %s", via.c_str());
}