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