|  | /* | 
|  | * Copyright 2017 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "tests/TestUtils.h" | 
|  |  | 
|  | #include "include/encode/SkPngEncoder.h" | 
|  | #include "include/utils/SkBase64.h" | 
|  | #include "src/core/SkUtils.h" | 
|  | #include "src/gpu/GrContextPriv.h" | 
|  | #include "src/gpu/GrDrawingManager.h" | 
|  | #include "src/gpu/GrGpu.h" | 
|  | #include "src/gpu/GrImageInfo.h" | 
|  | #include "src/gpu/GrSurfaceContext.h" | 
|  | #include "src/gpu/GrSurfaceProxy.h" | 
|  | #include "src/gpu/GrTextureProxy.h" | 
|  | #include "src/gpu/SkGr.h" | 
|  |  | 
|  | void TestReadPixels(skiatest::Reporter* reporter, | 
|  | GrSurfaceContext* srcContext, | 
|  | uint32_t expectedPixelValues[], | 
|  | const char* testName) { | 
|  | int pixelCnt = srcContext->width() * srcContext->height(); | 
|  | SkAutoTMalloc<uint32_t> pixels(pixelCnt); | 
|  | memset(pixels.get(), 0, sizeof(uint32_t)*pixelCnt); | 
|  |  | 
|  | SkImageInfo ii = SkImageInfo::Make(srcContext->width(), srcContext->height(), | 
|  | kRGBA_8888_SkColorType, kPremul_SkAlphaType); | 
|  | bool read = srcContext->readPixels(ii, pixels.get(), 0, {0, 0}); | 
|  | if (!read) { | 
|  | ERRORF(reporter, "%s: Error reading from texture.", testName); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < pixelCnt; ++i) { | 
|  | if (pixels.get()[i] != expectedPixelValues[i]) { | 
|  | ERRORF(reporter, "%s: Error, pixel value %d should be 0x%08x, got 0x%08x.", | 
|  | testName, i, expectedPixelValues[i], pixels.get()[i]); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void TestWritePixels(skiatest::Reporter* reporter, | 
|  | GrSurfaceContext* dstContext, | 
|  | bool expectedToWork, | 
|  | const char* testName) { | 
|  | int pixelCnt = dstContext->width() * dstContext->height(); | 
|  | SkAutoTMalloc<uint32_t> pixels(pixelCnt); | 
|  | for (int y = 0; y < dstContext->width(); ++y) { | 
|  | for (int x = 0; x < dstContext->height(); ++x) { | 
|  | pixels.get()[y * dstContext->width() + x] = | 
|  | SkColorToPremulGrColor(SkColorSetARGB(2*y, x, y, x + y)); | 
|  | } | 
|  | } | 
|  |  | 
|  | SkImageInfo ii = SkImageInfo::Make(dstContext->width(), dstContext->height(), | 
|  | kRGBA_8888_SkColorType, kPremul_SkAlphaType); | 
|  | bool write = dstContext->writePixels(ii, pixels.get(), 0, {0, 0}); | 
|  | if (!write) { | 
|  | if (expectedToWork) { | 
|  | ERRORF(reporter, "%s: Error writing to texture.", testName); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (write && !expectedToWork) { | 
|  | ERRORF(reporter, "%s: writePixels succeeded when it wasn't supposed to.", testName); | 
|  | return; | 
|  | } | 
|  |  | 
|  | TestReadPixels(reporter, dstContext, pixels.get(), testName); | 
|  | } | 
|  |  | 
|  | void TestCopyFromSurface(skiatest::Reporter* reporter, | 
|  | GrContext* context, | 
|  | GrSurfaceProxy* proxy, | 
|  | GrSurfaceOrigin origin, | 
|  | GrColorType colorType, | 
|  | uint32_t expectedPixelValues[], | 
|  | const char* testName) { | 
|  | GrSurfaceProxyView view = GrSurfaceProxy::Copy(context, proxy, origin, colorType, | 
|  | GrMipMapped::kNo, SkBackingFit::kExact, | 
|  | SkBudgeted::kYes); | 
|  | SkASSERT(view.asTextureProxy()); | 
|  |  | 
|  | auto dstContext = GrSurfaceContext::Make(context, std::move(view), colorType, | 
|  | kPremul_SkAlphaType, nullptr); | 
|  | SkASSERT(dstContext); | 
|  |  | 
|  | TestReadPixels(reporter, dstContext.get(), expectedPixelValues, testName); | 
|  | } | 
|  |  | 
|  | void FillPixelData(int width, int height, GrColor* data) { | 
|  | for (int j = 0; j < height; ++j) { | 
|  | for (int i = 0; i < width; ++i) { | 
|  | unsigned int red = (unsigned int)(256.f * (i / (float)width)); | 
|  | unsigned int green = (unsigned int)(256.f * (j / (float)height)); | 
|  | data[i + j * width] = GrColorPackRGBA(red - (red >> 8), green - (green >> 8), | 
|  | 0xff, 0xff); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool CreateBackendTexture(GrContext* context, | 
|  | GrBackendTexture* backendTex, | 
|  | const SkImageInfo& ii, | 
|  | const SkColor4f& color, | 
|  | GrMipMapped mipMapped, | 
|  | GrRenderable renderable) { | 
|  | *backendTex = context->createBackendTexture(ii.width(), ii.height(), ii.colorType(), | 
|  | color, mipMapped, renderable); | 
|  | return backendTex->isValid(); | 
|  | } | 
|  |  | 
|  | void DeleteBackendTexture(GrContext* context, const GrBackendTexture& backendTex) { | 
|  | GrFlushInfo flushInfo; | 
|  | flushInfo.fFlags = kSyncCpu_GrFlushFlag; | 
|  | context->flush(flushInfo); | 
|  | context->deleteBackendTexture(backendTex); | 
|  | } | 
|  |  | 
|  | bool DoesFullBufferContainCorrectColor(const GrColor* srcBuffer, | 
|  | const GrColor* dstBuffer, | 
|  | int width, int height) { | 
|  | const GrColor* srcPtr = srcBuffer; | 
|  | const GrColor* dstPtr = dstBuffer; | 
|  | for (int j = 0; j < height; ++j) { | 
|  | for (int i = 0; i < width; ++i) { | 
|  | if (srcPtr[i] != dstPtr[i]) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | srcPtr += width; | 
|  | dstPtr += width; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool BipmapToBase64DataURI(const SkBitmap& bitmap, SkString* dst) { | 
|  | SkPixmap pm; | 
|  | if (!bitmap.peekPixels(&pm)) { | 
|  | dst->set("peekPixels failed"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // We're going to embed this PNG in a data URI, so make it as small as possible | 
|  | SkPngEncoder::Options options; | 
|  | options.fFilterFlags = SkPngEncoder::FilterFlag::kAll; | 
|  | options.fZLibLevel = 9; | 
|  |  | 
|  | SkDynamicMemoryWStream wStream; | 
|  | if (!SkPngEncoder::Encode(&wStream, pm, options)) { | 
|  | dst->set("SkPngEncoder::Encode failed"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | sk_sp<SkData> pngData = wStream.detachAsData(); | 
|  | size_t len = SkBase64::Encode(pngData->data(), pngData->size(), nullptr); | 
|  |  | 
|  | // The PNG can be almost arbitrarily large. We don't want to fill our logs with enormous URLs. | 
|  | // Infra says these can be pretty big, as long as we're only outputting them on failure. | 
|  | static const size_t kMaxBase64Length = 1024 * 1024; | 
|  | if (len > kMaxBase64Length) { | 
|  | dst->printf("Encoded image too large (%u bytes)", static_cast<uint32_t>(len)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | dst->resize(len); | 
|  | SkBase64::Encode(pngData->data(), pngData->size(), dst->writable_str()); | 
|  | dst->prepend("data:image/png;base64,"); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | using AccessPixelFn = const float*(const char* floatBuffer, int x, int y); | 
|  |  | 
|  | bool compare_pixels(int width, int height, | 
|  | const char* floatA, std::function<AccessPixelFn>& atA, | 
|  | const char* floatB, std::function<AccessPixelFn>& atB, | 
|  | const float tolRGBA[4], std::function<ComparePixmapsErrorReporter>& error) { | 
|  |  | 
|  | for (int y = 0; y < height; ++y) { | 
|  | for (int x = 0; x < width; ++x) { | 
|  | const float* rgbaA = atA(floatA, x, y); | 
|  | const float* rgbaB = atB(floatB, x, y); | 
|  | float diffs[4]; | 
|  | bool bad = false; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | diffs[i] = rgbaB[i] - rgbaA[i]; | 
|  | if (std::abs(diffs[i]) > std::abs(tolRGBA[i])) { | 
|  | bad = true; | 
|  | } | 
|  | } | 
|  | if (bad) { | 
|  | error(x, y, diffs); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ComparePixels(const GrImageInfo& infoA, const char* a, size_t rowBytesA, | 
|  | const GrImageInfo& infoB, const char* b, size_t rowBytesB, | 
|  | const float tolRGBA[4], std::function<ComparePixmapsErrorReporter>& error) { | 
|  | if (infoA.width() != infoB.width() || infoA.height() != infoB.height()) { | 
|  | static constexpr float kDummyDiffs[4] = {}; | 
|  | error(-1, -1, kDummyDiffs); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | SkAlphaType floatAlphaType = infoA.alphaType(); | 
|  | // If one is premul and the other is unpremul we do the comparison in premul space. | 
|  | if ((infoA.alphaType() == kPremul_SkAlphaType || | 
|  | infoB.alphaType() == kPremul_SkAlphaType) && | 
|  | (infoA.alphaType() == kUnpremul_SkAlphaType || | 
|  | infoB.alphaType() == kUnpremul_SkAlphaType)) { | 
|  | floatAlphaType = kPremul_SkAlphaType; | 
|  | } | 
|  | sk_sp<SkColorSpace> floatCS; | 
|  | if (SkColorSpace::Equals(infoA.colorSpace(), infoB.colorSpace())) { | 
|  | floatCS = infoA.refColorSpace(); | 
|  | } else { | 
|  | floatCS = SkColorSpace::MakeSRGBLinear(); | 
|  | } | 
|  | GrImageInfo floatInfo(GrColorType::kRGBA_F32, floatAlphaType, std::move(floatCS), | 
|  | infoA.width(), infoA.height()); | 
|  |  | 
|  | size_t floatBpp = GrColorTypeBytesPerPixel(GrColorType::kRGBA_F32); | 
|  | size_t floatRowBytes = floatBpp * infoA.width(); | 
|  | std::unique_ptr<char[]> floatA(new char[floatRowBytes * infoA.height()]); | 
|  | std::unique_ptr<char[]> floatB(new char[floatRowBytes * infoA.height()]); | 
|  | SkAssertResult(GrConvertPixels(floatInfo, floatA.get(), floatRowBytes, infoA, a, rowBytesA)); | 
|  | SkAssertResult(GrConvertPixels(floatInfo, floatB.get(), floatRowBytes, infoB, b, rowBytesB)); | 
|  |  | 
|  | auto at = std::function<AccessPixelFn>( | 
|  | [floatBpp, floatRowBytes](const char* floatBuffer, int x, int y) { | 
|  | return reinterpret_cast<const float*>(floatBuffer + y * floatRowBytes + x * floatBpp); | 
|  | }); | 
|  |  | 
|  | return compare_pixels(infoA.width(), infoA.height(), | 
|  | floatA.get(), at, floatB.get(), at, | 
|  | tolRGBA, error); | 
|  | } | 
|  |  | 
|  | bool ComparePixels(const SkPixmap& a, const SkPixmap& b, const float tolRGBA[4], | 
|  | std::function<ComparePixmapsErrorReporter>& error) { | 
|  | return ComparePixels(a.info(), static_cast<const char*>(a.addr()), a.rowBytes(), | 
|  | b.info(), static_cast<const char*>(b.addr()), b.rowBytes(), | 
|  | tolRGBA, error); | 
|  | } | 
|  |  | 
|  | bool CheckSolidPixels(const SkColor4f& col, const SkPixmap& pixmap, | 
|  | const float tolRGBA[4], std::function<ComparePixmapsErrorReporter>& error) { | 
|  |  | 
|  | size_t floatBpp = GrColorTypeBytesPerPixel(GrColorType::kRGBA_F32); | 
|  |  | 
|  | std::unique_ptr<char[]> floatA(new char[floatBpp]); | 
|  | // First convert 'col' to be compatible with 'pixmap' | 
|  | { | 
|  | sk_sp<SkColorSpace> srcCS = SkColorSpace::MakeSRGBLinear(); | 
|  | GrImageInfo srcInfo(GrColorType::kRGBA_F32, kUnpremul_SkAlphaType, std::move(srcCS), 1, 1); | 
|  | GrImageInfo dstInfo(GrColorType::kRGBA_F32, pixmap.alphaType(), pixmap.refColorSpace(), 1, 1); | 
|  |  | 
|  | SkAssertResult(GrConvertPixels(dstInfo, floatA.get(), floatBpp, srcInfo, | 
|  | col.vec(), floatBpp)); | 
|  | } | 
|  |  | 
|  | size_t floatRowBytes = floatBpp * pixmap.width(); | 
|  | std::unique_ptr<char[]> floatB(new char[floatRowBytes * pixmap.height()]); | 
|  | // Then convert 'pixmap' to RGBA_F32 | 
|  | { | 
|  | GrImageInfo dstInfo(GrColorType::kRGBA_F32, pixmap.alphaType(), pixmap.refColorSpace(), | 
|  | pixmap.width(), pixmap.height()); | 
|  |  | 
|  | SkAssertResult(GrConvertPixels(dstInfo, floatB.get(), floatRowBytes, pixmap.info(), | 
|  | pixmap.addr(), pixmap.rowBytes())); | 
|  | } | 
|  |  | 
|  | auto atA = std::function<AccessPixelFn>( | 
|  | [](const char* floatBuffer, int /* x */, int /* y */) { | 
|  | return reinterpret_cast<const float*>(floatBuffer); | 
|  | }); | 
|  |  | 
|  | auto atB = std::function<AccessPixelFn>( | 
|  | [floatBpp, floatRowBytes](const char* floatBuffer, int x, int y) { | 
|  | return reinterpret_cast<const float*>(floatBuffer + y * floatRowBytes + x * floatBpp); | 
|  | }); | 
|  |  | 
|  | return compare_pixels(pixmap.width(), pixmap.height(), floatA.get(), atA, floatB.get(), atB, | 
|  | tolRGBA, error); | 
|  | } | 
|  |  | 
|  | void CheckSingleThreadedProxyRefs(skiatest::Reporter* reporter, | 
|  | GrSurfaceProxy* proxy, | 
|  | int32_t expectedProxyRefs, | 
|  | int32_t expectedBackingRefs) { | 
|  | int32_t actualBackingRefs = proxy->testingOnly_getBackingRefCnt(); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, proxy->refCntGreaterThan(expectedProxyRefs - 1) && | 
|  | !proxy->refCntGreaterThan(expectedProxyRefs)); | 
|  | REPORTER_ASSERT(reporter, actualBackingRefs == expectedBackingRefs); | 
|  | } | 
|  |  | 
|  | #include "src/utils/SkCharToGlyphCache.h" | 
|  |  | 
|  | static SkGlyphID hash_to_glyph(uint32_t value) { | 
|  | return SkToU16(((value >> 16) ^ value) & 0xFFFF); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | class UnicharGen { | 
|  | SkUnichar fU; | 
|  | const int fStep; | 
|  | public: | 
|  | UnicharGen(int step) : fU(0), fStep(step) {} | 
|  |  | 
|  | SkUnichar next() { | 
|  | fU += fStep; | 
|  | return fU; | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | DEF_TEST(chartoglyph_cache, reporter) { | 
|  | SkCharToGlyphCache cache; | 
|  | const int step = 3; | 
|  |  | 
|  | UnicharGen gen(step); | 
|  | for (int i = 0; i < 500; ++i) { | 
|  | SkUnichar c = gen.next(); | 
|  | SkGlyphID glyph = hash_to_glyph(c); | 
|  |  | 
|  | int index = cache.findGlyphIndex(c); | 
|  | if (index >= 0) { | 
|  | index = cache.findGlyphIndex(c); | 
|  | } | 
|  | REPORTER_ASSERT(reporter, index < 0); | 
|  | cache.insertCharAndGlyph(~index, c, glyph); | 
|  |  | 
|  | UnicharGen gen2(step); | 
|  | for (int j = 0; j <= i; ++j) { | 
|  | c = gen2.next(); | 
|  | glyph = hash_to_glyph(c); | 
|  | index = cache.findGlyphIndex(c); | 
|  | if ((unsigned)index != glyph) { | 
|  | index = cache.findGlyphIndex(c); | 
|  | } | 
|  | REPORTER_ASSERT(reporter, (unsigned)index == glyph); | 
|  | } | 
|  | } | 
|  | } |