1 /*
2  * Copyright 2019 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/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkSurface.h"
14 #include "include/core/SkYUVAInfo.h"
15 #include "include/core/SkYUVAPixmaps.h"
16 #include "include/gpu/GrDirectContext.h"
17 #include "include/gpu/GrRecordingContext.h"
18 #include "src/core/SkAutoPixmapStorage.h"
19 #include "src/core/SkScopeExit.h"
20 #include "tools/Resources.h"
21 #include "tools/ToolUtils.h"
22 #include "tools/gpu/YUVUtils.h"
23 
24 namespace {
25 struct AsyncContext {
26     bool fCalled = false;
27     std::unique_ptr<const SkImage::AsyncReadResult> fResult;
28 };
29 }  // anonymous namespace
30 
31 // Making this a lambda in the test functions caused:
32 //   "error: cannot compile this forwarded non-trivially copyable parameter yet"
33 // on x86/Win/Clang bot, referring to 'result'.
async_callback(void * c,std::unique_ptr<const SkImage::AsyncReadResult> result)34 static void async_callback(void* c, std::unique_ptr<const SkImage::AsyncReadResult> result) {
35     auto context = static_cast<AsyncContext*>(c);
36     context->fResult = std::move(result);
37     context->fCalled = true;
38 };
39 
40 // Draws the image to a surface, does a asyncRescaleAndReadPixels of the image, and then sticks
41 // the result in a raster image.
42 template <typename Src>
do_read_and_scale(Src * src,GrDirectContext * direct,const SkIRect & srcRect,const SkImageInfo & ii,SkImage::RescaleGamma rescaleGamma,SkFilterQuality quality)43 static sk_sp<SkImage> do_read_and_scale(Src* src,
44                                         GrDirectContext* direct,
45                                         const SkIRect& srcRect,
46                                         const SkImageInfo& ii,
47                                         SkImage::RescaleGamma rescaleGamma,
48                                         SkFilterQuality quality) {
49     auto* asyncContext = new AsyncContext();
50     src->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, quality, async_callback,
51                                    asyncContext);
52     if (direct) {
53         direct->submit();
54     }
55     while (!asyncContext->fCalled) {
56         // Only GPU should actually be asynchronous.
57         SkASSERT(direct);
58         direct->checkAsyncWorkCompletion();
59     }
60     if (!asyncContext->fResult) {
61         return nullptr;
62     }
63     SkPixmap pixmap(ii, asyncContext->fResult->data(0), asyncContext->fResult->rowBytes(0));
64     auto releasePixels = [](const void*, void* c) { delete static_cast<AsyncContext*>(c); };
65     return SkImage::MakeFromRaster(pixmap, releasePixels, asyncContext);
66 }
67 
68 template <typename Src>
do_read_and_scale_yuv(Src * src,GrDirectContext * direct,SkYUVColorSpace yuvCS,const SkIRect & srcRect,SkISize size,SkImage::RescaleGamma rescaleGamma,SkFilterQuality quality,SkScopeExit * cleanup)69 static sk_sp<SkImage> do_read_and_scale_yuv(Src* src,
70                                             GrDirectContext* direct,
71                                             SkYUVColorSpace yuvCS,
72                                             const SkIRect& srcRect,
73                                             SkISize size,
74                                             SkImage::RescaleGamma rescaleGamma,
75                                             SkFilterQuality quality,
76                                             SkScopeExit* cleanup) {
77     SkASSERT(!(size.width() & 0b1) && !(size.height() & 0b1));
78 
79     SkISize uvSize = {size.width()/2, size.height()/2};
80     SkImageInfo yII  = SkImageInfo::Make(size,   kGray_8_SkColorType, kPremul_SkAlphaType);
81     SkImageInfo uvII = SkImageInfo::Make(uvSize, kGray_8_SkColorType, kPremul_SkAlphaType);
82 
83     AsyncContext asyncContext;
84     src->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, size,
85                                          rescaleGamma, quality, async_callback, &asyncContext);
86     if (direct) {
87         direct->submit();
88     }
89     while (!asyncContext.fCalled) {
90         // Only GPU should actually be asynchronous.
91         SkASSERT(direct);
92         direct->checkAsyncWorkCompletion();
93     }
94     if (!asyncContext.fResult) {
95         return nullptr;
96     }
97     SkYUVAInfo yuvaInfo(size,
98                         SkYUVAInfo::PlaneConfig::kY_U_V,
99                         SkYUVAInfo::Subsampling::k420,
100                         yuvCS);
101     SkPixmap yuvPMs[] = {
102             {yII,  asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0)},
103             {uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1)},
104             {uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2)}
105     };
106     auto pixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, yuvPMs);
107     SkASSERT(pixmaps.isValid());
108     auto lazyYUVImage = sk_gpu_test::LazyYUVImage::Make(pixmaps);
109     SkASSERT(lazyYUVImage);
110     return lazyYUVImage->refImage(direct, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
111 }
112 
113 // Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
114 // rescale in src gamma and rescale in linear gamma.
115 template <typename Src>
do_rescale_grid(SkCanvas * canvas,Src * src,GrDirectContext * direct,const SkIRect & srcRect,SkISize newSize,bool doYUV420,SkString * errorMsg,int pad=0)116 static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas,
117                                           Src* src,
118                                           GrDirectContext* direct,
119                                           const SkIRect& srcRect,
120                                           SkISize newSize,
121                                           bool doYUV420,
122                                           SkString* errorMsg,
123                                           int pad = 0) {
124     if (doYUV420 && !direct) {
125         errorMsg->printf("YUV420 only supported on direct GPU for now.");
126         return skiagm::DrawResult::kSkip;
127     }
128     if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
129         *errorMsg = "Not supported on recording/vector backends.";
130         return skiagm::DrawResult::kSkip;
131     }
132     const auto ii = canvas->imageInfo().makeDimensions(newSize);
133 
134     SkYUVColorSpace yuvColorSpace = kRec601_SkYUVColorSpace;
135     canvas->save();
136     for (auto gamma : {SkImage::RescaleGamma::kSrc, SkImage::RescaleGamma::kLinear}) {
137         canvas->save();
138         for (auto quality : {kNone_SkFilterQuality, kLow_SkFilterQuality, kHigh_SkFilterQuality}) {
139             SkScopeExit cleanup;
140             sk_sp<SkImage> result;
141             if (doYUV420) {
142                 result = do_read_and_scale_yuv(src, direct, yuvColorSpace, srcRect, newSize, gamma,
143                                                quality, &cleanup);
144                 if (!result) {
145                     errorMsg->printf("YUV420 async call failed. Allowed for now.");
146                     return skiagm::DrawResult::kSkip;
147                 }
148                 int nextCS = static_cast<int>(yuvColorSpace + 1) % (kLastEnum_SkYUVColorSpace + 1);
149                 yuvColorSpace = static_cast<SkYUVColorSpace>(nextCS);
150             } else {
151                 result = do_read_and_scale(src, direct, srcRect, ii, gamma, quality);
152                 if (!result) {
153                     errorMsg->printf("async read call failed.");
154                     return skiagm::DrawResult::kFail;
155                 }
156             }
157             canvas->drawImage(result, 0, 0);
158             canvas->translate(newSize.width() + pad, 0);
159         }
160         canvas->restore();
161         canvas->translate(0, newSize.height() + pad);
162     }
163     canvas->restore();
164     return skiagm::DrawResult::kOk;
165 }
166 
do_rescale_image_grid(SkCanvas * canvas,const char * imageFile,const SkIRect & srcRect,SkISize newSize,bool doSurface,bool doYUV420,SkString * errorMsg)167 static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas,
168                                                 const char* imageFile,
169                                                 const SkIRect& srcRect,
170                                                 SkISize newSize,
171                                                 bool doSurface,
172                                                 bool doYUV420,
173                                                 SkString* errorMsg) {
174     auto image = GetResourceAsImage(imageFile);
175     if (!image) {
176         errorMsg->printf("Could not load image file %s.", imageFile);
177         return skiagm::DrawResult::kFail;
178     }
179     if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
180         *errorMsg = "Not supported on recording/vector backends.";
181         return skiagm::DrawResult::kSkip;
182     }
183 
184     auto dContext = GrAsDirectContext(canvas->recordingContext());
185     if (!dContext && canvas->recordingContext()) {
186         *errorMsg = "Not supported in DDL mode";
187         return skiagm::DrawResult::kSkip;
188     }
189 
190     if (doSurface) {
191         // Turn the image into a surface in order to call the read and rescale API
192         auto surfInfo = image->imageInfo().makeDimensions(image->dimensions());
193         auto surface = canvas->makeSurface(surfInfo);
194         if (!surface && surfInfo.colorType() == kBGRA_8888_SkColorType) {
195             surfInfo = surfInfo.makeColorType(kRGBA_8888_SkColorType);
196             surface = canvas->makeSurface(surfInfo);
197         }
198         if (!surface) {
199             *errorMsg = "Could not create surface for image.";
200             // When testing abandoned GrContext we expect surface creation to fail.
201             if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
202                 return skiagm::DrawResult::kSkip;
203             }
204             return skiagm::DrawResult::kFail;
205         }
206         SkPaint paint;
207         paint.setBlendMode(SkBlendMode::kSrc);
208         surface->getCanvas()->drawImage(image, 0, 0, &paint);
209         return do_rescale_grid(canvas, surface.get(), dContext, srcRect, newSize,
210                                doYUV420, errorMsg);
211     } else if (dContext) {
212         image = image->makeTextureImage(dContext);
213         if (!image) {
214             *errorMsg = "Could not create image.";
215             // When testing abandoned GrContext we expect surface creation to fail.
216             if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
217                 return skiagm::DrawResult::kSkip;
218             }
219             return skiagm::DrawResult::kFail;
220         }
221     }
222     return do_rescale_grid(canvas, image.get(), dContext, srcRect, newSize, doYUV420,
223                            errorMsg);
224 }
225 
226 #define DEF_RESCALE_AND_READ_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                      \
227     DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
228         ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);          \
229         return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, false,   \
230                                      errorMsg);                                            \
231     }
232 
233 #define DEF_RESCALE_AND_READ_YUV_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                          \
234     DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) {  \
235         ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);                  \
236         return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
237     }
238 
239 #define DEF_RESCALE_AND_READ_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                       \
240     DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
241         ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);          \
242         return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, false, false,  \
243                                      errorMsg);                                            \
244     }
245 
246 #define DEF_RESCALE_AND_READ_YUV_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                           \
247     DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) {  \
248         ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);                  \
249         return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
250     }
251 
252 DEF_RESCALE_AND_READ_YUV_SURF_GM(
253         images/yellow_rose.webp, rose, SkIRect::MakeXYWH(50, 5, 200, 150), 410, 376)
254 
255 DEF_RESCALE_AND_READ_YUV_IMG_GM(
256         images/yellow_rose.webp, rose_down, SkIRect::MakeXYWH(50, 5, 200, 150), 106, 60)
257 
258 DEF_RESCALE_AND_READ_SURF_GM(
259         images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100), 410, 410)
260 
261 DEF_RESCALE_AND_READ_SURF_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45)
262 DEF_RESCALE_AND_READ_IMG_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 800, 400)
263 
264 DEF_RESCALE_AND_READ_IMG_GM(
265         images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637), (int)(0.7 * 105))
266 DEF_RESCALE_AND_READ_SURF_GM(
267         images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637), (int)(1.2 * 105))
268 DEF_RESCALE_AND_READ_IMG_GM(images/text.png,
269                             text_up_large,
270                             SkIRect::MakeXYWH(300, 0, 300, 105),
271                             (int)(2.4 * 300),
272                             (int)(2.4 * 105))
273 
274 // Exercises non-scaling YUV420. Reads from the original canvas's surface in order to
275 // exercise case where source surface is not a texture (in glbert config).
276 DEF_SIMPLE_GM_CAN_FAIL(async_yuv_no_scale, canvas, errorMsg, 400, 300) {
277     auto surface = canvas->getSurface();
278     if (!surface) {
279         *errorMsg = "Not supported on recording/vector backends.";
280         return skiagm::DrawResult::kSkip;
281     }
282 
283     auto dContext = GrAsDirectContext(surface->recordingContext());
284     if (!dContext && surface->recordingContext()) {
285         *errorMsg = "Not supported in DDL mode";
286         return skiagm::DrawResult::kSkip;
287     }
288 
289     auto image = GetResourceAsImage("images/yellow_rose.webp");
290     if (!image) {
291         return skiagm::DrawResult::kFail;
292     }
293     SkPaint paint;
294     canvas->drawImage(image.get(), 0, 0);
295 
296     SkScopeExit scopeExit;
297     auto yuvImage = do_read_and_scale_yuv(
298             surface, dContext, kRec601_SkYUVColorSpace, SkIRect::MakeWH(400, 300),
299             {400, 300}, SkImage::RescaleGamma::kSrc, kNone_SkFilterQuality, &scopeExit);
300 
301     canvas->clear(SK_ColorWHITE);
302     canvas->drawImage(yuvImage.get(), 0, 0);
303 
304     return skiagm::DrawResult::kOk;
305 }
306 
307 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_no_bleed, canvas, errorMsg, 60, 60) {
308     if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
309         *errorMsg = "Not supported on recording/vector backends.";
310         return skiagm::DrawResult::kSkip;
311     }
312 
313     auto dContext = GrAsDirectContext(canvas->recordingContext());
314     if (!dContext && canvas->recordingContext()) {
315         *errorMsg = "Not supported in DDL mode";
316         return skiagm::DrawResult::kSkip;
317     }
318 
319     static constexpr int kBorder = 5;
320     static constexpr int kInner = 5;
321     const auto srcRect = SkIRect::MakeXYWH(kBorder, kBorder, kInner, kInner);
322     auto surfaceII =
323             SkImageInfo::Make(kInner + 2 * kBorder, kInner + 2 * kBorder, kRGBA_8888_SkColorType,
324                               kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
325     auto surface = canvas->makeSurface(surfaceII);
326     if (!surface) {
327         *errorMsg = "Could not create surface for image.";
328         // When testing abandoned GrContext we expect surface creation to fail.
329         if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
330             return skiagm::DrawResult::kSkip;
331         }
332         return skiagm::DrawResult::kFail;
333     }
334     surface->getCanvas()->clear(SK_ColorRED);
335     surface->getCanvas()->save();
336     surface->getCanvas()->clipRect(SkRect::Make(srcRect), SkClipOp::kIntersect, false);
337     surface->getCanvas()->clear(SK_ColorBLUE);
338     surface->getCanvas()->restore();
339     static constexpr int kPad = 2;
340     canvas->translate(kPad, kPad);
341     skiagm::DrawResult result;
342     SkISize downSize = {static_cast<int>(kInner/2),  static_cast<int>(kInner / 2)};
343     result = do_rescale_grid(canvas, surface.get(), dContext, srcRect, downSize, false, errorMsg,
344                              kPad);
345 
346     if (result != skiagm::DrawResult::kOk) {
347         return result;
348     }
349     canvas->translate(0, 4 * downSize.height());
350     SkISize upSize = {static_cast<int>(kInner * 3.5), static_cast<int>(kInner * 4.6)};
351     result = do_rescale_grid(canvas, surface.get(), dContext, srcRect, upSize, false, errorMsg,
352                              kPad);
353     if (result != skiagm::DrawResult::kOk) {
354         return result;
355     }
356     return skiagm::DrawResult::kOk;
357 }
358