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