1 /* 2 * Copyright 2015 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 // This test only works with the GPU backend. 9 10 #include "gm/gm.h" 11 #include "include/core/SkBitmap.h" 12 #include "include/core/SkCanvas.h" 13 #include "include/core/SkColor.h" 14 #include "include/core/SkColorFilter.h" 15 #include "include/core/SkImage.h" 16 #include "include/core/SkImageInfo.h" 17 #include "include/core/SkPaint.h" 18 #include "include/core/SkPixmap.h" 19 #include "include/core/SkRefCnt.h" 20 #include "include/core/SkScalar.h" 21 #include "include/core/SkSize.h" 22 #include "include/core/SkString.h" 23 #include "include/core/SkSurface.h" 24 #include "include/core/SkTypes.h" 25 #include "include/gpu/GrBackendSurface.h" 26 #include "include/gpu/GrDirectContext.h" 27 #include "include/gpu/GrTypes.h" 28 #include "include/private/SkTo.h" 29 #include "src/core/SkMathPriv.h" 30 #include "src/core/SkYUVMath.h" 31 #include "tools/Resources.h" 32 #include "tools/gpu/YUVUtils.h" 33 34 namespace skiagm { 35 class ImageFromYUVTextures : public GM { 36 public: ImageFromYUVTextures()37 ImageFromYUVTextures() { 38 this->setBGColor(0xFFFFFFFF); 39 } 40 41 protected: onShortName()42 SkString onShortName() override { 43 return SkString("image_from_yuv_textures"); 44 } 45 onISize()46 SkISize onISize() override { return {1420, 610}; } 47 CreatePlanes(const char * name)48 static std::unique_ptr<sk_gpu_test::LazyYUVImage> CreatePlanes(const char* name) { 49 SkBitmap bmp; 50 if (!GetResourceAsBitmap(name, &bmp)) { 51 return {}; 52 } 53 if (bmp.colorType() != kRGBA_8888_SkColorType) { 54 auto info = bmp.info().makeColorType(kRGBA_8888_SkColorType); 55 SkBitmap copy; 56 copy.allocPixels(info); 57 SkAssertResult(bmp.readPixels(copy.pixmap())); 58 bmp = copy; 59 } 60 SkYUVAPixmapInfo pixmapInfo({bmp.dimensions(), 61 SkYUVAInfo::PlaneConfig::kY_U_V_A, 62 SkYUVAInfo::Subsampling::k420, 63 kJPEG_Full_SkYUVColorSpace}, 64 SkYUVAPixmapInfo::DataType::kUnorm8, 65 nullptr); 66 auto pixmaps = SkYUVAPixmaps::Allocate(pixmapInfo); 67 68 unsigned char* yuvPixels[] = { 69 static_cast<unsigned char*>(pixmaps.planes()[0].writable_addr()), 70 static_cast<unsigned char*>(pixmaps.planes()[1].writable_addr()), 71 static_cast<unsigned char*>(pixmaps.planes()[2].writable_addr()), 72 static_cast<unsigned char*>(pixmaps.planes()[3].writable_addr()), 73 }; 74 75 float m[20]; 76 SkColorMatrix_RGB2YUV(pixmaps.yuvaInfo().yuvColorSpace(), m); 77 // Here we encode using the kJPEG_SkYUVColorSpace (i.e., full-swing Rec 601) even though 78 // we will draw it with all the supported yuv color spaces when converted back to RGB 79 for (int j = 0; j < pixmaps.planes()[0].height(); ++j) { 80 for (int i = 0; i < pixmaps.planes()[0].width(); ++i) { 81 auto rgba = *bmp.getAddr32(i, j); 82 auto r = (rgba & 0x000000ff) >> 0; 83 auto g = (rgba & 0x0000ff00) >> 8; 84 auto b = (rgba & 0x00ff0000) >> 16; 85 auto a = (rgba & 0xff000000) >> 24; 86 yuvPixels[0][j*pixmaps.planes()[0].width() + i] = SkToU8( 87 sk_float_round2int(m[0]*r + m[1]*g + m[2]*b + m[3]*a + 255*m[4])); 88 yuvPixels[3][j*pixmaps.planes()[0].width() + i] = SkToU8(sk_float_round2int( 89 m[15]*r + m[16]*g + m[17]*b + m[18]*a + 255*m[19])); 90 } 91 } 92 for (int j = 0; j < pixmaps.planes()[1].height(); ++j) { 93 for (int i = 0; i < pixmaps.planes()[1].width(); ++i) { 94 // Average together 4 pixels of RGB. 95 int rgba[] = {0, 0, 0, 0}; 96 int denom = 0; 97 int ylimit = std::min(2*j + 2, pixmaps.planes()[0].height()); 98 int xlimit = std::min(2*i + 2, pixmaps.planes()[0].width()); 99 for (int y = 2*j; y < ylimit; ++y) { 100 for (int x = 2*i; x < xlimit; ++x) { 101 auto src = *bmp.getAddr32(x, y); 102 rgba[0] += (src & 0x000000ff) >> 0; 103 rgba[1] += (src & 0x0000ff00) >> 8; 104 rgba[2] += (src & 0x00ff0000) >> 16; 105 rgba[3] += (src & 0xff000000) >> 24; 106 ++denom; 107 } 108 } 109 for (int c = 0; c < 4; ++c) { 110 rgba[c] /= denom; 111 } 112 int uvIndex = j*pixmaps.planes()[1].width() + i; 113 yuvPixels[1][uvIndex] = SkToU8(sk_float_round2int( 114 m[5]*rgba[0] + m[6]*rgba[1] + m[7]*rgba[2] + m[8]*rgba[3] + 255*m[9])); 115 yuvPixels[2][uvIndex] = SkToU8(sk_float_round2int( 116 m[10]*rgba[0] + m[11]*rgba[1] + m[12]*rgba[2] + m[13]*rgba[3] + 255*m[14])); 117 } 118 } 119 return sk_gpu_test::LazyYUVImage::Make(std::move(pixmaps)); 120 } 121 makeYUVAImage(GrDirectContext * context)122 sk_sp<SkImage> makeYUVAImage(GrDirectContext* context) { 123 return fLazyYUVImage->refImage(context, sk_gpu_test::LazyYUVImage::Type::kFromTextures); 124 } 125 createReferenceImage(GrDirectContext * dContext)126 sk_sp<SkImage> createReferenceImage(GrDirectContext* dContext) { 127 auto planarImage = this->makeYUVAImage(dContext); 128 if (!planarImage) { 129 return nullptr; 130 } 131 132 auto resultInfo = SkImageInfo::Make(fLazyYUVImage->dimensions(), 133 kRGBA_8888_SkColorType, 134 kPremul_SkAlphaType); 135 auto resultSurface = SkSurface::MakeRenderTarget(dContext, 136 SkBudgeted::kYes, 137 resultInfo, 138 1, 139 kTopLeft_GrSurfaceOrigin, 140 nullptr); 141 if (!resultSurface) { 142 return nullptr; 143 } 144 145 resultSurface->getCanvas()->drawImage(std::move(planarImage), 0, 0); 146 return resultSurface->makeImageSnapshot(); 147 } 148 onGpuSetup(GrDirectContext * dContext,SkString * errorMsg)149 DrawResult onGpuSetup(GrDirectContext* dContext, SkString* errorMsg) override { 150 if (!dContext || dContext->abandoned()) { 151 *errorMsg = "DirectContext required to create YUV images"; 152 return DrawResult::kSkip; 153 } 154 155 if (!fLazyYUVImage) { 156 fLazyYUVImage = CreatePlanes("images/mandrill_32.png"); 157 } 158 159 // We make a version of this image for each draw because, if any draw flattens it to 160 // RGBA, then all subsequent draws would use the RGBA texture. 161 for (int i = 0; i < kNumImages; ++i) { 162 fYUVAImages[i] = this->makeYUVAImage(dContext); 163 if (!fYUVAImages[i]) { 164 *errorMsg = "Couldn't create src YUVA image."; 165 return DrawResult::kFail; 166 } 167 } 168 169 fReferenceImage = this->createReferenceImage(dContext); 170 if (!fReferenceImage) { 171 *errorMsg = "Couldn't create reference YUVA image."; 172 return DrawResult::kFail; 173 } 174 175 // Some backends (e.g., Vulkan) require all work be completed for backend textures 176 // before they are deleted. Since we don't know when we'll next have access to a 177 // direct context, flush all the work now. 178 dContext->flush(); 179 dContext->submit(true); 180 181 return DrawResult::kOk; 182 } 183 onGpuTeardown()184 void onGpuTeardown() override { 185 for (sk_sp<SkImage>& image : fYUVAImages) { 186 image.reset(); 187 } 188 fReferenceImage.reset(); 189 } 190 getYUVAImage(int index)191 SkImage* getYUVAImage(int index) { 192 SkASSERT(index >= 0 && index < kNumImages); 193 return fYUVAImages[index].get(); 194 } 195 onDraw(SkCanvas * canvas)196 void onDraw(SkCanvas* canvas) override { 197 auto draw_image = [canvas](SkImage* image, const SkSamplingOptions& sampling) -> SkSize { 198 if (!image) { 199 return {0, 0}; 200 } 201 canvas->drawImage(image, 0, 0, sampling, nullptr); 202 return {SkIntToScalar(image->width()), SkIntToScalar(image->height())}; 203 }; 204 205 auto draw_image_rect = [canvas](SkImage* image, 206 const SkSamplingOptions& sampling) -> SkSize { 207 if (!image) { 208 return {0, 0}; 209 } 210 auto subset = SkRect::Make(image->dimensions()); 211 subset.inset(subset.width() * .05f, subset.height() * .1f); 212 auto dst = SkRect::MakeWH(subset.width(), subset.height()); 213 canvas->drawImageRect(image, subset, dst, sampling, nullptr, 214 SkCanvas::kStrict_SrcRectConstraint); 215 return {dst.width(), dst.height()}; 216 }; 217 218 auto draw_image_shader = [canvas](SkImage* image, 219 const SkSamplingOptions& sampling) -> SkSize { 220 if (!image) { 221 return {0, 0}; 222 } 223 SkMatrix m; 224 m.setRotate(45, image->width()/2.f, image->height()/2.f); 225 SkPaint paint; 226 paint.setShader(image->makeShader(SkTileMode::kMirror, SkTileMode::kDecal, 227 sampling, m)); 228 auto rect = SkRect::MakeWH(image->width() * 1.3f, image->height()); 229 canvas->drawRect(rect, paint); 230 return {rect.width(), rect.height()}; 231 }; 232 233 canvas->translate(kPad, kPad); 234 int imageIndex = 0; 235 using DrawSig = SkSize(SkImage* image, const SkSamplingOptions&); 236 using DF = std::function<DrawSig>; 237 for (const auto& draw : {DF(draw_image), DF(draw_image_rect), DF(draw_image_shader)}) { 238 for (auto scale : {1.f, 4.f, 0.75f}) { 239 SkScalar h = 0; 240 canvas->save(); 241 for (const auto& sampling : { 242 SkSamplingOptions(SkFilterMode::kNearest), 243 SkSamplingOptions(SkFilterMode::kLinear), 244 SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest), 245 SkSamplingOptions(SkCubicResampler::Mitchell())}) 246 { 247 canvas->save(); 248 canvas->scale(scale, scale); 249 auto s1 = draw(this->getYUVAImage(imageIndex++), sampling); 250 canvas->restore(); 251 canvas->translate(kPad + SkScalarCeilToScalar(scale*s1.width()), 0); 252 canvas->save(); 253 canvas->scale(scale, scale); 254 auto s2 = draw(fReferenceImage.get(), sampling); 255 canvas->restore(); 256 canvas->translate(kPad + SkScalarCeilToScalar(scale*s2.width()), 0); 257 h = std::max({h, s1.height(), s2.height()}); 258 } 259 canvas->restore(); 260 canvas->translate(0, kPad + SkScalarCeilToScalar(scale*h)); 261 } 262 } 263 } 264 265 private: 266 std::unique_ptr<sk_gpu_test::LazyYUVImage> fLazyYUVImage; 267 268 // 3 draws x 3 scales x 4 filter qualities 269 inline static constexpr int kNumImages = 3 * 3 * 4; 270 sk_sp<SkImage> fYUVAImages[kNumImages]; 271 sk_sp<SkImage> fReferenceImage; 272 273 inline static constexpr SkScalar kPad = 10.0f; 274 275 using INHERITED = GM; 276 }; 277 278 DEF_GM(return new ImageFromYUVTextures;) 279 } // namespace skiagm 280