From 54dc4878b02765efea39e68b218df1e4bfff4b88 Mon Sep 17 00:00:00 2001 From: reed Date: Tue, 13 Sep 2016 08:09:45 -0700 Subject: add pipecanvas BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2201323003 Review-Url: https://codereview.chromium.org/2201323003 --- dm/DM.cpp | 2 + dm/DMSrcSink.cpp | 26 ++ dm/DMSrcSink.h | 15 + gyp/core.gyp | 1 + gyp/core.gypi | 3 + include/core/SkWriteBuffer.h | 12 +- samplecode/SampleApp.cpp | 54 ++- samplecode/SampleApp.h | 7 + src/core/SkDeduper.h | 39 ++ src/core/SkPipe.h | 74 +++ src/core/SkReadBuffer.cpp | 17 +- src/core/SkReadBuffer.h | 10 +- src/core/SkWriteBuffer.cpp | 91 ++-- src/pipe/SkPipeCanvas.cpp | 1056 ++++++++++++++++++++++++++++++++++++++++++ src/pipe/SkPipeCanvas.h | 166 +++++++ src/pipe/SkPipeFormat.h | 220 +++++++++ src/pipe/SkPipeReader.cpp | 944 +++++++++++++++++++++++++++++++++++++ src/pipe/SkRefSet.h | 40 ++ tests/PipeTest.cpp | 88 ++++ 19 files changed, 2811 insertions(+), 54 deletions(-) create mode 100644 src/core/SkDeduper.h create mode 100644 src/core/SkPipe.h create mode 100644 src/pipe/SkPipeCanvas.cpp create mode 100644 src/pipe/SkPipeCanvas.h create mode 100644 src/pipe/SkPipeFormat.h create mode 100644 src/pipe/SkPipeReader.cpp create mode 100644 src/pipe/SkRefSet.h create mode 100644 tests/PipeTest.cpp diff --git a/dm/DM.cpp b/dm/DM.cpp index a8fb43b30f..1598bcddea 100644 --- a/dm/DM.cpp +++ b/dm/DM.cpp @@ -868,6 +868,7 @@ static Sink* create_sink(const SkCommandLineConfig* config) { SINK("f16", RasterSink, kRGBA_F16_SkColorType, srgbColorSpace->makeLinearGamma()); SINK("pdf", PDFSink); SINK("skp", SKPSink); + SINK("pipe", PipeSink); SINK("svg", SVGSink); SINK("null", NullSink); SINK("xps", XPSSink); @@ -880,6 +881,7 @@ static Sink* create_sink(const SkCommandLineConfig* config) { static Sink* create_via(const SkString& tag, Sink* wrapped) { #define VIA(t, via, ...) if (tag.equals(t)) { return new via(__VA_ARGS__); } VIA("lite", ViaLite, wrapped); + VIA("pipe", ViaPipe, wrapped); VIA("twice", ViaTwice, wrapped); VIA("serialize", ViaSerialization, wrapped); VIA("pic", ViaPicture, wrapped); diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index c3c8a7bc6e..6b330231d2 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -30,6 +30,7 @@ #include "SkOpts.h" #include "SkPictureData.h" #include "SkPictureRecorder.h" +#include "SkPipe.h" #include "SkRandom.h" #include "SkRecordDraw.h" #include "SkRecorder.h" @@ -1241,6 +1242,15 @@ Error XPSSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const } return draw_skdocument(src, doc.get(), dst); } + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +PipeSink::PipeSink() {} + +Error PipeSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const { + return src.draw(SkPipeSerializer().beginWrite(SkRect::Make(src.size()), dst)); +} + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ SKPSink::SKPSink() {} @@ -1511,6 +1521,22 @@ Error ViaDefer::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkStri /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +Error ViaPipe::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const { + auto size = src.size(); + return draw_to_canvas(fSink, bitmap, stream, log, size, [&](SkCanvas* canvas) -> Error { + SkDynamicMemoryWStream tmpStream; + Error err = src.draw(SkPipeSerializer().beginWrite(SkRect::Make(size), &tmpStream)); + if (!err.isEmpty()) { + return err; + } + sk_sp data = tmpStream.detachAsData(); + SkPipeDeserializer().playback(data->data(), data->size(), canvas); + return check_against_reference(bitmap, src, fSink); + }); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + // Draw the Src into two pictures, then draw the second picture into the wrapped Sink. // This tests that any shortcuts we may take while recording that second picture are legal. Error ViaSecondPicture::draw( diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h index 0e11633b18..dfc4955b5a 100644 --- a/dm/DMSrcSink.h +++ b/dm/DMSrcSink.h @@ -333,6 +333,15 @@ public: SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; } }; +class PipeSink : public Sink { +public: + PipeSink(); + + Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override; + const char* fileExtension() const override { return "skpipe"; } + SinkFlags flags() const override { return SinkFlags{ SinkFlags::kVector, SinkFlags::kDirect }; } +}; + class RasterSink : public Sink { public: explicit RasterSink(SkColorType, sk_sp = nullptr); @@ -408,6 +417,12 @@ public: Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override; }; +class ViaPipe : public Via { +public: + explicit ViaPipe(Sink* sink) : Via(sink) {} + Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override; +}; + class ViaDefer : public Via { public: explicit ViaDefer(Sink* sink) : Via(sink) {} diff --git a/gyp/core.gyp b/gyp/core.gyp index e6f7e8caa1..8077778350 100644 --- a/gyp/core.gyp +++ b/gyp/core.gyp @@ -29,6 +29,7 @@ '../src/sfnt', '../src/image', '../src/opts', + '../src/pipe', '../src/utils', ], 'sources': [ diff --git a/gyp/core.gypi b/gyp/core.gypi index 9764d98621..50e3c3c09e 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -362,6 +362,9 @@ # '<(skia_src_path)/image/SkSurface_Gpu.cpp', '<(skia_src_path)/image/SkSurface_Raster.cpp', + '<(skia_src_path)/pipe/SkPipeCanvas.cpp', + '<(skia_src_path)/pipe/SkPipeReader.cpp', + '<(skia_include_path)/core/SkBBHFactory.h', '<(skia_include_path)/core/SkBitmap.h', '<(skia_include_path)/core/SkBitmapDevice.h', diff --git a/include/core/SkWriteBuffer.h b/include/core/SkWriteBuffer.h index 87ac8bf214..d77c073dda 100644 --- a/include/core/SkWriteBuffer.h +++ b/include/core/SkWriteBuffer.h @@ -19,6 +19,7 @@ #include "../private/SkTHash.h" class SkBitmap; +class SkDeduper; class SkFactorySet; class SkFlattenable; class SkRefCntSet; @@ -60,12 +61,17 @@ public: virtual void writeImage(const SkImage*) = 0; virtual void writeTypeface(SkTypeface* typeface) = 0; virtual void writePaint(const SkPaint& paint) = 0; + + void setDeduper(SkDeduper* deduper) { fDeduper = deduper; } + +protected: + SkDeduper* fDeduper = nullptr; }; /** * Concrete implementation that serializes to a flat binary blob. */ -class SkBinaryWriteBuffer final : public SkWriteBuffer { +class SkBinaryWriteBuffer : public SkWriteBuffer { public: enum Flags { kCrossProcess_Flag = 1 << 0, @@ -79,6 +85,10 @@ public: return SkToBool(fFlags & kCrossProcess_Flag); } + void write(const void* buffer, size_t bytes) { + fWriter.write(buffer, bytes); + } + void reset(void* storage = NULL, size_t storageSize = 0) { fWriter.reset(storage, storageSize); } diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp index b6b9cde06c..4c930c247b 100644 --- a/samplecode/SampleApp.cpp +++ b/samplecode/SampleApp.cpp @@ -34,6 +34,9 @@ #include "SkWindow.h" #include "sk_tool_utils.h" +#include "SkReadBuffer.h" +#include "SkStream.h" + #if SK_SUPPORT_GPU # include "gl/GrGLInterface.h" # include "gl/GrGLUtil.h" @@ -540,6 +543,26 @@ private: /////////////////////////////////////////////////////////////////////////////// +class SampleTFSerializer : public SkTypefaceSerializer { +public: + sk_sp serialize(SkTypeface* tf) override { + tf->ref(); + return SkData::MakeWithCopy(&tf, sizeof(tf)); + } +}; + +class SampleTFDeserializer : public SkTypefaceDeserializer { +public: + sk_sp deserialize(const void* data, size_t size) override { + SkASSERT(sizeof(SkTypeface*) == size); + SkTypeface* tf; + memcpy(&tf, data, size); + return sk_sp(tf); // this was ref'd in SampleTFSerializer + } +}; + +/////////////////////////////////////////////////////////////////////////////// + enum TilingMode { kNo_Tiling, kAbs_128x128_Tiling, @@ -696,11 +719,14 @@ DEFINE_string(pdfPath, "", "Path to direcotry of pdf files."); #endif #include "SkTaskGroup.h" +#include "SkForceLinking.h" SampleWindow::SampleWindow(void* hwnd, int argc, char** argv, DeviceManager* devManager) : INHERITED(hwnd) , fDevManager(nullptr) { + SkForceLinking(false); + SkCommandLineFlags::Parse(argc, argv); fCurrIndex = -1; @@ -851,6 +877,11 @@ SampleWindow::SampleWindow(void* hwnd, int argc, char** argv, DeviceManager* dev fSaveToPdf = false; fSaveToSKP = false; + if (true) { + fPipeSerializer.setTypefaceSerializer(new SampleTFSerializer); + fPipeDeserializer.setTypefaceDeserializer(new SampleTFDeserializer); + } + int sinkID = this->getSinkID(); fAppMenu = new SkOSMenu; fAppMenu->setTitle("Global Settings"); @@ -1304,7 +1335,10 @@ SkCanvas* SampleWindow::beforeChildren(SkCanvas* canvas) { } else if (fSaveToSKP) { canvas = fRecorder.beginRecording(9999, 9999, nullptr, 0); } else if (fUsePicture) { - canvas = fRecorder.beginRecording(9999, 9999, nullptr, 0); + fPipeStream.reset(new SkDynamicMemoryWStream); + canvas = fPipeSerializer.beginWrite(SkRect::MakeWH(this->width(), this->height()), + fPipeStream.get()); +// canvas = fRecorder.beginRecording(9999, 9999, nullptr, 0); } else { canvas = this->INHERITED::beforeChildren(canvas); } @@ -1366,17 +1400,15 @@ void SampleWindow::afterChildren(SkCanvas* orig) { } if (fUsePicture) { - sk_sp picture(fRecorder.finishRecordingAsPicture()); - - // serialize/deserialize? - if (false) { - SkDynamicMemoryWStream wstream; - picture->serialize(&wstream); - - SkAutoTDelete rstream(wstream.detachAsStream()); - picture = SkPicture::MakeFromStream(rstream); + if (true) { + fPipeSerializer.endWrite(); + sk_sp data(fPipeStream->detachAsData()); + fPipeDeserializer.playback(data->data(), data->size(), orig); + fPipeStream.reset(); + } else { + sk_sp picture(fRecorder.finishRecordingAsPicture()); + orig->drawPicture(picture.get()); } - orig->drawPicture(picture.get()); } // Do this after presentGL and other finishing, rather than in afterChild diff --git a/samplecode/SampleApp.h b/samplecode/SampleApp.h index 75416426b4..2ac9f0553e 100644 --- a/samplecode/SampleApp.h +++ b/samplecode/SampleApp.h @@ -13,11 +13,14 @@ #include "SkPicture.h" #include "SkPictureRecorder.h" #include "SkScalar.h" +#include "SkStream.h" #include "SkTDArray.h" #include "SkTouchGesture.h" #include "SkWindow.h" #include "timer/Timer.h" +#include "SkPipe.h" + class GrContext; class GrRenderTarget; @@ -165,6 +168,10 @@ private: int fCurrIndex; + std::unique_ptr fPipeStream; + SkPipeSerializer fPipeSerializer; + SkPipeDeserializer fPipeDeserializer; + SkPictureRecorder fRecorder; SkAutoTDelete fFlagsFilterCanvas; SkPath fClipPath; diff --git a/src/core/SkDeduper.h b/src/core/SkDeduper.h new file mode 100644 index 0000000000..f82f4fd8c2 --- /dev/null +++ b/src/core/SkDeduper.h @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkDeduper_DEFINED +#define SkDeduper_DEFINED + +#include "SkTypes.h" + +class SkImage; +class SkPicture; +class SkTypeface; + +class SkDeduper { +public: + virtual ~SkDeduper() {} + + // These return 0 on failure + + virtual int findOrDefineImage(SkImage*) = 0; + virtual int findOrDefinePicture(SkPicture*) = 0; + virtual int findOrDefineTypeface(SkTypeface*) = 0; + virtual int findOrDefineFactory(SkFlattenable*) = 0; +}; + +class SkInflator { +public: + virtual ~SkInflator() {} + + virtual SkImage* getImage(int) = 0; + virtual SkPicture* getPicture(int) = 0; + virtual SkTypeface* getTypeface(int) = 0; + virtual SkFlattenable::Factory getFactory(int) = 0; +}; + +#endif diff --git a/src/core/SkPipe.h b/src/core/SkPipe.h new file mode 100644 index 0000000000..d3d5a362e7 --- /dev/null +++ b/src/core/SkPipe.h @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPipe_DEFINED +#define SkPipe_DEFINED + +#include "SkTypes.h" + +class SkCanvas; +class SkImage; +class SkPicture; +class SkTypefaceSerializer; +class SkTypefaceDeserializer; +class SkWStream; + +class SkPipeSerializer { +public: + SkPipeSerializer(); + ~SkPipeSerializer(); + + // Ownership is not transferred, so caller must ceep the serializer alive + void setTypefaceSerializer(SkTypefaceSerializer*); + + void resetCache(); + + void write(SkPicture*, SkWStream*); + void write(SkImage*, SkWStream*); + + SkCanvas* beginWrite(const SkRect& cullBounds, SkWStream*); + void endWrite(); + +private: + class Impl; + std::unique_ptr fImpl; +}; + +class SkPipeDeserializer { +public: + SkPipeDeserializer(); + ~SkPipeDeserializer(); + + // Ownership is not transferred, so caller must ceep the deserializer alive + void setTypefaceDeserializer(SkTypefaceDeserializer*); + + sk_sp readPicture(const void*, size_t); + sk_sp readImage(const void*, size_t); + bool playback(const void*, size_t, SkCanvas*); + +private: + class Impl; + std::unique_ptr fImpl; +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkTypefaceSerializer { +public: + virtual ~SkTypefaceSerializer() {} + + virtual sk_sp serialize(SkTypeface*) = 0; +}; + +class SkTypefaceDeserializer { +public: + virtual ~SkTypefaceDeserializer() {} + + virtual sk_sp deserialize(const void* data, size_t size) = 0; +}; + +#endif diff --git a/src/core/SkReadBuffer.cpp b/src/core/SkReadBuffer.cpp index 43eaf20261..4b9d5987a1 100644 --- a/src/core/SkReadBuffer.cpp +++ b/src/core/SkReadBuffer.cpp @@ -6,6 +6,7 @@ */ #include "SkBitmap.h" +#include "SkDeduper.h" #include "SkErrorInternals.h" #include "SkImage.h" #include "SkImageDeserializer.h" @@ -258,6 +259,11 @@ sk_sp SkReadBuffer::readBitmapAsImage() { } sk_sp SkReadBuffer::readImage() { + if (fInflator) { + SkImage* img = fInflator->getImage(this->read32()); + return img ? sk_ref_sp(img) : nullptr; + } + int width = this->read32(); int height = this->read32(); if (width <= 0 || height <= 0) { // SkImage never has a zero dimension @@ -298,6 +304,10 @@ sk_sp SkReadBuffer::readImage() { } sk_sp SkReadBuffer::readTypeface() { + if (fInflator) { + return sk_ref_sp(fInflator->getTypeface(this->read32())); + } + uint32_t index = fReader.readU32(); if (0 == index || index > (unsigned)fTFCount) { return nullptr; @@ -314,7 +324,12 @@ SkFlattenable* SkReadBuffer::readFlattenable(SkFlattenable::Type ft) { SkFlattenable::Factory factory = nullptr; - if (fFactoryCount > 0) { + if (fInflator) { + factory = fInflator->getFactory(this->read32()); + if (!factory) { + return nullptr; + } + } else if (fFactoryCount > 0) { int32_t index = fReader.readU32(); if (0 == index) { return nullptr; // writer failed to give us the flattenable diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h index 7c4ecc6bd3..1873c7d3f4 100644 --- a/src/core/SkReadBuffer.h +++ b/src/core/SkReadBuffer.h @@ -27,6 +27,7 @@ class SkBitmap; class SkImage; +class SkInflator; #if defined(SK_DEBUG) && defined(SK_BUILD_FOR_MAC) #define DEBUG_NON_DETERMINISTIC_ASSERT @@ -131,7 +132,7 @@ public: virtual void readRegion(SkRegion* region); virtual void readPath(SkPath* path); - void readPaint(SkPaint* paint) { paint->unflatten(*this); } + virtual void readPaint(SkPaint* paint) { paint->unflatten(*this); } virtual SkFlattenable* readFlattenable(SkFlattenable::Type); template sk_sp readFlattenable() { @@ -210,6 +211,11 @@ public: return this->validate(index >= 0 && index < count); } + SkInflator* getInflator() const { return fInflator; } + void setInflator(SkInflator* inf) { fInflator = inf; } + +// sk_sp inflateImage(); + protected: /** * Allows subclass to check if we are using factories for expansion @@ -256,6 +262,8 @@ private: // have decoded. int fDecodedBitmapIndex; #endif // DEBUG_NON_DETERMINISTIC_ASSERT + + SkInflator* fInflator = nullptr; }; #endif // SkReadBuffer_DEFINED diff --git a/src/core/SkWriteBuffer.cpp b/src/core/SkWriteBuffer.cpp index 33ac03b49d..1159ef3383 100644 --- a/src/core/SkWriteBuffer.cpp +++ b/src/core/SkWriteBuffer.cpp @@ -8,11 +8,14 @@ #include "SkWriteBuffer.h" #include "SkBitmap.h" #include "SkData.h" +#include "SkDeduper.h" #include "SkPixelRef.h" #include "SkPtrRecorder.h" #include "SkStream.h" #include "SkTypeface.h" +/////////////////////////////////////////////////////////////////////////////////////////////////// + SkBinaryWriteBuffer::SkBinaryWriteBuffer(uint32_t flags) : fFlags(flags) , fFactorySet(nullptr) @@ -173,6 +176,11 @@ void SkBinaryWriteBuffer::writeBitmap(const SkBitmap& bitmap) { } void SkBinaryWriteBuffer::writeImage(const SkImage* image) { + if (fDeduper) { + this->write32(fDeduper->findOrDefineImage(const_cast(image))); + return; + } + this->writeInt(image->width()); this->writeInt(image->height()); @@ -193,6 +201,11 @@ void SkBinaryWriteBuffer::writeImage(const SkImage* image) { } void SkBinaryWriteBuffer::writeTypeface(SkTypeface* obj) { + if (fDeduper) { + this->write32(fDeduper->findOrDefineTypeface(obj)); + return; + } + if (nullptr == obj || nullptr == fTFSet) { fWriter.write32(0); } else { @@ -222,53 +235,51 @@ void SkBinaryWriteBuffer::setPixelSerializer(SkPixelSerializer* serializer) { } void SkBinaryWriteBuffer::writeFlattenable(const SkFlattenable* flattenable) { - /* - * The first 32 bits tell us... - * 0: failure to write the flattenable - * >0: index (1-based) into fFactorySet or fFlattenableDict or - * the first character of a string - */ if (nullptr == flattenable) { this->write32(0); return; } - /* - * We can write 1 of 2 versions of the flattenable: - * 1. index into fFactorySet : This assumes the writer will later - * resolve the function-ptrs into strings for its reader. SkPicture - * does exactly this, by writing a table of names (matching the indices) - * up front in its serialized form. - * 2. string name of the flattenable or index into fFlattenableDict: We - * store the string to allow the reader to specify its own factories - * after write time. In order to improve compression, if we have - * already written the string, we write its index instead. - */ - if (fFactorySet) { - SkFlattenable::Factory factory = flattenable->getFactory(); - SkASSERT(factory); - this->write32(fFactorySet->add(factory)); + if (fDeduper) { + this->write32(fDeduper->findOrDefineFactory(const_cast(flattenable))); } else { - const char* name = flattenable->getTypeName(); - SkASSERT(name); - SkString key(name); - if (uint32_t* indexPtr = fFlattenableDict.find(key)) { - // We will write the index as a 32-bit int. We want the first byte - // that we send to be zero - this will act as a sentinel that we - // have an index (not a string). This means that we will send the - // the index shifted left by 8. The remaining 24-bits should be - // plenty to store the index. Note that this strategy depends on - // being little endian. - SkASSERT(0 == *indexPtr >> 24); - this->write32(*indexPtr << 8); + /* + * We can write 1 of 2 versions of the flattenable: + * 1. index into fFactorySet : This assumes the writer will later + * resolve the function-ptrs into strings for its reader. SkPicture + * does exactly this, by writing a table of names (matching the indices) + * up front in its serialized form. + * 2. string name of the flattenable or index into fFlattenableDict: We + * store the string to allow the reader to specify its own factories + * after write time. In order to improve compression, if we have + * already written the string, we write its index instead. + */ + if (fFactorySet) { + SkFlattenable::Factory factory = flattenable->getFactory(); + SkASSERT(factory); + this->write32(fFactorySet->add(factory)); } else { - // Otherwise write the string. Clients should not use the empty - // string as a name, or we will have a problem. - SkASSERT(strcmp("", name)); - this->writeString(name); - - // Add key to dictionary. - fFlattenableDict.set(key, fFlattenableDict.count() + 1); + const char* name = flattenable->getTypeName(); + SkASSERT(name); + SkString key(name); + if (uint32_t* indexPtr = fFlattenableDict.find(key)) { + // We will write the index as a 32-bit int. We want the first byte + // that we send to be zero - this will act as a sentinel that we + // have an index (not a string). This means that we will send the + // the index shifted left by 8. The remaining 24-bits should be + // plenty to store the index. Note that this strategy depends on + // being little endian. + SkASSERT(0 == *indexPtr >> 24); + this->write32(*indexPtr << 8); + } else { + // Otherwise write the string. Clients should not use the empty + // string as a name, or we will have a problem. + SkASSERT(strcmp("", name)); + this->writeString(name); + + // Add key to dictionary. + fFlattenableDict.set(key, fFlattenableDict.count() + 1); + } } } diff --git a/src/pipe/SkPipeCanvas.cpp b/src/pipe/SkPipeCanvas.cpp new file mode 100644 index 0000000000..062d416537 --- /dev/null +++ b/src/pipe/SkPipeCanvas.cpp @@ -0,0 +1,1056 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPathEffect.h" +#include "SkColorFilter.h" +#include "SkDrawLooper.h" +#include "SkImageFilter.h" +#include "SkMaskFilter.h" +#include "SkPipeCanvas.h" +#include "SkPipeFormat.h" +#include "SkRasterizer.h" +#include "SkRSXform.h" +#include "SkShader.h" +#include "SkStream.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" + +template void write_rrect(T* writer, const SkRRect& rrect) { + char tmp[SkRRect::kSizeInMemory]; + rrect.writeToMemory(tmp); + writer->write(tmp, SkRRect::kSizeInMemory); +} + +template void write_pad(T* writer, const void* buffer, size_t len) { + writer->write(buffer, len & ~3); + if (len & 3) { + const char* src = (const char*)buffer + (len & ~3); + len &= 3; + uint32_t tmp = 0; + memcpy(&tmp, src, len); + writer->write(&tmp, 4); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static uint16_t compute_nondef(const SkPaint& paint, PaintUsage usage) { + // kRespectsStroke_PaintUsage is only valid if other bits are also set + SkASSERT(0 != (usage & ~kRespectsStroke_PaintUsage)); + + const SkScalar kTextSize_Default = 12; + const SkScalar kTextScaleX_Default = 1; + const SkScalar kTextSkewX_Default = 0; + const SkScalar kStrokeWidth_Default = 0; + const SkScalar kStrokeMiter_Default = 4; + const SkColor kColor_Default = SK_ColorBLACK; + + unsigned bits = (paint.getColor() != kColor_Default) ? kColor_NonDef : 0; + + if (usage & kText_PaintUsage) { + bits |= (paint.getTextSize() != kTextSize_Default ? kTextSize_NonDef : 0); + bits |= (paint.getTextScaleX() != kTextScaleX_Default ? kTextScaleX_NonDef : 0); + bits |= (paint.getTextSkewX() != kTextSkewX_Default ? kTextSkewX_NonDef : 0); + bits |= (paint.getTypeface() ? kTypeface_NonDef : 0); + } + + // TODO: kImage_PaintUsage only needs the shader/maskfilter IF its colortype is kAlpha_8 + + if (usage & (kVertices_PaintUsage | kDrawPaint_PaintUsage | kImage_PaintUsage | + kText_PaintUsage | kGeometry_PaintUsage | kTextBlob_PaintUsage)) { + bits |= (paint.getShader() ? kShader_NonDef : 0); + } + + if (usage & (kText_PaintUsage | kGeometry_PaintUsage | kTextBlob_PaintUsage)) { + bits |= (paint.getPathEffect() ? kPathEffect_NonDef : 0); + bits |= (paint.getRasterizer() ? kRasterizer_NonDef : 0); + + if (paint.getStyle() != SkPaint::kFill_Style || (usage & kRespectsStroke_PaintUsage)) { + bits |= (paint.getStrokeWidth() != kStrokeWidth_Default ? kStrokeWidth_NonDef : 0); + bits |= (paint.getStrokeMiter() != kStrokeMiter_Default ? kStrokeMiter_NonDef : 0); + } + } + + if (usage & + (kText_PaintUsage | kGeometry_PaintUsage | kImage_PaintUsage | kTextBlob_PaintUsage)) + { + bits |= (paint.getMaskFilter() ? kMaskFilter_NonDef : 0); + } + + bits |= (paint.getXfermode() ? kXfermode_NonDef : 0); + bits |= (paint.getColorFilter() ? kColorFilter_NonDef : 0); + bits |= (paint.getImageFilter() ? kImageFilter_NonDef : 0); + bits |= (paint.getDrawLooper() ? kDrawLooper_NonDef : 0); + + return SkToU16(bits); +} + +static uint32_t pack_paint_flags(unsigned flags, unsigned hint, unsigned align, + unsigned filter, unsigned style, unsigned caps, unsigned joins, + unsigned encoding) { + SkASSERT(kFlags_BPF + kHint_BPF + kAlign_BPF + kFilter_BPF <= 32); + + ASSERT_FITS_IN(flags, kFlags_BPF); + ASSERT_FITS_IN(filter, kFilter_BPF); + ASSERT_FITS_IN(style, kStyle_BPF); + ASSERT_FITS_IN(caps, kCaps_BPF); + ASSERT_FITS_IN(joins, kJoins_BPF); + ASSERT_FITS_IN(hint, kHint_BPF); + ASSERT_FITS_IN(align, kAlign_BPF); + ASSERT_FITS_IN(encoding, kEncoding_BPF); + + // left-align the fields of "known" size, and right-align the last (flatFlags) so it can easly + // add more bits in the future. + + uint32_t packed = 0; + int shift = 32; + + shift -= kFlags_BPF; packed |= (flags << shift); + shift -= kFilter_BPF; packed |= (filter << shift); + shift -= kStyle_BPF; packed |= (style << shift); + // these are only needed for stroking (geometry or text) + shift -= kCaps_BPF; packed |= (caps << shift); + shift -= kJoins_BPF; packed |= (joins << shift); + // these are only needed for text + shift -= kHint_BPF; packed |= (hint << shift); + shift -= kAlign_BPF; packed |= (align << shift); + shift -= kEncoding_BPF; packed |= (encoding << shift); + + return packed; +} + +#define CHECK_WRITE_SCALAR(writer, nondef, paint, Field) \ + do { if (nondef & (k##Field##_NonDef)) { \ + writer.writeScalar(paint.get##Field()); \ + }} while (0) + +#define CHECK_WRITE_FLATTENABLE(writer, nondef, paint, Field) \ + do { if (nondef & (k##Field##_NonDef)) { \ + SkFlattenable* f = paint.get##Field(); \ + SkASSERT(f != nullptr); \ + writer.writeFlattenable(f); \ + } } while (0) + +/* + * Header: + * paint flags : 32 + * non_def bits : 16 + * xfermode enum : 8 + * pad zeros : 8 + */ +static void write_paint(SkWriteBuffer& writer, const SkPaint& paint, unsigned usage) { + uint32_t packedFlags = pack_paint_flags(paint.getFlags(), paint.getHinting(), + paint.getTextAlign(), paint.getFilterQuality(), + paint.getStyle(), paint.getStrokeCap(), + paint.getStrokeJoin(), paint.getTextEncoding()); + writer.write32(packedFlags); + + unsigned nondef = compute_nondef(paint, (PaintUsage)usage); + SkXfermode::Mode mode; + if (SkXfermode::AsMode(paint.getXfermode(), &mode)) { + nondef &= ~kXfermode_NonDef; // don't need to store a pointer since we have an enum + } else { + SkASSERT(nondef & kXfermode_NonDef); + mode = (SkXfermode::Mode)0; + } + const uint8_t pad = 0; + writer.write32((nondef << 16) | ((unsigned)mode << 8) | pad); + + CHECK_WRITE_SCALAR(writer, nondef, paint, TextSize); + CHECK_WRITE_SCALAR(writer, nondef, paint, TextScaleX); + CHECK_WRITE_SCALAR(writer, nondef, paint, TextSkewX); + CHECK_WRITE_SCALAR(writer, nondef, paint, StrokeWidth); + CHECK_WRITE_SCALAR(writer, nondef, paint, StrokeMiter); + + if (nondef & kColor_NonDef) { + writer.write32(paint.getColor()); + } + if (nondef & kTypeface_NonDef) { + // TODO: explore idea of writing bits indicating "use the prev (or prev N) face" + // e.g. 1-N bits is an index into a ring buffer of typefaces + SkTypeface* tf = paint.getTypeface(); + SkASSERT(tf); + writer.writeTypeface(tf); + } + + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, PathEffect); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, Shader); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, Xfermode); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, MaskFilter); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, ColorFilter); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, Rasterizer); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, ImageFilter); + CHECK_WRITE_FLATTENABLE(writer, nondef, paint, DrawLooper); +} + +class SkPipeWriter : public SkBinaryWriteBuffer { + enum { + N = 1024/4, + }; + uint32_t fStorage[N]; + SkWStream* fStream; + +public: + SkPipeWriter(SkWStream* stream, SkDeduper* deduper) + : SkBinaryWriteBuffer(fStorage, sizeof(fStorage)) + , fStream(stream) + { + this->setDeduper(deduper); + } + + SkPipeWriter(SkPipeCanvas* pc) : SkPipeWriter(pc->fStream, pc->fDeduper) {} + + ~SkPipeWriter() override { + SkASSERT(SkIsAlign4(fStream->bytesWritten())); + this->writeToStream(fStream); + } + + void writePaint(const SkPaint& paint) override { + write_paint(*this, paint, kUnknown_PaintUsage); + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkPipeCanvas::SkPipeCanvas(const SkRect& cull, SkPipeDeduper* deduper, SkWStream* stream) + : INHERITED(SkScalarCeilToInt(cull.width()), SkScalarCeilToInt(cull.height())) + , fDeduper(deduper) + , fStream(stream) +{} + +SkPipeCanvas::~SkPipeCanvas() {} + +void SkPipeCanvas::willSave() { + fStream->write32(pack_verb(SkPipeVerb::kSave)); + this->INHERITED::willSave(); +} + +SkCanvas::SaveLayerStrategy SkPipeCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) { + SkPipeWriter writer(this); + uint32_t extra = rec.fSaveLayerFlags; + + // remap this wacky flag + if (extra & (1 << 31)/*SkCanvas::kDontClipToLayer_PrivateSaveLayerFlag*/) { + extra &= ~(1 << 31); + extra |= kDontClipToLayer_SaveLayerMask; + } + + if (rec.fBounds) { + extra |= kHasBounds_SaveLayerMask; + } + if (rec.fPaint) { + extra |= kHasPaint_SaveLayerMask; + } + if (rec.fBackdrop) { + extra |= kHasBackdrop_SaveLayerMask; + } + + writer.write32(pack_verb(SkPipeVerb::kSaveLayer, extra)); + if (rec.fBounds) { + writer.writeRect(*rec.fBounds); + } + if (rec.fPaint) { + write_paint(writer, *rec.fPaint, kSaveLayer_PaintUsage); + } + if (rec.fBackdrop) { + writer.writeFlattenable(rec.fBackdrop); + } + return kNoLayer_SaveLayerStrategy; +} + +void SkPipeCanvas::willRestore() { + fStream->write32(pack_verb(SkPipeVerb::kRestore)); + this->INHERITED::willRestore(); +} + +template void write_sparse_matrix(T* writer, const SkMatrix& matrix) { + SkMatrix::TypeMask tm = matrix.getType(); + SkScalar tmp[9]; + if (tm & SkMatrix::kPerspective_Mask) { + matrix.get9(tmp); + writer->write(tmp, 9 * sizeof(SkScalar)); + } else if (tm & SkMatrix::kAffine_Mask) { + tmp[0] = matrix[SkMatrix::kMScaleX]; + tmp[1] = matrix[SkMatrix::kMSkewX]; + tmp[2] = matrix[SkMatrix::kMTransX]; + tmp[3] = matrix[SkMatrix::kMScaleY]; + tmp[4] = matrix[SkMatrix::kMSkewY]; + tmp[5] = matrix[SkMatrix::kMTransY]; + writer->write(tmp, 6 * sizeof(SkScalar)); + } else if (tm & SkMatrix::kScale_Mask) { + tmp[0] = matrix[SkMatrix::kMScaleX]; + tmp[1] = matrix[SkMatrix::kMTransX]; + tmp[2] = matrix[SkMatrix::kMScaleY]; + tmp[3] = matrix[SkMatrix::kMTransY]; + writer->write(tmp, 4 * sizeof(SkScalar)); + } else if (tm & SkMatrix::kTranslate_Mask) { + tmp[0] = matrix[SkMatrix::kMTransX]; + tmp[1] = matrix[SkMatrix::kMTransY]; + writer->write(tmp, 2 * sizeof(SkScalar)); + } + // else write nothing for Identity +} + +static void do_concat(SkWStream* stream, const SkMatrix& matrix, bool isSetMatrix) { + unsigned mtype = matrix.getType(); + SkASSERT(0 == (mtype & ~kTypeMask_ConcatMask)); + unsigned extra = mtype; + if (isSetMatrix) { + extra |= kSetMatrix_ConcatMask; + } + if (mtype || isSetMatrix) { + stream->write32(pack_verb(SkPipeVerb::kConcat, extra)); + write_sparse_matrix(stream, matrix); + } +} + +void SkPipeCanvas::didConcat(const SkMatrix& matrix) { + do_concat(fStream, matrix, false); + this->INHERITED::didConcat(matrix); +} + +void SkPipeCanvas::didSetMatrix(const SkMatrix& matrix) { + do_concat(fStream, matrix, true); + this->INHERITED::didSetMatrix(matrix); +} + +void SkPipeCanvas::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + fStream->write32(pack_verb(SkPipeVerb::kClipRect, ((unsigned)op << 1) | edgeStyle)); + fStream->write(&rect, 4 * sizeof(SkScalar)); + + this->INHERITED::onClipRect(rect, op, edgeStyle); +} + +void SkPipeCanvas::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + fStream->write32(pack_verb(SkPipeVerb::kClipRRect, ((unsigned)op << 1) | edgeStyle)); + write_rrect(fStream, rrect); + + this->INHERITED::onClipRRect(rrect, op, edgeStyle); +} + +void SkPipeCanvas::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kClipPath, ((unsigned)op << 1) | edgeStyle)); + writer.writePath(path); + + this->INHERITED::onClipPath(path, op, edgeStyle); +} + +void SkPipeCanvas::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kClipRegion, (unsigned)op << 1)); + writer.writeRegion(deviceRgn); + + this->INHERITED::onClipRegion(deviceRgn, op); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkPipeCanvas::onDrawArc(const SkRect& bounds, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawArc, (int)useCenter)); + writer.writeRect(bounds); + writer.writeScalar(startAngle); + writer.writeScalar(sweepAngle); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawAtlas(const SkImage* image, const SkRSXform xform[], const SkRect rect[], + const SkColor colors[], int count, SkXfermode::Mode mode, + const SkRect* cull, const SkPaint* paint) { + unsigned extra = (unsigned)mode; + SkASSERT(0 == (extra & ~kMode_DrawAtlasMask)); + if (colors) { + extra |= kHasColors_DrawAtlasMask; + } + if (cull) { + extra |= kHasCull_DrawAtlasMask; + } + if (paint) { + extra |= kHasPaint_DrawAtlasMask; + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawAtlas, extra)); + writer.writeImage(image); + writer.write32(count); + writer.write(xform, count * sizeof(SkRSXform)); + writer.write(rect, count * sizeof(SkRect)); + if (colors) { + writer.write(colors, count * sizeof(SkColor)); + } + if (cull) { + writer.writeRect(*cull); + } + if (paint) { + write_paint(writer, *paint, kImage_PaintUsage); + } +} + +void SkPipeCanvas::onDrawPaint(const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPaint)); + write_paint(writer, paint, kDrawPaint_PaintUsage); +} + +void SkPipeCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPoints, mode)); + writer.write32(SkToU32(count)); + writer.write(pts, count * sizeof(SkPoint)); + write_paint(writer, paint, kGeometry_PaintUsage | kRespectsStroke_PaintUsage); +} + +void SkPipeCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawRect)); + writer.write(&rect, sizeof(SkRect)); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawOval(const SkRect& rect, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawOval)); + writer.write(&rect, sizeof(SkRect)); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawRRect)); + write_rrect(&writer, rrect); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawDRRect)); + write_rrect(&writer, outer); + write_rrect(&writer, inner); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPath)); + writer.writePath(path); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static sk_sp make_from_bitmap(const SkBitmap& bitmap) { + // If we just "make" an image, it will force a CPU copy (if its mutable), only to have + // us then either find it in our cache, or compress and send it. + // + // Better could be to look it up in our cache first, and only create/compress it if we have to. + // + // But for now, just do the dumb thing... + return SkImage::MakeFromBitmap(bitmap); +} + +void SkPipeCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, + const SkPaint* paint) { + sk_sp image = make_from_bitmap(bitmap); + if (image) { + this->onDrawImage(image.get(), x, y, paint); + } +} + +void SkPipeCanvas::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + sk_sp image = make_from_bitmap(bitmap); + if (image) { + this->onDrawImageRect(image.get(), src, dst, paint, constraint); + } +} + +void SkPipeCanvas::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint* paint) { + sk_sp image = make_from_bitmap(bitmap); + if (image) { + this->onDrawImageNine(image.get(), center, dst, paint); + } +} + +void SkPipeCanvas::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + sk_sp image = make_from_bitmap(bitmap); + if (image) { + this->onDrawImageLattice(image.get(), lattice, dst, paint); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkPipeCanvas::onDrawImage(const SkImage* image, SkScalar left, SkScalar top, + const SkPaint* paint) { + unsigned extra = 0; + if (paint) { + extra |= kHasPaint_DrawImageMask; + } + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawImage, extra)); + writer.writeImage(image); + writer.writeScalar(left); + writer.writeScalar(top); + if (paint) { + write_paint(writer, *paint, kImage_PaintUsage); + } +} + +void SkPipeCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + SkASSERT(0 == ((unsigned)constraint & ~1)); + unsigned extra = (unsigned)constraint; + if (paint) { + extra |= kHasPaint_DrawImageRectMask; + } + if (src) { + extra |= kHasSrcRect_DrawImageRectMask; + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawImageRect, extra)); + writer.writeImage(image); + if (src) { + writer.write(src, sizeof(*src)); + } + writer.write(&dst, sizeof(dst)); + if (paint) { + write_paint(writer, *paint, kImage_PaintUsage); + } +} + +void SkPipeCanvas::onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + unsigned extra = 0; + if (paint) { + extra |= kHasPaint_DrawImageNineMask; + } + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawImageNine, extra)); + writer.writeImage(image); + writer.write(¢er, sizeof(center)); + writer.write(&dst, sizeof(dst)); + if (paint) { + write_paint(writer, *paint, kImage_PaintUsage); + } +} + +void SkPipeCanvas::onDrawImageLattice(const SkImage* image, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + unsigned extra = 0; + if (paint) { + extra |= kHasPaint_DrawImageLatticeMask; + } + if (lattice.fFlags) { + extra |= kHasFlags_DrawImageLatticeMask; + } + if (lattice.fXCount >= kCount_DrawImageLatticeMask) { + extra |= kCount_DrawImageLatticeMask << kXCount_DrawImageLatticeShift; + } else { + extra |= lattice.fXCount << kXCount_DrawImageLatticeShift; + } + if (lattice.fYCount >= kCount_DrawImageLatticeMask) { + extra |= kCount_DrawImageLatticeMask << kYCount_DrawImageLatticeShift; + } else { + extra |= lattice.fYCount << kYCount_DrawImageLatticeShift; + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawImageLattice, extra)); + writer.writeImage(image); + if (lattice.fXCount >= kCount_DrawImageLatticeMask) { + writer.write32(lattice.fXCount); + } + if (lattice.fYCount >= kCount_DrawImageLatticeMask) { + writer.write32(lattice.fYCount); + } + // Often these divs will be small (8 or 16 bits). Consider sniffing that and writing a flag + // so we can store them smaller. + writer.write(lattice.fXDivs, lattice.fXCount * sizeof(int32_t)); + writer.write(lattice.fYDivs, lattice.fYCount * sizeof(int32_t)); + if (lattice.fFlags) { + int32_t count = (lattice.fXCount + 1) * (lattice.fYCount + 1); + SkASSERT(count > 0); + write_pad(&writer, lattice.fFlags, count); + } + writer.write(&dst, sizeof(dst)); + if (paint) { + write_paint(writer, *paint, kImage_PaintUsage); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkPipeCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkASSERT(byteLength); + + bool compact = fits_in(byteLength, 24); + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawText, compact ? (unsigned)byteLength : 0)); + if (!compact) { + writer.write32(SkToU32(byteLength)); + } + write_pad(&writer, text, byteLength); + writer.writeScalar(x); + writer.writeScalar(y); + write_paint(writer, paint, kText_PaintUsage); +} + +void SkPipeCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint& paint) { + SkASSERT(byteLength); + + bool compact = fits_in(byteLength, 24); + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPosText, compact ? (unsigned)byteLength : 0)); + if (!compact) { + writer.write32(SkToU32(byteLength)); + } + write_pad(&writer, text, byteLength); + writer.writePointArray(pos, paint.countText(text, byteLength)); + write_paint(writer, paint, kText_PaintUsage); +} + +void SkPipeCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint& paint) { + SkASSERT(byteLength); + + bool compact = fits_in(byteLength, 24); + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPosTextH, compact ? (unsigned)byteLength : 0)); + if (!compact) { + writer.write32(SkToU32(byteLength)); + } + write_pad(&writer, text, byteLength); + writer.writeScalarArray(xpos, paint.countText(text, byteLength)); + writer.writeScalar(constY); + write_paint(writer, paint, kText_PaintUsage); +} + +void SkPipeCanvas::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint& paint) { + SkASSERT(byteLength > 0); + + unsigned extra = 0; + if (byteLength <= kTextLength_DrawTextOnPathMask) { + extra |= byteLength; + } // else we will write the length after the packedverb + SkMatrix::TypeMask tm = matrix ? matrix->getType() : SkMatrix::kIdentity_Mask; + extra |= (unsigned)tm << kMatrixType_DrawTextOnPathShift; + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawTextOnPath, extra)); + if (byteLength > kTextLength_DrawTextOnPathMask) { + writer.write32(byteLength); + } + write_pad(&writer, text, byteLength); + writer.writePath(path); + if (matrix) { + write_sparse_matrix(&writer, *matrix); + } + write_paint(writer, paint, kText_PaintUsage); +} + +void SkPipeCanvas::onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[], + const SkRect* cull, const SkPaint& paint) { + SkASSERT(byteLength); + + bool compact = fits_in(byteLength, 23); + unsigned extra = compact ? (byteLength << 1) : 0; + if (cull) { + extra |= 1; + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawTextRSXform, extra)); + if (!compact) { + writer.write32(SkToU32(byteLength)); + } + write_pad(&writer, text, byteLength); + + int count = paint.countText(text, byteLength); + writer.write32(count); // maybe we can/should store this in extra as well? + writer.write(xform, count * sizeof(SkRSXform)); + if (cull) { + writer.writeRect(*cull); + } + write_paint(writer, paint, kText_PaintUsage); +} + +void SkPipeCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint &paint) { + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawTextBlob, 0)); + blob->flatten(writer); + writer.writeScalar(x); + writer.writeScalar(y); + write_paint(writer, paint, kTextBlob_PaintUsage); +} + +void SkPipeCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) { + unsigned extra = fDeduper->findOrDefinePicture(const_cast(picture)); + if (matrix) { + extra |= kHasMatrix_DrawPictureExtra; + } + if (paint) { + extra |= kHasPaint_DrawPictureExtra; + } + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawPicture, extra)); + if (matrix) { + writer.writeMatrix(*matrix); + } + if (paint) { + write_paint(writer, *paint, kSaveLayer_PaintUsage); + } +} + +void SkPipeCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + size_t size = region.writeToMemory(nullptr); + unsigned extra = 0; + if (fits_in(size, 24)) { + extra = SkToUInt(size); + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawRegion, extra)); + if (0 == extra) { + writer.write32(size); + } + SkAutoSMalloc<2048> storage(size); + region.writeToMemory(storage.get()); + write_pad(&writer, storage.get(), size); + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + SkASSERT(vertexCount > 0); + + unsigned extra = 0; + if (vertexCount <= kVCount_DrawVerticesMask) { + extra |= vertexCount; + } + extra |= (unsigned)vmode << kVMode_DrawVerticesShift; + + SkXfermode::Mode mode = SkXfermode::kModulate_Mode; + if (xmode && !SkXfermode::AsMode(xmode, &mode)) { + mode = (SkXfermode::Mode)0xFF; // sentinel for read the xfer later + } + extra |= (unsigned)mode << kXMode_DrawVerticesShift; + + if (texs) { + extra |= kHasTex_DrawVerticesMask; + } + if (colors) { + extra |= kHasColors_DrawVerticesMask; + } + if (indexCount > 0) { + extra |= kHasIndices_DrawVerticesMask; + } + + SkPipeWriter writer(this); + writer.write32(pack_verb(SkPipeVerb::kDrawVertices, extra)); + if (vertexCount > kVCount_DrawVerticesMask) { + writer.write32(vertexCount); + } + if (mode == (SkXfermode::Mode)0xFF) { + writer.writeFlattenable(xmode); + } + writer.write(vertices, vertexCount * sizeof(SkPoint)); + if (texs) { + writer.write(texs, vertexCount * sizeof(SkPoint)); + } + if (colors) { + writer.write(colors, vertexCount * sizeof(SkColor)); + } + if (indexCount > 0) { + writer.write32(indexCount); + SkASSERT(SkIsAlign2(indexCount)); + writer.write(indices, indexCount * sizeof(uint16_t)); + } + write_paint(writer, paint, kVertices_PaintUsage); +} + +void SkPipeCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkXfermode* xfer, + const SkPaint& paint) { + SkPipeWriter writer(this); + unsigned extra = 0; + SkXfermode::Mode mode = SkXfermode::kModulate_Mode; + if (xfer && !xfer->asMode(&mode)) { + mode = (SkXfermode::Mode)kExplicitXfer_DrawPatchExtraValue; + } else { + xfer = nullptr; // signal that we're using the mode enum + } + SkASSERT(0 == (mode & ~kModeEnum_DrawPatchExtraMask)); + extra = (unsigned)mode; + if (colors) { + extra |= kHasColors_DrawPatchExtraMask; + } + if (texCoords) { + extra |= kHasTexture_DrawPatchExtraMask; + } + writer.write32(pack_verb(SkPipeVerb::kDrawPatch, extra)); + writer.write(cubics, sizeof(SkPoint) * 12); + if (colors) { + writer.write(colors, sizeof(SkColor) * 4); + } + if (texCoords) { + writer.write(texCoords, sizeof(SkPoint) * 4); + } + if (xfer) { + xfer->flatten(writer); + } + write_paint(writer, paint, kGeometry_PaintUsage); +} + +void SkPipeCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* data) { + const size_t len = strlen(key) + 1; // must write the trailing 0 + bool compact = fits_in(len, 23); + uint32_t extra = compact ? (unsigned)len : 0; + extra <<= 1; // make room for has_data_sentinel + if (data) { + extra |= 1; + } + + fStream->write32(pack_verb(SkPipeVerb::kDrawAnnotation, extra)); + fStream->write(&rect, sizeof(SkRect)); + if (!compact) { + fStream->write32(SkToU32(len)); + } + write_pad(fStream, key, len); + if (data) { + fStream->write32(SkToU32(data->size())); + write_pad(fStream, data->data(), data->size()); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class A8Serializer : public SkPixelSerializer { +protected: + bool onUseEncodedData(const void* data, size_t len) { + return true; + } + + SkData* onEncode(const SkPixmap& pmap) { + if (kAlpha_8_SkColorType == pmap.colorType()) { + SkDynamicMemoryWStream stream; + stream.write("skiaimgf", 8); + stream.write32(pmap.width()); + stream.write32(pmap.height()); + stream.write16(pmap.colorType()); + stream.write16(pmap.alphaType()); + stream.write32(0); // no colorspace for now + for (int y = 0; y < pmap.height(); ++y) { + stream.write(pmap.addr8(0, y), pmap.width()); + } + return stream.detachAsData().release(); + } + return nullptr; + } +}; + +static bool show_deduper_traffic = false; + +int SkPipeDeduper::findOrDefineImage(SkImage* image) { + int index = fImages.find(image->uniqueID()); + SkASSERT(index >= 0); + if (index) { + if (show_deduper_traffic) { + SkDebugf(" reuseImage(%d)\n", index - 1); + } + return index; + } + + A8Serializer serial; + sk_sp data(image->encode(&serial)); + if (!data) { + data.reset(image->encode()); + } + if (data) { + index = fImages.add(image->uniqueID()); + SkASSERT(index > 0); + SkASSERT(fits_in(index, 24)); + fStream->write32(pack_verb(SkPipeVerb::kDefineImage, index)); + + uint32_t len = SkToU32(data->size()); + fStream->write32(SkAlign4(len)); + write_pad(fStream, data->data(), len); + + if (show_deduper_traffic) { + int size = image->width() * image->height() << 2; + SkDebugf(" defineImage(%d) %d -> %d\n", index - 1, size, len); + } + return index; + } + SkDebugf("+++ failed to encode image [%d %d]\n", image->width(), image->height()); + return 0; // failed to encode +} + +int SkPipeDeduper::findOrDefinePicture(SkPicture* picture) { + int index = fPictures.find(picture->uniqueID()); + SkASSERT(index >= 0); + if (index) { + if (show_deduper_traffic) { + SkDebugf(" reusePicture(%d)\n", index - 1); + } + return index; + } + + size_t prevWritten = fStream->bytesWritten(); + unsigned extra = 0; // 0 means we're defining a new picture, non-zero means undef_index + 1 + fStream->write32(pack_verb(SkPipeVerb::kDefinePicture, extra)); + const SkRect cull = picture->cullRect(); + fStream->write(&cull, sizeof(cull)); + picture->playback(fPipeCanvas); + // call fPictures.add *after* we're written the picture, so that any nested pictures will have + // already been defined, and we get the "last" index value. + index = fPictures.add(picture->uniqueID()); + ASSERT_FITS_IN(index, kObjectDefinitionBits); + fStream->write32(pack_verb(SkPipeVerb::kEndPicture, index)); + + SkDebugf(" definePicture(%d) %d\n", + index - 1, SkToU32(fStream->bytesWritten() - prevWritten)); + return index; +} + +static sk_sp encode(SkTypeface* tf) { + SkDynamicMemoryWStream stream; + tf->serialize(&stream); + return sk_sp(stream.detachAsData()); +} + +int SkPipeDeduper::findOrDefineTypeface(SkTypeface* typeface) { + if (!typeface) { + return 0; // default + } + + int index = fTypefaces.find(typeface->uniqueID()); + SkASSERT(index >= 0); + if (index) { + if (show_deduper_traffic) { + SkDebugf(" reuseTypeface(%d)\n", index - 1); + } + return index; + } + + sk_sp data = fTFSerializer ? fTFSerializer->serialize(typeface) : encode(typeface); + if (data) { + index = fTypefaces.add(typeface->uniqueID()); + SkASSERT(index > 0); + SkASSERT(fits_in(index, 24)); + fStream->write32(pack_verb(SkPipeVerb::kDefineTypeface, index)); + + uint32_t len = SkToU32(data->size()); + fStream->write32(SkAlign4(len)); + write_pad(fStream, data->data(), len); + + if (show_deduper_traffic) { + SkDebugf(" defineTypeface(%d) %d\n", index - 1, len); + } + return index; + } + SkDebugf("+++ failed to encode typeface %d\n", typeface->uniqueID()); + return 0; // failed to encode +} + +int SkPipeDeduper::findOrDefineFactory(SkFlattenable* flattenable) { + if (!flattenable) { + return 0; + } + + int index = fFactories.find(flattenable->getFactory()); + SkASSERT(index >= 0); + if (index) { + if (show_deduper_traffic) { + SkDebugf(" reuseFactory(%d)\n", index - 1); + } + return index; + } + + index = fFactories.add(flattenable->getFactory()); + ASSERT_FITS_IN(index, kIndex_DefineFactoryExtraBits); + const char* name = flattenable->getTypeName(); + size_t len = strlen(name); + ASSERT_FITS_IN(len, kNameLength_DefineFactoryExtraBits); + unsigned extra = (index << kNameLength_DefineFactoryExtraBits) | len; + size_t prevWritten = fStream->bytesWritten(); + fStream->write32(pack_verb(SkPipeVerb::kDefineFactory, extra)); + write_pad(fStream, name, len + 1); + if (false) { + SkDebugf(" defineFactory(%d) %d %s\n", + index - 1, SkToU32(fStream->bytesWritten() - prevWritten), name); + } + return index; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "SkPipe.h" + +class SkPipeSerializer::Impl { +public: + SkPipeDeduper fDeduper; + std::unique_ptr fCanvas; +}; + +SkPipeSerializer::SkPipeSerializer() : fImpl(new Impl) {} + +SkPipeSerializer::~SkPipeSerializer() { + if (fImpl->fCanvas) { + this->endWrite(); + } +} + +void SkPipeSerializer::setTypefaceSerializer(SkTypefaceSerializer* tfs) { + fImpl->fDeduper.setTypefaceSerializer(tfs); +} + +void SkPipeSerializer::resetCache() { + fImpl->fDeduper.resetCaches(); +} + +void SkPipeSerializer::write(SkPicture* picture, SkWStream* stream) { + stream->write32(kDefinePicture_ExtPipeVerb); + SkRect cull = picture->cullRect(); + stream->write(&cull.fLeft, sizeof(SkRect)); + picture->playback(this->beginWrite(cull, stream)); + this->endWrite(); +} + +void SkPipeSerializer::write(SkImage* image, SkWStream* stream) { + stream->write32(kDefineImage_ExtPipeVerb); + SkPipeWriter writer(stream, &fImpl->fDeduper); + writer.writeImage(image); +} + +SkCanvas* SkPipeSerializer::beginWrite(const SkRect& cull, SkWStream* stream) { + SkASSERT(nullptr == fImpl->fCanvas); + fImpl->fCanvas.reset(new SkPipeCanvas(cull, &fImpl->fDeduper, stream)); + fImpl->fDeduper.setStream(stream); + fImpl->fDeduper.setCanvas(fImpl->fCanvas.get()); + return fImpl->fCanvas.get(); +} + +void SkPipeSerializer::endWrite() { + fImpl->fCanvas->restoreToCount(1); + fImpl->fCanvas.reset(nullptr); +} diff --git a/src/pipe/SkPipeCanvas.h b/src/pipe/SkPipeCanvas.h new file mode 100644 index 0000000000..25353c922f --- /dev/null +++ b/src/pipe/SkPipeCanvas.h @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPipeCanvas_DEFINED +#define SkPipeCanvas_DEFINED + +#include "SkCanvas.h" +#include "SkDeduper.h" +#include "SkImage.h" +#include "SkPipe.h" +#include "SkTypeface.h" +#include "SkWriteBuffer.h" + +class SkPipeCanvas; +class SkPipeWriter; + +template class SkTIndexSet { +public: + void reset() { fArray.reset(); } + + // returns the found index or 0 + int find(const T& key) { + const Rec* stop = fArray.end(); + for (const Rec* curr = fArray.begin(); curr < stop; ++curr) { + if (key == curr->fKey) { + return curr->fIndex; + } + } + return 0; + } + + // returns the new index + int add(const T& key) { + Rec* rec = fArray.append(); + rec->fKey = key; + rec->fIndex = fNextIndex++; + return rec->fIndex; + } + +private: + struct Rec { + T fKey; + int fIndex; + }; + + SkTDArray fArray; + int fNextIndex = 1; +}; + +class SkPipeDeduper : public SkDeduper { +public: + void resetCaches() { + fImages.reset(); + fPictures.reset(); + fTypefaces.reset(); + fFactories.reset(); + } + + void setCanvas(SkPipeCanvas* canvas) { fPipeCanvas = canvas; } + void setStream(SkWStream* stream) { fStream = stream; } + void setTypefaceSerializer(SkTypefaceSerializer* tfs) { fTFSerializer = tfs; } + + int findOrDefineImage(SkImage*) override; + int findOrDefinePicture(SkPicture*) override; + int findOrDefineTypeface(SkTypeface*) override; + int findOrDefineFactory(SkFlattenable*) override; + +private: + SkPipeCanvas* fPipeCanvas = nullptr; + SkWStream* fStream = nullptr; + + SkTypefaceSerializer* fTFSerializer = nullptr; + + // All our keys (at the moment) are 32bit uniqueIDs + SkTIndexSet fImages; + SkTIndexSet fPictures; + SkTIndexSet fTypefaces; + SkTIndexSet fFactories; +}; + + +class SkPipeCanvas : public SkCanvas { +public: + SkPipeCanvas(const SkRect& cull, SkPipeDeduper*, SkWStream*); + ~SkPipeCanvas() override; + +protected: + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + void willRestore() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + + void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, + const SkPaint&) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], + int count, SkXfermode::Mode, const SkRect* cull, const SkPaint*) override; + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint&) override; + void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint&) override; + void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint&) override; + void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath&, const SkMatrix*, + const SkPaint&) override; + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint&) override; + void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[], + const SkRect* cull, const SkPaint& paint) override; + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], + SkXfermode*, const SkPaint&) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + + void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst, + const SkPaint*, SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst, + const SkPaint*) override; + void onDrawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint&) override; + + void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkRegion::Op) override; + + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + + // These we turn into images + void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*, + SrcRectConstraint) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst, + const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice& lattice, const SkRect& dst, + const SkPaint*) override; + +private: + SkPipeDeduper* fDeduper; + SkWStream* fStream; + + friend class SkPipeWriter; + + typedef SkCanvas INHERITED; +}; + + +#endif diff --git a/src/pipe/SkPipeFormat.h b/src/pipe/SkPipeFormat.h new file mode 100644 index 0000000000..f521657a7c --- /dev/null +++ b/src/pipe/SkPipeFormat.h @@ -0,0 +1,220 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPipeFormat_DEFINED +#define SkPipeFormat_DEFINED + +#include "SkTypes.h" + +#define kDefinePicture_ExtPipeVerb SkSetFourByteTag('s', 'k', 'p', 'i') +#define kDefineImage_ExtPipeVerb SkSetFourByteTag('s', 'k', 'i', 'm') + +enum class SkPipeVerb : uint8_t { + kSave, // extra == 0 + kSaveLayer, + kRestore, // extra == 0 + kConcat, // extra == SkMatrix::MaskType + + kClipRect, // extra == (SkRegion::Op << 1) | isAntiAlias:1 + kClipRRect, // extra == (SkRegion::Op << 1) | isAntiAlias:1 + kClipPath, // extra == (SkRegion::Op << 1) | isAntiAlias:1 + kClipRegion, // extra == (SkRegion::Op << 1) + + kDrawArc, // extra == useCenter + kDrawAtlas, // extra == has_colors | has_cull | has_paint | mode + kDrawDRRect, + kDrawText, // extra == byteLength:24 else next 32 + kDrawPosText, // extra == byteLength:24 else next 32 + kDrawPosTextH, // extra == byteLength:24 else next 32 + kDrawRegion, // extra == size:24 of region, or 0 means next 32 + kDrawTextOnPath, + kDrawTextBlob, + kDrawTextRSXform, // extra == (byteLength:23 << 1) else next 32 | has_cull_rect:1 + kDrawPatch, + kDrawPaint, // extra == 0 + kDrawPoints, // extra == PointMode + kDrawRect, // extra == 0 + kDrawPath, // extra == 0 + kDrawOval, // extra == 0 + kDrawRRect, // extra == 0 + + kDrawImage, // extra == has_paint:1 + kDrawImageRect, // extra == constraint | has_src_rect | has_paint + kDrawImageNine, // extra == has_paint:1 + kDrawImageLattice, // extra == x_count:8 | y_count:8 | has_paint:1 + + kDrawVertices, + + kDrawPicture, // extra == picture_index + kDrawAnnotation, // extra == (key_len_plus_1:23 << 1) else next 32 | has_data:1 + + kDefineImage, // extra == image_index + kDefineTypeface, + kDefineFactory, // extra == factory_index (followed by padded getTypeName string) + kDefinePicture, // extra == 0 or forget_index + 1 (0 means we're defining a new picture) + kEndPicture, // extra == picture_index +}; + +enum PaintUsage { + kText_PaintUsage = 1 << 0, + kTextBlob_PaintUsage = 1 << 1, + kGeometry_PaintUsage = 1 << 2, + kImage_PaintUsage = 1 << 3, + kSaveLayer_PaintUsage = 1 << 4, + kDrawPaint_PaintUsage = 1 << 5, + kVertices_PaintUsage = 1 << 6, + kRespectsStroke_PaintUsage = 1 << 7, + kUnknown_PaintUsage = 0xFF, +}; + +// must sum to <= 32 +enum BitsPerField { + kFlags_BPF = 16, + kFilter_BPF = 2, + kStyle_BPF = 2, + kCaps_BPF = 2, + kJoins_BPF = 2, + kHint_BPF = 2, + kAlign_BPF = 2, + kEncoding_BPF = 2, +}; + +enum { + kTextSize_NonDef = 1 << 0, + kTextScaleX_NonDef = 1 << 1, + kTextSkewX_NonDef = 1 << 2, + kStrokeWidth_NonDef = 1 << 3, + kStrokeMiter_NonDef = 1 << 4, + kColor_NonDef = 1 << 5, + kTypeface_NonDef = 1 << 6, + kPathEffect_NonDef = 1 << 7, + kShader_NonDef = 1 << 8, + kXfermode_NonDef = 1 << 9, + kMaskFilter_NonDef = 1 << 10, + kColorFilter_NonDef = 1 << 11, + kRasterizer_NonDef = 1 << 12, + kImageFilter_NonDef = 1 << 13, + kDrawLooper_NonDef = 1 << 14, +}; + +enum { + kFlags_SaveLayerMask = 0xFF, + kHasBounds_SaveLayerMask = 1 << 8, + kHasPaint_SaveLayerMask = 1 << 9, + kHasBackdrop_SaveLayerMask = 1 << 10, + kDontClipToLayer_SaveLayerMask = 1 << 11, +}; + +enum { + kObjectDefinitionBits = 20, + kIndex_ObjectDefinitionMask = ((1 << kObjectDefinitionBits) - 1), + kUser_ObjectDefinitionMask = 0x7 << kObjectDefinitionBits, + kUndef_ObjectDefinitionMask = 1 << 23, + // (Undef:1 | User:3 | Index:20) must fit in extra:24 +}; + +enum { + kTypeMask_ConcatMask = 0xF, + kSetMatrix_ConcatMask = 1 << 4, +}; + +enum { + kMode_DrawAtlasMask = 0xFF, + kHasColors_DrawAtlasMask = 1 << 8, + kHasCull_DrawAtlasMask = 1 << 9, + kHasPaint_DrawAtlasMask = 1 << 10, +}; + +enum { + kHasPaint_DrawImageMask = 1 << 0, +}; + +enum { + kConstraint_DrawImageRectMask = 1 << 0, + kHasPaint_DrawImageRectMask = 1 << 1, + kHasSrcRect_DrawImageRectMask = 1 << 2, +}; + +enum { + kHasPaint_DrawImageNineMask = 1 << 0, +}; + +enum { + // picture_index takes the first kObjectDefinitionBits bits + kHasMatrix_DrawPictureExtra = 1 << 21, + kHasPaint_DrawPictureExtra = 1 << 22, +}; + +enum { + kIndex_DefineFactoryExtraBits = 10, + kNameLength_DefineFactoryExtraBits = 14, // includes trailing 0 + kNameLength_DefineFactoryExtraMask = (1 << kNameLength_DefineFactoryExtraBits) - 1, +}; + +enum { + kModeEnum_DrawPatchExtraMask = 0xFF, + kExplicitXfer_DrawPatchExtraValue = 0xFF, + kHasColors_DrawPatchExtraMask = 0x100, + kHasTexture_DrawPatchExtraMask = 0x200, +}; + +enum { + // if we store a zero for VCount, then read an int after the packedverb for the vcount + kVCount_DrawVerticesMask = (1 << 11) - 1, + + kVMode_DrawVerticesShift = 11, + kVMode_DrawVerticesMask = 3 << kVMode_DrawVerticesShift, + + kXMode_DrawVerticesShift = 13, + kXMode_DrawVerticesMask = 0xFF << kXMode_DrawVerticesShift, + + kHasTex_DrawVerticesMask = 1 << 21, + kHasColors_DrawVerticesMask = 1 << 22, + kHasIndices_DrawVerticesMask = 1 << 23, +}; + +enum { + kTextLength_DrawTextOnPathMask = (1 << 16) - 1, + kMatrixType_DrawTextOnPathShift = 16, + kMatrixType_DrawTextOnPathMask = 0xF << kMatrixType_DrawTextOnPathShift, +}; + +enum { + kHasPaint_DrawImageLatticeMask = 1 << 0, + kHasFlags_DrawImageLatticeMask = 1 << 1, + kXCount_DrawImageLatticeShift = 2, // bits 2:9 are xcount or FF means 32bits follow + kYCount_DrawImageLatticeShift = 10, // bits 10:17 are ycount or FF means 32bits follow + kCount_DrawImageLatticeMask = 0xFF, // sentinel for 32bits follow, + // thus max inline count is 254 +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static inline bool fits_in(int value, int bits) { + return value >= 0 && value < (1 << bits); +} + +static inline void ASSERT_FITS_IN(int value, int bits) { + SkASSERT(fits_in(value, bits)); +} + +static inline uint32_t pack_verb(SkPipeVerb verb, unsigned extra = 0) { + //SkDebugf("pack [%d] %d\n", verb, extra); + ASSERT_FITS_IN((unsigned)verb, 8); + ASSERT_FITS_IN(extra, 24); + return ((uint32_t)verb << 24) | extra; +} + +static inline SkPipeVerb unpack_verb(uint32_t data) { + return (SkPipeVerb)(data >> 24); +} + +static inline unsigned unpack_verb_extra(uint32_t data) { + return data & 0xFFFFFF; +} + +#endif diff --git a/src/pipe/SkPipeReader.cpp b/src/pipe/SkPipeReader.cpp new file mode 100644 index 0000000000..d4375486d0 --- /dev/null +++ b/src/pipe/SkPipeReader.cpp @@ -0,0 +1,944 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCanvas.h" +#include "SkDeduper.h" +#include "SkPicture.h" +#include "SkPictureRecorder.h" +#include "SkPipe.h" +#include "SkPipeFormat.h" +#include "SkReadBuffer.h" +#include "SkRefSet.h" +#include "SkRSXform.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" + +class SkPipeReader; + +static bool do_playback(SkPipeReader& reader, SkCanvas* canvas, int* endPictureIndex = nullptr); + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkPipeInflator : public SkInflator { +public: + SkPipeInflator(SkRefSet* images, SkRefSet* pictures, + SkRefSet* typefaces, SkTDArray* factories, + SkTypefaceDeserializer* tfd) + : fImages(images) + , fPictures(pictures) + , fTypefaces(typefaces) + , fFactories(factories) + , fTFDeserializer(tfd) + {} + + SkImage* getImage(int index) override { + return index ? fImages->get(index - 1) : nullptr; + } + SkPicture* getPicture(int index) override { + return index ? fPictures->get(index - 1) : nullptr; + } + SkTypeface* getTypeface(int index) override { + return fTypefaces->get(index - 1); + } + SkFlattenable::Factory getFactory(int index) override { + return index ? fFactories->getAt(index - 1) : nullptr; + } + + bool setImage(int index, SkImage* img) { + return fImages->set(index - 1, img); + } + bool setPicture(int index, SkPicture* pic) { + return fPictures->set(index - 1, pic); + } + bool setTypeface(int index, SkTypeface* face) { + return fTypefaces->set(index - 1, face); + } + bool setFactory(int index, SkFlattenable::Factory factory) { + SkASSERT(index > 0); + SkASSERT(factory); + index -= 1; + if ((unsigned)index < (unsigned)fFactories->count()) { + (*fFactories)[index] = factory; + return true; + } + if (fFactories->count() == index) { + *fFactories->append() = factory; + return true; + } + SkDebugf("setFactory: index [%d] out of range %d\n", index, fFactories->count()); + return false; + } + + void setTypefaceDeserializer(SkTypefaceDeserializer* tfd) { + fTFDeserializer = tfd; + } + + sk_sp makeTypeface(const void* data, size_t size); + +private: + SkRefSet* fImages; + SkRefSet* fPictures; + SkRefSet* fTypefaces; + SkTDArray* fFactories; + + SkTypefaceDeserializer* fTFDeserializer; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +template const T* skip(SkReadBuffer& reader, int count = 1) { + return (const T*)reader.skip(count * sizeof(T)); +} + +static SkRRect read_rrect(SkReadBuffer& reader) { + SkRRect rrect; + rrect.readFromMemory(reader.skip(SkRRect::kSizeInMemory), SkRRect::kSizeInMemory); + return rrect; +} + +static SkMatrix read_sparse_matrix(SkReadBuffer& reader, SkMatrix::TypeMask tm) { + SkMatrix matrix; + matrix.reset(); + + if (tm & SkMatrix::kPerspective_Mask) { + matrix.set9(skip(reader, 9)); + } else if (tm & SkMatrix::kAffine_Mask) { + const SkScalar* tmp = skip(reader, 6); + matrix[SkMatrix::kMScaleX] = tmp[0]; + matrix[SkMatrix::kMSkewX] = tmp[1]; + matrix[SkMatrix::kMTransX] = tmp[2]; + matrix[SkMatrix::kMScaleY] = tmp[3]; + matrix[SkMatrix::kMSkewY] = tmp[4]; + matrix[SkMatrix::kMTransY] = tmp[5]; + } else if (tm & SkMatrix::kScale_Mask) { + const SkScalar* tmp = skip(reader, 4); + matrix[SkMatrix::kMScaleX] = tmp[0]; + matrix[SkMatrix::kMTransX] = tmp[1]; + matrix[SkMatrix::kMScaleY] = tmp[2]; + matrix[SkMatrix::kMTransY] = tmp[3]; + } else if (tm & SkMatrix::kTranslate_Mask) { + const SkScalar* tmp = skip(reader, 2); + matrix[SkMatrix::kMTransX] = tmp[0]; + matrix[SkMatrix::kMTransY] = tmp[1]; + } + // else read nothing for Identity + return matrix; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#define CHECK_SET_SCALAR(Field) \ + do { if (nondef & k##Field##_NonDef) { \ + paint.set##Field(reader.readScalar()); \ + }} while (0) + +#define CHECK_SET_FLATTENABLE(Field) \ + do { if (nondef & k##Field##_NonDef) { \ + paint.set##Field(reader.read##Field()); \ + }} while (0) + +/* + * Header: + * paint flags : 32 + * non_def bits : 16 + * xfermode enum : 8 + * pad zeros : 8 + */ +static SkPaint read_paint(SkReadBuffer& reader) { + uint32_t packedFlags = reader.read32(); + uint32_t extra = reader.read32(); + unsigned nondef = extra >> 16; + SkXfermode::Mode mode = (SkXfermode::Mode)((extra >> 8) & 0xFF); + SkASSERT((extra & 0xFF) == 0); + + SkPaint paint; + + packedFlags >>= 2; // currently unused + paint.setTextEncoding((SkPaint::TextEncoding)(packedFlags & 3)); packedFlags >>= 2; + paint.setTextAlign((SkPaint::Align)(packedFlags & 3)); packedFlags >>= 2; + paint.setHinting((SkPaint::Hinting)(packedFlags & 3)); packedFlags >>= 2; + paint.setStrokeJoin((SkPaint::Join)(packedFlags & 3)); packedFlags >>= 2; + paint.setStrokeCap((SkPaint::Cap)(packedFlags & 3)); packedFlags >>= 2; + paint.setStyle((SkPaint::Style)(packedFlags & 3)); packedFlags >>= 2; + paint.setFilterQuality((SkFilterQuality)(packedFlags & 3)); packedFlags >>= 2; + paint.setFlags(packedFlags); + + CHECK_SET_SCALAR(TextSize); + CHECK_SET_SCALAR(TextScaleX); + CHECK_SET_SCALAR(TextSkewX); + CHECK_SET_SCALAR(StrokeWidth); + CHECK_SET_SCALAR(StrokeMiter); + + if (nondef & kColor_NonDef) { + paint.setColor(reader.read32()); + } + + CHECK_SET_FLATTENABLE(Typeface); + CHECK_SET_FLATTENABLE(PathEffect); + CHECK_SET_FLATTENABLE(Shader); + CHECK_SET_FLATTENABLE(Xfermode); + CHECK_SET_FLATTENABLE(MaskFilter); + CHECK_SET_FLATTENABLE(ColorFilter); + CHECK_SET_FLATTENABLE(Rasterizer); + CHECK_SET_FLATTENABLE(ImageFilter); + CHECK_SET_FLATTENABLE(DrawLooper); + + if (!(nondef & kXfermode_NonDef)) { + paint.setXfermodeMode(mode); + } + + return paint; +} + +class SkPipeReader : public SkReadBuffer { +public: + SkPipeReader(SkPipeDeserializer* sink, const void* data, size_t size) + : SkReadBuffer(data, size) + , fSink(sink) + {} + + SkPipeDeserializer* fSink; + + SkFlattenable::Factory findFactory(const char name[]) { + SkFlattenable::Factory factory; + // Check if a custom Factory has been specified for this flattenable. + if (!(factory = this->getCustomFactory(SkString(name)))) { + // If there is no custom Factory, check for a default. + factory = SkFlattenable::NameToFactory(name); + } + return factory; + } + + void readPaint(SkPaint* paint) override { + *paint = read_paint(*this); + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef void (*SkPipeHandler)(SkPipeReader&, uint32_t packedVerb, SkCanvas*); + +static void save_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kSave == unpack_verb(packedVerb)); + canvas->save(); +} + +static void saveLayer_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kSaveLayer == unpack_verb(packedVerb)); + unsigned extra = unpack_verb_extra(packedVerb); + const SkRect* bounds = (extra & kHasBounds_SaveLayerMask) ? skip(reader) : nullptr; + SkPaint paintStorage, *paint = nullptr; + if (extra & kHasPaint_SaveLayerMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + sk_sp backdrop; + if (extra & kHasBackdrop_SaveLayerMask) { + backdrop = reader.readImageFilter(); + } + SkCanvas::SaveLayerFlags flags = (SkCanvas::SaveLayerFlags)(extra & kFlags_SaveLayerMask); + + // unremap this wacky flag + if (extra & kDontClipToLayer_SaveLayerMask) { + flags |= (1 << 31);//SkCanvas::kDontClipToLayer_PrivateSaveLayerFlag; + } + + canvas->saveLayer(SkCanvas::SaveLayerRec(bounds, paint, backdrop.get(), flags)); +} + +static void restore_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kRestore == unpack_verb(packedVerb)); + canvas->restore(); +} + +static void concat_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kConcat == unpack_verb(packedVerb)); + SkMatrix::TypeMask tm = (SkMatrix::TypeMask)(packedVerb & kTypeMask_ConcatMask); + const SkMatrix matrix = read_sparse_matrix(reader, tm); + if (packedVerb & kSetMatrix_ConcatMask) { + canvas->setMatrix(matrix); + } else { + canvas->concat(matrix); + } +} + +static void clipRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kClipRect == unpack_verb(packedVerb)); + SkRegion::Op op = (SkRegion::Op)(unpack_verb_extra(packedVerb) >> 1); + bool isAA = unpack_verb_extra(packedVerb) & 1; + canvas->clipRect(*skip(reader), op, isAA); +} + +static void clipRRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kClipRRect == unpack_verb(packedVerb)); + SkRegion::Op op = (SkRegion::Op)(unpack_verb_extra(packedVerb) >> 1); + bool isAA = unpack_verb_extra(packedVerb) & 1; + canvas->clipRRect(read_rrect(reader), op, isAA); +} + +static void clipPath_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kClipPath == unpack_verb(packedVerb)); + SkRegion::Op op = (SkRegion::Op)(unpack_verb_extra(packedVerb) >> 1); + bool isAA = unpack_verb_extra(packedVerb) & 1; + SkPath path; + reader.readPath(&path); + canvas->clipPath(path, op, isAA); +} + +static void clipRegion_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kClipRegion == unpack_verb(packedVerb)); + SkRegion::Op op = (SkRegion::Op)(unpack_verb_extra(packedVerb) >> 1); + SkRegion region; + reader.readRegion(®ion); + canvas->clipRegion(region, op); +} + +static void drawArc_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawArc == unpack_verb(packedVerb)); + const bool useCenter = (bool)(unpack_verb_extra(packedVerb) & 1); + const SkScalar* scalars = skip(reader, 6); // bounds[0..3], start[4], sweep[5] + const SkRect* bounds = (const SkRect*)scalars; + canvas->drawArc(*bounds, scalars[4], scalars[5], useCenter, read_paint(reader)); +} + +static void drawAtlas_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawAtlas == unpack_verb(packedVerb)); + SkXfermode::Mode mode = (SkXfermode::Mode)(packedVerb & kMode_DrawAtlasMask); + sk_sp image(reader.readImage()); + int count = reader.read32(); + const SkRSXform* xform = skip(reader, count); + const SkRect* rect = skip(reader, count); + const SkColor* color = nullptr; + if (packedVerb & kHasColors_DrawAtlasMask) { + color = skip(reader, count); + } + const SkRect* cull = nullptr; + if (packedVerb & kHasCull_DrawAtlasMask) { + cull = skip(reader); + } + SkPaint paintStorage, *paint = nullptr; + if (packedVerb & kHasPaint_DrawAtlasMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + canvas->drawAtlas(image, xform, rect, color, count, mode, cull, paint); +} + +static void drawDRRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawDRRect == unpack_verb(packedVerb)); + const SkRRect outer = read_rrect(reader); + const SkRRect inner = read_rrect(reader); + canvas->drawDRRect(outer, inner, read_paint(reader)); +} + +static void drawText_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawText == unpack_verb(packedVerb)); + uint32_t len = unpack_verb_extra(packedVerb); + if (0 == len) { + len = reader.read32(); + } + const void* text = reader.skip(SkAlign4(len)); + SkScalar x = reader.readScalar(); + SkScalar y = reader.readScalar(); + canvas->drawText(text, len, x, y, read_paint(reader)); +} + +static void drawPosText_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPosText == unpack_verb(packedVerb)); + uint32_t len = unpack_verb_extra(packedVerb); + if (0 == len) { + len = reader.read32(); + } + const void* text = reader.skip(SkAlign4(len)); + int count = reader.read32(); + const SkPoint* pos = skip(reader, count); + SkPaint paint = read_paint(reader); + SkASSERT(paint.countText(text, len) == count); + canvas->drawPosText(text, len, pos, paint); +} + +static void drawPosTextH_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPosTextH == unpack_verb(packedVerb)); + uint32_t len = unpack_verb_extra(packedVerb); + if (0 == len) { + len = reader.read32(); + } + const void* text = reader.skip(SkAlign4(len)); + int count = reader.read32(); + const SkScalar* xpos = skip(reader, count); + SkScalar constY = reader.readScalar(); + SkPaint paint = read_paint(reader); + SkASSERT(paint.countText(text, len) == count); + canvas->drawPosTextH(text, len, xpos, constY, paint); +} + +static void drawTextOnPath_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawTextOnPath == unpack_verb(packedVerb)); + uint32_t byteLength = packedVerb & kTextLength_DrawTextOnPathMask; + SkMatrix::TypeMask tm = (SkMatrix::TypeMask) + ((packedVerb & kMatrixType_DrawTextOnPathMask) >> kMatrixType_DrawTextOnPathShift); + + if (0 == byteLength) { + byteLength = reader.read32(); + } + const void* text = reader.skip(SkAlign4(byteLength)); + SkPath path; + reader.readPath(&path); + const SkMatrix* matrix = nullptr; + SkMatrix matrixStorage; + if (tm != SkMatrix::kIdentity_Mask) { + matrixStorage = read_sparse_matrix(reader, tm); + matrix = &matrixStorage; + } + canvas->drawTextOnPath(text, byteLength, path, matrix, read_paint(reader)); +} + +static void drawTextBlob_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + sk_sp tb = SkTextBlob::MakeFromBuffer(reader); + SkScalar x = reader.readScalar(); + SkScalar y = reader.readScalar(); + canvas->drawTextBlob(tb, x, y, read_paint(reader)); +} + +static void drawTextRSXform_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawTextRSXform == unpack_verb(packedVerb)); + uint32_t len = unpack_verb_extra(packedVerb) >> 1; + if (0 == len) { + len = reader.read32(); + } + const void* text = reader.skip(SkAlign4(len)); + int count = reader.read32(); + const SkRSXform* xform = skip(reader, count); + const SkRect* cull = (packedVerb & 1) ? skip(reader) : nullptr; + SkPaint paint = read_paint(reader); + SkASSERT(paint.countText(text, len) == count); + canvas->drawTextRSXform(text, len, xform, cull, paint); +} + +static void drawPatch_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPatch == unpack_verb(packedVerb)); + const SkColor* colors = nullptr; + const SkPoint* tex = nullptr; + const SkPoint* cubics = skip(reader, 12); + if (packedVerb & kHasColors_DrawPatchExtraMask) { + colors = skip(reader, 4); + } + if (packedVerb & kHasTexture_DrawPatchExtraMask) { + tex = skip(reader, 4); + } + sk_sp xfer; + unsigned mode = packedVerb & kModeEnum_DrawPatchExtraMask; + if (kExplicitXfer_DrawPatchExtraValue == mode) { + xfer = reader.readXfermode(); + } else { + if (mode != SkXfermode::kSrcOver_Mode) { + xfer = SkXfermode::Make((SkXfermode::Mode)mode); + } + } + canvas->drawPatch(cubics, colors, tex, xfer.get(), read_paint(reader)); +} + +static void drawPaint_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPaint == unpack_verb(packedVerb)); + canvas->drawPaint(read_paint(reader)); +} + +static void drawRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawRect == unpack_verb(packedVerb)); + const SkRect* rect = skip(reader); + canvas->drawRect(*rect, read_paint(reader)); +} + +static void drawRegion_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawRegion == unpack_verb(packedVerb)); + size_t size = unpack_verb_extra(packedVerb); + if (0 == size) { + size = reader.read32(); + } + SkRegion region; + region.readFromMemory(skip(reader, SkAlign4(size)), size); + canvas->drawRegion(region, read_paint(reader)); +} + +static void drawOval_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawOval == unpack_verb(packedVerb)); + const SkRect* rect = skip(reader); + canvas->drawOval(*rect, read_paint(reader)); +} + +static void drawRRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawRRect == unpack_verb(packedVerb)); + SkRRect rrect = read_rrect(reader); + canvas->drawRRect(rrect, read_paint(reader)); +} + +static void drawPath_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPath == unpack_verb(packedVerb)); + SkPath path; + reader.readPath(&path); + canvas->drawPath(path, read_paint(reader)); +} + +static void drawPoints_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPoints == unpack_verb(packedVerb)); + SkCanvas::PointMode mode = (SkCanvas::PointMode)unpack_verb_extra(packedVerb); + int count = reader.read32(); + const SkPoint* points = skip(reader, count); + canvas->drawPoints(mode, count, points, read_paint(reader)); +} + +static void drawImage_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawImage == unpack_verb(packedVerb)); + sk_sp image(reader.readImage()); + SkScalar x = reader.readScalar(); + SkScalar y = reader.readScalar(); + SkPaint paintStorage, *paint = nullptr; + if (packedVerb & kHasPaint_DrawImageMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + canvas->drawImage(image, x, y, paint); +} + +static void drawImageRect_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawImageRect == unpack_verb(packedVerb)); + sk_sp image(reader.readImage()); + SkCanvas::SrcRectConstraint constraint = + (SkCanvas::SrcRectConstraint)(packedVerb & kConstraint_DrawImageRectMask); + const SkRect* src = (packedVerb & kHasSrcRect_DrawImageRectMask) ? + skip(reader) : nullptr; + const SkRect* dst = skip(reader); + SkPaint paintStorage, *paint = nullptr; + if (packedVerb & kHasPaint_DrawImageRectMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + if (src) { + canvas->drawImageRect(image, *src, *dst, paint, constraint); + } else { + canvas->drawImageRect(image, *dst, paint); + } +} + +static void drawImageNine_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawImageNine == unpack_verb(packedVerb)); + sk_sp image(reader.readImage()); + const SkIRect* center = skip(reader); + const SkRect* dst = skip(reader); + SkPaint paintStorage, *paint = nullptr; + if (packedVerb & kHasPaint_DrawImageNineMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + canvas->drawImageNine(image, *center, *dst, paint); +} + +static void drawImageLattice_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawImageLattice == unpack_verb(packedVerb)); + sk_sp image(reader.readImage()); + + SkCanvas::Lattice lattice; + lattice.fXCount = (packedVerb >> kXCount_DrawImageLatticeShift) & kCount_DrawImageLatticeMask; + if (lattice.fXCount == kCount_DrawImageLatticeMask) { + lattice.fXCount = reader.read32(); + } + lattice.fYCount = (packedVerb >> kXCount_DrawImageLatticeShift) & kCount_DrawImageLatticeMask; + if (lattice.fYCount == kCount_DrawImageLatticeMask) { + lattice.fYCount = reader.read32(); + } + lattice.fXDivs = skip(reader, lattice.fXCount); + lattice.fYDivs = skip(reader, lattice.fYCount); + if (packedVerb & kHasFlags_DrawImageLatticeMask) { + int32_t count = (lattice.fXCount + 1) * (lattice.fYCount + 1); + SkASSERT(count > 0); + lattice.fFlags = skip(reader, SkAlign4(count)); + } else { + lattice.fFlags = nullptr; + } + const SkRect* dst = skip(reader); + + SkPaint paintStorage, *paint = nullptr; + if (packedVerb & kHasPaint_DrawImageLatticeMask) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + canvas->drawImageLattice(image.get(), lattice, *dst, paint); +} + +static void drawVertices_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawVertices == unpack_verb(packedVerb)); + SkCanvas::VertexMode vmode = (SkCanvas::VertexMode) + ((packedVerb & kVMode_DrawVerticesMask) >> kVMode_DrawVerticesShift); + int vertexCount = packedVerb & kVCount_DrawVerticesMask; + if (0 == vertexCount) { + vertexCount = reader.read32(); + } + sk_sp xfer; + unsigned xmode = (packedVerb & kXMode_DrawVerticesMask) >> kXMode_DrawVerticesShift; + if (0xFF == xmode) { + xfer = reader.readXfermode(); + } else { + xfer = SkXfermode::Make((SkXfermode::Mode)xmode); + } + const SkPoint* vertices = skip(reader, vertexCount); + const SkPoint* texs = nullptr; + if (packedVerb & kHasTex_DrawVerticesMask) { + texs = skip(reader, vertexCount); + } + const SkColor* colors = nullptr; + if (packedVerb & kHasColors_DrawVerticesMask) { + colors = skip(reader, vertexCount); + } + int indexCount = 0; + const uint16_t* indices = nullptr; + if (packedVerb & kHasIndices_DrawVerticesMask) { + indexCount = reader.read32(); + indices = skip(reader, indexCount); + } + + canvas->drawVertices(vmode, vertexCount, vertices, texs, colors, xfer.get(), + indices, indexCount, read_paint(reader)); +} + +static void drawPicture_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawPicture == unpack_verb(packedVerb)); + unsigned extra = unpack_verb_extra(packedVerb); + int index = extra & kIndex_ObjectDefinitionMask; + SkPicture* pic = reader.getInflator()->getPicture(index); + SkMatrix matrixStorage, *matrix = nullptr; + SkPaint paintStorage, *paint = nullptr; + if (extra & kHasMatrix_DrawPictureExtra) { + reader.readMatrix(&matrixStorage); + matrix = &matrixStorage; + } + if (extra & kHasPaint_DrawPictureExtra) { + paintStorage = read_paint(reader); + paint = &paintStorage; + } + canvas->drawPicture(pic, matrix, paint); +} + +static void drawAnnotation_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDrawAnnotation == unpack_verb(packedVerb)); + const SkRect* rect = skip(reader); + + // len includes the key's trailing 0 + uint32_t len = unpack_verb_extra(packedVerb) >> 1; + if (0 == len) { + len = reader.read32(); + } + const char* key = skip(reader, len); + sk_sp data; + if (packedVerb & 1) { + uint32_t size = reader.read32(); + data = SkData::MakeWithCopy(reader.skip(SkAlign4(size)), size); + } + canvas->drawAnnotation(*rect, key, data); +} + +#if 0 + stream.write("skiacodc", 8); + stream.write32(pmap.width()); + stream.write32(pmap.height()); + stream.write16(pmap.colorType()); + stream.write16(pmap.alphaType()); + stream.write32(0); // no colorspace for now + for (int y = 0; y < pmap.height(); ++y) { + stream.write(pmap.addr8(0, y), pmap.width()); + } +#endif + +static sk_sp make_from_skiaimageformat(const void* encoded, size_t encodedSize) { + if (encodedSize < 24) { + return nullptr; + } + + SkMemoryStream stream(encoded, encodedSize); + char signature[8]; + stream.read(signature, 8); + if (memcmp(signature, "skiaimgf", 8)) { + return nullptr; + } + + int width = stream.readU32(); + int height = stream.readU32(); + SkColorType ct = (SkColorType)stream.readU16(); + SkAlphaType at = (SkAlphaType)stream.readU16(); + SkASSERT(kAlpha_8_SkColorType == ct); + + SkDEBUGCODE(size_t colorSpaceSize =) stream.readU32(); + SkASSERT(0 == colorSpaceSize); + + SkImageInfo info = SkImageInfo::Make(width, height, ct, at); + size_t size = width * height; + sk_sp pixels = SkData::MakeUninitialized(size); + stream.read(pixels->writable_data(), size); + SkASSERT(encodedSize == SkAlign4(stream.getPosition())); + return SkImage::MakeRasterData(info, pixels, width); +} + +static sk_sp make_from_encoded(const sk_sp& data) { + sk_sp image = make_from_skiaimageformat(data->data(), data->size()); + if (!image) { + image = SkImage::MakeFromEncoded(data); + } + return image; +} + +static void defineImage_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDefineImage == unpack_verb(packedVerb)); + SkPipeInflator* inflator = (SkPipeInflator*)reader.getInflator(); + uint32_t extra = unpack_verb_extra(packedVerb); + int index = extra & kIndex_ObjectDefinitionMask; + + if (extra & kUndef_ObjectDefinitionMask) { + // zero-index means we are "forgetting" that cache entry + inflator->setImage(index, nullptr); + } else { + // we are defining a new image + sk_sp data = reader.readByteArrayAsData(); + sk_sp image = make_from_encoded(data); + if (!image) { + SkDebugf("-- failed to decode\n"); + } + inflator->setImage(index, image.get()); + } +} + +sk_sp SkPipeInflator::makeTypeface(const void* data, size_t size) { + if (fTFDeserializer) { + return fTFDeserializer->deserialize(data, size); + } + SkMemoryStream stream(data, size, false); + return SkTypeface::MakeDeserialize(&stream); +} + +static void defineTypeface_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDefineTypeface == unpack_verb(packedVerb)); + SkPipeInflator* inflator = (SkPipeInflator*)reader.getInflator(); + uint32_t extra = unpack_verb_extra(packedVerb); + int index = extra & kIndex_ObjectDefinitionMask; + + if (extra & kUndef_ObjectDefinitionMask) { + // zero-index means we are "forgetting" that cache entry + inflator->setTypeface(index, nullptr); + } else { + // we are defining a new image + sk_sp data = reader.readByteArrayAsData(); + // TODO: seems like we could "peek" to see the array, and not need to copy it. + sk_sp tf = inflator->makeTypeface(data->data(), data->size()); + inflator->setTypeface(index, tf.get()); + } +} + +static void defineFactory_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDefineFactory == unpack_verb(packedVerb)); + SkPipeInflator* inflator = (SkPipeInflator*)reader.getInflator(); + uint32_t extra = unpack_verb_extra(packedVerb); + int index = extra >> kNameLength_DefineFactoryExtraBits; + size_t len = extra & kNameLength_DefineFactoryExtraMask; + // +1 for the trailing null char + const char* name = (const char*)reader.skip(SkAlign4(len + 1)); + SkFlattenable::Factory factory = reader.findFactory(name); + if (factory) { + inflator->setFactory(index, factory); + } +} + +static void definePicture_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + SkASSERT(SkPipeVerb::kDefinePicture == unpack_verb(packedVerb)); + int deleteIndex = unpack_verb_extra(packedVerb); + + SkPipeInflator* inflator = (SkPipeInflator*)reader.getInflator(); + + if (deleteIndex) { + inflator->setPicture(deleteIndex - 1, nullptr); + } else { + SkPictureRecorder recorder; + int pictureIndex = -1; // invalid + const SkRect* cull = skip(reader); + do_playback(reader, recorder.beginRecording(*cull), &pictureIndex); + SkASSERT(pictureIndex > 0); + sk_sp picture = recorder.finishRecordingAsPicture(); + inflator->setPicture(pictureIndex, picture.get()); + } +} + +static void endPicture_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) { + sk_throw(); // never call me +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +struct HandlerRec { + SkPipeHandler fProc; + const char* fName; +}; + +#define HANDLER(name) { name##_handler, #name } +const HandlerRec gPipeHandlers[] = { + HANDLER(save), + HANDLER(saveLayer), + HANDLER(restore), + HANDLER(concat), + + HANDLER(clipRect), + HANDLER(clipRRect), + HANDLER(clipPath), + HANDLER(clipRegion), + + HANDLER(drawArc), + HANDLER(drawAtlas), + HANDLER(drawDRRect), + HANDLER(drawText), + HANDLER(drawPosText), + HANDLER(drawPosTextH), + HANDLER(drawRegion), + HANDLER(drawTextOnPath), + HANDLER(drawTextBlob), + HANDLER(drawTextRSXform), + HANDLER(drawPatch), + HANDLER(drawPaint), + HANDLER(drawPoints), + HANDLER(drawRect), + HANDLER(drawPath), + HANDLER(drawOval), + HANDLER(drawRRect), + + HANDLER(drawImage), + HANDLER(drawImageRect), + HANDLER(drawImageNine), + HANDLER(drawImageLattice), + + HANDLER(drawVertices), + + HANDLER(drawPicture), + HANDLER(drawAnnotation), + + HANDLER(defineImage), + HANDLER(defineTypeface), + HANDLER(defineFactory), + HANDLER(definePicture), + HANDLER(endPicture), // handled special -- should never be called +}; +#undef HANDLER + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class SkPipeDeserializer::Impl { +public: + SkRefSet fImages; + SkRefSet fPictures; + SkRefSet fTypefaces; + SkTDArray fFactories; + + SkTypefaceDeserializer* fTFDeserializer = nullptr; +}; + +SkPipeDeserializer::SkPipeDeserializer() : fImpl(new Impl) {} +SkPipeDeserializer::~SkPipeDeserializer() {} + +void SkPipeDeserializer::setTypefaceDeserializer(SkTypefaceDeserializer* tfd) { + fImpl->fTFDeserializer = tfd; +} + +sk_sp SkPipeDeserializer::readPicture(const void* data, size_t size) { + if (size < sizeof(uint32_t) + sizeof(SkRect)) { + return nullptr; + } + + uint32_t header; + memcpy(&header, data, 4); size -= 4; data = (const char*)data + 4; + if (kDefinePicture_ExtPipeVerb != header) { + return nullptr; + } + SkRect cull; + memcpy(&cull, data, sizeof(SkRect)); + size -= sizeof(SkRect); data = (const char*)data + sizeof(SkRect); + + SkPictureRecorder recorder; + this->playback(data, size, recorder.beginRecording(cull)); + return recorder.finishRecordingAsPicture(); +} + +sk_sp SkPipeDeserializer::readImage(const void* data, size_t size) { + if (size < sizeof(uint32_t)) { + return nullptr; + } + + uint32_t header; + memcpy(&header, data, 4); size -= 4; data = (const char*)data + 4; + if (kDefineImage_ExtPipeVerb != header) { + return nullptr; + } + + SkPipeInflator inflator(&fImpl->fImages, &fImpl->fPictures, + &fImpl->fTypefaces, &fImpl->fFactories, + fImpl->fTFDeserializer); + SkPipeReader reader(this, data, size); + reader.setInflator(&inflator); + return sk_sp(reader.readImage()); +} + +static bool do_playback(SkPipeReader& reader, SkCanvas* canvas, int* endPictureIndex) { + int indent = 0; + + const bool showEachVerb = false; + int counter = 0; + while (!reader.eof()) { + uint32_t prevOffset = reader.offset(); + uint32_t packedVerb = reader.read32(); + SkPipeVerb verb = unpack_verb(packedVerb); + if ((unsigned)verb >= SK_ARRAY_COUNT(gPipeHandlers)) { + SkDebugf("------- bad verb %d\n", verb); + return false; + } + if (SkPipeVerb::kRestore == verb) { + indent -= 1; + SkASSERT(indent >= 0); + } + + if (SkPipeVerb::kEndPicture == verb) { + if (endPictureIndex) { + *endPictureIndex = unpack_verb_extra(packedVerb); + } + return true; + } + HandlerRec rec = gPipeHandlers[(unsigned)verb]; + rec.fProc(reader, packedVerb, canvas); + if (showEachVerb) { + for (int i = 0; i < indent; ++i) { + SkDebugf(" "); + } + SkDebugf("%d [%d] %s %d\n", prevOffset, counter++, rec.fName, reader.offset() - prevOffset); + } + if (!reader.isValid()) { + SkDebugf("-------- bad reader\n"); + return false; + } + + switch (verb) { + case SkPipeVerb::kSave: + case SkPipeVerb::kSaveLayer: + indent += 1; + break; + default: + break; + } + } + return true; +} + +bool SkPipeDeserializer::playback(const void* data, size_t size, SkCanvas* canvas) { + SkPipeInflator inflator(&fImpl->fImages, &fImpl->fPictures, + &fImpl->fTypefaces, &fImpl->fFactories, + fImpl->fTFDeserializer); + SkPipeReader reader(this, data, size); + reader.setInflator(&inflator); + return do_playback(reader, canvas); +} + diff --git a/src/pipe/SkRefSet.h b/src/pipe/SkRefSet.h new file mode 100644 index 0000000000..5f23ba2191 --- /dev/null +++ b/src/pipe/SkRefSet.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRefSet_DEFINED +#define SkRefSet_DEFINED + +#include "SkRefCnt.h" +#include "SkTDArray.h" + +template class SkRefSet { +public: + ~SkRefSet() { fArray.unrefAll(); } + + T* get(int index) const { + SkASSERT((unsigned)index < (unsigned)fArray.count()); + return fArray[index]; + } + + bool set(int index, T* value) { + if ((unsigned)index < (unsigned)fArray.count()) { + SkRefCnt_SafeAssign(fArray[index], value); + return true; + } + if (fArray.count() == index && value) { + *fArray.append() = SkRef(value); + return true; + } + SkDebugf("SkRefSet: index [%d] out of range %d\n", index, fArray.count()); + return false; + } + +private: + SkTDArray fArray; +}; + +#endif diff --git a/tests/PipeTest.cpp b/tests/PipeTest.cpp new file mode 100644 index 0000000000..f345debb8c --- /dev/null +++ b/tests/PipeTest.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Resources.h" +#include "SkCanvas.h" +#include "SkPipe.h" +#include "SkPaint.h" +#include "SkStream.h" +#include "SkSurface.h" +#include "Test.h" + +#include "SkNullCanvas.h" +#include "SkAutoPixmapStorage.h" + +static void drain(SkPipeDeserializer* deserial, SkDynamicMemoryWStream* stream) { + std::unique_ptr canvas(SkCreateNullCanvas()); + sk_sp data = stream->detachAsData(); + deserial->playback(data->data(), data->size(), canvas.get()); +} + +static sk_sp drain_as_image(SkPipeDeserializer* deserial, SkDynamicMemoryWStream* stream) { + sk_sp data = stream->detachAsData(); + return deserial->readImage(data->data(), data->size()); +} + +static bool deep_equal(SkImage* a, SkImage* b) { + if (a->width() != b->width() || a->height() != b->height()) { + return false; + } + + const SkImageInfo info = SkImageInfo::MakeN32Premul(a->width(), a->height()); + SkAutoPixmapStorage pmapA, pmapB; + pmapA.alloc(info); + pmapB.alloc(info); + + if (!a->readPixels(pmapA, 0, 0) || !b->readPixels(pmapB, 0, 0)) { + return false; + } + + for (int y = 0; y < info.height(); ++y) { + if (memcmp(pmapA.addr32(0, y), pmapB.addr32(0, y), info.width() * sizeof(SkPMColor))) { + return false; + } + } + return true; +} + +DEF_TEST(Pipe_image, reporter) { + sk_sp img = GetResourceAsImage("mandrill_128.png"); + SkASSERT(img.get()); + + SkPipeSerializer serializer; + SkPipeDeserializer deserializer; + + SkDynamicMemoryWStream stream; + SkCanvas* wc = serializer.beginWrite(SkRect::MakeWH(100, 100), &stream); + wc->drawImage(img, 0, 0, nullptr); + serializer.endWrite(); + size_t offset0 = stream.bytesWritten(); + REPORTER_ASSERT(reporter, offset0 > 100); // the raw image must be sorta big + drain(&deserializer, &stream); + + // try drawing the same image again -- it should be much smaller + wc = serializer.beginWrite(SkRect::MakeWH(100, 100), &stream); + wc->drawImage(img, 0, 0, nullptr); + size_t offset1 = stream.bytesWritten(); + serializer.endWrite(); + REPORTER_ASSERT(reporter, offset1 <= 32); + drain(&deserializer, &stream); + + // try serializing the same image directly, again it should be small + serializer.write(img.get(), &stream); + size_t offset2 = stream.bytesWritten(); + REPORTER_ASSERT(reporter, offset2 <= 32); + auto img1 = drain_as_image(&deserializer, &stream); + REPORTER_ASSERT(reporter, deep_equal(img.get(), img1.get())); + + // try serializing the same image directly (again), check that it is the same! + serializer.write(img.get(), &stream); + size_t offset3 = stream.bytesWritten(); + REPORTER_ASSERT(reporter, offset3 <= 32); + auto img2 = drain_as_image(&deserializer, &stream); + REPORTER_ASSERT(reporter, img1.get() == img2.get()); +} -- cgit v1.2.3