|  | /* | 
|  | * Copyright 2013 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | * | 
|  | * This test confirms that a MultiPictureDocument can be serialized and deserialized without error. | 
|  | * And that the pictures within it are re-created accurately | 
|  | */ | 
|  |  | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkColor.h" | 
|  | #include "include/core/SkDocument.h" | 
|  | #include "include/core/SkFont.h" | 
|  | #include "include/core/SkImage.h" | 
|  | #include "include/core/SkImageInfo.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPath.h" | 
|  | #include "include/core/SkPicture.h" | 
|  | #include "include/core/SkPictureRecorder.h" | 
|  | #include "include/core/SkRRect.h" | 
|  | #include "include/core/SkRect.h" | 
|  | #include "include/core/SkRefCnt.h" | 
|  | #include "include/core/SkSamplingOptions.h" | 
|  | #include "include/core/SkSerialProcs.h" | 
|  | #include "include/core/SkStream.h" | 
|  | #include "include/core/SkString.h" | 
|  | #include "include/core/SkSurface.h" | 
|  | #include "include/core/SkTextBlob.h" | 
|  | #include "include/core/SkTypeface.h" | 
|  | #include "include/docs/SkMultiPictureDocument.h" | 
|  | #include "tests/Test.h" | 
|  | #include "tools/ToolUtils.h" | 
|  | #include "tools/fonts/FontToolUtils.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #if defined(SK_CODEC_DECODES_PNG_WITH_LIBPNG) | 
|  | #include "tools/SkSharingProc.h" | 
|  |  | 
|  | // Covers rects, ovals, paths, images, text | 
|  | static void draw_basic(SkCanvas* canvas, int seed, sk_sp<SkImage> image) { | 
|  | canvas->drawColor(SK_ColorWHITE); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setStrokeWidth(seed); | 
|  | paint.setColor(SK_ColorRED); | 
|  |  | 
|  | SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60); | 
|  | canvas->drawRect(rect, paint); | 
|  |  | 
|  | SkRRect oval; | 
|  | oval.setOval(rect); | 
|  | oval.offset(40, 60+seed); | 
|  | paint.setColor(SK_ColorBLUE); | 
|  | canvas->drawRRect(oval, paint); | 
|  |  | 
|  | paint.setColor(SK_ColorCYAN); | 
|  | canvas->drawCircle(180, 50, 5*seed, paint); | 
|  |  | 
|  | rect.offset(80, 0); | 
|  | paint.setColor(SK_ColorYELLOW); | 
|  | canvas->drawRoundRect(rect, 10, 10, paint); | 
|  |  | 
|  | SkPath path; | 
|  | path.cubicTo(768, 0, -512, 256, 256, 256); | 
|  | paint.setColor(SK_ColorGREEN); | 
|  | canvas->drawPath(path, paint); | 
|  |  | 
|  | canvas->drawImage(image, 128-seed, 128, SkSamplingOptions(), &paint); | 
|  |  | 
|  | if (seed % 2 == 0) { | 
|  | SkRect rect2 = SkRect::MakeXYWH(0, 0, 40, 60); | 
|  | canvas->drawImageRect(image, rect2, SkSamplingOptions(), &paint); | 
|  | } | 
|  |  | 
|  | SkPaint paint2; | 
|  | SkFont font = ToolUtils::DefaultFont(); | 
|  | font.setSize(2 + seed); | 
|  | auto text = SkTextBlob::MakeFromString(SkStringPrintf("Frame %d", seed).c_str(), font); | 
|  | canvas->drawTextBlob(text.get(), 50, 25, paint2); | 
|  | } | 
|  |  | 
|  | // Covers all of the above and drawing nested sub-pictures. | 
|  | static void draw_advanced(SkCanvas* canvas, int seed, sk_sp<SkImage> image, sk_sp<SkPicture> sub) { | 
|  | draw_basic(canvas, seed, image); | 
|  |  | 
|  | // Use subpicture twice in different places | 
|  | canvas->drawPicture(sub); | 
|  | canvas->save(); | 
|  | canvas->translate(seed, seed); | 
|  | canvas->drawPicture(sub); | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | // Test serialization and deserialization of multi picture document | 
|  | DEF_TEST(SkMultiPictureDocument_Serialize_and_deserialize, reporter) { | 
|  | // Create the stream we will serialize into. | 
|  | SkDynamicMemoryWStream stream; | 
|  |  | 
|  | // Create the image sharing proc. | 
|  | SkSharingSerialContext ctx; | 
|  | ctx.setDirectContext(nullptr); | 
|  | SkSerialProcs procs; | 
|  | procs.fImageProc = SkSharingSerialContext::serializeImage; | 
|  | procs.fImageCtx = &ctx; | 
|  |  | 
|  | // Create the multi picture document used for recording frames. | 
|  | sk_sp<SkDocument> multipic = SkMultiPictureDocument::Make(&stream, &procs); | 
|  |  | 
|  | static const int NUM_FRAMES = 12; | 
|  | static const int WIDTH = 256; | 
|  | static const int HEIGHT = 256; | 
|  |  | 
|  | // Make an image to be used in a later step. | 
|  | auto surface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(100, 100))); | 
|  | surface->getCanvas()->clear(SK_ColorGREEN); | 
|  | sk_sp<SkImage> image(surface->makeImageSnapshot()); | 
|  | REPORTER_ASSERT(reporter, image); | 
|  |  | 
|  | // Make a subpicture to be used in a later step | 
|  | SkPictureRecorder pr; | 
|  | SkCanvas* subCanvas = pr.beginRecording(100, 100); | 
|  | draw_basic(subCanvas, 42, image); | 
|  | sk_sp<SkPicture> sub = pr.finishRecordingAsPicture(); | 
|  |  | 
|  | const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT); | 
|  | std::vector<sk_sp<SkImage>> expectedImages; | 
|  |  | 
|  | for (int i=0; i<NUM_FRAMES; i++) { | 
|  | SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT); | 
|  | draw_advanced(pictureCanvas, i, image, sub); | 
|  | multipic->endPage(); | 
|  | // Also draw the picture to an image for later comparison | 
|  | auto surf = SkSurfaces::Raster(info); | 
|  | draw_advanced(surf->getCanvas(), i, image, sub); | 
|  | expectedImages.push_back(surf->makeImageSnapshot()); | 
|  | } | 
|  | // Finalize | 
|  | multipic->close(); | 
|  |  | 
|  | // Confirm written data is at least as large as the magic word | 
|  | std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream(); | 
|  | REPORTER_ASSERT(reporter, writtenStream->getLength() > 24, | 
|  | "Written data length too short (%zu)", writtenStream->getLength()); | 
|  | // SkDebugf("Multi Frame file size = %zu\n", writtenStream->getLength()); | 
|  |  | 
|  | // Set up deserialization | 
|  | SkSharingDeserialContext deserialContext; | 
|  | SkDeserialProcs dprocs; | 
|  | dprocs.fImageProc = SkSharingDeserialContext::deserializeImage; | 
|  | dprocs.fImageCtx = &deserialContext; | 
|  |  | 
|  | // Confirm data is a MultiPictureDocument | 
|  | int frame_count = SkMultiPictureDocument::ReadPageCount(writtenStream.get()); | 
|  | REPORTER_ASSERT(reporter, frame_count == NUM_FRAMES, | 
|  | "Expected %d frames, got %d. \n 0 frames may indicate the written file was not a " | 
|  | "MultiPictureDocument.", NUM_FRAMES, frame_count); | 
|  |  | 
|  | // Deserialize | 
|  | std::vector<SkDocumentPage> frames(frame_count); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkMultiPictureDocument::Read(writtenStream.get(), frames.data(), frame_count, &dprocs), | 
|  | "Failed while reading MultiPictureDocument"); | 
|  |  | 
|  | // Examine each frame. | 
|  | int i=0; | 
|  | for (const auto& frame : frames) { | 
|  | SkRect bounds = frame.fPicture->cullRect(); | 
|  | REPORTER_ASSERT(reporter, bounds.width() == WIDTH, | 
|  | "Page width: expected (%d) got (%d)", WIDTH, (int)bounds.width()); | 
|  | REPORTER_ASSERT(reporter, bounds.height() == HEIGHT, | 
|  | "Page height: expected (%d) got (%d)", HEIGHT, (int)bounds.height()); | 
|  |  | 
|  | auto surf = SkSurfaces::Raster(info); | 
|  | surf->getCanvas()->drawPicture(frame.fPicture); | 
|  | auto img = surf->makeImageSnapshot(); | 
|  | REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[i].get()), | 
|  | "Frame %d is wrong", i); | 
|  |  | 
|  | i++; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | #if defined(SK_GANESH) && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 | 
|  |  | 
|  | #include "include/android/AHardwareBufferUtils.h" | 
|  | #include "include/android/GrAHardwareBufferUtils.h" | 
|  | #include "include/core/SkBitmap.h" | 
|  | #include "include/core/SkColorSpace.h" | 
|  | #include "include/core/SkColorType.h" | 
|  | #include "include/gpu/ganesh/GrDirectContext.h" | 
|  | #include "include/gpu/ganesh/SkImageGanesh.h" | 
|  | #include "src/gpu/ganesh/GrCaps.h" | 
|  | #include "src/gpu/ganesh/GrDirectContextPriv.h" | 
|  |  | 
|  | #include <android/hardware_buffer.h> | 
|  |  | 
|  | static const int DEV_W = 16, DEV_H = 16; | 
|  |  | 
|  | static SkPMColor get_src_color(int x, int y) { | 
|  | SkASSERT(x >= 0 && x < DEV_W); | 
|  | SkASSERT(y >= 0 && y < DEV_H); | 
|  |  | 
|  | U8CPU r = x; | 
|  | U8CPU g = y; | 
|  | U8CPU b = 0xc; | 
|  |  | 
|  | U8CPU a = 0xff; | 
|  | switch ((x+y) % 5) { | 
|  | case 0: | 
|  | a = 0xff; | 
|  | break; | 
|  | case 1: | 
|  | a = 0x80; | 
|  | break; | 
|  | case 2: | 
|  | a = 0xCC; | 
|  | break; | 
|  | case 4: | 
|  | a = 0x01; | 
|  | break; | 
|  | case 3: | 
|  | a = 0x00; | 
|  | break; | 
|  | } | 
|  | a = 0xff; | 
|  | return SkPremultiplyARGBInline(a, r, g, b); | 
|  | } | 
|  |  | 
|  | static SkBitmap make_src_bitmap() { | 
|  | static SkBitmap bmp; | 
|  | if (bmp.isNull()) { | 
|  | bmp.allocN32Pixels(DEV_W, DEV_H); | 
|  | intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels()); | 
|  | for (int y = 0; y < DEV_H; ++y) { | 
|  | for (int x = 0; x < DEV_W; ++x) { | 
|  | SkPMColor* pixel = reinterpret_cast<SkPMColor*>( | 
|  | pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel()); | 
|  | *pixel = get_src_color(x, y); | 
|  | } | 
|  | } | 
|  | } | 
|  | return bmp; | 
|  | } | 
|  |  | 
|  | static void cleanup_resources(AHardwareBuffer* buffer) { | 
|  | if (buffer) { | 
|  | AHardwareBuffer_release(buffer); | 
|  | } | 
|  | } | 
|  |  | 
|  | static sk_sp<SkImage> makeAHardwareBufferTestImage( | 
|  | skiatest::Reporter* reporter, GrDirectContext* context, AHardwareBuffer* buffer) { | 
|  |  | 
|  | const SkBitmap srcBitmap = make_src_bitmap(); | 
|  |  | 
|  | AHardwareBuffer_Desc hwbDesc; | 
|  | hwbDesc.width = DEV_W; | 
|  | hwbDesc.height = DEV_H; | 
|  | hwbDesc.layers = 1; | 
|  | hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | | 
|  | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | | 
|  | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE; | 
|  | hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | 
|  | // The following three are not used in the allocate | 
|  | hwbDesc.stride = 0; | 
|  | hwbDesc.rfu0= 0; | 
|  | hwbDesc.rfu1= 0; | 
|  |  | 
|  | if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) { | 
|  | ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error); | 
|  | cleanup_resources(buffer); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Get actual desc for allocated buffer so we know the stride for uploading cpu data. | 
|  | AHardwareBuffer_describe(buffer, &hwbDesc); | 
|  |  | 
|  | void* bufferAddr; | 
|  | if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr, | 
|  | &bufferAddr)) { | 
|  | ERRORF(reporter, "Failed to lock hardware buffer"); | 
|  | cleanup_resources(buffer); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // fill buffer | 
|  | int bbp = srcBitmap.bytesPerPixel(); | 
|  | uint32_t* src = (uint32_t*)srcBitmap.getPixels(); | 
|  | int nextLineStep = DEV_W; | 
|  | uint32_t* dst = static_cast<uint32_t*>(bufferAddr); | 
|  | for (int y = 0; y < DEV_H; ++y) { | 
|  | memcpy(dst, src, DEV_W * bbp); | 
|  | src += nextLineStep; | 
|  | dst += hwbDesc.stride; | 
|  | } | 
|  | AHardwareBuffer_unlock(buffer, nullptr); | 
|  |  | 
|  | // Make SkImage from buffer in a way that mimics libs/hwui/AutoBackendTextureRelease | 
|  | GrBackendFormat backendFormat = | 
|  | GrAHardwareBufferUtils::GetBackendFormat(context, buffer, hwbDesc.format, false); | 
|  | GrAHardwareBufferUtils::DeleteImageProc deleteProc; | 
|  | GrAHardwareBufferUtils::UpdateImageProc updateProc; | 
|  | GrAHardwareBufferUtils::TexImageCtx imageCtx; | 
|  | GrBackendTexture texture = GrAHardwareBufferUtils::MakeBackendTexture( | 
|  | context, buffer, hwbDesc.width, hwbDesc.height, | 
|  | &deleteProc, // set by MakeBackendTexture | 
|  | &updateProc, // set by MakeBackendTexture | 
|  | &imageCtx, // set by MakeBackendTexture | 
|  | false,   // don't make protected image | 
|  | backendFormat, | 
|  | false   // isRenderable | 
|  | ); | 
|  | SkColorType colorType = AHardwareBufferUtils::GetSkColorTypeFromBufferFormat(hwbDesc.format); | 
|  | sk_sp<SkImage> image = SkImages::BorrowTextureFrom(context, | 
|  | texture, | 
|  | kTopLeft_GrSurfaceOrigin, | 
|  | colorType, | 
|  | kPremul_SkAlphaType, | 
|  | SkColorSpace::MakeSRGB(), | 
|  | deleteProc, | 
|  | imageCtx); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, image); | 
|  | REPORTER_ASSERT(reporter, image->isTextureBacked()); | 
|  | return image; | 
|  | } | 
|  |  | 
|  | // Test the onEndPage callback's intended use by processing an mskp containing AHardwareBuffer-backed SkImages | 
|  | // Expected behavior is that the callback is called while the AHardwareBuffer is still valid and the | 
|  | // images are copied so .close() can still access them. | 
|  | // Confirm deserialized file contains images with correct data. | 
|  | DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SkMultiPictureDocument_AHardwarebuffer, | 
|  | reporter, | 
|  | ctx_info, | 
|  | CtsEnforcement::kApiLevel_T) { | 
|  | auto context = ctx_info.directContext(); | 
|  | if (!context->priv().caps()->supportsAHardwareBufferImages()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Create the stream we will serialize into. | 
|  | SkDynamicMemoryWStream stream; | 
|  |  | 
|  | // Create the image sharing proc. | 
|  | SkSharingSerialContext ctx; | 
|  | ctx.setDirectContext(context); | 
|  | SkSerialProcs procs; | 
|  | procs.fImageProc = SkSharingSerialContext::serializeImage; | 
|  | procs.fImageCtx = &ctx; | 
|  |  | 
|  | // Create the multi picture document used for recording frames. | 
|  | // Pass a lambda as the onEndPage callback that captures our sharing context | 
|  | sk_sp<SkDocument> multipic = SkMultiPictureDocument::Make(&stream, &procs, | 
|  | [sharingCtx = &ctx](const SkPicture* pic) { | 
|  | SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx); | 
|  | }); | 
|  |  | 
|  | static const int WIDTH = 256; | 
|  | static const int HEIGHT = 256; | 
|  |  | 
|  | // Make an image to be used in a later step. | 
|  | AHardwareBuffer* ahbuffer = nullptr; | 
|  | sk_sp<SkImage> image = makeAHardwareBufferTestImage(reporter, context, ahbuffer); | 
|  |  | 
|  | const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT); | 
|  | std::vector<sk_sp<SkImage>> expectedImages; | 
|  |  | 
|  | // Record single frame | 
|  | SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT); | 
|  | draw_basic(pictureCanvas, 0, image); | 
|  | multipic->endPage(); | 
|  | // Also draw the picture to an image for later comparison | 
|  | auto surf = SkSurfaces::Raster(info); | 
|  | draw_basic(surf->getCanvas(), 0, image); | 
|  | expectedImages.push_back(surf->makeImageSnapshot()); | 
|  |  | 
|  | // Release Ahardwarebuffer. If the code under test has not copied it already, | 
|  | // close() will fail. | 
|  | // Note that this only works because we're doing one frame only. If this test were recording | 
|  | // two or more frames, it would have change the buffer contents instead. | 
|  | cleanup_resources(ahbuffer); | 
|  |  | 
|  | // Finalize | 
|  | multipic->close(); | 
|  |  | 
|  | // Confirm written data is at least as large as the magic word | 
|  | std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream(); | 
|  | REPORTER_ASSERT(reporter, writtenStream->getLength() > 24, | 
|  | "Written data length too short (%zu)", writtenStream->getLength()); | 
|  |  | 
|  | // Set up deserialization | 
|  | SkSharingDeserialContext deserialContext; | 
|  | SkDeserialProcs dprocs; | 
|  | dprocs.fImageProc = SkSharingDeserialContext::deserializeImage; | 
|  | dprocs.fImageCtx = &deserialContext; | 
|  |  | 
|  | // Confirm data is a MultiPictureDocument | 
|  | int frame_count = SkMultiPictureDocument::ReadPageCount(writtenStream.get()); | 
|  | REPORTER_ASSERT(reporter, frame_count == 1, | 
|  | "Expected 1 frame, got %d. \n 0 frames may indicate the written file was not a " | 
|  | "MultiPictureDocument.", frame_count); | 
|  |  | 
|  | // Deserialize | 
|  | std::vector<SkDocumentPage> frames(frame_count); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkMultiPictureDocument::Read(writtenStream.get(), frames.data(), frame_count, &dprocs), | 
|  | "Failed while reading MultiPictureDocument"); | 
|  |  | 
|  | // Examine frame. | 
|  | SkRect bounds = frames[0].fPicture->cullRect(); | 
|  | REPORTER_ASSERT(reporter, bounds.width() == WIDTH, | 
|  | "Page width: expected (%d) got (%d)", WIDTH, (int)bounds.width()); | 
|  | REPORTER_ASSERT(reporter, bounds.height() == HEIGHT, | 
|  | "Page height: expected (%d) got (%d)", HEIGHT, (int)bounds.height()); | 
|  |  | 
|  | auto surf2 = SkSurfaces::Raster(info); | 
|  | surf2->getCanvas()->drawPicture(frames[0].fPicture); | 
|  | auto img = surf2->makeImageSnapshot(); | 
|  | REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[0].get())); | 
|  | } | 
|  |  | 
|  | #endif // android compilation |