1 /* 2 * Copyright 2011 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 "SkCanvas.h" 9 #include "SkColorData.h" 10 #include "SkImageInfoPriv.h" 11 #include "SkMathPriv.h" 12 #include "SkSurface.h" 13 #include "Test.h" 14 #include "sk_tool_utils.h" 15 16 #include "GrBackendSurface.h" 17 #include "GrContext.h" 18 #include "GrContextPriv.h" 19 #include "GrGpu.h" 20 #include "GrProxyProvider.h" 21 22 #include <initializer_list> 23 24 static const int DEV_W = 100, DEV_H = 100; 25 static const SkIRect DEV_RECT = SkIRect::MakeWH(DEV_W, DEV_H); 26 static const U8CPU DEV_PAD = 0xee; 27 28 static SkPMColor get_canvas_color(int x, int y) { 29 SkASSERT(x >= 0 && x < DEV_W); 30 SkASSERT(y >= 0 && y < DEV_H); 31 32 U8CPU r = x; 33 U8CPU g = y; 34 U8CPU b = 0xc; 35 36 U8CPU a = 0x0; 37 switch ((x+y) % 5) { 38 case 0: 39 a = 0xff; 40 break; 41 case 1: 42 a = 0x80; 43 break; 44 case 2: 45 a = 0xCC; 46 break; 47 case 3: 48 a = 0x00; 49 break; 50 case 4: 51 a = 0x01; 52 break; 53 } 54 return SkPremultiplyARGBInline(a, r, g, b); 55 } 56 57 // assumes any premu/.unpremul has been applied 58 static uint32_t pack_color_type(SkColorType ct, U8CPU a, U8CPU r, U8CPU g, U8CPU b) { 59 uint32_t r32; 60 uint8_t* result = reinterpret_cast<uint8_t*>(&r32); 61 switch (ct) { 62 case kBGRA_8888_SkColorType: 63 result[0] = b; 64 result[1] = g; 65 result[2] = r; 66 result[3] = a; 67 break; 68 case kRGBA_8888_SkColorType: // fallthrough 69 case kRGB_888x_SkColorType: 70 result[0] = r; 71 result[1] = g; 72 result[2] = b; 73 result[3] = a; 74 break; 75 default: 76 SkASSERT(0); 77 return 0; 78 } 79 return r32; 80 } 81 82 static uint32_t get_bitmap_color(int x, int y, int w, SkColorType ct, SkAlphaType at) { 83 int n = y * w + x; 84 U8CPU b = n & 0xff; 85 U8CPU g = (n >> 8) & 0xff; 86 U8CPU r = (n >> 16) & 0xff; 87 U8CPU a = 0; 88 switch ((x+y) % 5) { 89 case 4: 90 a = 0xff; 91 break; 92 case 3: 93 a = 0x80; 94 break; 95 case 2: 96 a = 0xCC; 97 break; 98 case 1: 99 a = 0x01; 100 break; 101 case 0: 102 a = 0x00; 103 break; 104 } 105 if (kPremul_SkAlphaType == at) { 106 r = SkMulDiv255Ceiling(r, a); 107 g = SkMulDiv255Ceiling(g, a); 108 b = SkMulDiv255Ceiling(b, a); 109 } 110 return pack_color_type(ct, a, r, g , b); 111 } 112 113 static void fill_surface(SkSurface* surface) { 114 SkBitmap bmp; 115 bmp.allocN32Pixels(DEV_W, DEV_H); 116 for (int y = 0; y < DEV_H; ++y) { 117 for (int x = 0; x < DEV_W; ++x) { 118 *bmp.getAddr32(x, y) = get_canvas_color(x, y); 119 } 120 } 121 surface->writePixels(bmp, 0, 0); 122 } 123 124 /** 125 * Lucky for us, alpha is always in the same spot (SK_A32_SHIFT), for both RGBA and BGRA. 126 * Thus this routine doesn't need to know the exact colortype 127 */ 128 static uint32_t premul(uint32_t color) { 129 unsigned a = SkGetPackedA32(color); 130 // these next three are not necessarily r,g,b in that order, but they are r,g,b in some order. 131 unsigned c0 = SkGetPackedR32(color); 132 unsigned c1 = SkGetPackedG32(color); 133 unsigned c2 = SkGetPackedB32(color); 134 c0 = SkMulDiv255Ceiling(c0, a); 135 c1 = SkMulDiv255Ceiling(c1, a); 136 c2 = SkMulDiv255Ceiling(c2, a); 137 return SkPackARGB32NoCheck(a, c0, c1, c2); 138 } 139 140 static SkPMColor convert_to_PMColor(SkColorType ct, SkAlphaType at, uint32_t color) { 141 if (kUnpremul_SkAlphaType == at) { 142 color = premul(color); 143 } 144 switch (ct) { 145 case kRGBA_8888_SkColorType: 146 case kRGB_888x_SkColorType: // fallthrough 147 color = SkSwizzle_RGBA_to_PMColor(color); 148 break; 149 case kBGRA_8888_SkColorType: 150 color = SkSwizzle_BGRA_to_PMColor(color); 151 break; 152 default: 153 SkASSERT(0); 154 break; 155 } 156 return color; 157 } 158 159 static bool check_pixel(SkPMColor a, SkPMColor b, bool didPremulConversion) { 160 if (!didPremulConversion) { 161 return a == b; 162 } 163 int32_t aA = static_cast<int32_t>(SkGetPackedA32(a)); 164 int32_t aR = static_cast<int32_t>(SkGetPackedR32(a)); 165 int32_t aG = static_cast<int32_t>(SkGetPackedG32(a)); 166 int32_t aB = SkGetPackedB32(a); 167 168 int32_t bA = static_cast<int32_t>(SkGetPackedA32(b)); 169 int32_t bR = static_cast<int32_t>(SkGetPackedR32(b)); 170 int32_t bG = static_cast<int32_t>(SkGetPackedG32(b)); 171 int32_t bB = static_cast<int32_t>(SkGetPackedB32(b)); 172 173 return aA == bA && 174 SkAbs32(aR - bR) <= 1 && 175 SkAbs32(aG - bG) <= 1 && 176 SkAbs32(aB - bB) <= 1; 177 } 178 179 bool write_should_succeed(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo, bool isGPU) { 180 if (!SkImageInfoValidConversion(dstInfo, srcInfo)) { 181 return false; 182 } 183 if (!isGPU) { 184 return true; 185 } 186 // The GPU backend supports writing unpremul data to a premul dst but not vice versa. 187 if (srcInfo.alphaType() == kPremul_SkAlphaType && 188 dstInfo.alphaType() == kUnpremul_SkAlphaType) { 189 return false; 190 } 191 if (!SkColorTypeIsAlwaysOpaque(srcInfo.colorType()) && 192 SkColorTypeIsAlwaysOpaque(dstInfo.colorType())) { 193 return false; 194 } 195 // The source has no alpha value and the dst is only alpha 196 if (SkColorTypeIsAlwaysOpaque(srcInfo.colorType()) && 197 SkColorTypeIsAlphaOnly(dstInfo.colorType())) { 198 return false; 199 } 200 return true; 201 } 202 203 static bool check_write(skiatest::Reporter* reporter, SkSurface* surf, SkAlphaType surfaceAlphaType, 204 const SkBitmap& bitmap, int writeX, int writeY) { 205 size_t canvasRowBytes; 206 const uint32_t* canvasPixels; 207 208 // Can't use canvas->peekPixels(), as we are trying to look at GPU pixels sometimes as well. 209 // At some point this will be unsupported, as we won't allow accessBitmap() to magically call 210 // readPixels for the client. 211 SkBitmap secretDevBitmap; 212 secretDevBitmap.allocN32Pixels(surf->width(), surf->height()); 213 if (!surf->readPixels(secretDevBitmap, 0, 0)) { 214 return false; 215 } 216 217 canvasRowBytes = secretDevBitmap.rowBytes(); 218 canvasPixels = static_cast<const uint32_t*>(secretDevBitmap.getPixels()); 219 220 if (nullptr == canvasPixels) { 221 return false; 222 } 223 224 if (surf->width() != DEV_W || surf->height() != DEV_H) { 225 return false; 226 } 227 228 const SkImageInfo bmInfo = bitmap.info(); 229 230 SkIRect writeRect = SkIRect::MakeXYWH(writeX, writeY, bitmap.width(), bitmap.height()); 231 for (int cy = 0; cy < DEV_H; ++cy) { 232 for (int cx = 0; cx < DEV_W; ++cx) { 233 SkPMColor canvasPixel = canvasPixels[cx]; 234 if (writeRect.contains(cx, cy)) { 235 int bx = cx - writeX; 236 int by = cy - writeY; 237 uint32_t bmpColor8888 = get_bitmap_color(bx, by, bitmap.width(), 238 bmInfo.colorType(), bmInfo.alphaType()); 239 bool mul = (kUnpremul_SkAlphaType == bmInfo.alphaType()); 240 SkPMColor bmpPMColor = convert_to_PMColor(bmInfo.colorType(), bmInfo.alphaType(), 241 bmpColor8888); 242 if (bmInfo.alphaType() == kOpaque_SkAlphaType || 243 surfaceAlphaType == kOpaque_SkAlphaType) { 244 bmpPMColor |= 0xFF000000; 245 } 246 if (!check_pixel(bmpPMColor, canvasPixel, mul)) { 247 ERRORF(reporter, "Expected canvas pixel at %d, %d to be 0x%08x, got 0x%08x. " 248 "Write performed premul: %d", cx, cy, bmpPMColor, canvasPixel, mul); 249 return false; 250 } 251 } else { 252 SkPMColor testColor = get_canvas_color(cx, cy); 253 if (canvasPixel != testColor) { 254 ERRORF(reporter, "Canvas pixel outside write rect at %d, %d changed." 255 " Should be 0x%08x, got 0x%08x. ", cx, cy, testColor, canvasPixel); 256 return false; 257 } 258 } 259 } 260 if (cy != DEV_H -1) { 261 const char* pad = reinterpret_cast<const char*>(canvasPixels + DEV_W); 262 for (size_t px = 0; px < canvasRowBytes - 4 * DEV_W; ++px) { 263 bool check; 264 REPORTER_ASSERT(reporter, check = (pad[px] == static_cast<char>(DEV_PAD))); 265 if (!check) { 266 return false; 267 } 268 } 269 } 270 canvasPixels += canvasRowBytes/4; 271 } 272 273 return true; 274 } 275 276 #include "SkMallocPixelRef.h" 277 278 // This is a tricky pattern, because we have to setConfig+rowBytes AND specify 279 // a custom pixelRef (which also has to specify its rowBytes), so we have to be 280 // sure that the two rowBytes match (and the infos match). 281 // 282 static bool alloc_row_bytes(SkBitmap* bm, const SkImageInfo& info, size_t rowBytes) { 283 if (!bm->setInfo(info, rowBytes)) { 284 return false; 285 } 286 sk_sp<SkPixelRef> pr = SkMallocPixelRef::MakeAllocate(info, rowBytes); 287 bm->setPixelRef(std::move(pr), 0, 0); 288 return true; 289 } 290 291 static void free_pixels(void* pixels, void* ctx) { 292 sk_free(pixels); 293 } 294 295 static bool setup_bitmap(SkBitmap* bm, SkColorType ct, SkAlphaType at, int w, int h, int tightRB) { 296 size_t rowBytes = tightRB ? 0 : 4 * w + 60; 297 SkImageInfo info = SkImageInfo::Make(w, h, ct, at); 298 if (!alloc_row_bytes(bm, info, rowBytes)) { 299 return false; 300 } 301 for (int y = 0; y < h; ++y) { 302 for (int x = 0; x < w; ++x) { 303 *bm->getAddr32(x, y) = get_bitmap_color(x, y, w, ct, at); 304 } 305 } 306 return true; 307 } 308 309 static void call_writepixels(SkSurface* surface) { 310 const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1); 311 SkPMColor pixel = 0; 312 surface->writePixels({info, &pixel, sizeof(SkPMColor)}, 0, 0); 313 } 314 315 DEF_TEST(WritePixelsSurfaceGenID, reporter) { 316 const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100); 317 auto surface(SkSurface::MakeRaster(info)); 318 uint32_t genID1 = surface->generationID(); 319 call_writepixels(surface.get()); 320 uint32_t genID2 = surface->generationID(); 321 REPORTER_ASSERT(reporter, genID1 != genID2); 322 } 323 324 static void test_write_pixels(skiatest::Reporter* reporter, SkSurface* surface, 325 const SkImageInfo& surfaceInfo) { 326 const SkIRect testRects[] = { 327 // entire thing 328 DEV_RECT, 329 // larger on all sides 330 SkIRect::MakeLTRB(-10, -10, DEV_W + 10, DEV_H + 10), 331 // fully contained 332 SkIRect::MakeLTRB(DEV_W / 4, DEV_H / 4, 3 * DEV_W / 4, 3 * DEV_H / 4), 333 // outside top left 334 SkIRect::MakeLTRB(-10, -10, -1, -1), 335 // touching top left corner 336 SkIRect::MakeLTRB(-10, -10, 0, 0), 337 // overlapping top left corner 338 SkIRect::MakeLTRB(-10, -10, DEV_W / 4, DEV_H / 4), 339 // overlapping top left and top right corners 340 SkIRect::MakeLTRB(-10, -10, DEV_W + 10, DEV_H / 4), 341 // touching entire top edge 342 SkIRect::MakeLTRB(-10, -10, DEV_W + 10, 0), 343 // overlapping top right corner 344 SkIRect::MakeLTRB(3 * DEV_W / 4, -10, DEV_W + 10, DEV_H / 4), 345 // contained in x, overlapping top edge 346 SkIRect::MakeLTRB(DEV_W / 4, -10, 3 * DEV_W / 4, DEV_H / 4), 347 // outside top right corner 348 SkIRect::MakeLTRB(DEV_W + 1, -10, DEV_W + 10, -1), 349 // touching top right corner 350 SkIRect::MakeLTRB(DEV_W, -10, DEV_W + 10, 0), 351 // overlapping top left and bottom left corners 352 SkIRect::MakeLTRB(-10, -10, DEV_W / 4, DEV_H + 10), 353 // touching entire left edge 354 SkIRect::MakeLTRB(-10, -10, 0, DEV_H + 10), 355 // overlapping bottom left corner 356 SkIRect::MakeLTRB(-10, 3 * DEV_H / 4, DEV_W / 4, DEV_H + 10), 357 // contained in y, overlapping left edge 358 SkIRect::MakeLTRB(-10, DEV_H / 4, DEV_W / 4, 3 * DEV_H / 4), 359 // outside bottom left corner 360 SkIRect::MakeLTRB(-10, DEV_H + 1, -1, DEV_H + 10), 361 // touching bottom left corner 362 SkIRect::MakeLTRB(-10, DEV_H, 0, DEV_H + 10), 363 // overlapping bottom left and bottom right corners 364 SkIRect::MakeLTRB(-10, 3 * DEV_H / 4, DEV_W + 10, DEV_H + 10), 365 // touching entire left edge 366 SkIRect::MakeLTRB(0, DEV_H, DEV_W, DEV_H + 10), 367 // overlapping bottom right corner 368 SkIRect::MakeLTRB(3 * DEV_W / 4, 3 * DEV_H / 4, DEV_W + 10, DEV_H + 10), 369 // overlapping top right and bottom right corners 370 SkIRect::MakeLTRB(3 * DEV_W / 4, -10, DEV_W + 10, DEV_H + 10), 371 }; 372 373 SkCanvas* canvas = surface->getCanvas(); 374 375 static const struct { 376 SkColorType fColorType; 377 SkAlphaType fAlphaType; 378 } gSrcConfigs[] = { 379 {kRGBA_8888_SkColorType, kPremul_SkAlphaType}, 380 {kRGBA_8888_SkColorType, kUnpremul_SkAlphaType}, 381 {kRGB_888x_SkColorType, kOpaque_SkAlphaType}, 382 {kBGRA_8888_SkColorType, kPremul_SkAlphaType}, 383 {kBGRA_8888_SkColorType, kUnpremul_SkAlphaType}, 384 }; 385 for (size_t r = 0; r < SK_ARRAY_COUNT(testRects); ++r) { 386 const SkIRect& rect = testRects[r]; 387 for (int tightBmp = 0; tightBmp < 2; ++tightBmp) { 388 for (size_t c = 0; c < SK_ARRAY_COUNT(gSrcConfigs); ++c) { 389 const SkColorType ct = gSrcConfigs[c].fColorType; 390 const SkAlphaType at = gSrcConfigs[c].fAlphaType; 391 392 bool isGPU = SkToBool(surface->getCanvas()->getGrContext()); 393 fill_surface(surface); 394 SkBitmap bmp; 395 REPORTER_ASSERT(reporter, setup_bitmap(&bmp, ct, at, rect.width(), 396 rect.height(), SkToBool(tightBmp))); 397 uint32_t idBefore = surface->generationID(); 398 399 // sk_tool_utils::write_pixels(&canvas, bmp, rect.fLeft, rect.fTop, ct, at); 400 surface->writePixels(bmp, rect.fLeft, rect.fTop); 401 402 uint32_t idAfter = surface->generationID(); 403 REPORTER_ASSERT(reporter, check_write(reporter, surface, surfaceInfo.alphaType(), 404 bmp, rect.fLeft, rect.fTop)); 405 406 // we should change the genID iff pixels were actually written. 407 SkIRect canvasRect = SkIRect::MakeSize(canvas->getBaseLayerSize()); 408 SkIRect writeRect = SkIRect::MakeXYWH(rect.fLeft, rect.fTop, 409 bmp.width(), bmp.height()); 410 bool expectSuccess = SkIRect::Intersects(canvasRect, writeRect) && 411 write_should_succeed(surfaceInfo, bmp.info(), isGPU); 412 REPORTER_ASSERT(reporter, expectSuccess == (idBefore != idAfter)); 413 } 414 } 415 } 416 } 417 418 DEF_TEST(WritePixels, reporter) { 419 const SkImageInfo info = SkImageInfo::MakeN32Premul(DEV_W, DEV_H); 420 for (auto& tightRowBytes : { true, false }) { 421 const size_t rowBytes = tightRowBytes ? info.minRowBytes() : 4 * DEV_W + 100; 422 const size_t size = info.computeByteSize(rowBytes); 423 void* pixels = sk_malloc_throw(size); 424 // if rowBytes isn't tight then set the padding to a known value 425 if (!tightRowBytes) { 426 memset(pixels, DEV_PAD, size); 427 } 428 auto surface(SkSurface::MakeRasterDirectReleaseProc(info, pixels, rowBytes, 429 free_pixels, nullptr)); 430 test_write_pixels(reporter, surface.get(), info); 431 } 432 } 433 434 static void test_write_pixels(skiatest::Reporter* reporter, GrContext* context, int sampleCnt) { 435 const SkImageInfo ii = SkImageInfo::MakeN32Premul(DEV_W, DEV_H); 436 for (auto& origin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin }) { 437 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(context, 438 SkBudgeted::kNo, ii, sampleCnt, 439 origin, nullptr)); 440 if (surface) { 441 test_write_pixels(reporter, surface.get(), ii); 442 } 443 } 444 } 445 446 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(WritePixels_Gpu, reporter, ctxInfo) { 447 test_write_pixels(reporter, ctxInfo.grContext(), 1); 448 } 449 450 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(WritePixelsMSAA_Gpu, reporter, ctxInfo) { 451 test_write_pixels(reporter, ctxInfo.grContext(), 1); 452 } 453 454 static void test_write_pixels_non_texture(skiatest::Reporter* reporter, GrContext* context, 455 int sampleCnt) { 456 GrGpu* gpu = context->contextPriv().getGpu(); 457 458 for (auto& origin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin }) { 459 GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( 460 nullptr, DEV_W, DEV_H, GrColorType::kRGBA_8888, true, GrMipMapped::kNo); 461 if (!backendTex.isValid()) { 462 continue; 463 } 464 SkColorType colorType = kN32_SkColorType; 465 sk_sp<SkSurface> surface(SkSurface::MakeFromBackendTextureAsRenderTarget( 466 context, backendTex, origin, sampleCnt, colorType, nullptr, nullptr)); 467 if (surface) { 468 auto ii = SkImageInfo::MakeN32Premul(DEV_W, DEV_H); 469 test_write_pixels(reporter, surface.get(), ii); 470 } 471 gpu->deleteTestingOnlyBackendTexture(backendTex); 472 } 473 } 474 475 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(WritePixelsNonTexture_Gpu, reporter, ctxInfo) { 476 test_write_pixels_non_texture(reporter, ctxInfo.grContext(), 1); 477 } 478 479 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(WritePixelsNonTextureMSAA_Gpu, reporter, ctxInfo) { 480 test_write_pixels_non_texture(reporter, ctxInfo.grContext(), 4); 481 } 482 483 static sk_sp<SkSurface> create_surf(GrContext* context, int width, int height) { 484 const SkImageInfo ii = SkImageInfo::Make(width, height, 485 kRGBA_8888_SkColorType, kPremul_SkAlphaType); 486 487 sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, ii); 488 surf->flush(); 489 return surf; 490 } 491 492 static sk_sp<SkImage> upload(const sk_sp<SkSurface>& surf, SkColor color) { 493 const SkImageInfo smII = SkImageInfo::Make(16, 16, kRGBA_8888_SkColorType, kPremul_SkAlphaType); 494 SkBitmap bm; 495 bm.allocPixels(smII); 496 bm.eraseColor(color); 497 498 surf->writePixels(bm, 0, 0); 499 500 return surf->makeImageSnapshot(); 501 } 502 503 // This is tests whether the first writePixels is completed before the 504 // second writePixels takes effect (i.e., that writePixels correctly flushes 505 // in between uses of the shared backing resource). 506 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(WritePixelsPendingIO, reporter, ctxInfo) { 507 GrContext* context = ctxInfo.grContext(); 508 GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider(); 509 510 static const int kFullSize = 62; 511 static const int kHalfSize = 31; 512 513 static const uint32_t kLeftColor = 0xFF222222; 514 static const uint32_t kRightColor = 0xFFAAAAAA; 515 516 const SkImageInfo fullII = SkImageInfo::Make(kFullSize, kFullSize, 517 kRGBA_8888_SkColorType, kPremul_SkAlphaType); 518 const SkImageInfo halfII = SkImageInfo::Make(kHalfSize, kFullSize, 519 kRGBA_8888_SkColorType, kPremul_SkAlphaType); 520 521 sk_sp<SkSurface> dest = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, fullII); 522 523 { 524 // Seed the resource cached with a scratch texture that will be 525 // reused by writeSurfacePixels 526 GrSurfaceDesc desc; 527 desc.fFlags = kNone_GrSurfaceFlags; 528 desc.fWidth = 32; 529 desc.fHeight = 64; 530 desc.fConfig = kRGBA_8888_GrPixelConfig; 531 532 const GrBackendFormat format = 533 context->contextPriv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType); 534 535 sk_sp<GrTextureProxy> temp = proxyProvider->createProxy( 536 format, desc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kApprox, SkBudgeted::kYes); 537 temp->instantiate(context->contextPriv().resourceProvider()); 538 } 539 540 // Create the surfaces and flush them to ensure there is no lingering pendingIO 541 sk_sp<SkSurface> leftSurf = create_surf(context, kHalfSize, kFullSize); 542 sk_sp<SkSurface> rightSurf = create_surf(context, kHalfSize, kFullSize); 543 544 sk_sp<SkImage> leftImg = upload(std::move(leftSurf), kLeftColor); 545 dest->getCanvas()->drawImage(std::move(leftImg), 0, 0); 546 547 sk_sp<SkImage> rightImg = upload(std::move(rightSurf), kRightColor); 548 dest->getCanvas()->drawImage(std::move(rightImg), kHalfSize, 0); 549 550 SkBitmap bm; 551 bm.allocPixels(fullII); 552 SkAssertResult(dest->readPixels(bm, 0, 0)); 553 554 bool isCorrect = true; 555 for (int y = 0; isCorrect && y < 16; ++y) { 556 const uint32_t* sl = bm.getAddr32(0, y); 557 558 for (int x = 0; x < 16; ++x) { 559 if (kLeftColor != sl[x]) { 560 isCorrect = false; 561 break; 562 } 563 } 564 for (int x = kHalfSize; x < kHalfSize+16; ++x) { 565 if (kRightColor != sl[x]) { 566 isCorrect = false; 567 break; 568 } 569 } 570 } 571 572 REPORTER_ASSERT(reporter, isCorrect); 573 } 574