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 "gm/gm.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorFilter.h"
14 #include "include/core/SkColorPriv.h"
15 #include "include/core/SkColorSpace.h"
16 #include "include/core/SkFont.h"
17 #include "include/core/SkFontStyle.h"
18 #include "include/core/SkFontTypes.h"
19 #include "include/core/SkImage.h"
20 #include "include/core/SkImageGenerator.h"
21 #include "include/core/SkImageInfo.h"
22 #include "include/core/SkMatrix.h"
23 #include "include/core/SkPaint.h"
24 #include "include/core/SkPath.h"
25 #include "include/core/SkPixmap.h"
26 #include "include/core/SkPoint.h"
27 #include "include/core/SkRect.h"
28 #include "include/core/SkRefCnt.h"
29 #include "include/core/SkScalar.h"
30 #include "include/core/SkSize.h"
31 #include "include/core/SkString.h"
32 #include "include/core/SkTypeface.h"
33 #include "include/core/SkTypes.h"
34 #include "include/gpu/GrBackendSurface.h"
35 #include "include/gpu/GrDirectContext.h"
36 #include "include/gpu/GrRecordingContext.h"
37 #include "include/gpu/GrTypes.h"
38 #include "include/gpu/ganesh/SkImageGanesh.h"
39 #include "include/private/base/SkTArray.h"
40 #include "include/private/base/SkTDArray.h"
41 #include "include/private/base/SkTPin.h"
42 #include "include/private/base/SkTemplates.h"
43 #include "include/private/gpu/ganesh/GrTypesPriv.h"
44 #include "include/utils/SkTextUtils.h"
45 #include "src/base/SkHalf.h"
46 #include "src/core/SkConvertPixels.h"
47 #include "src/core/SkYUVMath.h"
48 #include "src/gpu/ganesh/GrCaps.h"
49 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
50 #include "tools/DecodeUtils.h"
51 #include "tools/ToolUtils.h"
52 #include "tools/fonts/FontToolUtils.h"
53 #include "tools/gpu/YUVUtils.h"
54
55 #include <math.h>
56 #include <string.h>
57 #include <initializer_list>
58 #include <memory>
59 #include <utility>
60 #include <vector>
61
62 static const int kTileWidthHeight = 128;
63 static const int kLabelWidth = 64;
64 static const int kLabelHeight = 32;
65 static const int kSubsetPadding = 8;
66 static const int kPad = 1;
67
68 using Recorder = skgpu::graphite::Recorder;
69
70 enum YUVFormat {
71 // 4:2:0 formats, 24 bpp
72 kP016_YUVFormat, // 16-bit Y plane + 2x2 down sampled interleaved U/V plane (2 textures)
73 // 4:2:0 formats, "15 bpp" (but really 24 bpp)
74 kP010_YUVFormat, // same as kP016 except "10 bpp". Note that it is the same memory layout
75 // except that the bottom 6 bits are zeroed out (2 textures)
76 // TODO: we're cheating a bit w/ P010 and just treating it as unorm 16. This means its
77 // fully saturated values are 65504 rather than 65535 (that is just .9995 out of 1.0 though).
78
79 // This is laid out the same as kP016 and kP010 but uses F16 unstead of U16. In this case
80 // the 10 bits/channel vs 16 bits/channel distinction isn't relevant.
81 kP016F_YUVFormat,
82
83 // 4:4:4 formats, 64 bpp
84 kY416_YUVFormat, // 16-bit AVYU values all interleaved (1 texture)
85
86 // 4:4:4 formats, 32 bpp
87 kAYUV_YUVFormat, // 8-bit YUVA values all interleaved (1 texture)
88 kY410_YUVFormat, // AVYU w/ 10bpp for YUV and 2 for A all interleaved (1 texture)
89
90 // 4:2:0 formats, 12 bpp
91 kNV12_YUVFormat, // 8-bit Y plane + 2x2 down sampled interleaved U/V planes (2 textures)
92 kNV21_YUVFormat, // same as kNV12 but w/ U/V reversed in the interleaved texture (2 textures)
93
94 kI420_YUVFormat, // 8-bit Y plane + separate 2x2 down sampled U and V planes (3 textures)
95 kYV12_YUVFormat, // 8-bit Y plane + separate 2x2 down sampled V and U planes (3 textures)
96
97 kLast_YUVFormat = kYV12_YUVFormat
98 };
99
100 // Does the YUVFormat contain a slot for alpha? If not an external alpha plane is required for
101 // transparency.
has_alpha_channel(YUVFormat format)102 static bool has_alpha_channel(YUVFormat format) {
103 switch (format) {
104 case kP016_YUVFormat: return false;
105 case kP010_YUVFormat: return false;
106 case kP016F_YUVFormat: return false;
107 case kY416_YUVFormat: return true;
108 case kAYUV_YUVFormat: return true;
109 case kY410_YUVFormat: return true;
110 case kNV12_YUVFormat: return false;
111 case kNV21_YUVFormat: return false;
112 case kI420_YUVFormat: return false;
113 case kYV12_YUVFormat: return false;
114 }
115 SkUNREACHABLE;
116 }
117
118 class YUVAPlanarConfig {
119 public:
YUVAPlanarConfig(YUVFormat format,bool opaque,SkEncodedOrigin origin)120 YUVAPlanarConfig(YUVFormat format, bool opaque, SkEncodedOrigin origin) : fOrigin(origin) {
121 switch (format) {
122 case kP016_YUVFormat:
123 case kP010_YUVFormat:
124 case kP016F_YUVFormat:
125 case kNV12_YUVFormat:
126 if (opaque) {
127 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_UV;
128 fSubsampling = SkYUVAInfo::Subsampling::k420;
129 } else {
130 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_UV_A;
131 fSubsampling = SkYUVAInfo::Subsampling::k420;
132 }
133 break;
134 case kY416_YUVFormat:
135 case kY410_YUVFormat:
136 if (opaque) {
137 fPlaneConfig = SkYUVAInfo::PlaneConfig::kUYV;
138 fSubsampling = SkYUVAInfo::Subsampling::k444;
139 } else {
140 fPlaneConfig = SkYUVAInfo::PlaneConfig::kUYVA;
141 fSubsampling = SkYUVAInfo::Subsampling::k444;
142 }
143 break;
144 case kAYUV_YUVFormat:
145 if (opaque) {
146 fPlaneConfig = SkYUVAInfo::PlaneConfig::kYUV;
147 fSubsampling = SkYUVAInfo::Subsampling::k444;
148 } else {
149 fPlaneConfig = SkYUVAInfo::PlaneConfig::kYUVA;
150 fSubsampling = SkYUVAInfo::Subsampling::k444;
151 }
152 break;
153 case kNV21_YUVFormat:
154 if (opaque) {
155 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_VU;
156 fSubsampling = SkYUVAInfo::Subsampling::k420;
157 } else {
158 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_VU_A;
159 fSubsampling = SkYUVAInfo::Subsampling::k420;
160 }
161 break;
162 case kI420_YUVFormat:
163 if (opaque) {
164 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_U_V;
165 fSubsampling = SkYUVAInfo::Subsampling::k420;
166 } else {
167 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_U_V_A;
168 fSubsampling = SkYUVAInfo::Subsampling::k420;
169 }
170 break;
171 case kYV12_YUVFormat:
172 if (opaque) {
173 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_V_U;
174 fSubsampling = SkYUVAInfo::Subsampling::k420;
175 } else {
176 fPlaneConfig = SkYUVAInfo::PlaneConfig::kY_V_U_A;
177 fSubsampling = SkYUVAInfo::Subsampling::k420;
178 }
179 break;
180 }
181 }
182
numPlanes() const183 int numPlanes() const { return SkYUVAInfo::NumPlanes(fPlaneConfig); }
184
185 SkYUVAPixmaps makeYUVAPixmaps(SkISize dimensions,
186 SkYUVColorSpace yuvColorSpace,
187 const SkBitmap bitmaps[],
188 int numBitmaps) const;
189
190 private:
191 SkYUVAInfo::PlaneConfig fPlaneConfig;
192 SkYUVAInfo::Subsampling fSubsampling;
193 SkEncodedOrigin fOrigin;
194 };
195
makeYUVAPixmaps(SkISize dimensions,SkYUVColorSpace yuvColorSpace,const SkBitmap bitmaps[],int numBitmaps) const196 SkYUVAPixmaps YUVAPlanarConfig::makeYUVAPixmaps(SkISize dimensions,
197 SkYUVColorSpace yuvColorSpace,
198 const SkBitmap bitmaps[],
199 int numBitmaps) const {
200 SkYUVAInfo info(dimensions, fPlaneConfig, fSubsampling, yuvColorSpace, fOrigin);
201 SkPixmap pmaps[SkYUVAInfo::kMaxPlanes];
202 int n = info.numPlanes();
203 if (numBitmaps < n) {
204 return {};
205 }
206 for (int i = 0; i < n; ++i) {
207 pmaps[i] = bitmaps[i].pixmap();
208 }
209 return SkYUVAPixmaps::FromExternalPixmaps(info, pmaps);
210 }
211
212 // All the planes we need to construct the various YUV formats
213 struct PlaneData {
214 SkBitmap fYFull;
215 SkBitmap fUFull;
216 SkBitmap fVFull;
217 SkBitmap fAFull;
218 SkBitmap fUQuarter; // 2x2 downsampled U channel
219 SkBitmap fVQuarter; // 2x2 downsampled V channel
220
221 SkBitmap fFull;
222 SkBitmap fQuarter; // 2x2 downsampled YUVA
223 };
224
225 const SkYUVColorSpace color_space_array[] = {
226 kJPEG_Full_SkYUVColorSpace, //!< describes full range
227 kRec601_Limited_SkYUVColorSpace, //!< describes SDTV range
228 kRec709_Full_SkYUVColorSpace, //!< describes HDTV range
229 kRec709_Limited_SkYUVColorSpace,
230 kBT2020_8bit_Full_SkYUVColorSpace, //!< describes UHDTV range, non-constant-luminance
231 kBT2020_8bit_Limited_SkYUVColorSpace,
232 kBT2020_10bit_Limited_SkYUVColorSpace,
233 kBT2020_12bit_Full_SkYUVColorSpace,
234 kFCC_Limited_SkYUVColorSpace, //!< describes FCC range
235 kSMPTE240_Limited_SkYUVColorSpace, //!< describes SMPTE240M range
236 kYDZDX_Limited_SkYUVColorSpace, //!< describes YDZDX range
237 kGBR_Limited_SkYUVColorSpace, //!< describes GBR range
238 kYCgCo_8bit_Full_SkYUVColorSpace, //!< describes YCgCo matrix
239 kYCgCo_8bit_Limited_SkYUVColorSpace,
240 kYCgCo_10bit_Full_SkYUVColorSpace,
241 kYCgCo_12bit_Full_SkYUVColorSpace,
242 kYCgCo_12bit_Limited_SkYUVColorSpace,
243 kIdentity_SkYUVColorSpace
244 };
245
246 // Add a portion of a circle to 'path'. The points 'o1' and 'o2' are on the border of the circle
247 // and have tangents 'v1' and 'v2'.
add_arc(SkPath * path,const SkPoint & o1,const SkVector & v1,const SkPoint & o2,const SkVector & v2,SkTDArray<SkRect> * circles,bool takeLongWayRound)248 static void add_arc(SkPath* path,
249 const SkPoint& o1, const SkVector& v1,
250 const SkPoint& o2, const SkVector& v2,
251 SkTDArray<SkRect>* circles, bool takeLongWayRound) {
252
253 SkVector v3 = { -v1.fY, v1.fX };
254 SkVector v4 = { v2.fY, -v2.fX };
255
256 SkScalar t = ((o2.fX - o1.fX) * v4.fY - (o2.fY - o1.fY) * v4.fX) / v3.cross(v4);
257 SkPoint center = { o1.fX + t * v3.fX, o1.fY + t * v3.fY };
258
259 SkRect r = { center.fX - t, center.fY - t, center.fX + t, center.fY + t };
260
261 if (circles) {
262 circles->push_back(r);
263 }
264
265 SkVector startV = o1 - center, endV = o2 - center;
266 startV.normalize();
267 endV.normalize();
268
269 SkScalar startDeg = SkRadiansToDegrees(SkScalarATan2(startV.fY, startV.fX));
270 SkScalar endDeg = SkRadiansToDegrees(SkScalarATan2(endV.fY, endV.fX));
271
272 startDeg += 360.0f;
273 startDeg = fmodf(startDeg, 360.0f);
274
275 endDeg += 360.0f;
276 endDeg = fmodf(endDeg, 360.0f);
277
278 if (endDeg < startDeg) {
279 endDeg += 360.0f;
280 }
281
282 SkScalar sweepDeg = SkTAbs(endDeg - startDeg);
283 if (!takeLongWayRound) {
284 sweepDeg = sweepDeg - 360;
285 }
286
287 path->arcTo(r, startDeg, sweepDeg, false);
288 }
289
create_splat(const SkPoint & o,SkScalar innerRadius,SkScalar outerRadius,SkScalar ratio,int numLobes,SkTDArray<SkRect> * circles)290 static SkPath create_splat(const SkPoint& o, SkScalar innerRadius, SkScalar outerRadius,
291 SkScalar ratio, int numLobes, SkTDArray<SkRect>* circles) {
292 if (numLobes <= 1) {
293 return SkPath();
294 }
295
296 SkPath p;
297
298 int numDivisions = 2 * numLobes;
299 SkScalar fullLobeDegrees = 360.0f / numLobes;
300 SkScalar outDegrees = ratio * fullLobeDegrees / (ratio + 1.0f);
301 SkScalar innerDegrees = fullLobeDegrees / (ratio + 1.0f);
302 SkMatrix outerStep, innerStep;
303 outerStep.setRotate(outDegrees);
304 innerStep.setRotate(innerDegrees);
305 SkVector curV = SkVector::Make(0.0f, 1.0f);
306
307 if (circles) {
308 circles->push_back(SkRect::MakeLTRB(o.fX - innerRadius, o.fY - innerRadius,
309 o.fX + innerRadius, o.fY + innerRadius));
310 }
311
312 p.moveTo(o.fX + innerRadius * curV.fX, o.fY + innerRadius * curV.fY);
313
314 for (int i = 0; i < numDivisions; ++i) {
315
316 SkVector nextV;
317 if (0 == (i % 2)) {
318 nextV = outerStep.mapVector(curV.fX, curV.fY);
319
320 SkPoint top = SkPoint::Make(o.fX + outerRadius * curV.fX,
321 o.fY + outerRadius * curV.fY);
322 SkPoint nextTop = SkPoint::Make(o.fX + outerRadius * nextV.fX,
323 o.fY + outerRadius * nextV.fY);
324
325 p.lineTo(top);
326 add_arc(&p, top, curV, nextTop, nextV, circles, true);
327 } else {
328 nextV = innerStep.mapVector(curV.fX, curV.fY);
329
330 SkPoint bot = SkPoint::Make(o.fX + innerRadius * curV.fX,
331 o.fY + innerRadius * curV.fY);
332 SkPoint nextBot = SkPoint::Make(o.fX + innerRadius * nextV.fX,
333 o.fY + innerRadius * nextV.fY);
334
335 p.lineTo(bot);
336 add_arc(&p, bot, curV, nextBot, nextV, nullptr, false);
337 }
338
339 curV = nextV;
340 }
341
342 p.close();
343
344 return p;
345 }
346
make_bitmap(SkColorType colorType,const SkPath & path,const SkTDArray<SkRect> & circles,bool opaque,bool padWithRed)347 static SkBitmap make_bitmap(SkColorType colorType, const SkPath& path,
348 const SkTDArray<SkRect>& circles, bool opaque, bool padWithRed) {
349 const SkColor kGreen = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 178, 240, 104));
350 const SkColor kBlue = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 173, 167, 252));
351 const SkColor kYellow = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 255, 221, 117));
352 const SkColor kMagenta = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 255, 60, 217));
353 const SkColor kCyan = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 45, 237, 205));
354
355 int widthHeight = kTileWidthHeight + (padWithRed ? 2 * kSubsetPadding : 0);
356
357 SkImageInfo ii = SkImageInfo::Make(widthHeight, widthHeight,
358 colorType, kPremul_SkAlphaType);
359
360 SkBitmap bm;
361 bm.allocPixels(ii);
362
363 std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(ii,
364 bm.getPixels(),
365 bm.rowBytes());
366 if (padWithRed) {
367 canvas->clear(SK_ColorRED);
368 canvas->translate(kSubsetPadding, kSubsetPadding);
369 canvas->clipRect(SkRect::MakeWH(kTileWidthHeight, kTileWidthHeight));
370 }
371 canvas->clear(opaque ? kGreen : SK_ColorTRANSPARENT);
372
373 SkPaint paint;
374 paint.setAntiAlias(false); // serialize-8888 doesn't seem to work well w/ partial transparency
375 paint.setColor(kBlue);
376
377 canvas->drawPath(path, paint);
378
379 paint.setBlendMode(SkBlendMode::kSrc);
380 for (int i = 0; i < circles.size(); ++i) {
381 SkColor color;
382 switch (i % 3) {
383 case 0: color = kYellow; break;
384 case 1: color = kMagenta; break;
385 default: color = kCyan; break;
386 }
387 paint.setColor(color);
388 paint.setAlpha(opaque ? 0xFF : 0x40);
389 SkRect r = circles[i];
390 r.inset(r.width()/4, r.height()/4);
391 canvas->drawOval(r, paint);
392 }
393
394 return bm;
395 }
396
convert_rgba_to_yuva(const float mtx[20],SkColor col,uint8_t yuv[4])397 static void convert_rgba_to_yuva(const float mtx[20], SkColor col, uint8_t yuv[4]) {
398 const uint8_t r = SkColorGetR(col);
399 const uint8_t g = SkColorGetG(col);
400 const uint8_t b = SkColorGetB(col);
401
402 yuv[0] = SkTPin(SkScalarRoundToInt(mtx[ 0]*r + mtx[ 1]*g + mtx[ 2]*b + mtx[ 4]*255), 0, 255);
403 yuv[1] = SkTPin(SkScalarRoundToInt(mtx[ 5]*r + mtx[ 6]*g + mtx[ 7]*b + mtx[ 9]*255), 0, 255);
404 yuv[2] = SkTPin(SkScalarRoundToInt(mtx[10]*r + mtx[11]*g + mtx[12]*b + mtx[14]*255), 0, 255);
405 yuv[3] = SkColorGetA(col);
406 }
407
extract_planes(const SkBitmap & origBM,SkYUVColorSpace yuvColorSpace,SkEncodedOrigin origin,PlaneData * planes)408 static void extract_planes(const SkBitmap& origBM,
409 SkYUVColorSpace yuvColorSpace,
410 SkEncodedOrigin origin,
411 PlaneData* planes) {
412 SkImageInfo ii = origBM.info();
413 if (SkEncodedOriginSwapsWidthHeight(origin)) {
414 ii = ii.makeWH(ii.height(), ii.width());
415 }
416 SkBitmap orientedBM;
417 orientedBM.allocPixels(ii);
418 SkCanvas canvas(orientedBM);
419 SkMatrix matrix = SkEncodedOriginToMatrix(origin, origBM.width(), origBM.height());
420 SkAssertResult(matrix.invert(&matrix));
421 canvas.concat(matrix);
422 canvas.drawImage(origBM.asImage(), 0, 0);
423
424 if (yuvColorSpace == kIdentity_SkYUVColorSpace) {
425 // To test the identity color space we use JPEG YUV planes
426 yuvColorSpace = kJPEG_SkYUVColorSpace;
427 }
428
429 SkASSERT(!(ii.width() % 2));
430 SkASSERT(!(ii.height() % 2));
431 planes->fYFull.allocPixels(
432 SkImageInfo::Make(ii.dimensions(), kGray_8_SkColorType, kUnpremul_SkAlphaType));
433 planes->fUFull.allocPixels(
434 SkImageInfo::Make(ii.dimensions(), kGray_8_SkColorType, kUnpremul_SkAlphaType));
435 planes->fVFull.allocPixels(
436 SkImageInfo::Make(ii.dimensions(), kGray_8_SkColorType, kUnpremul_SkAlphaType));
437 planes->fAFull.allocPixels(SkImageInfo::MakeA8(ii.dimensions()));
438 planes->fUQuarter.allocPixels(SkImageInfo::Make(ii.width()/2, ii.height()/2,
439 kGray_8_SkColorType, kUnpremul_SkAlphaType));
440 planes->fVQuarter.allocPixels(SkImageInfo::Make(ii.width()/2, ii.height()/2,
441 kGray_8_SkColorType, kUnpremul_SkAlphaType));
442
443 planes->fFull.allocPixels(
444 SkImageInfo::Make(ii.dimensions(), kRGBA_F32_SkColorType, kUnpremul_SkAlphaType));
445 planes->fQuarter.allocPixels(SkImageInfo::Make(ii.width()/2, ii.height()/2,
446 kRGBA_F32_SkColorType, kUnpremul_SkAlphaType));
447
448 float mtx[20];
449 SkColorMatrix_RGB2YUV(yuvColorSpace, mtx);
450
451 SkColor4f* dst = (SkColor4f *) planes->fFull.getAddr(0, 0);
452 for (int y = 0; y < orientedBM.height(); ++y) {
453 for (int x = 0; x < orientedBM.width(); ++x) {
454 SkColor col = orientedBM.getColor(x, y);
455
456 uint8_t yuva[4];
457
458 convert_rgba_to_yuva(mtx, col, yuva);
459
460 *planes->fYFull.getAddr8(x, y) = yuva[0];
461 *planes->fUFull.getAddr8(x, y) = yuva[1];
462 *planes->fVFull.getAddr8(x, y) = yuva[2];
463 *planes->fAFull.getAddr8(x, y) = yuva[3];
464
465 // TODO: render in F32 rather than converting here
466 dst->fR = yuva[0] / 255.0f;
467 dst->fG = yuva[1] / 255.0f;
468 dst->fB = yuva[2] / 255.0f;
469 dst->fA = yuva[3] / 255.0f;
470 ++dst;
471 }
472 }
473
474 dst = (SkColor4f *) planes->fQuarter.getAddr(0, 0);
475 for (int y = 0; y < orientedBM.height()/2; ++y) {
476 for (int x = 0; x < orientedBM.width()/2; ++x) {
477 uint32_t yAccum = 0, uAccum = 0, vAccum = 0, aAccum = 0;
478
479 yAccum += *planes->fYFull.getAddr8(2*x, 2*y);
480 yAccum += *planes->fYFull.getAddr8(2*x+1, 2*y);
481 yAccum += *planes->fYFull.getAddr8(2*x, 2*y+1);
482 yAccum += *planes->fYFull.getAddr8(2*x+1, 2*y+1);
483
484 uAccum += *planes->fUFull.getAddr8(2*x, 2*y);
485 uAccum += *planes->fUFull.getAddr8(2*x+1, 2*y);
486 uAccum += *planes->fUFull.getAddr8(2*x, 2*y+1);
487 uAccum += *planes->fUFull.getAddr8(2*x+1, 2*y+1);
488
489 *planes->fUQuarter.getAddr8(x, y) = uAccum / 4.0f;
490
491 vAccum += *planes->fVFull.getAddr8(2*x, 2*y);
492 vAccum += *planes->fVFull.getAddr8(2*x+1, 2*y);
493 vAccum += *planes->fVFull.getAddr8(2*x, 2*y+1);
494 vAccum += *planes->fVFull.getAddr8(2*x+1, 2*y+1);
495
496 *planes->fVQuarter.getAddr8(x, y) = vAccum / 4.0f;
497
498 aAccum += *planes->fAFull.getAddr8(2*x, 2*y);
499 aAccum += *planes->fAFull.getAddr8(2*x+1, 2*y);
500 aAccum += *planes->fAFull.getAddr8(2*x, 2*y+1);
501 aAccum += *planes->fAFull.getAddr8(2*x+1, 2*y+1);
502
503 // TODO: render in F32 rather than converting here
504 dst->fR = yAccum / (4.0f * 255.0f);
505 dst->fG = uAccum / (4.0f * 255.0f);
506 dst->fB = vAccum / (4.0f * 255.0f);
507 dst->fA = aAccum / (4.0f * 255.0f);
508 ++dst;
509 }
510 }
511 }
512
513 // Create a 2x2 downsampled SkBitmap. It is stored in an RG texture. It can optionally be
514 // uv (i.e., NV12) or vu (i.e., NV21).
make_quarter_2_channel(const SkBitmap & fullY,const SkBitmap & quarterU,const SkBitmap & quarterV,bool uv)515 static SkBitmap make_quarter_2_channel(const SkBitmap& fullY,
516 const SkBitmap& quarterU,
517 const SkBitmap& quarterV,
518 bool uv) {
519 SkBitmap result;
520
521 result.allocPixels(SkImageInfo::Make(fullY.width()/2,
522 fullY.height()/2,
523 kR8G8_unorm_SkColorType,
524 kUnpremul_SkAlphaType));
525
526 for (int y = 0; y < fullY.height()/2; ++y) {
527 for (int x = 0; x < fullY.width()/2; ++x) {
528 uint8_t u8 = *quarterU.getAddr8(x, y);
529 uint8_t v8 = *quarterV.getAddr8(x, y);
530
531 if (uv) {
532 *result.getAddr16(x, y) = (v8 << 8) | u8;
533 } else {
534 *result.getAddr16(x, y) = (u8 << 8) | v8;
535 }
536 }
537 }
538
539 return result;
540 }
541
542 // Create some flavor of a 16bits/channel bitmap from a RGBA_F32 source
make_16(const SkBitmap & src,SkColorType dstCT,std::function<void (uint16_t * dstPixel,const float * srcPixel)> convert)543 static SkBitmap make_16(const SkBitmap& src, SkColorType dstCT,
544 std::function<void(uint16_t* dstPixel, const float* srcPixel)> convert) {
545 SkASSERT(src.colorType() == kRGBA_F32_SkColorType);
546
547 SkBitmap result;
548
549 result.allocPixels(SkImageInfo::Make(src.dimensions(), dstCT, kUnpremul_SkAlphaType));
550
551 for (int y = 0; y < src.height(); ++y) {
552 for (int x = 0; x < src.width(); ++x) {
553 const float* srcPixel = (const float*) src.getAddr(x, y);
554 uint16_t* dstPixel = (uint16_t*) result.getAddr(x, y);
555
556 convert(dstPixel, srcPixel);
557 }
558 }
559
560 return result;
561 }
562
flt_2_uint16(float flt)563 static uint16_t flt_2_uint16(float flt) { return SkScalarRoundToInt(flt * 65535.0f); }
564
565 // Recombine the separate planes into some YUV format. Returns the number of planes.
create_YUV(const PlaneData & planes,YUVFormat yuvFormat,SkBitmap resultBMs[],bool opaque)566 static int create_YUV(const PlaneData& planes,
567 YUVFormat yuvFormat,
568 SkBitmap resultBMs[],
569 bool opaque) {
570 int nextLayer = 0;
571
572 switch (yuvFormat) {
573 case kY416_YUVFormat: {
574 resultBMs[nextLayer++] = make_16(planes.fFull, kR16G16B16A16_unorm_SkColorType,
575 [] (uint16_t* dstPixel, const float* srcPixel) {
576 dstPixel[0] = flt_2_uint16(srcPixel[1]); // U
577 dstPixel[1] = flt_2_uint16(srcPixel[0]); // Y
578 dstPixel[2] = flt_2_uint16(srcPixel[2]); // V
579 dstPixel[3] = flt_2_uint16(srcPixel[3]); // A
580 });
581 break;
582 }
583 case kAYUV_YUVFormat: {
584 SkBitmap yuvaFull;
585
586 yuvaFull.allocPixels(SkImageInfo::Make(planes.fYFull.width(), planes.fYFull.height(),
587 kRGBA_8888_SkColorType, kUnpremul_SkAlphaType));
588
589 for (int y = 0; y < planes.fYFull.height(); ++y) {
590 for (int x = 0; x < planes.fYFull.width(); ++x) {
591
592 uint8_t Y = *planes.fYFull.getAddr8(x, y);
593 uint8_t U = *planes.fUFull.getAddr8(x, y);
594 uint8_t V = *planes.fVFull.getAddr8(x, y);
595 uint8_t A = *planes.fAFull.getAddr8(x, y);
596
597 // NOT premul!
598 // V and Y swapped to match RGBA layout
599 SkColor c = SkColorSetARGB(A, V, U, Y);
600 *yuvaFull.getAddr32(x, y) = c;
601 }
602 }
603
604 resultBMs[nextLayer++] = yuvaFull;
605 break;
606 }
607 case kY410_YUVFormat: {
608 SkBitmap yuvaFull;
609 uint32_t Y, U, V;
610 uint8_t A;
611
612 yuvaFull.allocPixels(SkImageInfo::Make(planes.fYFull.width(), planes.fYFull.height(),
613 kRGBA_1010102_SkColorType,
614 kUnpremul_SkAlphaType));
615
616 for (int y = 0; y < planes.fYFull.height(); ++y) {
617 for (int x = 0; x < planes.fYFull.width(); ++x) {
618
619 Y = SkScalarRoundToInt((*planes.fYFull.getAddr8(x, y) / 255.0f) * 1023.0f);
620 U = SkScalarRoundToInt((*planes.fUFull.getAddr8(x, y) / 255.0f) * 1023.0f);
621 V = SkScalarRoundToInt((*planes.fVFull.getAddr8(x, y) / 255.0f) * 1023.0f);
622 A = SkScalarRoundToInt((*planes.fAFull.getAddr8(x, y) / 255.0f) * 3.0f);
623
624 // NOT premul!
625 *yuvaFull.getAddr32(x, y) = (A << 30) | (V << 20) | (Y << 10) | (U << 0);
626 }
627 }
628
629 resultBMs[nextLayer++] = yuvaFull;
630 break;
631 }
632 case kP016_YUVFormat: // fall through
633 case kP010_YUVFormat: {
634 resultBMs[nextLayer++] = make_16(planes.fFull, kA16_unorm_SkColorType,
635 [tenBitsPP = (yuvFormat == kP010_YUVFormat)]
636 (uint16_t* dstPixel, const float* srcPixel) {
637 uint16_t val16 = flt_2_uint16(srcPixel[0]);
638 dstPixel[0] = tenBitsPP ? (val16 & 0xFFC0)
639 : val16;
640 });
641 resultBMs[nextLayer++] = make_16(planes.fQuarter, kR16G16_unorm_SkColorType,
642 [tenBitsPP = (yuvFormat == kP010_YUVFormat)]
643 (uint16_t* dstPixel, const float* srcPixel) {
644 uint16_t u16 = flt_2_uint16(srcPixel[1]);
645 uint16_t v16 = flt_2_uint16(srcPixel[2]);
646 dstPixel[0] = tenBitsPP ? (u16 & 0xFFC0) : u16;
647 dstPixel[1] = tenBitsPP ? (v16 & 0xFFC0) : v16;
648 });
649 if (!opaque) {
650 resultBMs[nextLayer++] = make_16(planes.fFull, kA16_unorm_SkColorType,
651 [tenBitsPP = (yuvFormat == kP010_YUVFormat)]
652 (uint16_t* dstPixel, const float* srcPixel) {
653 uint16_t val16 = flt_2_uint16(srcPixel[3]);
654 dstPixel[0] = tenBitsPP ? (val16 & 0xFFC0)
655 : val16;
656 });
657 }
658 return nextLayer;
659 }
660 case kP016F_YUVFormat: {
661 resultBMs[nextLayer++] = make_16(planes.fFull, kA16_float_SkColorType,
662 [] (uint16_t* dstPixel, const float* srcPixel) {
663 dstPixel[0] = SkFloatToHalf(srcPixel[0]);
664 });
665 resultBMs[nextLayer++] = make_16(planes.fQuarter, kR16G16_float_SkColorType,
666 [] (uint16_t* dstPixel, const float* srcPixel) {
667 dstPixel[0] = SkFloatToHalf(srcPixel[1]);
668 dstPixel[1] = SkFloatToHalf(srcPixel[2]);
669 });
670 if (!opaque) {
671 resultBMs[nextLayer++] = make_16(planes.fFull, kA16_float_SkColorType,
672 [] (uint16_t* dstPixel, const float* srcPixel) {
673 dstPixel[0] = SkFloatToHalf(srcPixel[3]);
674 });
675 }
676 return nextLayer;
677 }
678 case kNV12_YUVFormat: {
679 SkBitmap uvQuarter = make_quarter_2_channel(planes.fYFull,
680 planes.fUQuarter,
681 planes.fVQuarter, true);
682 resultBMs[nextLayer++] = planes.fYFull;
683 resultBMs[nextLayer++] = uvQuarter;
684 break;
685 }
686 case kNV21_YUVFormat: {
687 SkBitmap vuQuarter = make_quarter_2_channel(planes.fYFull,
688 planes.fUQuarter,
689 planes.fVQuarter, false);
690 resultBMs[nextLayer++] = planes.fYFull;
691 resultBMs[nextLayer++] = vuQuarter;
692 break;
693 }
694 case kI420_YUVFormat:
695 resultBMs[nextLayer++] = planes.fYFull;
696 resultBMs[nextLayer++] = planes.fUQuarter;
697 resultBMs[nextLayer++] = planes.fVQuarter;
698 break;
699 case kYV12_YUVFormat:
700 resultBMs[nextLayer++] = planes.fYFull;
701 resultBMs[nextLayer++] = planes.fVQuarter;
702 resultBMs[nextLayer++] = planes.fUQuarter;
703 break;
704 }
705
706 if (!opaque && !has_alpha_channel(yuvFormat)) {
707 resultBMs[nextLayer++] = planes.fAFull;
708 }
709 return nextLayer;
710 }
711
draw_col_label(SkCanvas * canvas,int x,int yuvColorSpace,bool opaque)712 static void draw_col_label(SkCanvas* canvas, int x, int yuvColorSpace, bool opaque) {
713 static const char* kYUVColorSpaceNames[] = {"JPEG", "601", "709F", "709L",
714 "2020_8F", "2020_8L", "2020_10L", "2020_12F",
715 "FCCL", "SMPTE240L","YDZDXL", "GBRL",
716 "YCGCO_8F", "YCGCO_8L", "YCGCO_10F","YCGCO_12F",
717 "YCGCO_12L","Identity"};
718 static_assert(std::size(kYUVColorSpaceNames) == std::size(color_space_array));
719
720 SkPaint paint;
721 SkFont font(ToolUtils::CreatePortableTypeface("Sans", SkFontStyle::Bold()), 16);
722 font.setEdging(SkFont::Edging::kAlias);
723
724 SkRect textRect;
725 SkString colLabel;
726
727 colLabel.printf("%s", kYUVColorSpaceNames[yuvColorSpace]);
728 font.measureText(colLabel.c_str(), colLabel.size(), SkTextEncoding::kUTF8, &textRect);
729 int y = textRect.height();
730
731 SkTextUtils::DrawString(canvas, colLabel.c_str(), x, y, font, paint, SkTextUtils::kCenter_Align);
732
733 colLabel.printf("%s", opaque ? "Opaque" : "Transparent");
734
735 font.measureText(colLabel.c_str(), colLabel.size(), SkTextEncoding::kUTF8, &textRect);
736 y += textRect.height();
737
738 SkTextUtils::DrawString(canvas, colLabel.c_str(), x, y, font, paint, SkTextUtils::kCenter_Align);
739 }
740
draw_row_label(SkCanvas * canvas,int y,int yuvFormat)741 static void draw_row_label(SkCanvas* canvas, int y, int yuvFormat) {
742 static const char* kYUVFormatNames[] = {
743 "P016", "P010", "P016F", "Y416", "AYUV", "Y410", "NV12", "NV21", "I420", "YV12"
744 };
745 static_assert(std::size(kYUVFormatNames) == kLast_YUVFormat + 1);
746
747 SkPaint paint;
748 SkFont font(ToolUtils::CreatePortableTypeface("Sans", SkFontStyle::Bold()), 16);
749 font.setEdging(SkFont::Edging::kAlias);
750
751 SkRect textRect;
752 SkString rowLabel;
753
754 rowLabel.printf("%s", kYUVFormatNames[yuvFormat]);
755 font.measureText(rowLabel.c_str(), rowLabel.size(), SkTextEncoding::kUTF8, &textRect);
756 y += kTileWidthHeight/2 + textRect.height()/2;
757
758 canvas->drawString(rowLabel, 0, y, font, paint);
759 }
760
yuv_to_rgb_colorfilter()761 static sk_sp<SkColorFilter> yuv_to_rgb_colorfilter() {
762 static const float kJPEGConversionMatrix[20] = {
763 1.0f, 0.0f, 1.402f, 0.0f, -180.0f/255,
764 1.0f, -0.344136f, -0.714136f, 0.0f, 136.0f/255,
765 1.0f, 1.772f, 0.0f, 0.0f, -227.6f/255,
766 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
767 };
768
769 return SkColorFilters::Matrix(kJPEGConversionMatrix);
770 }
771
772 namespace skiagm {
773
774 // This GM creates an opaque and transparent bitmap, extracts the planes and then recombines
775 // them into various YUV formats. It then renders the results in the grid:
776 //
777 // JPEG 601 709 Identity
778 // Transparent Opaque Transparent Opaque Transparent Opaque Transparent Opaque
779 // originals
780 // P016
781 // P010
782 // P016F
783 // Y416
784 // AYUV
785 // Y410
786 // NV12
787 // NV21
788 // I420
789 // YV12
790 class WackyYUVFormatsGM : public GM {
791 public:
792 using Type = sk_gpu_test::LazyYUVImage::Type;
793
WackyYUVFormatsGM(bool useTargetColorSpace,bool useSubset,bool useCubicSampling,Type type)794 WackyYUVFormatsGM(bool useTargetColorSpace, bool useSubset, bool useCubicSampling, Type type)
795 : fUseTargetColorSpace(useTargetColorSpace)
796 , fUseSubset(useSubset)
797 , fUseCubicSampling(useCubicSampling)
798 , fImageType(type) {
799 this->setBGColor(0xFFCCCCCC);
800 }
801
802 protected:
getName() const803 SkString getName() const override {
804 SkString name("wacky_yuv_formats");
805 if (fUseTargetColorSpace) {
806 name += "_cs";
807 }
808 if (fUseSubset) {
809 name += "_domain";
810 }
811 if (fUseCubicSampling) {
812 name += "_cubic";
813 }
814 switch (fImageType) {
815 case Type::kFromPixmaps:
816 name += "_frompixmaps";
817 break;
818 case Type::kFromTextures:
819 break;
820 case Type::kFromGenerator:
821 name += "_imggen";
822 break;
823 case Type::kFromImages:
824 name += "_fromimages";
825 break;
826 }
827
828 return name;
829 }
830
getISize()831 SkISize getISize() override {
832 int numCols = 2 * (std::size(color_space_array)); // opacity x #-color-spaces
833 int numRows = 1 + (kLast_YUVFormat + 1); // original + #-yuv-formats
834 int wh = SkScalarCeilToInt(kTileWidthHeight * (fUseSubset ? 1.5f : 1.f));
835 return SkISize::Make(kLabelWidth + numCols * (wh + kPad),
836 kLabelHeight + numRows * (wh + kPad));
837 }
838
createBitmaps()839 void createBitmaps() {
840 SkPoint origin = { kTileWidthHeight/2.0f, kTileWidthHeight/2.0f };
841 float outerRadius = kTileWidthHeight/2.0f - 20.0f;
842 float innerRadius = 20.0f;
843
844 {
845 // transparent
846 SkTDArray<SkRect> circles;
847 SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
848 fOriginalBMs[0] = make_bitmap(kRGBA_8888_SkColorType, path, circles, false, fUseSubset);
849 }
850
851 {
852 // opaque
853 SkTDArray<SkRect> circles;
854 SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
855 fOriginalBMs[1] = make_bitmap(kRGBA_8888_SkColorType, path, circles, true, fUseSubset);
856 }
857
858 if (fUseTargetColorSpace) {
859 fTargetColorSpace = SkColorSpace::MakeSRGB()->makeColorSpin();
860 }
861 }
862
createImages(GrDirectContext * dContext,Recorder * recorder)863 bool createImages(GrDirectContext* dContext, Recorder* recorder) {
864 int origin = 0;
865 for (bool opaque : { false, true }) {
866 for (size_t cs = 0; cs < std::size(color_space_array); ++cs) {
867 PlaneData planes;
868 extract_planes(fOriginalBMs[opaque],
869 color_space_array[cs],
870 static_cast<SkEncodedOrigin>(origin + 1), // valid origins are 1...8
871 &planes);
872
873 for (int f = kP016_YUVFormat; f <= kLast_YUVFormat; ++f) {
874 auto format = static_cast<YUVFormat>(f);
875 SkBitmap resultBMs[4];
876
877 int numPlanes = create_YUV(planes, format, resultBMs, opaque);
878 const YUVAPlanarConfig planarConfig(format,
879 opaque,
880 static_cast<SkEncodedOrigin>(origin + 1));
881 SkYUVAPixmaps pixmaps =
882 planarConfig.makeYUVAPixmaps(fOriginalBMs[opaque].dimensions(),
883 color_space_array[cs],
884 resultBMs,
885 numPlanes);
886 auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(std::move(pixmaps));
887 #if defined(SK_GRAPHITE)
888 if (recorder) {
889 fImages[opaque][cs][format] = lazyYUV->refImage(recorder, fImageType);
890 } else
891 #endif
892 {
893 fImages[opaque][cs][format] = lazyYUV->refImage(dContext, fImageType);
894 }
895 }
896 origin = (origin + 1) % 8;
897 }
898 }
899
900 if (dContext) {
901 // Some backends (e.g., Vulkan) require all work be completed for backend textures
902 // before they are deleted. Since we don't know when we'll next have access to a
903 // direct context, flush all the work now.
904 dContext->flush();
905 dContext->submit(GrSyncCpu::kYes);
906 }
907
908 return true;
909 }
910
onGpuSetup(SkCanvas * canvas,SkString * errorMsg,GraphiteTestContext *)911 DrawResult onGpuSetup(SkCanvas* canvas, SkString* errorMsg, GraphiteTestContext*) override {
912 auto dContext = GrAsDirectContext(canvas->recordingContext());
913 auto recorder = canvas->recorder();
914 this->createBitmaps();
915
916 if (dContext && dContext->abandoned()) {
917 // This isn't a GpuGM so a null 'context' is okay but an abandoned context
918 // if forbidden.
919 return DrawResult::kSkip;
920 }
921
922 // Only the generator is expected to work with the CPU backend.
923 if (fImageType != Type::kFromGenerator && !dContext && !recorder) {
924 return DrawResult::kSkip;
925 }
926
927 if (!this->createImages(dContext, recorder)) {
928 *errorMsg = "Failed to create YUV images";
929 return DrawResult::kFail;
930 }
931
932 return DrawResult::kOk;
933 }
934
onGpuTeardown()935 void onGpuTeardown() override {
936 for (int i = 0; i < 2; ++i) {
937 for (size_t j = 0; j < std::size(color_space_array); ++j) {
938 for (int k = 0; k <= kLast_YUVFormat; ++k) {
939 fImages[i][j][k] = nullptr;
940 }
941 }
942 }
943 }
944
onDraw(SkCanvas * canvas)945 void onDraw(SkCanvas* canvas) override {
946 auto direct = GrAsDirectContext(canvas->recordingContext());
947 #if defined(SK_GRAPHITE)
948 auto recorder = canvas->recorder();
949 #endif
950
951 float cellWidth = kTileWidthHeight, cellHeight = kTileWidthHeight;
952 if (fUseSubset) {
953 cellWidth *= 1.5f;
954 cellHeight *= 1.5f;
955 }
956
957 SkRect srcRect = SkRect::Make(fOriginalBMs[0].dimensions());
958 SkRect dstRect = SkRect::MakeXYWH(kLabelWidth, 0.f, srcRect.width(), srcRect.height());
959
960 SkCanvas::SrcRectConstraint constraint = SkCanvas::kFast_SrcRectConstraint;
961 if (fUseSubset) {
962 srcRect.inset(kSubsetPadding, kSubsetPadding);
963 // Draw a larger rectangle to ensure bilerp filtering would normally read outside the
964 // srcRect and hit the red pixels, if strict constraint weren't used.
965 dstRect.fRight = kLabelWidth + 1.5f * srcRect.width();
966 dstRect.fBottom = 1.5f * srcRect.height();
967 constraint = SkCanvas::kStrict_SrcRectConstraint;
968 }
969
970 SkSamplingOptions sampling = fUseCubicSampling
971 ? SkSamplingOptions(SkCubicResampler::Mitchell())
972 : SkSamplingOptions(SkFilterMode::kLinear);
973 for (size_t cs = kJPEG_SkYUVColorSpace; cs < std::size(color_space_array); ++cs) {
974 SkPaint paint;
975 if (kIdentity_SkYUVColorSpace == color_space_array[cs]) {
976 // The identity color space needs post processing to appear correctly
977 paint.setColorFilter(yuv_to_rgb_colorfilter());
978 }
979
980 for (int opaque : { 0, 1 }) {
981 dstRect.offsetTo(dstRect.fLeft, kLabelHeight);
982
983 draw_col_label(canvas, dstRect.fLeft + cellWidth / 2, cs, opaque);
984
985 canvas->drawImageRect(fOriginalBMs[opaque].asImage(), srcRect, dstRect,
986 SkSamplingOptions(), nullptr, constraint);
987 dstRect.offset(0.f, cellHeight + kPad);
988
989 for (int format = kP016_YUVFormat; format <= kLast_YUVFormat; ++format) {
990 draw_row_label(canvas, dstRect.fTop, format);
991 if (fUseTargetColorSpace && fImages[opaque][cs][format]) {
992 // Making a CS-specific version of a kIdentity_SkYUVColorSpace YUV image
993 // doesn't make a whole lot of sense. The colorSpace conversion will
994 // operate on the YUV components rather than the RGB components.
995 sk_sp<SkImage> csImage;
996 #if defined(SK_GRAPHITE)
997 if (recorder) {
998 csImage = fImages[opaque][cs][format]->makeColorSpace(
999 recorder, fTargetColorSpace, {});
1000 } else
1001 #endif
1002 {
1003 csImage = fImages[opaque][cs][format]->makeColorSpace(
1004 direct, fTargetColorSpace);
1005 }
1006 canvas->drawImageRect(csImage, srcRect, dstRect, sampling,
1007 &paint, constraint);
1008 } else {
1009 canvas->drawImageRect(fImages[opaque][cs][format], srcRect, dstRect,
1010 sampling, &paint, constraint);
1011 }
1012 dstRect.offset(0.f, cellHeight + kPad);
1013 }
1014
1015 dstRect.offset(cellWidth + kPad, 0.f);
1016 }
1017 }
1018 }
1019
1020 private:
1021 SkBitmap fOriginalBMs[2];
1022 sk_sp<SkImage> fImages[2][std::size(color_space_array)][kLast_YUVFormat + 1];
1023 bool fUseTargetColorSpace;
1024 bool fUseSubset;
1025 bool fUseCubicSampling;
1026 Type fImageType;
1027 sk_sp<SkColorSpace> fTargetColorSpace;
1028
1029 using INHERITED = GM;
1030 };
1031
1032 //////////////////////////////////////////////////////////////////////////////
1033
1034 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1035 /*useSubset=*/false,
1036 /*useCubicSampling=*/false,
1037 WackyYUVFormatsGM::Type::kFromTextures);)
1038 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1039 /*useSubset=*/true,
1040 /*useCubicSampling=*/false,
1041 WackyYUVFormatsGM::Type::kFromTextures);)
1042 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/true,
1043 /*useSubset=*/false,
1044 /*useCubicSampling=*/false,
1045 WackyYUVFormatsGM::Type::kFromTextures);)
1046 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1047 /*useSubset=*/false,
1048 /*useCubicSampling=*/true,
1049 WackyYUVFormatsGM::Type::kFromTextures);)
1050 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1051 /*useSubset=*/false,
1052 /*useCubicSampling=*/false,
1053 WackyYUVFormatsGM::Type::kFromGenerator);)
1054 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1055 /*useSubset=*/false,
1056 /*useCubicSampling=*/false,
1057 WackyYUVFormatsGM::Type::kFromPixmaps);)
1058 #if defined(SK_GRAPHITE)
1059 DEF_GM(return new WackyYUVFormatsGM(/*useTargetColorSpace=*/false,
1060 /*useSubset=*/false,
1061 /*useCubicSampling=*/false,
1062 WackyYUVFormatsGM::Type::kFromImages);)
1063 #endif
1064
1065 class YUVMakeColorSpaceGM : public GM {
1066 public:
YUVMakeColorSpaceGM()1067 YUVMakeColorSpaceGM() {
1068 this->setBGColor(0xFFCCCCCC);
1069 }
1070
1071 protected:
getName() const1072 SkString getName() const override { return SkString("yuv_make_color_space"); }
1073
getISize()1074 SkISize getISize() override {
1075 int numCols = 4; // (transparent, opaque) x (untagged, tagged)
1076 int numRows = 5; // original, YUV, subset, makeNonTextureImage, readPixels
1077 return SkISize::Make(numCols * (kTileWidthHeight + kPad) + kPad,
1078 numRows * (kTileWidthHeight + kPad) + kPad);
1079 }
1080
createBitmaps()1081 void createBitmaps() {
1082 SkPoint origin = { kTileWidthHeight/2.0f, kTileWidthHeight/2.0f };
1083 float outerRadius = kTileWidthHeight/2.0f - 20.0f;
1084 float innerRadius = 20.0f;
1085
1086 {
1087 // transparent
1088 SkTDArray<SkRect> circles;
1089 SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
1090 fOriginalBMs[0] = make_bitmap(kN32_SkColorType, path, circles, false, false);
1091 }
1092
1093 {
1094 // opaque
1095 SkTDArray<SkRect> circles;
1096 SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
1097 fOriginalBMs[1] = make_bitmap(kN32_SkColorType, path, circles, true, false);
1098 }
1099
1100 fTargetColorSpace = SkColorSpace::MakeSRGB()->makeColorSpin();
1101 }
1102
createImages(GrDirectContext * context)1103 bool createImages(GrDirectContext* context) {
1104 for (bool opaque : { false, true }) {
1105 PlaneData planes;
1106 extract_planes(fOriginalBMs[opaque],
1107 kJPEG_SkYUVColorSpace,
1108 kTopLeft_SkEncodedOrigin,
1109 &planes);
1110
1111 SkBitmap resultBMs[4];
1112
1113 create_YUV(planes, kAYUV_YUVFormat, resultBMs, opaque);
1114
1115 YUVAPlanarConfig planarConfig(kAYUV_YUVFormat, opaque, kTopLeft_SkEncodedOrigin);
1116
1117 auto yuvaPixmaps = planarConfig.makeYUVAPixmaps(fOriginalBMs[opaque].dimensions(),
1118 kJPEG_Full_SkYUVColorSpace,
1119 resultBMs,
1120 std::size(resultBMs));
1121
1122 int i = 0;
1123 for (sk_sp<SkColorSpace> cs : {sk_sp<SkColorSpace>(nullptr),
1124 SkColorSpace::MakeSRGB()}) {
1125 auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(
1126 yuvaPixmaps, skgpu::Mipmapped::kNo, std::move(cs));
1127 fImages[opaque][i++] =
1128 lazyYUV->refImage(context, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
1129 }
1130 }
1131
1132 // Some backends (e.g., Vulkan) require all work be completed for backend textures before
1133 // they are deleted. Since we don't know when we'll next have access to a direct context,
1134 // flush all the work now.
1135 context->flush();
1136 context->submit(GrSyncCpu::kYes);
1137
1138 return true;
1139 }
1140
onGpuSetup(SkCanvas * canvas,SkString * errorMsg,GraphiteTestContext *)1141 DrawResult onGpuSetup(SkCanvas* canvas, SkString* errorMsg, GraphiteTestContext*) override {
1142 auto dContext = GrAsDirectContext(canvas->recordingContext());
1143 if (!dContext || dContext->abandoned()) {
1144 *errorMsg = "DirectContext required to create YUV images";
1145 return DrawResult::kSkip;
1146 }
1147
1148 this->createBitmaps();
1149 if (!this->createImages(dContext)) {
1150 *errorMsg = "Failed to create YUV images";
1151 return DrawResult::kFail;
1152 }
1153
1154 return DrawResult::kOk;
1155 }
1156
onGpuTeardown()1157 void onGpuTeardown() override {
1158 fImages[0][0] = fImages[0][1] = fImages[1][0] = fImages[1][1] = nullptr;
1159 }
1160
onDraw(SkCanvas * canvas,SkString * msg)1161 DrawResult onDraw(SkCanvas* canvas, SkString* msg) override {
1162 SkASSERT(fImages[0][0] && fImages[0][1] && fImages[1][0] && fImages[1][1]);
1163
1164 auto dContext = GrAsDirectContext(canvas->recordingContext());
1165 if (!dContext) {
1166 *msg = "YUV ColorSpace image creation requires a direct context.";
1167 return DrawResult::kSkip;
1168 }
1169
1170 int x = kPad;
1171 for (int tagged : { 0, 1 }) {
1172 for (int opaque : { 0, 1 }) {
1173 int y = kPad;
1174
1175 auto raster = fOriginalBMs[opaque].asImage()->makeColorSpace(
1176 nullptr, fTargetColorSpace);
1177 canvas->drawImage(raster, x, y);
1178 y += kTileWidthHeight + kPad;
1179
1180 if (fImages[opaque][tagged]) {
1181 auto yuv = fImages[opaque][tagged]->makeColorSpace(dContext, fTargetColorSpace);
1182 SkASSERT(yuv);
1183 SkASSERT(SkColorSpace::Equals(yuv->colorSpace(), fTargetColorSpace.get()));
1184 canvas->drawImage(yuv, x, y);
1185 y += kTileWidthHeight + kPad;
1186
1187 SkIRect bounds = SkIRect::MakeWH(kTileWidthHeight / 2, kTileWidthHeight / 2);
1188 auto subset = SkImages::SubsetTextureFrom(dContext, yuv.get(), bounds);
1189 SkASSERT(subset);
1190 canvas->drawImage(subset, x, y);
1191 y += kTileWidthHeight + kPad;
1192
1193 auto nonTexture = yuv->makeNonTextureImage();
1194 SkASSERT(nonTexture);
1195 canvas->drawImage(nonTexture, x, y);
1196 y += kTileWidthHeight + kPad;
1197
1198 SkBitmap readBack;
1199 readBack.allocPixels(yuv->imageInfo());
1200 SkAssertResult(yuv->readPixels(dContext, readBack.pixmap(), 0, 0));
1201 canvas->drawImage(readBack.asImage(), x, y);
1202 }
1203 x += kTileWidthHeight + kPad;
1204 }
1205 }
1206 return DrawResult::kOk;
1207 }
1208
1209 private:
1210 SkBitmap fOriginalBMs[2];
1211 sk_sp<SkImage> fImages[2][2];
1212 sk_sp<SkColorSpace> fTargetColorSpace;
1213
1214 using INHERITED = GM;
1215 };
1216
1217 DEF_GM(return new YUVMakeColorSpaceGM();)
1218
1219 } // namespace skiagm
1220
1221 ///////////////
1222
1223 #include "include/effects/SkColorMatrix.h"
1224 #include "src/core/SkAutoPixmapStorage.h"
1225 #include "tools/Resources.h"
1226
draw_diff(SkCanvas * canvas,SkScalar x,SkScalar y,const SkImage * a,const SkImage * b)1227 static void draw_diff(SkCanvas* canvas, SkScalar x, SkScalar y,
1228 const SkImage* a, const SkImage* b) {
1229 auto sh = SkShaders::Blend(SkBlendMode::kDifference,
1230 a->makeShader(SkSamplingOptions()),
1231 b->makeShader(SkSamplingOptions()));
1232 SkPaint paint;
1233 paint.setShader(sh);
1234 canvas->save();
1235 canvas->translate(x, y);
1236 canvas->drawRect(SkRect::MakeWH(a->width(), a->height()), paint);
1237
1238 SkColorMatrix cm;
1239 cm.setScale(64, 64, 64);
1240 paint.setShader(sh->makeWithColorFilter(SkColorFilters::Matrix(cm)));
1241 canvas->translate(0, a->height());
1242 canvas->drawRect(SkRect::MakeWH(a->width(), a->height()), paint);
1243
1244 canvas->restore();
1245 }
1246
1247 // Exercises SkColorMatrix_RGB2YUV for yuv colorspaces, showing the planes, and the
1248 // resulting (recombined) images (gpu only for now).
1249 //
1250 class YUVSplitterGM : public skiagm::GM {
1251 sk_sp<SkImage> fOrig;
1252
1253 public:
YUVSplitterGM()1254 YUVSplitterGM() {}
1255
1256 protected:
getName() const1257 SkString getName() const override { return SkString("yuv_splitter"); }
1258
getISize()1259 SkISize getISize() override { return SkISize::Make(1280, 768); }
1260
onOnceBeforeDraw()1261 void onOnceBeforeDraw() override {
1262 fOrig = ToolUtils::GetResourceAsImage("images/mandrill_256.png");
1263 }
1264
onDraw(SkCanvas * canvas)1265 void onDraw(SkCanvas* canvas) override {
1266 canvas->translate(fOrig->width(), 0);
1267 canvas->save();
1268 SkYUVAInfo info;
1269 std::array<sk_sp<SkImage>, SkYUVAInfo::kMaxPlanes> planes;
1270 for (auto cs : {kRec709_SkYUVColorSpace,
1271 kRec601_SkYUVColorSpace,
1272 kJPEG_SkYUVColorSpace,
1273 kBT2020_SkYUVColorSpace}) {
1274 std::tie(planes, info) = sk_gpu_test::MakeYUVAPlanesAsA8(fOrig.get(),
1275 cs,
1276 SkYUVAInfo::Subsampling::k444,
1277 /*recording context*/ nullptr);
1278 SkPixmap pixmaps[4];
1279 for (int i = 0; i < info.numPlanes(); ++i) {
1280 planes[i]->peekPixels(&pixmaps[i]);
1281 }
1282 auto yuvaPixmaps = SkYUVAPixmaps::FromExternalPixmaps(info, pixmaps);
1283 auto img = SkImages::TextureFromYUVAPixmaps(canvas->recordingContext(),
1284 yuvaPixmaps,
1285 skgpu::Mipmapped::kNo,
1286 /* limit to max tex size */ false,
1287 /* color space */ nullptr);
1288 if (img) {
1289 canvas->drawImage(img, 0, 0);
1290 draw_diff(canvas, 0, fOrig->height(), fOrig.get(), img.get());
1291 }
1292 canvas->translate(fOrig->width(), 0);
1293 }
1294 canvas->restore();
1295 canvas->translate(-fOrig->width(), 0);
1296 int y = 0;
1297 for (int i = 0; i < info.numPlanes(); ++i) {
1298 canvas->drawImage(planes[i], 0, y);
1299 y += planes[i]->height();
1300 }
1301 }
1302
1303 private:
1304 using INHERITED = GM;
1305 };
1306 DEF_GM( return new YUVSplitterGM; )
1307