xref: /aosp_15_r20/external/skia/tools/testrunners/gm/vias/SimpleVias.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
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/gm.h"
9 #include "include/codec/SkPngDecoder.h"
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkPictureRecorder.h"
14 #include "include/core/SkSerialProcs.h"
15 #include "include/core/SkStream.h"
16 #include "include/core/SkString.h"
17 #include "include/core/SkSurface.h"
18 #include "include/encode/SkPngEncoder.h"
19 #include "include/private/base/SkAssert.h"
20 #include "src/base/SkBase64.h"
21 #include "tools/testrunners/gm/vias/Draw.h"
22 
23 #include <sstream>
24 #include <string>
25 
26 // Implements the "direct" via. It draws the GM directly on the surface under test without any
27 // additional behaviors. Equivalent to running a GM with DM without using any vias.
draw_direct(skiagm::GM * gm,SkSurface * surface)28 static GMOutput draw_direct(skiagm::GM* gm, SkSurface* surface) {
29     // Run the GM.
30     SkString msg;
31     skiagm::GM::DrawResult result = gm->draw(surface->getCanvas(), &msg);
32     if (result != skiagm::DrawResult::kOk) {
33         return {result, msg.c_str()};
34     }
35 
36     // Extract a bitmap from the surface.
37     SkBitmap bitmap;
38     bitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(), SkBitmap::kZeroPixels_AllocFlag);
39     if (!surface->readPixels(bitmap, 0, 0)) {
40         return {skiagm::DrawResult::kFail, "Could not read pixels from surface"};
41     }
42     return {result, msg.c_str(), bitmap};
43 }
44 
45 // Encodes a bitmap as a base-64 image/png URI. In the presence of errors, the returned string will
46 // contain a human-friendly error message. Otherwise the string will start with "data:image/png".
47 //
48 // Based on
49 // https://skia.googlesource.com/skia/+/650c980daa72d887602e701db8f84072e26d4d48/tests/TestUtils.cpp#127.
bitmap_to_base64_data_uri(const SkBitmap & bitmap)50 static std::string bitmap_to_base64_data_uri(const SkBitmap& bitmap) {
51     SkPixmap pm;
52     if (!bitmap.peekPixels(&pm)) {
53         return "peekPixels failed";
54     }
55 
56     // We're going to embed this PNG in a data URI, so make it as small as possible.
57     SkPngEncoder::Options options;
58     options.fFilterFlags = SkPngEncoder::FilterFlag::kAll;
59     options.fZLibLevel = 9;
60 
61     SkDynamicMemoryWStream wStream;
62     if (!SkPngEncoder::Encode(&wStream, pm, options)) {
63         return "SkPngEncoder::Encode failed";
64     }
65 
66     sk_sp<SkData> pngData = wStream.detachAsData();
67     size_t len = SkBase64::EncodedSize(pngData->size());
68 
69     // The PNG can be almost arbitrarily large. We don't want to fill our logs with enormous URLs
70     // and should only output them on failure.
71     static const size_t kMaxBase64Length = 1024 * 1024;
72     if (len > kMaxBase64Length) {
73         return SkStringPrintf("Encoded image too large (%zu bytes)", len).c_str();
74     }
75 
76     std::string out;
77     out.resize(len);
78     SkBase64::Encode(pngData->data(), pngData->size(), out.data());
79     return "data:image/png;base64," + out;
80 }
81 
82 // Implements the "picture" and "picture_serialization" vias. The "serialize" argument determines
83 // which of the two vias is used.
84 //
85 // The "picture" via is based on DM's ViaPicture class here:
86 // https://skia.googlesource.com/skia/+/dcc56df202cca129edda3f6f8bae04ec306b264e/dm/DMSrcSink.cpp#2310.
87 //
88 // The "picture_serialization" via is based on DM's ViaSerialization class here:
89 // https://skia.googlesource.com/skia/+/dcc56df202cca129edda3f6f8bae04ec306b264e/dm/DMSrcSink.cpp#2281.
draw_via_picture(skiagm::GM * gm,SkSurface * surface,bool serialize)90 static GMOutput draw_via_picture(skiagm::GM* gm, SkSurface* surface, bool serialize) {
91     // Draw GM on a recording canvas.
92     SkPictureRecorder recorder;
93     SkCanvas* recordingCanvas =
94             recorder.beginRecording(gm->getISize().width(), gm->getISize().height());
95     SkString msg;
96     skiagm::DrawResult result = gm->draw(recordingCanvas, &msg);
97     if (result != skiagm::DrawResult::kOk) {
98         return {result, msg.c_str()};
99     }
100 
101     // Finish recording, and optionally serialize and then deserialize the resulting picture using
102     // the PNG encoder/decoder.
103     sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
104     if (serialize) {
105         SkSerialProcs serialProcs = {.fImageProc = [](SkImage* img, void*) -> sk_sp<SkData> {
106             SkASSERT_RELEASE(!img->isTextureBacked());
107             return SkPngEncoder::Encode(nullptr, img, SkPngEncoder::Options{});
108         }};
109         SkDeserialProcs deserialProcs = {.fImageDataProc = [](sk_sp<SkData> data,
110                                                               std::optional<SkAlphaType>,
111                                                               void* ctx) -> sk_sp<SkImage> {
112             SkCodec::Result decodeResult;
113             std::unique_ptr<SkCodec> codec = SkPngDecoder::Decode(data, &decodeResult);
114             SkASSERT(decodeResult == SkCodec::Result::kSuccess);
115             auto [image, getImageResult] = codec->getImage();
116             SkASSERT(getImageResult == SkCodec::Result::kSuccess);
117             return image;
118         }};
119         pic = SkPicture::MakeFromData(pic->serialize(&serialProcs).get(), &deserialProcs);
120     }
121 
122     // Draw the recorded picture on the surface under test and extract it as a bitmap.
123     surface->getCanvas()->drawPicture(pic);
124     SkBitmap recordedBitmap;
125     recordedBitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(),
126                                     SkBitmap::kZeroPixels_AllocFlag);
127     if (!surface->readPixels(recordedBitmap, 0, 0)) {
128         return {skiagm::DrawResult::kFail, "Could not read recorded picture pixels from surface"};
129     }
130 
131     // Draw GM on the surface under test and extract the reference bitmap.
132     result = gm->draw(surface->getCanvas(), &msg);
133     if (result != skiagm::DrawResult::kOk) {
134         return {result, msg.c_str()};
135     }
136     SkBitmap referenceBitmap;
137     referenceBitmap.allocPixelsFlags(surface->getCanvas()->imageInfo(),
138                                      SkBitmap::kZeroPixels_AllocFlag);
139     if (!surface->readPixels(referenceBitmap, 0, 0)) {
140         return {skiagm::DrawResult::kFail, "Could not read reference picture pixels from surface"};
141     }
142 
143     // The recorded and reference bitmaps should be identical.
144     if (recordedBitmap.computeByteSize() != referenceBitmap.computeByteSize()) {
145         return {skiagm::DrawResult::kFail,
146                 SkStringPrintf("Recorded and reference bitmap dimensions do not match: "
147                                "expected byte size %zu, width %d and height %d; "
148                                "got %zu, %d and %d",
149                                referenceBitmap.computeByteSize(),
150                                referenceBitmap.bounds().width(),
151                                referenceBitmap.bounds().height(),
152                                recordedBitmap.computeByteSize(),
153                                recordedBitmap.bounds().width(),
154                                recordedBitmap.bounds().height())
155                         .c_str()};
156     }
157     if (0 != memcmp(recordedBitmap.getPixels(),
158                     referenceBitmap.getPixels(),
159                     referenceBitmap.computeByteSize())) {
160         return {skiagm::DrawResult::kFail,
161                 SkStringPrintf("Recorded and reference bitmap pixels do not match.\n"
162                                "Recorded image:\n%s\nReference image:\n%s",
163                                bitmap_to_base64_data_uri(recordedBitmap).c_str(),
164                                bitmap_to_base64_data_uri(referenceBitmap).c_str())
165                         .c_str()};
166     }
167 
168     return {result, msg.c_str(), referenceBitmap};
169 }
170 
171 // This draw() implementation supports the "direct", "picture" and "picture_serialization" vias.
172 //
173 // The "direct" via draws the GM directly on the surface under test with no additional behaviors.
174 // It is equivalent to running a GM with DM without using a via.
175 //
176 // The "picture" via tests that if we record a GM using an SkPictureRecorder, the bitmap produced
177 // by drawing the recorded picture on the surface under test is the same as the bitmap obtained by
178 // drawing the GM directly on the surface under test.
179 //
180 // The "picture_serialization" via is identical to the "picture" via, except that the recorded
181 // picture is serialized and then deserialized before being drawn on the surface under test.
draw(skiagm::GM * gm,SkSurface * surface,std::string via)182 GMOutput draw(skiagm::GM* gm, SkSurface* surface, std::string via) {
183     if (via == "direct") {
184         return draw_direct(gm, surface);
185     } else if (via == "picture") {
186         return draw_via_picture(gm, surface, /* serialize= */ false);
187     } else if (via == "picture_serialization") {
188         return draw_via_picture(gm, surface, /* serialize= */ true);
189     }
190     SK_ABORT("unknown --via flag value: %s", via.c_str());
191 }
192