From 32a4910e57b1fdd3c8671de1ee85e05ca21d079f Mon Sep 17 00:00:00 2001
From: Cary Clark <caryclark@google.com>
Date: Sun, 20 May 2018 23:15:43 +0000
Subject: [PATCH] Revert "remove toString"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit 5191880cbf3ee4d122b0d11b4945fbab0784fda7.

Reason for revert: broke flutter

Original change's description:
> remove toString
>
> toString may have been used by obsolete debugger only
> find out if that is so
>
> R=​brianosman@google.com,bsalomon@google.com
>
> Docs-Preview: https://skia.org/?cl=119894
> Bug:830651
> Change-Id: I737f19b7d3fbc869bea2f443fa3b5ed7c1393ffd
> Reviewed-on: https://skia-review.googlesource.com/119894
> Commit-Queue: Cary Clark <caryclark@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>

TBR=bsalomon@google.com,brianosman@google.com,caryclark@google.com,caryclark@skia.org

Change-Id: I9f81de6c3615ee0608bcea9081b77239b4b8816c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 830651
Reviewed-on: https://skia-review.googlesource.com/129340
Reviewed-by: Cary Clark <caryclark@google.com>
Commit-Queue: Cary Clark <caryclark@google.com>
---
 docs/SkBitmap_Reference.bmh                   |  32 ++++
 docs/SkImage_Reference.bmh                    |  33 ++++
 docs/SkMatrix_Reference.bmh                   |  35 +++-
 docs/SkPaint_Reference.bmh                    |  30 ++++
 docs/undocumented.bmh                         |   8 +
 gm/imagefiltersbase.cpp                       |  12 ++
 include/core/SkBitmap.h                       |   7 +
 include/core/SkColorFilter.h                  |   2 +
 include/core/SkDrawLooper.h                   |   1 +
 include/core/SkImage.h                        |   8 +
 include/core/SkImageFilter.h                  |   4 +-
 include/core/SkMaskFilter.h                   |   1 +
 include/core/SkMatrix.h                       |   8 +
 include/core/SkPaint.h                        |   7 +
 include/core/SkPathEffect.h                   |   1 +
 include/effects/Sk1DPathEffect.h              |   1 +
 include/effects/Sk2DPathEffect.h              |   4 +
 include/effects/SkColorFilterImageFilter.h    |   1 +
 include/effects/SkComposeImageFilter.h        |   1 +
 include/effects/SkCornerPathEffect.h          |   1 +
 include/effects/SkDiscretePathEffect.h        |   1 +
 include/effects/SkDisplacementMapEffect.h     |   2 +
 include/effects/SkDropShadowImageFilter.h     |   1 +
 include/effects/SkImageSource.h               |   1 +
 include/effects/SkLayerDrawLooper.h           |   2 +
 include/effects/SkLumaColorFilter.h           |   1 +
 include/effects/SkMagnifierImageFilter.h      |   1 +
 .../effects/SkMatrixConvolutionImageFilter.h  |   1 +
 include/effects/SkMergeImageFilter.h          |   1 +
 include/effects/SkMorphologyImageFilter.h     |   2 +
 include/effects/SkOffsetImageFilter.h         |   1 +
 include/effects/SkOverdrawColorFilter.h       |   2 +
 include/effects/SkPaintImageFilter.h          |   1 +
 include/effects/SkPictureImageFilter.h        |   1 +
 include/effects/SkTileImageFilter.h           |   1 +
 include/effects/SkToSRGBColorFilter.h         |   1 +
 samplecode/ClockFaceView.cpp                  |   4 +
 src/core/SkBitmap.cpp                         |  27 ++++
 src/core/SkBlitter.cpp                        |  13 ++
 src/core/SkBlurImageFilter.cpp                |  13 ++
 src/core/SkBlurMF.cpp                         |  18 +++
 src/core/SkColorFilter.cpp                    |  15 ++
 src/core/SkColorMatrixFilterRowMajor255.cpp   |  13 ++
 src/core/SkColorMatrixFilterRowMajor255.h     |   2 +
 src/core/SkDraw_vertices.cpp                  |  10 ++
 src/core/SkImageFilter.cpp                    |  29 ++++
 src/core/SkLocalMatrixImageFilter.cpp         |   5 +
 src/core/SkLocalMatrixImageFilter.h           |   1 +
 src/core/SkMaskFilter.cpp                     |  14 ++
 src/core/SkMatrix.cpp                         |   8 +-
 src/core/SkMatrixImageFilter.cpp              |  22 +++
 src/core/SkMatrixImageFilter.h                |   1 +
 src/core/SkModeColorFilter.cpp                |   7 +
 src/core/SkModeColorFilter.h                  |   2 +
 src/core/SkPaint.cpp                          | 149 ++++++++++++++++++
 src/core/SkPathEffect.cpp                     |  27 ++++
 src/core/SkStringUtils.cpp                    |  10 ++
 src/core/SkStringUtils.h                      |  10 ++
 src/effects/Sk1DPathEffect.cpp                |   8 +
 src/effects/Sk2DPathEffect.cpp                |  23 +++
 src/effects/SkAlphaThresholdFilter.cpp        |   8 +
 src/effects/SkArithmeticImageFilter.cpp       |  17 ++
 src/effects/SkColorFilterImageFilter.cpp      |  15 ++
 src/effects/SkColorMatrixFilter.cpp           |   2 +
 src/effects/SkComposeImageFilter.cpp          |  14 ++
 src/effects/SkCornerPathEffect.cpp            |   6 +
 src/effects/SkDashImpl.h                      |   1 +
 src/effects/SkDashPathEffect.cpp              |  12 ++
 src/effects/SkDiscretePathEffect.cpp          |   6 +
 src/effects/SkDisplacementMapEffect.cpp       |  14 ++
 src/effects/SkDropShadowImageFilter.cpp       |  21 +++
 src/effects/SkEmbossMaskFilter.cpp            |  19 +++
 src/effects/SkEmbossMaskFilter.h              |   1 +
 src/effects/SkHighContrastFilter.cpp          |   6 +
 src/effects/SkImageSource.cpp                 |   9 ++
 src/effects/SkLayerDrawLooper.cpp             |  56 +++++++
 src/effects/SkLightingImageFilter.cpp         |  14 ++
 src/effects/SkLumaColorFilter.cpp             |   4 +
 src/effects/SkMagnifierImageFilter.cpp        |   8 +
 .../SkMatrixConvolutionImageFilter.cpp        |  15 ++
 src/effects/SkMergeImageFilter.cpp            |  12 ++
 src/effects/SkMorphologyImageFilter.cpp       |  12 ++
 src/effects/SkOffsetImageFilter.cpp           |  10 ++
 src/effects/SkOverdrawColorFilter.cpp         |   8 +
 src/effects/SkPaintImageFilter.cpp            |   6 +
 src/effects/SkPictureImageFilter.cpp          |  12 ++
 src/effects/SkShaderMaskFilter.cpp            |   5 +
 src/effects/SkTableColorFilter.cpp            |  31 ++++
 src/effects/SkTableMaskFilter.cpp             |  13 ++
 src/effects/SkTileImageFilter.cpp             |  14 ++
 src/effects/SkToSRGBColorFilter.cpp           |   5 +
 src/effects/SkTrimPE.h                        |   1 +
 src/effects/SkTrimPathEffect.cpp              |   4 +
 src/effects/SkXfermodeImageFilter.cpp         |  17 ++
 src/gpu/GrTestUtils.h                         |   1 +
 src/gpu/text/GrSDFMaskFilter.cpp              |   5 +
 src/image/SkImage.cpp                         |   6 +
 src/shaders/SkColorFilterShader.cpp           |  13 ++
 src/shaders/SkColorFilterShader.h             |   1 +
 src/shaders/SkColorShader.cpp                 |  21 +++
 src/shaders/SkColorShader.h                   |   2 +
 src/shaders/SkComposeShader.cpp               |  15 ++
 src/shaders/SkComposeShader.h                 |   1 +
 src/shaders/SkEmptyShader.h                   |   1 +
 src/shaders/SkImageShader.cpp                 |  11 ++
 src/shaders/SkImageShader.h                   |   1 +
 src/shaders/SkLocalMatrixShader.cpp           |  10 ++
 src/shaders/SkLocalMatrixShader.h             |   1 +
 src/shaders/SkPerlinNoiseShader.cpp           |  32 ++++
 src/shaders/SkPictureShader.cpp               |  16 ++
 src/shaders/SkPictureShader.h                 |   1 +
 src/shaders/SkShader.cpp                      |  17 ++
 src/shaders/SkShaderBase.h                    |   2 +
 src/shaders/gradients/SkGradientShader.cpp    |  32 ++++
 src/shaders/gradients/SkGradientShaderPriv.h  |   1 +
 src/shaders/gradients/SkLinearGradient.cpp    |  10 ++
 src/shaders/gradients/SkLinearGradient.h      |   1 +
 src/shaders/gradients/SkRadialGradient.cpp    |  16 ++
 src/shaders/gradients/SkRadialGradient.h      |   1 +
 src/shaders/gradients/SkSweepGradient.cpp     |  14 ++
 src/shaders/gradients/SkSweepGradient.h       |   1 +
 .../gradients/SkTwoPointConicalGradient.cpp   |  25 +++
 .../gradients/SkTwoPointConicalGradient.h     |   1 +
 src/utils/SkShadowUtils.cpp                   |   5 +
 tests/CanvasTest.cpp                          |   5 +
 tests/GrShapeTest.cpp                         |   5 +
 tests/ImageFilterTest.cpp                     |  15 ++
 tests/PDFPrimitivesTest.cpp                   |   6 +
 tests/QuickRejectTest.cpp                     |   4 +
 tools/bookmaker/includeWriter.cpp             |   6 +-
 130 files changed, 1341 insertions(+), 10 deletions(-)

diff --git a/docs/SkBitmap_Reference.bmh b/docs/SkBitmap_Reference.bmh
index 353e3a18d5..967b9d1ca9 100644
--- a/docs/SkBitmap_Reference.bmh
+++ b/docs/SkBitmap_Reference.bmh
@@ -3438,6 +3438,38 @@ SK_DEBUG is defined at compile time.
 
 # ------------------------------------------------------------------------------
 
+#Method void toString(SkString* str) const;
+#In Utility
+#Line # converts Bitmap to machine readable form ##
+
+Creates string representation of Bitmap. The representation is read by
+internal debugging tools.
+
+#Param str  storage for string representation ##
+
+#Example
+    SkBitmap bitmap;
+    int width = 6;
+    int height = 11;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(width, height));
+    SkString string;
+    bitmap.toString(&string);
+    SkString match;
+    match.printf("(%d, %d)", width, height);
+    int start = string.find(match.c_str());
+    if (start >= 0) {
+        SkString whStr(&string.c_str()[start], match.size());
+        SkDebugf("bitmap dimensions %s\n", whStr.c_str());
+    }
+    #StdOut
+    bitmap dimensions (6, 11)
+    ##
+##
+
+#SeeAlso SkPaint::toString
+
+##
+
 #Class SkBitmap ##
 
 #Topic Bitmap ##
diff --git a/docs/SkImage_Reference.bmh b/docs/SkImage_Reference.bmh
index 40146e8f14..4f2549b03a 100644
--- a/docs/SkImage_Reference.bmh
+++ b/docs/SkImage_Reference.bmh
@@ -1749,6 +1749,39 @@ Returns nullptr if Image contents are not encoded.
 #Line # rarely called management functions ##
 ##
 
+#Method const char* toString(SkString* string) const
+#In Utility
+#Line # converts Image to machine readable form ##
+Appends Image description to string, including unique ID, width, height, and
+whether the image is opaque.
+
+#Param string  storage for description; existing content is preserved ##
+
+#Return string appended with Image description ##
+
+#Example
+#Image 4
+    struct {
+        const char* name;
+        sk_sp<SkImage> image;
+    } tests[] = { { "image", image }, { "bitmap", SkImage::MakeFromBitmap(source) },
+          { "texture", SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                            kTopLeft_GrSurfaceOrigin, kN32_SkColorType, kOpaque_SkAlphaType,
+                            nullptr) } };
+    SkString string;
+    SkPaint paint;
+    for (const auto& test : tests ) {
+        string.printf("%s: ", test.name);
+        test.image ? (void) test.image->toString(&string) : string.append("no image");
+        canvas->drawString(string, 10, 20, paint);
+        canvas->translate(0, 20);
+    }
+##
+
+#SeeAlso SkPaint::toString
+
+#Method ##
+
 # ------------------------------------------------------------------------------
 
 #Method sk_sp<SkImage> makeSubset(const SkIRect& subset) const
diff --git a/docs/SkMatrix_Reference.bmh b/docs/SkMatrix_Reference.bmh
index f48148e44e..e76accdb40 100644
--- a/docs/SkMatrix_Reference.bmh
+++ b/docs/SkMatrix_Reference.bmh
@@ -4103,7 +4103,40 @@ matrix != nearlyEqual
 ##
 ##
 
-#SeeAlso SkPath::dump
+#SeeAlso toString
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method void toString(SkString* str) const
+#In Utility
+#Line # converts Matrix to machine readable form ##
+Creates string representation of Matrix. Floating point values
+are written with limited precision; it may not be possible to reconstruct
+original Matrix from output.
+
+#Param str  storage for string representation of Matrix ##
+
+#Example
+    SkMatrix matrix;
+    matrix.setRotate(45);
+    SkString mStr, neStr;
+    matrix.toString(&mStr);
+    SkMatrix nearlyEqual;
+    nearlyEqual.setAll(0.7071f, -0.7071f, 0,   0.7071f, 0.7071f, 0,   0, 0, 1);
+    nearlyEqual.toString(&neStr);
+    SkDebugf("mStr  %s\n", mStr.c_str());
+    SkDebugf("neStr %s\n", neStr.c_str());
+    SkDebugf("matrix %c= nearlyEqual\n", matrix == nearlyEqual ? '=' : '!');
+#StdOut
+mStr  [  0.7071  -0.7071   0.0000][  0.7071   0.7071   0.0000][  0.0000   0.0000   1.0000]
+neStr [  0.7071  -0.7071   0.0000][  0.7071   0.7071   0.0000][  0.0000   0.0000   1.0000]
+matrix != nearlyEqual
+##
+##
+
+#SeeAlso dump
 
 ##
 
diff --git a/docs/SkPaint_Reference.bmh b/docs/SkPaint_Reference.bmh
index 9ae562fdf1..057135414e 100644
--- a/docs/SkPaint_Reference.bmh
+++ b/docs/SkPaint_Reference.bmh
@@ -5106,6 +5106,36 @@ Paint may draw to.
 #Line # rarely called management functions ##
 ##
 
+#Method void toString(SkString* str) const
+#In Utility
+#Line # converts Paint to machine readable form ##
+
+Creates string representation of Paint. The representation is read by
+internal debugging tools.
+
+#Param str  storage for string representation of Paint ##
+
+#Example
+    SkPaint paint;
+    SkString str;
+    paint.toString(&str);
+    const char textSize[] = "TextSize:";
+    const int trailerSize = strlen("</dd><dt>");
+    int textSizeLoc = str.find(textSize) + strlen(textSize) + trailerSize;
+    const char* sizeStart = &str.c_str()[textSizeLoc];
+    int textSizeEnd = SkStrFind(sizeStart, "</dd>");
+    SkDebugf("text size = %.*s\n", textSizeEnd, sizeStart);
+
+    #StdOut
+    text size = 12
+    ##
+
+##
+
+#SeeAlso SkPathEffect::toString SkMaskFilter::toString SkColorFilter::toString SkImageFilter::toString
+
+##
+
 # ------------------------------------------------------------------------------
 
 #Class SkPaint ##
diff --git a/docs/undocumented.bmh b/docs/undocumented.bmh
index 78a6624e7f..be0c1b5c2c 100644
--- a/docs/undocumented.bmh
+++ b/docs/undocumented.bmh
@@ -95,6 +95,8 @@ FT_Load_Glyph
 
 #Topic Color_Filter
 #Class SkColorFilter
+#Method void toString(SkString* str) const
+##
 #Class ##
 ##
 
@@ -287,6 +289,8 @@ FT_Load_Glyph
 
 #Topic Image_Filter
 #Class SkImageFilter
+#Method void toString(SkString* str) const
+##
 #Class ##
 #Topic ##
 
@@ -325,6 +329,8 @@ FT_Load_Glyph
 
 #Topic Mask_Filter
 #Class SkMaskFilter
+#Method void toString(SkString* str) const
+##
 #Class ##
 #Topic ##
 
@@ -434,6 +440,8 @@ FT_Load_Glyph
 
 #Topic Path_Effect
     #Class SkPathEffect
+    #Method void toString(SkString* str) const
+    ##
     #Class ##
 #Topic ##
 
diff --git a/gm/imagefiltersbase.cpp b/gm/imagefiltersbase.cpp
index d1a324a118..5432dee2a1 100644
--- a/gm/imagefiltersbase.cpp
+++ b/gm/imagefiltersbase.cpp
@@ -32,6 +32,7 @@ public:
         return sk_sp<SkImageFilter>(new FailImageFilter);
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(FailImageFilter)
 
 protected:
@@ -56,6 +57,11 @@ sk_sp<SkFlattenable> FailImageFilter::CreateProc(SkReadBuffer& buffer) {
     return FailImageFilter::Make();
 }
 
+void FailImageFilter::toString(SkString* str) const {
+    str->appendf("FailImageFilter: (");
+    str->append(")");
+}
+
 class IdentityImageFilter : public SkImageFilter {
 public:
     class Registrar {
@@ -70,6 +76,7 @@ public:
         return sk_sp<SkImageFilter>(new IdentityImageFilter(std::move(input)));
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(IdentityImageFilter)
 
 protected:
@@ -95,6 +102,11 @@ sk_sp<SkFlattenable> IdentityImageFilter::CreateProc(SkReadBuffer& buffer) {
     return IdentityImageFilter::Make(common.getInput(0));
 }
 
+void IdentityImageFilter::toString(SkString* str) const {
+    str->appendf("IdentityImageFilter: (");
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static void draw_paint(SkCanvas* canvas, const SkRect& r, sk_sp<SkImageFilter> imf) {
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 8c14a756ca..c4c9dc50a8 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -1246,6 +1246,13 @@ public:
         bool allocPixelRef(SkBitmap* bitmap) override;
     };
 
+    /** Creates string representation of SkBitmap. The representation is read by
+        internal debugging tools.
+
+        @param str  storage for string representation
+    */
+    void toString(SkString* str) const;
+
 private:
     enum Flags {
         kImageIsVolatile_Flag   = 0x02,
diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h
index eac58471c4..e8b09bae9c 100644
--- a/include/core/SkColorFilter.h
+++ b/include/core/SkColorFilter.h
@@ -138,6 +138,8 @@ public:
         return this->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT;
     }
 
+    virtual void toString(SkString* str) const = 0;
+
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
     SK_DEFINE_FLATTENABLE_TYPE(SkColorFilter)
 
diff --git a/include/core/SkDrawLooper.h b/include/core/SkDrawLooper.h
index f4b3373dca..4280daa4a9 100644
--- a/include/core/SkDrawLooper.h
+++ b/include/core/SkDrawLooper.h
@@ -96,6 +96,7 @@ public:
      */
     virtual bool asABlurShadow(BlurShadowRec*) const;
 
+    virtual void toString(SkString* str) const = 0;
     SK_DEFINE_FLATTENABLE_TYPE(SkDrawLooper)
 
 protected:
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index a7f3e4ab20..afcbb89a5b 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -727,6 +727,14 @@ public:
     */
     sk_sp<SkData> refEncodedData() const;
 
+    /** Appends SkImage description to string, including unique ID, width, height, and
+        whether the image is opaque.
+
+        @param string  storage for description; existing content is preserved
+        @return        string appended with SkImage description
+    */
+    const char* toString(SkString* string) const;
+
     /** Returns subset of SkImage. subset must be fully contained by SkImage dimensions().
         The implementation may share pixels, or may copy them.
 
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index e9594b535b..70f5513843 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -97,6 +97,7 @@ public:
             : fRect(rect), fFlags(flags) {}
         uint32_t flags() const { return fFlags; }
         const SkRect& rect() const { return fRect; }
+        void toString(SkString* str) const;
 
         /**
          *  Apply this cropRect to the imageBounds. If a given edge of the cropRect is not
@@ -248,8 +249,7 @@ public:
                                                  SkFilterQuality quality,
                                                  sk_sp<SkImageFilter> input);
 
-    // Deprecated; used only by chrome
-    virtual void toString(SkString* ) const {}
+    virtual void toString(SkString* str) const = 0;
     SK_DEFINE_FLATTENABLE_TYPE(SkImageFilter)
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
 
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index 45c5724843..bd2a20b825 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -59,6 +59,7 @@ public:
      */
     sk_sp<SkMaskFilter> makeWithMatrix(const SkMatrix&) const;
 
+    virtual void toString(SkString* str) const = 0;
     SK_DEFINE_FLATTENABLE_TYPE(SkMaskFilter)
 
 private:
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index 8ba4e1bb28..9733590167 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -1575,6 +1575,14 @@ public:
     */
     void dump() const;
 
+    /** Creates string representation of SkMatrix. Floating point values
+        are written with limited precision; it may not be possible to reconstruct
+        original SkMatrix from output.
+
+        @param str  storage for string representation of SkMatrix
+    */
+    void toString(SkString* str) const;
+
     /** Returns the minimum scaling factor of SkMatrix by decomposing the scaling and
         skewing elements.
         Returns -1 if scale factor overflows or SkMatrix contains perspective.
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 42a65cc1db..85c2e3b2f7 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -1598,6 +1598,13 @@ public:
     const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage,
                                       Style style) const;
 
+    /** Creates string representation of SkPaint. The representation is read by
+        internal debugging tools.
+
+        @param str  storage for string representation of SkPaint
+    */
+    void toString(SkString* str) const;
+
 private:
     typedef const SkGlyph& (*GlyphCacheProc)(SkGlyphCache*, const char**);
 
diff --git a/include/core/SkPathEffect.h b/include/core/SkPathEffect.h
index a85614a9d5..97b48ea503 100644
--- a/include/core/SkPathEffect.h
+++ b/include/core/SkPathEffect.h
@@ -145,6 +145,7 @@ public:
 
     virtual DashType asADash(DashInfo* info) const;
 
+    virtual void toString(SkString* str) const = 0;
     SK_DEFINE_FLATTENABLE_TYPE(SkPathEffect)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
diff --git a/include/effects/Sk1DPathEffect.h b/include/effects/Sk1DPathEffect.h
index b491da7818..88a36ff7c6 100644
--- a/include/effects/Sk1DPathEffect.h
+++ b/include/effects/Sk1DPathEffect.h
@@ -61,6 +61,7 @@ public:
     virtual bool filterPath(SkPath*, const SkPath&,
                             SkStrokeRec*, const SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPath1DPathEffect)
 
 protected:
diff --git a/include/effects/Sk2DPathEffect.h b/include/effects/Sk2DPathEffect.h
index 0a7922e388..5ebadab0d5 100644
--- a/include/effects/Sk2DPathEffect.h
+++ b/include/effects/Sk2DPathEffect.h
@@ -39,6 +39,8 @@ protected:
     explicit Sk2DPathEffect(const SkMatrix& mat);
     void flatten(SkWriteBuffer&) const override;
 
+    void toString(SkString* str) const override;
+
 private:
     SkMatrix    fMatrix, fInverse;
     bool        fMatrixIsInvertible;
@@ -63,6 +65,7 @@ public:
     virtual bool filterPath(SkPath* dst, const SkPath& src,
                             SkStrokeRec*, const SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLine2DPathEffect)
 
 protected:
@@ -90,6 +93,7 @@ public:
         return sk_sp<SkPathEffect>(new SkPath2DPathEffect(matrix, path));
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPath2DPathEffect)
 
 protected:
diff --git a/include/effects/SkColorFilterImageFilter.h b/include/effects/SkColorFilterImageFilter.h
index 1a7031fad3..491a2b05b0 100644
--- a/include/effects/SkColorFilterImageFilter.h
+++ b/include/effects/SkColorFilterImageFilter.h
@@ -18,6 +18,7 @@ public:
                                      sk_sp<SkImageFilter> input,
                                      const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterImageFilter)
 
 protected:
diff --git a/include/effects/SkComposeImageFilter.h b/include/effects/SkComposeImageFilter.h
index 4e1f27e422..0bfb7d8c5c 100644
--- a/include/effects/SkComposeImageFilter.h
+++ b/include/effects/SkComposeImageFilter.h
@@ -16,6 +16,7 @@ public:
 
     SkRect computeFastBounds(const SkRect& src) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeImageFilter)
 
 protected:
diff --git a/include/effects/SkCornerPathEffect.h b/include/effects/SkCornerPathEffect.h
index cea31415bc..ac9401a7bc 100644
--- a/include/effects/SkCornerPathEffect.h
+++ b/include/effects/SkCornerPathEffect.h
@@ -27,6 +27,7 @@ public:
     virtual bool filterPath(SkPath* dst, const SkPath& src,
                             SkStrokeRec*, const SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkCornerPathEffect)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
diff --git a/include/effects/SkDiscretePathEffect.h b/include/effects/SkDiscretePathEffect.h
index 45a184fcb2..eb2a994982 100644
--- a/include/effects/SkDiscretePathEffect.h
+++ b/include/effects/SkDiscretePathEffect.h
@@ -34,6 +34,7 @@ public:
     virtual bool filterPath(SkPath* dst, const SkPath& src,
                             SkStrokeRec*, const SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiscretePathEffect)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
diff --git a/include/effects/SkDisplacementMapEffect.h b/include/effects/SkDisplacementMapEffect.h
index ab5fdca773..daf03d742c 100644
--- a/include/effects/SkDisplacementMapEffect.h
+++ b/include/effects/SkDisplacementMapEffect.h
@@ -41,6 +41,8 @@ public:
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
+    void toString(SkString* str) const override;
+
 protected:
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
diff --git a/include/effects/SkDropShadowImageFilter.h b/include/effects/SkDropShadowImageFilter.h
index 7566a6eff3..d1ee44c168 100644
--- a/include/effects/SkDropShadowImageFilter.h
+++ b/include/effects/SkDropShadowImageFilter.h
@@ -29,6 +29,7 @@ public:
                                      const CropRect* cropRect = nullptr);
 
     SkRect computeFastBounds(const SkRect&) const override;
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDropShadowImageFilter)
 
 protected:
diff --git a/include/effects/SkImageSource.h b/include/effects/SkImageSource.h
index 1b5cbb3cf9..6ef855ab93 100644
--- a/include/effects/SkImageSource.h
+++ b/include/effects/SkImageSource.h
@@ -21,6 +21,7 @@ public:
 
     SkRect computeFastBounds(const SkRect& src) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkImageSource)
 
 protected:
diff --git a/include/effects/SkLayerDrawLooper.h b/include/effects/SkLayerDrawLooper.h
index 18a9b93991..10a7274394 100644
--- a/include/effects/SkLayerDrawLooper.h
+++ b/include/effects/SkLayerDrawLooper.h
@@ -75,6 +75,8 @@ public:
 
     bool asABlurShadow(BlurShadowRec* rec) const override;
 
+    void toString(SkString* str) const override;
+
     Factory getFactory() const override { return CreateProc; }
     static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer);
 
diff --git a/include/effects/SkLumaColorFilter.h b/include/effects/SkLumaColorFilter.h
index f81f4bdb21..bdf02d322e 100644
--- a/include/effects/SkLumaColorFilter.h
+++ b/include/effects/SkLumaColorFilter.h
@@ -33,6 +33,7 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLumaColorFilter)
 
 protected:
diff --git a/include/effects/SkMagnifierImageFilter.h b/include/effects/SkMagnifierImageFilter.h
index 460e40fe58..dda4190322 100644
--- a/include/effects/SkMagnifierImageFilter.h
+++ b/include/effects/SkMagnifierImageFilter.h
@@ -18,6 +18,7 @@ public:
                                      sk_sp<SkImageFilter> input,
                                      const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMagnifierImageFilter)
 
 protected:
diff --git a/include/effects/SkMatrixConvolutionImageFilter.h b/include/effects/SkMatrixConvolutionImageFilter.h
index b1875fd8a9..5361fef928 100644
--- a/include/effects/SkMatrixConvolutionImageFilter.h
+++ b/include/effects/SkMatrixConvolutionImageFilter.h
@@ -67,6 +67,7 @@ public:
                                      sk_sp<SkImageFilter> input,
                                      const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMatrixConvolutionImageFilter)
 
 protected:
diff --git a/include/effects/SkMergeImageFilter.h b/include/effects/SkMergeImageFilter.h
index ab8acc8411..b1a85d5c74 100644
--- a/include/effects/SkMergeImageFilter.h
+++ b/include/effects/SkMergeImageFilter.h
@@ -24,6 +24,7 @@ public:
         return Make(array, 2, cropRect);
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMergeImageFilter)
 
 protected:
diff --git a/include/effects/SkMorphologyImageFilter.h b/include/effects/SkMorphologyImageFilter.h
index 05a9c2e96d..d88a31dc65 100644
--- a/include/effects/SkMorphologyImageFilter.h
+++ b/include/effects/SkMorphologyImageFilter.h
@@ -61,6 +61,7 @@ public:
                                      sk_sp<SkImageFilter> input,
                                      const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDilateImageFilter)
 
 protected:
@@ -82,6 +83,7 @@ public:
                                      sk_sp<SkImageFilter> input,
                                      const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkErodeImageFilter)
 
 protected:
diff --git a/include/effects/SkOffsetImageFilter.h b/include/effects/SkOffsetImageFilter.h
index ac7be849a8..2d7b14f652 100644
--- a/include/effects/SkOffsetImageFilter.h
+++ b/include/effects/SkOffsetImageFilter.h
@@ -19,6 +19,7 @@ public:
 
     SkRect computeFastBounds(const SkRect& src) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkOffsetImageFilter)
 
 protected:
diff --git a/include/effects/SkOverdrawColorFilter.h b/include/effects/SkOverdrawColorFilter.h
index 2791eb06d0..63f636a0cc 100644
--- a/include/effects/SkOverdrawColorFilter.h
+++ b/include/effects/SkOverdrawColorFilter.h
@@ -32,6 +32,8 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
+
     static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer);
     Factory getFactory() const override { return CreateProc; }
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
diff --git a/include/effects/SkPaintImageFilter.h b/include/effects/SkPaintImageFilter.h
index 943e3cb50f..b27c2624b1 100644
--- a/include/effects/SkPaintImageFilter.h
+++ b/include/effects/SkPaintImageFilter.h
@@ -26,6 +26,7 @@ public:
 
     bool affectsTransparentBlack() const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPaintImageFilter)
 
 protected:
diff --git a/include/effects/SkPictureImageFilter.h b/include/effects/SkPictureImageFilter.h
index 92bf71d4b4..1885cfe6ae 100644
--- a/include/effects/SkPictureImageFilter.h
+++ b/include/effects/SkPictureImageFilter.h
@@ -24,6 +24,7 @@ public:
      */
     static sk_sp<SkImageFilter> Make(sk_sp<SkPicture> picture, const SkRect& cropRect);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPictureImageFilter)
 
 protected:
diff --git a/include/effects/SkTileImageFilter.h b/include/effects/SkTileImageFilter.h
index 94acb1abe6..f62d8a02d4 100644
--- a/include/effects/SkTileImageFilter.h
+++ b/include/effects/SkTileImageFilter.h
@@ -27,6 +27,7 @@ public:
                                MapDirection, const SkIRect* inputRect) const override;
     SkRect computeFastBounds(const SkRect& src) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTileImageFilter)
 
 protected:
diff --git a/include/effects/SkToSRGBColorFilter.h b/include/effects/SkToSRGBColorFilter.h
index fe202a7a4b..03bb37b172 100644
--- a/include/effects/SkToSRGBColorFilter.h
+++ b/include/effects/SkToSRGBColorFilter.h
@@ -26,6 +26,7 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkToSRGBColorFilter)
 
 private:
diff --git a/samplecode/ClockFaceView.cpp b/samplecode/ClockFaceView.cpp
index 0b1d2936eb..6041e3fe0b 100644
--- a/samplecode/ClockFaceView.cpp
+++ b/samplecode/ClockFaceView.cpp
@@ -85,6 +85,10 @@ public:
         return true;
     }
 
+    void toString(SkString* str) const override {
+        str->appendf("InverseFillPE: ()");
+    }
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(InverseFillPE)
 
 private:
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index 91510007cf..579573367f 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -633,6 +633,33 @@ void SkBitmap::validate() const {
 }
 #endif
 
+#include "SkString.h"
+void SkBitmap::toString(SkString* str) const {
+
+    static const char* gColorTypeNames[kLastEnum_SkColorType + 1] = {
+        "UNKNOWN", "A8", "565", "4444", "RGBA", "BGRA", "INDEX8",
+    };
+
+    str->appendf("bitmap: ((%d, %d) %s", this->width(), this->height(),
+                 gColorTypeNames[this->colorType()]);
+
+    str->append(" (");
+    if (this->isOpaque()) {
+        str->append("opaque");
+    } else {
+        str->append("transparent");
+    }
+    if (this->isImmutable()) {
+        str->append(", immutable");
+    } else {
+        str->append(", not-immutable");
+    }
+    str->append(")");
+
+    str->appendf(" pixelref:%p", this->pixelRef());
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkBitmap::peekPixels(SkPixmap* pmap) const {
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index 44cc8f1734..5cf8147019 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -861,6 +861,19 @@ public:
         typedef Context INHERITED;
     };
 
+    void toString(SkString* str) const override {
+        str->append("Sk3DShader: (");
+
+        if (fProxy) {
+            str->append("Proxy: ");
+            as_SB(fProxy)->toString(str);
+        }
+
+        this->INHERITED::toString(str);
+
+        str->append(")");
+    }
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sk3DShader)
 
 protected:
diff --git a/src/core/SkBlurImageFilter.cpp b/src/core/SkBlurImageFilter.cpp
index 818644818c..a53d061b37 100644
--- a/src/core/SkBlurImageFilter.cpp
+++ b/src/core/SkBlurImageFilter.cpp
@@ -41,6 +41,7 @@ public:
 
     SkRect computeFastBounds(const SkRect&) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl)
 
 protected:
@@ -696,3 +697,15 @@ SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMa
     SkVector sigma = map_sigma(fSigma, ctm);
     return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
 }
+
+void SkBlurImageFilterImpl::toString(SkString* str) const {
+    str->appendf("SkBlurImageFilterImpl: (");
+    str->appendf("sigma: (%f, %f) tileMode: %d input (", fSigma.fWidth, fSigma.fHeight,
+                 static_cast<int>(fTileMode));
+
+    if (this->getInput(0)) {
+        this->getInput(0)->toString(str);
+    }
+
+    str->append("))");
+}
diff --git a/src/core/SkBlurMF.cpp b/src/core/SkBlurMF.cpp
index 23cca83655..d716c33f68 100644
--- a/src/core/SkBlurMF.cpp
+++ b/src/core/SkBlurMF.cpp
@@ -77,6 +77,7 @@ public:
     void computeFastBounds(const SkRect&, SkRect*) const override;
     bool asABlur(BlurRec*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
 
 protected:
@@ -991,6 +992,23 @@ sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrContext* context,
 
 #endif // SK_SUPPORT_GPU
 
+
+void SkBlurMaskFilterImpl::toString(SkString* str) const {
+    str->append("SkBlurMaskFilterImpl: (");
+
+    str->append("sigma: ");
+    str->appendScalar(fSigma);
+    str->append(" ");
+
+    static const char* gStyleName[kLastEnum_SkBlurStyle + 1] = {
+        "normal", "solid", "outer", "inner"
+    };
+
+    str->appendf("style: %s ", gStyleName[fBlurStyle]);
+    str->appendf("respectCTM: %s ", fRespectCTM ? "true" : "false");
+    str->append(")");
+}
+
 void sk_register_blur_maskfilter_createproc() {
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
 }
diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp
index 41ebb42669..5362c1aca7 100644
--- a/src/core/SkColorFilter.cpp
+++ b/src/core/SkColorFilter.cpp
@@ -98,6 +98,15 @@ public:
         return fOuter->getFlags() & fInner->getFlags();
     }
 
+    void toString(SkString* str) const override {
+        SkString outerS, innerS;
+        fOuter->toString(&outerS);
+        fInner->toString(&innerS);
+        // These strings can be long.  SkString::appendf has limitations.
+        str->append(SkStringPrintf("SkComposeColorFilter: outer(%s) inner(%s)", outerS.c_str(),
+                                   innerS.c_str()));
+    }
+
     void onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkArenaAlloc* scratch,
                         bool shaderIsOpaque) const override {
         bool innerIsOpaque = shaderIsOpaque;
@@ -216,6 +225,8 @@ public:
     }
 #endif
 
+    void toString(SkString* str) const override;
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSRGBGammaColorFilter)
 
     void onAppendStages(SkRasterPipeline* p, SkColorSpace*, SkArenaAlloc* alloc,
@@ -256,6 +267,10 @@ sk_sp<SkFlattenable> SkSRGBGammaColorFilter::CreateProc(SkReadBuffer& buffer) {
     return sk_sp<SkFlattenable>(new SkSRGBGammaColorFilter(static_cast<Direction>(dir)));
 }
 
+void SkSRGBGammaColorFilter::toString(SkString* str) const {
+    str->append("srgbgamma");
+}
+
 template <SkSRGBGammaColorFilter::Direction dir>
 sk_sp<SkColorFilter> MakeSRGBGammaCF() {
     static SkColorFilter* gSingleton = new SkSRGBGammaColorFilter(dir);
diff --git a/src/core/SkColorMatrixFilterRowMajor255.cpp b/src/core/SkColorMatrixFilterRowMajor255.cpp
index 98fa95ae16..09b0e76136 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.cpp
+++ b/src/core/SkColorMatrixFilterRowMajor255.cpp
@@ -299,6 +299,19 @@ std::unique_ptr<GrFragmentProcessor> SkColorMatrixFilterRowMajor255::asFragmentP
 
 #endif
 
+void SkColorMatrixFilterRowMajor255::toString(SkString* str) const {
+    str->append("SkColorMatrixFilterRowMajor255: ");
+
+    str->append("matrix: (");
+    for (int i = 0; i < 20; ++i) {
+        str->appendScalar(fMatrix[i]);
+        if (i < 19) {
+            str->append(", ");
+        }
+    }
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkColorFilter> SkColorFilter::MakeMatrixFilterRowMajor255(const SkScalar array[20]) {
diff --git a/src/core/SkColorMatrixFilterRowMajor255.h b/src/core/SkColorMatrixFilterRowMajor255.h
index c2a38cf42f..0260379ef9 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.h
+++ b/src/core/SkColorMatrixFilterRowMajor255.h
@@ -27,6 +27,8 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorMatrixFilter)
 
 protected:
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index de6eaea5b6..000be07cea 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -77,6 +77,8 @@ public:
 
     bool isOpaque() const override { return fIsOpaque; }
 
+    void toString(SkString* str) const override;
+
     // For serialization.  This will never be called.
     Factory getFactory() const override { SK_ABORT("not reached"); return nullptr; }
 
@@ -97,6 +99,14 @@ private:
     typedef SkShaderBase INHERITED;
 };
 
+void SkTriColorShader::toString(SkString* str) const {
+    str->append("SkTriColorShader: (");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 static bool SK_WARN_UNUSED_RESULT
 update_tricolor_matrix(const SkMatrix& ctmInv, const SkPoint pts[], const SkPM4f colors[],
                        int index0, int index1, int index2, Matrix43* result) {
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 77095fb5b9..aabfef09bb 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -28,6 +28,35 @@
 #include "SkGr.h"
 #endif
 
+void SkImageFilter::CropRect::toString(SkString* str) const {
+    if (!fFlags) {
+        return;
+    }
+
+    str->appendf("cropRect (");
+    if (fFlags & CropRect::kHasLeft_CropEdge) {
+        str->appendf("%.2f, ", fRect.fLeft);
+    } else {
+        str->appendf("X, ");
+    }
+    if (fFlags & CropRect::kHasTop_CropEdge) {
+        str->appendf("%.2f, ", fRect.fTop);
+    } else {
+        str->appendf("X, ");
+    }
+    if (fFlags & CropRect::kHasWidth_CropEdge) {
+        str->appendf("%.2f, ", fRect.width());
+    } else {
+        str->appendf("X, ");
+    }
+    if (fFlags & CropRect::kHasHeight_CropEdge) {
+        str->appendf("%.2f", fRect.height());
+    } else {
+        str->appendf("X");
+    }
+    str->appendf(") ");
+}
+
 void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds,
                                       const SkMatrix& ctm,
                                       bool embiggen,
diff --git a/src/core/SkLocalMatrixImageFilter.cpp b/src/core/SkLocalMatrixImageFilter.cpp
index fb484c9448..07db7aff77 100644
--- a/src/core/SkLocalMatrixImageFilter.cpp
+++ b/src/core/SkLocalMatrixImageFilter.cpp
@@ -67,3 +67,8 @@ const {
     }
     return this->refMe();
 }
+
+void SkLocalMatrixImageFilter::toString(SkString* str) const {
+    str->append("SkLocalMatrixImageFilter: (");
+    str->append(")");
+}
diff --git a/src/core/SkLocalMatrixImageFilter.h b/src/core/SkLocalMatrixImageFilter.h
index 79cc73a4fd..89a7e9d145 100644
--- a/src/core/SkLocalMatrixImageFilter.h
+++ b/src/core/SkLocalMatrixImageFilter.h
@@ -18,6 +18,7 @@ class SkLocalMatrixImageFilter : public SkImageFilter {
 public:
     static sk_sp<SkImageFilter> Make(const SkMatrix& localM, sk_sp<SkImageFilter> input);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLocalMatrixImageFilter)
 
 protected:
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 3ff4f93405..52cdc3cc9b 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -405,6 +405,7 @@ public:
     }
 
     SkMask::Format getFormat() const override { return SkMask::kA8_Format; }
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeMF)
 
 protected:
@@ -469,6 +470,10 @@ sk_sp<SkFlattenable> SkComposeMF::CreateProc(SkReadBuffer& buffer) {
     return SkMaskFilter::MakeCompose(std::move(outer), std::move(inner));
 }
 
+void SkComposeMF::toString(SkString* str) const {
+    str->set("SkComposeMF:");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class SkCombineMF : public SkMaskFilterBase {
@@ -493,6 +498,7 @@ public:
 
     SkMask::Format getFormat() const override { return SkMask::kA8_Format; }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkCombineMF)
 
 protected:
@@ -619,6 +625,10 @@ sk_sp<SkFlattenable> SkCombineMF::CreateProc(SkReadBuffer& buffer) {
     return SkMaskFilter::MakeCombine(std::move(dst), std::move(src), mode);
 }
 
+void SkCombineMF::toString(SkString* str) const {
+    str->set("SkCombineMF:");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class SkMatrixMF : public SkMaskFilterBase {
@@ -642,6 +652,10 @@ public:
 
     SkMask::Format getFormat() const override { return as_MFB(fFilter)->getFormat(); }
 
+    void toString(SkString* str) const override {
+        str->set("SkMatrixMF:");
+    }
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLocalMatrixMF)
 
 protected:
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index d3bdb38b78..990f5b4652 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -1659,10 +1659,14 @@ size_t SkMatrix::readFromMemory(const void* buffer, size_t length) {
 
 void SkMatrix::dump() const {
     SkString str;
-    str.appendf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]",
+    this->toString(&str);
+    SkDebugf("%s\n", str.c_str());
+}
+
+void SkMatrix::toString(SkString* str) const {
+    str->appendf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]",
              fMat[0], fMat[1], fMat[2], fMat[3], fMat[4], fMat[5],
              fMat[6], fMat[7], fMat[8]);
-    SkDebugf("%s\n", str.c_str());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkMatrixImageFilter.cpp b/src/core/SkMatrixImageFilter.cpp
index e56abbc96a..47b0d4245e 100644
--- a/src/core/SkMatrixImageFilter.cpp
+++ b/src/core/SkMatrixImageFilter.cpp
@@ -143,3 +143,25 @@ SkIRect SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatr
 
     return result;
 }
+
+void SkMatrixImageFilter::toString(SkString* str) const {
+    str->appendf("SkMatrixImageFilter: (");
+
+    str->appendf("transform: (%f %f %f %f %f %f %f %f %f)",
+                 fTransform[SkMatrix::kMScaleX],
+                 fTransform[SkMatrix::kMSkewX],
+                 fTransform[SkMatrix::kMTransX],
+                 fTransform[SkMatrix::kMSkewY],
+                 fTransform[SkMatrix::kMScaleY],
+                 fTransform[SkMatrix::kMTransY],
+                 fTransform[SkMatrix::kMPersp0],
+                 fTransform[SkMatrix::kMPersp1],
+                 fTransform[SkMatrix::kMPersp2]);
+
+    str->append("<dt>FilterLevel:</dt><dd>");
+    static const char* gFilterLevelStrings[] = { "None", "Low", "Medium", "High" };
+    str->append(gFilterLevelStrings[fFilterQuality]);
+    str->append("</dd>");
+
+    str->appendf(")");
+}
diff --git a/src/core/SkMatrixImageFilter.h b/src/core/SkMatrixImageFilter.h
index 1a6bcf5ebf..b43df9166d 100644
--- a/src/core/SkMatrixImageFilter.h
+++ b/src/core/SkMatrixImageFilter.h
@@ -31,6 +31,7 @@ public:
 
     SkRect computeFastBounds(const SkRect&) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkMatrixImageFilter)
 
 protected:
diff --git a/src/core/SkModeColorFilter.cpp b/src/core/SkModeColorFilter.cpp
index 7e1e26e91a..b3d6aa2e8d 100644
--- a/src/core/SkModeColorFilter.cpp
+++ b/src/core/SkModeColorFilter.cpp
@@ -31,6 +31,13 @@ SkModeColorFilter::SkModeColorFilter(SkColor color, SkBlendMode mode) {
     fPMColor = SkPreMultiplyColor(fColor);
 }
 
+void SkModeColorFilter::toString(SkString* str) const {
+    str->append("SkModeColorFilter: color: 0x");
+    str->appendHex(fColor);
+    str->append(" mode: ");
+    str->append(SkBlendMode_Name(fMode));
+}
+
 bool SkModeColorFilter::asColorMode(SkColor* color, SkBlendMode* mode) const {
     if (color) {
         *color = fColor;
diff --git a/src/core/SkModeColorFilter.h b/src/core/SkModeColorFilter.h
index 025839fa47..a2a629f2c1 100644
--- a/src/core/SkModeColorFilter.h
+++ b/src/core/SkModeColorFilter.h
@@ -22,6 +22,8 @@ public:
     bool asColorMode(SkColor*, SkBlendMode*) const override;
     uint32_t getFlags() const override;
 
+    void toString(SkString* str) const override;
+
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
             GrContext*, const GrColorSpaceInfo&) const override;
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index c76b98121e..82dec0ea8c 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -1384,6 +1384,155 @@ const SkRect& SkPaint::doComputeFastBounds(const SkRect& origSrc,
     return *storage;
 }
 
+void SkPaint::toString(SkString* str) const {
+    str->append("<dl><dt>SkPaint:</dt><dd><dl>");
+
+    SkTypeface* typeface = this->getTypeface();
+    if (typeface) {
+        SkDynamicMemoryWStream ostream;
+        typeface->serialize(&ostream);
+        std::unique_ptr<SkStreamAsset> istream(ostream.detachAsStream());
+
+        SkFontDescriptor descriptor;
+        if (!SkFontDescriptor::Deserialize(istream.get(), &descriptor)) {
+            str->append("<dt>FontDescriptor deserialization failed</dt>");
+        } else {
+            str->append("<dt>Font Family Name:</dt><dd>");
+            str->append(descriptor.getFamilyName());
+            str->append("</dd><dt>Font Full Name:</dt><dd>");
+            str->append(descriptor.getFullName());
+            str->append("</dd><dt>Font PS Name:</dt><dd>");
+            str->append(descriptor.getPostscriptName());
+            str->append("</dd>");
+        }
+    }
+
+    str->append("<dt>TextSize:</dt><dd>");
+    str->appendScalar(this->getTextSize());
+    str->append("</dd>");
+
+    str->append("<dt>TextScaleX:</dt><dd>");
+    str->appendScalar(this->getTextScaleX());
+    str->append("</dd>");
+
+    str->append("<dt>TextSkewX:</dt><dd>");
+    str->appendScalar(this->getTextSkewX());
+    str->append("</dd>");
+
+    SkPathEffect* pathEffect = this->getPathEffect();
+    if (pathEffect) {
+        str->append("<dt>PathEffect:</dt><dd>");
+        pathEffect->toString(str);
+        str->append("</dd>");
+    }
+
+    if (const auto* shader = as_SB(this->getShader())) {
+        str->append("<dt>Shader:</dt><dd>");
+        shader->toString(str);
+        str->append("</dd>");
+    }
+
+    if (!this->isSrcOver()) {
+        str->appendf("<dt>Xfermode:</dt><dd>%d</dd>", fBlendMode);
+    }
+
+    SkMaskFilter* maskFilter = this->getMaskFilter();
+    if (maskFilter) {
+        str->append("<dt>MaskFilter:</dt><dd>");
+        as_MFB(maskFilter)->toString(str);
+        str->append("</dd>");
+    }
+
+    SkColorFilter* colorFilter = this->getColorFilter();
+    if (colorFilter) {
+        str->append("<dt>ColorFilter:</dt><dd>");
+        colorFilter->toString(str);
+        str->append("</dd>");
+    }
+
+    SkDrawLooper* looper = this->getLooper();
+    if (looper) {
+        str->append("<dt>DrawLooper:</dt><dd>");
+        looper->toString(str);
+        str->append("</dd>");
+    }
+
+    SkImageFilter* imageFilter = this->getImageFilter();
+    if (imageFilter) {
+        str->append("<dt>ImageFilter:</dt><dd>");
+        imageFilter->toString(str);
+        str->append("</dd>");
+    }
+
+    str->append("<dt>Color:</dt><dd>0x");
+    SkColor color = this->getColor();
+    str->appendHex(color);
+    str->append("</dd>");
+
+    str->append("<dt>Stroke Width:</dt><dd>");
+    str->appendScalar(this->getStrokeWidth());
+    str->append("</dd>");
+
+    str->append("<dt>Stroke Miter:</dt><dd>");
+    str->appendScalar(this->getStrokeMiter());
+    str->append("</dd>");
+
+    str->append("<dt>Flags:</dt><dd>(");
+    if (this->getFlags()) {
+        bool needSeparator = false;
+        SkAddFlagToString(str, this->isAntiAlias(), "AntiAlias", &needSeparator);
+        SkAddFlagToString(str, this->isDither(), "Dither", &needSeparator);
+        SkAddFlagToString(str, this->isFakeBoldText(), "FakeBoldText", &needSeparator);
+        SkAddFlagToString(str, this->isLinearText(), "LinearText", &needSeparator);
+        SkAddFlagToString(str, this->isSubpixelText(), "SubpixelText", &needSeparator);
+        SkAddFlagToString(str, this->isLCDRenderText(), "LCDRenderText", &needSeparator);
+        SkAddFlagToString(str, this->isEmbeddedBitmapText(),
+                          "EmbeddedBitmapText", &needSeparator);
+        SkAddFlagToString(str, this->isAutohinted(), "Autohinted", &needSeparator);
+        SkAddFlagToString(str, this->isVerticalText(), "VerticalText", &needSeparator);
+    } else {
+        str->append("None");
+    }
+    str->append(")</dd>");
+
+    str->append("<dt>FilterLevel:</dt><dd>");
+    static const char* gFilterQualityStrings[] = { "None", "Low", "Medium", "High" };
+    str->append(gFilterQualityStrings[this->getFilterQuality()]);
+    str->append("</dd>");
+
+    str->append("<dt>TextAlign:</dt><dd>");
+    static const char* gTextAlignStrings[SkPaint::kAlignCount] = { "Left", "Center", "Right" };
+    str->append(gTextAlignStrings[this->getTextAlign()]);
+    str->append("</dd>");
+
+    str->append("<dt>CapType:</dt><dd>");
+    static const char* gStrokeCapStrings[SkPaint::kCapCount] = { "Butt", "Round", "Square" };
+    str->append(gStrokeCapStrings[this->getStrokeCap()]);
+    str->append("</dd>");
+
+    str->append("<dt>JoinType:</dt><dd>");
+    static const char* gJoinStrings[SkPaint::kJoinCount] = { "Miter", "Round", "Bevel" };
+    str->append(gJoinStrings[this->getStrokeJoin()]);
+    str->append("</dd>");
+
+    str->append("<dt>Style:</dt><dd>");
+    static const char* gStyleStrings[SkPaint::kStyleCount] = { "Fill", "Stroke", "StrokeAndFill" };
+    str->append(gStyleStrings[this->getStyle()]);
+    str->append("</dd>");
+
+    str->append("<dt>TextEncoding:</dt><dd>");
+    static const char* gTextEncodingStrings[] = { "UTF8", "UTF16", "UTF32", "GlyphID" };
+    str->append(gTextEncodingStrings[this->getTextEncoding()]);
+    str->append("</dd>");
+
+    str->append("<dt>Hinting:</dt><dd>");
+    static const char* gHintingStrings[] = { "None", "Slight", "Normal", "Full" };
+    str->append(gHintingStrings[this->getHinting()]);
+    str->append("</dd>");
+
+    str->append("</dd></dl></dl>");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static bool has_thick_frame(const SkPaint& paint) {
diff --git a/src/core/SkPathEffect.cpp b/src/core/SkPathEffect.cpp
index e315275196..09d7e542c7 100644
--- a/src/core/SkPathEffect.cpp
+++ b/src/core/SkPathEffect.cpp
@@ -51,10 +51,23 @@ protected:
     sk_sp<SkPathEffect> fPE0;
     sk_sp<SkPathEffect> fPE1;
 
+    void toString(SkString* str) const override;
+
 private:
     typedef SkPathEffect INHERITED;
 };
 
+void SkPairPathEffect::toString(SkString* str) const {
+    str->appendf("first: ");
+    if (fPE0) {
+        fPE0->toString(str);
+    }
+    str->appendf(" second: ");
+    if (fPE1) {
+        fPE1->toString(str);
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 /** \class SkComposePathEffect
@@ -91,6 +104,7 @@ public:
     }
 
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposePathEffect)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
@@ -116,6 +130,12 @@ sk_sp<SkFlattenable> SkComposePathEffect::CreateProc(SkReadBuffer& buffer) {
     return SkComposePathEffect::Make(std::move(pe0), std::move(pe1));
 }
 
+void SkComposePathEffect::toString(SkString* str) const {
+    str->appendf("SkComposePathEffect: (");
+    this->INHERITED::toString(str);
+    str->appendf(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 /** \class SkSumPathEffect
@@ -148,6 +168,7 @@ public:
     }
 
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSumPathEffect)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
@@ -173,6 +194,12 @@ sk_sp<SkFlattenable> SkSumPathEffect::CreateProc(SkReadBuffer& buffer) {
     return SkSumPathEffect::Make(pe0, pe1);
 }
 
+void SkSumPathEffect::toString(SkString* str) const {
+    str->appendf("SkSumPathEffect: (");
+    this->INHERITED::toString(str);
+    str->appendf(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkPathEffect> SkPathEffect::MakeSum(sk_sp<SkPathEffect> first, sk_sp<SkPathEffect> second) {
diff --git a/src/core/SkStringUtils.cpp b/src/core/SkStringUtils.cpp
index 00d8c8787c..9745b2325b 100644
--- a/src/core/SkStringUtils.cpp
+++ b/src/core/SkStringUtils.cpp
@@ -9,6 +9,16 @@
 #include "SkStringUtils.h"
 #include "SkUtils.h"
 
+void SkAddFlagToString(SkString* string, bool flag, const char* flagStr, bool* needSeparator) {
+    if (flag) {
+        if (*needSeparator) {
+            string->append("|");
+        }
+        string->append(flagStr);
+        *needSeparator = true;
+    }
+}
+
 void SkAppendScalar(SkString* str, SkScalar value, SkScalarAsStringType asType) {
     switch (asType) {
         case kHex_SkScalarAsStringType:
diff --git a/src/core/SkStringUtils.h b/src/core/SkStringUtils.h
index ac587d6717..f2a1443ff4 100644
--- a/src/core/SkStringUtils.h
+++ b/src/core/SkStringUtils.h
@@ -12,6 +12,16 @@
 
 class SkString;
 
+/**
+ * Add 'flagStr' to 'string' and set 'needSeparator' to true only if 'flag' is
+ * true. If 'needSeparator' is true append a '|' before 'flagStr'. This method
+ * is used to streamline the creation of ASCII flag strings within the toString
+ * methods.
+ */
+void SkAddFlagToString(SkString* string, bool flag,
+                       const char* flagStr, bool* needSeparator);
+
+
 enum SkScalarAsStringType {
     kDec_SkScalarAsStringType,
     kHex_SkScalarAsStringType,
diff --git a/src/effects/Sk1DPathEffect.cpp b/src/effects/Sk1DPathEffect.cpp
index 1837479147..57f2d2b6ce 100644
--- a/src/effects/Sk1DPathEffect.cpp
+++ b/src/effects/Sk1DPathEffect.cpp
@@ -194,6 +194,14 @@ SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance,
     return fAdvance;
 }
 
+
+void SkPath1DPathEffect::toString(SkString* str) const {
+    str->appendf("SkPath1DPathEffect: (");
+    // TODO: add path and style
+    str->appendf("advance: %.2f phase %.2f", fAdvance, fInitialOffset);
+    str->appendf(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkPathEffect> SkPath1DPathEffect::Make(const SkPath& path, SkScalar advance, SkScalar phase,
diff --git a/src/effects/Sk2DPathEffect.cpp b/src/effects/Sk2DPathEffect.cpp
index 6c2c4c2854..a541b91476 100644
--- a/src/effects/Sk2DPathEffect.cpp
+++ b/src/effects/Sk2DPathEffect.cpp
@@ -74,6 +74,13 @@ void Sk2DPathEffect::flatten(SkWriteBuffer& buffer) const {
     buffer.writeMatrix(fMatrix);
 }
 
+void Sk2DPathEffect::toString(SkString* str) const {
+    str->appendf("(matrix: %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f)",
+            fMatrix[SkMatrix::kMScaleX], fMatrix[SkMatrix::kMSkewX],  fMatrix[SkMatrix::kMTransX],
+            fMatrix[SkMatrix::kMSkewY],  fMatrix[SkMatrix::kMScaleY], fMatrix[SkMatrix::kMTransY],
+            fMatrix[SkMatrix::kMPersp0], fMatrix[SkMatrix::kMPersp1], fMatrix[SkMatrix::kMPersp2]);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkLine2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
@@ -110,6 +117,14 @@ void SkLine2DPathEffect::flatten(SkWriteBuffer &buffer) const {
     buffer.writeScalar(fWidth);
 }
 
+
+void SkLine2DPathEffect::toString(SkString* str) const {
+    str->appendf("SkLine2DPathEffect: (");
+    this->INHERITED::toString(str);
+    str->appendf("width: %f", fWidth);
+    str->appendf(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 SkPath2DPathEffect::SkPath2DPathEffect(const SkMatrix& m, const SkPath& p)
@@ -133,3 +148,11 @@ void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v,
                               SkPath* dst) const {
     dst->addPath(fPath, loc.fX, loc.fY);
 }
+
+void SkPath2DPathEffect::toString(SkString* str) const {
+    str->appendf("SkPath2DPathEffect: (");
+    this->INHERITED::toString(str);
+    // TODO: print out path information
+    str->appendf(")");
+}
+
diff --git a/src/effects/SkAlphaThresholdFilter.cpp b/src/effects/SkAlphaThresholdFilter.cpp
index 5bd2571842..f09ec50b67 100644
--- a/src/effects/SkAlphaThresholdFilter.cpp
+++ b/src/effects/SkAlphaThresholdFilter.cpp
@@ -31,6 +31,7 @@ public:
                                SkScalar outerThreshold, sk_sp<SkImageFilter> input,
                                const CropRect* cropRect = nullptr);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkAlphaThresholdFilterImpl)
     friend void SkAlphaThresholdFilter::InitializeFlattenables();
 
@@ -280,3 +281,10 @@ const {
     }
     return this->refMe();
 }
+
+void SkAlphaThresholdFilterImpl::toString(SkString* str) const {
+    str->appendf("SkAlphaThresholdImageFilter: (");
+    str->appendf("inner: %f outer: %f", fInnerThreshold, fOuterThreshold);
+    str->append(")");
+}
+
diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp
index 578cc0d350..c022ec955e 100644
--- a/src/effects/SkArithmeticImageFilter.cpp
+++ b/src/effects/SkArithmeticImageFilter.cpp
@@ -37,6 +37,7 @@ public:
                               sk_sp<SkImageFilter> inputs[2], const CropRect* cropRect)
             : INHERITED(inputs, 2, cropRect), fK{k1, k2, k3, k4}, fEnforcePMColor(enforcePMColor) {}
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ArithmeticImageFilterImpl)
 
 protected:
@@ -431,6 +432,22 @@ const {
     return this->refMe();
 }
 
+void ArithmeticImageFilterImpl::toString(SkString* str) const {
+    str->appendf("SkArithmeticImageFilter: (");
+    str->appendf("K[]: (%f %f %f %f)", fK[0], fK[1], fK[2], fK[3]);
+    if (this->getInput(0)) {
+        str->appendf("foreground: (");
+        this->getInput(0)->toString(str);
+        str->appendf(")");
+    }
+    if (this->getInput(1)) {
+        str->appendf("background: (");
+        this->getInput(1)->toString(str);
+        str->appendf(")");
+    }
+    str->append(")");
+}
+
 sk_sp<SkImageFilter> SkArithmeticImageFilter::Make(float k1, float k2, float k3, float k4,
                                                    bool enforcePMColor,
                                                    sk_sp<SkImageFilter> background,
diff --git a/src/effects/SkColorFilterImageFilter.cpp b/src/effects/SkColorFilterImageFilter.cpp
index 9bea7943e3..514d04622e 100644
--- a/src/effects/SkColorFilterImageFilter.cpp
+++ b/src/effects/SkColorFilterImageFilter.cpp
@@ -144,3 +144,18 @@ bool SkColorFilterImageFilter::onIsColorFilterNode(SkColorFilter** filter) const
 bool SkColorFilterImageFilter::affectsTransparentBlack() const {
     return fColorFilter->affectsTransparentBlack();
 }
+
+void SkColorFilterImageFilter::toString(SkString* str) const {
+    str->appendf("SkColorFilterImageFilter: (");
+
+    str->appendf("input: (");
+
+    if (this->getInput(0)) {
+        this->getInput(0)->toString(str);
+    }
+
+    str->appendf(") color filter: ");
+    fColorFilter->toString(str);
+
+    str->append(")");
+}
diff --git a/src/effects/SkColorMatrixFilter.cpp b/src/effects/SkColorMatrixFilter.cpp
index c68c47e884..7a562b4aa0 100644
--- a/src/effects/SkColorMatrixFilter.cpp
+++ b/src/effects/SkColorMatrixFilter.cpp
@@ -72,6 +72,8 @@ public:
     }
 #endif
 
+    void toString(SkString* str) const override { fMatrixFilter->toString(str); }
+
 private:
     SkColor              fMul, fAdd;
     sk_sp<SkColorFilter> fMatrixFilter;
diff --git a/src/effects/SkComposeImageFilter.cpp b/src/effects/SkComposeImageFilter.cpp
index 842588d9c6..51cec9c671 100644
--- a/src/effects/SkComposeImageFilter.cpp
+++ b/src/effects/SkComposeImageFilter.cpp
@@ -88,3 +88,17 @@ sk_sp<SkFlattenable> SkComposeImageFilter::CreateProc(SkReadBuffer& buffer) {
     return SkComposeImageFilter::Make(common.getInput(0), common.getInput(1));
 }
 
+void SkComposeImageFilter::toString(SkString* str) const {
+    SkImageFilter* outer = getInput(0);
+    SkImageFilter* inner = getInput(1);
+
+    str->appendf("SkComposeImageFilter: (");
+
+    str->appendf("outer: ");
+    outer->toString(str);
+
+    str->appendf("inner: ");
+    inner->toString(str);
+
+    str->appendf(")");
+}
diff --git a/src/effects/SkCornerPathEffect.cpp b/src/effects/SkCornerPathEffect.cpp
index a88bc3224c..ec137b9c60 100644
--- a/src/effects/SkCornerPathEffect.cpp
+++ b/src/effects/SkCornerPathEffect.cpp
@@ -151,3 +151,9 @@ sk_sp<SkFlattenable> SkCornerPathEffect::CreateProc(SkReadBuffer& buffer) {
 void SkCornerPathEffect::flatten(SkWriteBuffer& buffer) const {
     buffer.writeScalar(fRadius);
 }
+
+void SkCornerPathEffect::toString(SkString* str) const {
+    str->appendf("SkCornerPathEffect: (");
+    str->appendf("radius: %.2f", fRadius);
+    str->appendf(")");
+}
diff --git a/src/effects/SkDashImpl.h b/src/effects/SkDashImpl.h
index f2c8255772..f5c4244dc3 100644
--- a/src/effects/SkDashImpl.h
+++ b/src/effects/SkDashImpl.h
@@ -21,6 +21,7 @@ public:
 
     DashType asADash(DashInfo* info) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDashImpl)
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index 7f003da6ab..cdadcf907d 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -380,6 +380,18 @@ sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
     return nullptr;
 }
 
+void SkDashImpl::toString(SkString* str) const {
+    str->appendf("SkDashPathEffect: (");
+    str->appendf("count: %d phase %.2f intervals: (", fCount, fPhase);
+    for (int i = 0; i < fCount; ++i) {
+        str->appendf("%.2f", fIntervals[i]);
+        if (i < fCount-1) {
+            str->appendf(", ");
+        }
+    }
+    str->appendf("))");
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkPathEffect> SkDashPathEffect::Make(const SkScalar intervals[], int count, SkScalar phase) {
diff --git a/src/effects/SkDiscretePathEffect.cpp b/src/effects/SkDiscretePathEffect.cpp
index 46cd42bece..57d8f9639e 100644
--- a/src/effects/SkDiscretePathEffect.cpp
+++ b/src/effects/SkDiscretePathEffect.cpp
@@ -143,3 +143,9 @@ void SkDiscretePathEffect::flatten(SkWriteBuffer& buffer) const {
     buffer.writeScalar(fPerterb);
     buffer.writeUInt(fSeedAssist);
 }
+
+void SkDiscretePathEffect::toString(SkString* str) const {
+    str->appendf("SkDiscretePathEffect: (");
+    str->appendf("segLength: %.2f deviation: %.2f seed %d", fSegLength, fPerterb, fSeedAssist);
+    str->append(")");
+}
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index 9dcf0bee45..d40e521b0a 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -404,6 +404,20 @@ SkIRect SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatr
     return src;
 }
 
+void SkDisplacementMapEffect::toString(SkString* str) const {
+    str->appendf("SkDisplacementMapEffect: (");
+    str->appendf("scale: %f ", fScale);
+    str->appendf("displacement: (");
+    if (this->getDisplacementInput()) {
+        this->getDisplacementInput()->toString(str);
+    }
+    str->appendf(") color: (");
+    if (this->getColorInput()) {
+        this->getColorInput()->toString(str);
+    }
+    str->appendf("))");
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
diff --git a/src/effects/SkDropShadowImageFilter.cpp b/src/effects/SkDropShadowImageFilter.cpp
index d19c05fe5d..7532894bce 100644
--- a/src/effects/SkDropShadowImageFilter.cpp
+++ b/src/effects/SkDropShadowImageFilter.cpp
@@ -159,3 +159,24 @@ SkIRect SkDropShadowImageFilter::onFilterNodeBounds(const SkIRect& src, const Sk
     return dst;
 }
 
+void SkDropShadowImageFilter::toString(SkString* str) const {
+    str->appendf("SkDropShadowImageFilter: (");
+
+    str->appendf("dX: %f ", fDx);
+    str->appendf("dY: %f ", fDy);
+    str->appendf("sigmaX: %f ", fSigmaX);
+    str->appendf("sigmaY: %f ", fSigmaY);
+
+    str->append("Color: ");
+    str->appendHex(fColor);
+
+    static const char* gModeStrings[] = {
+        "kDrawShadowAndForeground", "kDrawShadowOnly"
+    };
+
+    static_assert(kShadowModeCount == SK_ARRAY_COUNT(gModeStrings), "enum_mismatch");
+
+    str->appendf(" mode: %s", gModeStrings[fShadowMode]);
+
+    str->append(")");
+}
diff --git a/src/effects/SkEmbossMaskFilter.cpp b/src/effects/SkEmbossMaskFilter.cpp
index ef26520172..4a4c8d0e34 100644
--- a/src/effects/SkEmbossMaskFilter.cpp
+++ b/src/effects/SkEmbossMaskFilter.cpp
@@ -140,3 +140,22 @@ void SkEmbossMaskFilter::flatten(SkWriteBuffer& buffer) const {
     buffer.writeByteArray(&tmpLight, sizeof(tmpLight));
     buffer.writeScalar(fBlurSigma);
 }
+
+void SkEmbossMaskFilter::toString(SkString* str) const {
+    str->append("SkEmbossMaskFilter: (");
+
+    str->append("direction: (");
+    str->appendScalar(fLight.fDirection[0]);
+    str->append(", ");
+    str->appendScalar(fLight.fDirection[1]);
+    str->append(", ");
+    str->appendScalar(fLight.fDirection[2]);
+    str->append(") ");
+
+    str->appendf("ambient: %d specular: %d ",
+        fLight.fAmbient, fLight.fSpecular);
+
+    str->append("blurSigma: ");
+    str->appendScalar(fBlurSigma);
+    str->append(")");
+}
diff --git a/src/effects/SkEmbossMaskFilter.h b/src/effects/SkEmbossMaskFilter.h
index 9c3cb62d93..bc992a09b5 100644
--- a/src/effects/SkEmbossMaskFilter.h
+++ b/src/effects/SkEmbossMaskFilter.h
@@ -32,6 +32,7 @@ public:
     bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
                     SkIPoint* margin) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkEmbossMaskFilter)
 
 protected:
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index 607033ef20..b805212f18 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -45,6 +45,8 @@ public:
                         SkArenaAlloc* scratch,
                         bool shaderIsOpaque) const override;
 
+    void toString(SkString* str) const override;
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkHighContrast_Filter)
 
 protected:
@@ -155,6 +157,10 @@ sk_sp<SkColorFilter> SkHighContrastFilter::Make(
     return sk_make_sp<SkHighContrast_Filter>(config);
 }
 
+void SkHighContrast_Filter::toString(SkString* str) const {
+    str->append("SkHighContrastColorFilter ");
+}
+
 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkHighContrastFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkHighContrast_Filter)
 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/src/effects/SkImageSource.cpp b/src/effects/SkImageSource.cpp
index 960de37b33..0f48fb0c97 100644
--- a/src/effects/SkImageSource.cpp
+++ b/src/effects/SkImageSource.cpp
@@ -157,3 +157,12 @@ SkIRect SkImageSource::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ct
     return dstRect.roundOut();
 }
 
+void SkImageSource::toString(SkString* str) const {
+    str->appendf("SkImageSource: (");
+    str->appendf("src: (%f,%f,%f,%f) dst: (%f,%f,%f,%f) ",
+                 fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom,
+                 fDstRect.fLeft, fDstRect.fTop, fDstRect.fRight, fDstRect.fBottom);
+    str->appendf("image: (%d,%d)",
+                 fImage->width(), fImage->height());
+    str->append(")");
+}
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index 1610809456..db61e08c4f 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -279,6 +279,62 @@ sk_sp<SkFlattenable> SkLayerDrawLooper::CreateProc(SkReadBuffer& buffer) {
     return builder.detach();
 }
 
+void SkLayerDrawLooper::toString(SkString* str) const {
+    str->appendf("SkLayerDrawLooper (%d): ", fCount);
+
+    Rec* rec = fRecs;
+    for (int i = 0; i < fCount; i++) {
+        str->appendf("%d: paintBits: (", i);
+        if (0 == rec->fInfo.fPaintBits) {
+            str->append("None");
+        } else if (kEntirePaint_Bits == rec->fInfo.fPaintBits) {
+            str->append("EntirePaint");
+        } else {
+            bool needSeparator = false;
+            SkAddFlagToString(str, SkToBool(kStyle_Bit & rec->fInfo.fPaintBits), "Style",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kTextSkewX_Bit & rec->fInfo.fPaintBits), "TextSkewX",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kPathEffect_Bit & rec->fInfo.fPaintBits), "PathEffect",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kMaskFilter_Bit & rec->fInfo.fPaintBits), "MaskFilter",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kShader_Bit & rec->fInfo.fPaintBits), "Shader",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kColorFilter_Bit & rec->fInfo.fPaintBits), "ColorFilter",
+                              &needSeparator);
+            SkAddFlagToString(str, SkToBool(kXfermode_Bit & rec->fInfo.fPaintBits), "Xfermode",
+                              &needSeparator);
+        }
+        str->append(") ");
+
+        static const char* gModeStrings[(int)SkBlendMode::kLastMode+1] = {
+            "kClear", "kSrc", "kDst", "kSrcOver", "kDstOver", "kSrcIn", "kDstIn",
+            "kSrcOut", "kDstOut", "kSrcATop", "kDstATop", "kXor", "kPlus",
+            "kMultiply", "kScreen", "kOverlay", "kDarken", "kLighten", "kColorDodge",
+            "kColorBurn", "kHardLight", "kSoftLight", "kDifference", "kExclusion"
+        };
+
+        str->appendf("mode: %s ", gModeStrings[(int)rec->fInfo.fColorMode]);
+
+        str->append("offset: (");
+        str->appendScalar(rec->fInfo.fOffset.fX);
+        str->append(", ");
+        str->appendScalar(rec->fInfo.fOffset.fY);
+        str->append(") ");
+
+        str->append("postTranslate: ");
+        if (rec->fInfo.fPostTranslate) {
+            str->append("true ");
+        } else {
+            str->append("false ");
+        }
+
+        rec->fPaint.toString(str);
+        rec = rec->fNext;
+    }
+}
+
 SkLayerDrawLooper::Builder::Builder()
         : fRecs(nullptr),
           fTopRec(nullptr),
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index c812ccecce..0a1330583a 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -530,6 +530,7 @@ public:
                                      sk_sp<SkImageFilter>,
                                      const CropRect*);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiffuseLightingImageFilter)
     SkScalar kd() const { return fKD; }
 
@@ -564,6 +565,7 @@ public:
                                      SkScalar ks, SkScalar shininess,
                                      sk_sp<SkImageFilter>, const CropRect*);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpecularLightingImageFilter)
 
     SkScalar ks() const { return fKS; }
@@ -1369,6 +1371,12 @@ const {
     return this->refMe();
 }
 
+void SkDiffuseLightingImageFilter::toString(SkString* str) const {
+    str->appendf("SkDiffuseLightingImageFilter: (");
+    str->appendf("kD: %f\n", fKD);
+    str->append(")");
+}
+
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkDiffuseLightingImageFilter::makeFragmentProcessor(
         sk_sp<GrTextureProxy> proxy,
@@ -1518,6 +1526,12 @@ const {
     return this->refMe();
 }
 
+void SkSpecularLightingImageFilter::toString(SkString* str) const {
+    str->appendf("SkSpecularLightingImageFilter: (");
+    str->appendf("kS: %f shininess: %f", fKS, fShininess);
+    str->append(")");
+}
+
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkSpecularLightingImageFilter::makeFragmentProcessor(
         sk_sp<GrTextureProxy> proxy,
diff --git a/src/effects/SkLumaColorFilter.cpp b/src/effects/SkLumaColorFilter.cpp
index 12d6dd877b..847d5f929f 100644
--- a/src/effects/SkLumaColorFilter.cpp
+++ b/src/effects/SkLumaColorFilter.cpp
@@ -37,6 +37,10 @@ sk_sp<SkFlattenable> SkLumaColorFilter::CreateProc(SkReadBuffer&) {
 
 void SkLumaColorFilter::flatten(SkWriteBuffer&) const {}
 
+void SkLumaColorFilter::toString(SkString* str) const {
+    str->append("SkLumaColorFilter ");
+}
+
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkLumaColorFilter::asFragmentProcessor(
         GrContext*, const GrColorSpaceInfo&) const {
diff --git a/src/effects/SkMagnifierImageFilter.cpp b/src/effects/SkMagnifierImageFilter.cpp
index e28469e595..75e9817b8d 100644
--- a/src/effects/SkMagnifierImageFilter.cpp
+++ b/src/effects/SkMagnifierImageFilter.cpp
@@ -200,3 +200,11 @@ sk_sp<SkImageFilter> SkMagnifierImageFilter::onMakeColorSpace(SkColorSpaceXforme
     }
     return this->refMe();
 }
+
+void SkMagnifierImageFilter::toString(SkString* str) const {
+    str->appendf("SkMagnifierImageFilter: (");
+    str->appendf("src: (%f,%f,%f,%f) ",
+                 fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom);
+    str->appendf("inset: %f", fInset);
+    str->append(")");
+}
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
index 7668a02849..848676afaf 100644
--- a/src/effects/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -479,3 +479,18 @@ bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const {
     // pixels it will affect in object-space.
     return kRepeat_TileMode != fTileMode;
 }
+
+void SkMatrixConvolutionImageFilter::toString(SkString* str) const {
+    str->appendf("SkMatrixConvolutionImageFilter: (");
+    str->appendf("size: (%d,%d) kernel: (", fKernelSize.width(), fKernelSize.height());
+    for (int y = 0; y < fKernelSize.height(); y++) {
+        for (int x = 0; x < fKernelSize.width(); x++) {
+            str->appendf("%f ", fKernel[y * fKernelSize.width() + x]);
+        }
+    }
+    str->appendf(")");
+    str->appendf("gain: %f bias: %f ", fGain, fBias);
+    str->appendf("offset: (%d, %d) ", fKernelOffset.fX, fKernelOffset.fY);
+    str->appendf("convolveAlpha: %s", fConvolveAlpha ? "true" : "false");
+    str->append(")");
+}
diff --git a/src/effects/SkMergeImageFilter.cpp b/src/effects/SkMergeImageFilter.cpp
index 633ddd4ae1..41d545e187 100644
--- a/src/effects/SkMergeImageFilter.cpp
+++ b/src/effects/SkMergeImageFilter.cpp
@@ -121,3 +121,15 @@ void SkMergeImageFilter::flatten(SkWriteBuffer& buffer) const {
     this->INHERITED::flatten(buffer);
 }
 
+void SkMergeImageFilter::toString(SkString* str) const {
+    str->appendf("SkMergeImageFilter: (");
+
+    for (int i = 0; i < this->countInputs(); ++i) {
+        SkImageFilter* filter = this->getInput(i);
+        str->appendf("%d: (", i);
+        filter->toString(str);
+        str->appendf(")");
+    }
+
+    str->append(")");
+}
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index d46364aa5d..e421fdd28a 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -113,6 +113,18 @@ sk_sp<SkFlattenable> SkDilateImageFilter::CreateProc(SkReadBuffer& buffer) {
     return Make(width, height, common.getInput(0), &common.cropRect());
 }
 
+void SkErodeImageFilter::toString(SkString* str) const {
+    str->appendf("SkErodeImageFilter: (");
+    str->appendf("radius: (%d,%d)", this->radius().fWidth, this->radius().fHeight);
+    str->append(")");
+}
+
+void SkDilateImageFilter::toString(SkString* str) const {
+    str->appendf("SkDilateImageFilter: (");
+    str->appendf("radius: (%d,%d)", this->radius().fWidth, this->radius().fHeight);
+    str->append(")");
+}
+
 #if SK_SUPPORT_GPU
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp
index 25b7289974..5b1533b371 100644
--- a/src/effects/SkOffsetImageFilter.cpp
+++ b/src/effects/SkOffsetImageFilter.cpp
@@ -124,3 +124,13 @@ SkOffsetImageFilter::SkOffsetImageFilter(SkScalar dx, SkScalar dy,
     : INHERITED(&input, 1, cropRect) {
     fOffset.set(dx, dy);
 }
+
+void SkOffsetImageFilter::toString(SkString* str) const {
+    str->appendf("SkOffsetImageFilter: (");
+    str->appendf("offset: (%f, %f) ", fOffset.fX, fOffset.fY);
+    str->append("input: (");
+    if (this->getInput(0)) {
+        this->getInput(0)->toString(str);
+    }
+    str->append("))");
+}
diff --git a/src/effects/SkOverdrawColorFilter.cpp b/src/effects/SkOverdrawColorFilter.cpp
index 71dbb72011..01dab6544f 100644
--- a/src/effects/SkOverdrawColorFilter.cpp
+++ b/src/effects/SkOverdrawColorFilter.cpp
@@ -36,6 +36,14 @@ void SkOverdrawColorFilter::onAppendStages(SkRasterPipeline* p,
     p->append(SkRasterPipeline::callback, ctx);
 }
 
+void SkOverdrawColorFilter::toString(SkString* str) const {
+    str->append("SkOverdrawColorFilter (");
+    for (int i = 0; i < kNumColors; i++) {
+        str->appendf("%d: %x\n", i, fColors[i]);
+    }
+    str->append(")");
+}
+
 void SkOverdrawColorFilter::flatten(SkWriteBuffer& buffer) const {
     buffer.writeByteArray(fColors, kNumColors * sizeof(SkPMColor));
 }
diff --git a/src/effects/SkPaintImageFilter.cpp b/src/effects/SkPaintImageFilter.cpp
index c0be41fdb9..235f76f6a1 100644
--- a/src/effects/SkPaintImageFilter.cpp
+++ b/src/effects/SkPaintImageFilter.cpp
@@ -81,3 +81,9 @@ sk_sp<SkImageFilter> SkPaintImageFilter::onMakeColorSpace(SkColorSpaceXformer* x
 bool SkPaintImageFilter::affectsTransparentBlack() const {
     return true;
 }
+
+void SkPaintImageFilter::toString(SkString* str) const {
+    str->appendf("SkPaintImageFilter: (");
+    fPaint.toString(str);
+    str->append(")");
+}
diff --git a/src/effects/SkPictureImageFilter.cpp b/src/effects/SkPictureImageFilter.cpp
index e6069c3b21..93a494d1e7 100644
--- a/src/effects/SkPictureImageFilter.cpp
+++ b/src/effects/SkPictureImageFilter.cpp
@@ -130,3 +130,15 @@ sk_sp<SkImageFilter> SkPictureImageFilter::onMakeColorSpace(SkColorSpaceXformer*
 
     return sk_sp<SkImageFilter>(new SkPictureImageFilter(fPicture, fCropRect, std::move(dstCS)));
 }
+
+void SkPictureImageFilter::toString(SkString* str) const {
+    str->appendf("SkPictureImageFilter: (");
+    str->appendf("crop: (%f,%f,%f,%f) ",
+                 fCropRect.fLeft, fCropRect.fTop, fCropRect.fRight, fCropRect.fBottom);
+    if (fPicture) {
+        str->appendf("picture: (%f,%f,%f,%f)",
+                     fPicture->cullRect().fLeft, fPicture->cullRect().fTop,
+                     fPicture->cullRect().fRight, fPicture->cullRect().fBottom);
+    }
+    str->append(")");
+}
diff --git a/src/effects/SkShaderMaskFilter.cpp b/src/effects/SkShaderMaskFilter.cpp
index f2160a17a3..cfa15c5bad 100644
--- a/src/effects/SkShaderMaskFilter.cpp
+++ b/src/effects/SkShaderMaskFilter.cpp
@@ -27,6 +27,7 @@ public:
 
     bool asABlur(BlurRec*) const override { return false; }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkShaderMF)
 
 protected:
@@ -46,6 +47,10 @@ private:
     typedef SkMaskFilter INHERITED;
 };
 
+void SkShaderMF::toString(SkString* str) const {
+    str->set("SkShaderMF:");
+}
+
 sk_sp<SkFlattenable> SkShaderMF::CreateProc(SkReadBuffer& buffer) {
     return SkShaderMaskFilter::Make(buffer.readShader());
 }
diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp
index fcf76dcbae..47d6ae2d8c 100644
--- a/src/effects/SkTableColorFilter.cpp
+++ b/src/effects/SkTableColorFilter.cpp
@@ -90,6 +90,8 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTable_ColorFilter)
 
     enum {
@@ -138,6 +140,35 @@ private:
     typedef SkColorFilter INHERITED;
 };
 
+void SkTable_ColorFilter::toString(SkString* str) const {
+    const uint8_t* table = fStorage;
+    const uint8_t* tableA = gIdentityTable;
+    const uint8_t* tableR = gIdentityTable;
+    const uint8_t* tableG = gIdentityTable;
+    const uint8_t* tableB = gIdentityTable;
+    if (fFlags & kA_Flag) {
+        tableA = table; table += 256;
+    }
+    if (fFlags & kR_Flag) {
+        tableR = table; table += 256;
+    }
+    if (fFlags & kG_Flag) {
+        tableG = table; table += 256;
+    }
+    if (fFlags & kB_Flag) {
+        tableB = table;
+    }
+
+    str->append("SkTable_ColorFilter (");
+
+    for (int i = 0; i < 256; ++i) {
+        str->appendf("%d: %d,%d,%d,%d\n",
+                     i, tableR[i], tableG[i], tableB[i], tableA[i]);
+    }
+
+    str->append(")");
+}
+
 static const uint8_t gCountNibBits[] = {
     0, 1, 1, 2,
     1, 2, 2, 3,
diff --git a/src/effects/SkTableMaskFilter.cpp b/src/effects/SkTableMaskFilter.cpp
index 8ff58ec89d..2df2fa7f14 100644
--- a/src/effects/SkTableMaskFilter.cpp
+++ b/src/effects/SkTableMaskFilter.cpp
@@ -18,6 +18,7 @@ public:
     SkMask::Format getFormat() const override;
     bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTableMaskFilterImpl)
 
 protected:
@@ -166,3 +167,15 @@ void SkTableMaskFilter::MakeClipTable(uint8_t table[256], uint8_t min,
     SkDebugf("\n\n");
 #endif
 }
+
+void SkTableMaskFilterImpl::toString(SkString* str) const {
+    str->append("SkTableMaskFilter: (");
+
+    str->append("table: ");
+    for (int i = 0; i < 255; ++i) {
+        str->appendf("%d, ", fTable[i]);
+    }
+    str->appendf("%d", fTable[255]);
+
+    str->append(")");
+}
diff --git a/src/effects/SkTileImageFilter.cpp b/src/effects/SkTileImageFilter.cpp
index eddad2722f..1aac5814c5 100644
--- a/src/effects/SkTileImageFilter.cpp
+++ b/src/effects/SkTileImageFilter.cpp
@@ -157,3 +157,17 @@ void SkTileImageFilter::flatten(SkWriteBuffer& buffer) const {
     buffer.writeRect(fSrcRect);
     buffer.writeRect(fDstRect);
 }
+
+void SkTileImageFilter::toString(SkString* str) const {
+    str->appendf("SkTileImageFilter: (");
+    str->appendf("src: %.2f %.2f %.2f %.2f",
+                 fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom);
+    str->appendf(" dst: %.2f %.2f %.2f %.2f",
+                 fDstRect.fLeft, fDstRect.fTop, fDstRect.fRight, fDstRect.fBottom);
+    if (this->getInput(0)) {
+        str->appendf("input: (");
+        this->getInput(0)->toString(str);
+        str->appendf(")");
+    }
+    str->append(")");
+}
diff --git a/src/effects/SkToSRGBColorFilter.cpp b/src/effects/SkToSRGBColorFilter.cpp
index 09acabf94a..d2020ef338 100644
--- a/src/effects/SkToSRGBColorFilter.cpp
+++ b/src/effects/SkToSRGBColorFilter.cpp
@@ -71,6 +71,11 @@ void SkToSRGBColorFilter::flatten(SkWriteBuffer& buffer) const {
     buffer.writeDataAsByteArray(fSrcColorSpace->serialize().get());
 }
 
+void SkToSRGBColorFilter::toString(SkString* str) const {
+    // TODO
+    str->append("SkToSRGBColorFilter ");
+}
+
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkToSRGBColorFilter::asFragmentProcessor(
         GrContext*, const GrColorSpaceInfo&) const {
diff --git a/src/effects/SkTrimPE.h b/src/effects/SkTrimPE.h
index f66bccc821..7a05e72074 100644
--- a/src/effects/SkTrimPE.h
+++ b/src/effects/SkTrimPE.h
@@ -18,6 +18,7 @@ public:
 
     bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTrimPE)
 
 protected:
diff --git a/src/effects/SkTrimPathEffect.cpp b/src/effects/SkTrimPathEffect.cpp
index 8c3f56e2ba..d63393a4a6 100644
--- a/src/effects/SkTrimPathEffect.cpp
+++ b/src/effects/SkTrimPathEffect.cpp
@@ -97,6 +97,10 @@ sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
         (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
 }
 
+void SkTrimPE::toString(SkString* str) const {
+    str->appendf("SkTrimPathEffect: (%g %g)", fStartT, fStopT);
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) {
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index ff4a6299ed..9123818c84 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -34,6 +34,7 @@ public:
     SkXfermodeImageFilter_Base(SkBlendMode mode, sk_sp<SkImageFilter> inputs[2],
                                const CropRect* cropRect);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkXfermodeImageFilter_Base)
 
 protected:
@@ -248,6 +249,22 @@ void SkXfermodeImageFilter_Base::drawForeground(SkCanvas* canvas, SkSpecialImage
     canvas->drawPaint(paint);
 }
 
+void SkXfermodeImageFilter_Base::toString(SkString* str) const {
+    str->appendf("SkXfermodeImageFilter: (");
+    str->appendf("blendmode: (%d)", (int)fMode);
+    if (this->getInput(0)) {
+        str->appendf("foreground: (");
+        this->getInput(0)->toString(str);
+        str->appendf(")");
+    }
+    if (this->getInput(1)) {
+        str->appendf("background: (");
+        this->getInput(1)->toString(str);
+        str->appendf(")");
+    }
+    str->append(")");
+}
+
 #if SK_SUPPORT_GPU
 
 #include "effects/GrXfermodeFragmentProcessor.h"
diff --git a/src/gpu/GrTestUtils.h b/src/gpu/GrTestUtils.h
index 7627df4b19..0cfe6e45a3 100644
--- a/src/gpu/GrTestUtils.h
+++ b/src/gpu/GrTestUtils.h
@@ -74,6 +74,7 @@ public:
     bool filterPath(SkPath* dst, const SkPath&, SkStrokeRec* , const SkRect*) const override;
     DashType asADash(DashInfo* info) const override;
     Factory getFactory() const override { return nullptr; }
+    void toString(SkString*) const override {}
 
 private:
     TestDashPathEffect(const SkScalar* intervals, int count, SkScalar phase);
diff --git a/src/gpu/text/GrSDFMaskFilter.cpp b/src/gpu/text/GrSDFMaskFilter.cpp
index 82adaaa4ac..ff79cdb3f0 100644
--- a/src/gpu/text/GrSDFMaskFilter.cpp
+++ b/src/gpu/text/GrSDFMaskFilter.cpp
@@ -26,6 +26,7 @@ public:
 
     void computeFastBounds(const SkRect&, SkRect*) const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(GrSDFMaskFilterImpl)
 
 protected:
@@ -86,6 +87,10 @@ sk_sp<SkFlattenable> GrSDFMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
     return GrSDFMaskFilter::Make();
 }
 
+void GrSDFMaskFilterImpl::toString(SkString* str) const {
+    str->append("GrSDFMaskFilterImpl: ()");
+}
+
 void gr_register_sdf_maskfilter_createproc() {
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(GrSDFMaskFilterImpl)
 }
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index b57f727cad..3be804fca8 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -132,6 +132,12 @@ sk_sp<SkImage> SkImage::MakeFromEncoded(sk_sp<SkData> encoded, const SkIRect* su
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+const char* SkImage::toString(SkString* str) const {
+    str->appendf("image: (id:%d (%d, %d) %s)", this->uniqueID(), this->width(), this->height(),
+                 this->isOpaque() ? "opaque" : "");
+    return str->c_str();
+}
+
 sk_sp<SkImage> SkImage::makeSubset(const SkIRect& subset) const {
     if (subset.isEmpty()) {
         return nullptr;
diff --git a/src/shaders/SkColorFilterShader.cpp b/src/shaders/SkColorFilterShader.cpp
index 16bf56522c..0f25518669 100644
--- a/src/shaders/SkColorFilterShader.cpp
+++ b/src/shaders/SkColorFilterShader.cpp
@@ -71,6 +71,19 @@ std::unique_ptr<GrFragmentProcessor> SkColorFilterShader::asFragmentProcessor(
 }
 #endif
 
+void SkColorFilterShader::toString(SkString* str) const {
+    str->append("SkColorFilterShader: (");
+
+    str->append("Shader: ");
+    as_SB(fShader)->toString(str);
+    str->append(" Filter: ");
+    // TODO: add "fFilter->toString(str);" once SkColorFilter::toString is added
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkShader> SkShader::makeWithColorFilter(sk_sp<SkColorFilter> filter) const {
diff --git a/src/shaders/SkColorFilterShader.h b/src/shaders/SkColorFilterShader.h
index d97dd063a5..2f5b4693ee 100644
--- a/src/shaders/SkColorFilterShader.h
+++ b/src/shaders/SkColorFilterShader.h
@@ -21,6 +21,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterShader)
 
 protected:
diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp
index c3f86b9cfb..152a0b5529 100644
--- a/src/shaders/SkColorShader.cpp
+++ b/src/shaders/SkColorShader.cpp
@@ -97,6 +97,17 @@ std::unique_ptr<GrFragmentProcessor> SkColorShader::asFragmentProcessor(
 
 #endif
 
+void SkColorShader::toString(SkString* str) const {
+    str->append("SkColorShader: (");
+
+    str->append("Color: ");
+    str->appendHex(fColor);
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -212,6 +223,16 @@ std::unique_ptr<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(
 
 #endif
 
+void SkColor4Shader::toString(SkString* str) const {
+    str->append("SkColor4Shader: (");
+
+    str->append("RGBA:");
+    for (int i = 0; i < 4; ++i) {
+        str->appendf(" %g", fColor4.vec()[i]);
+    }
+    str->append(" )");
+}
+
 sk_sp<SkShader> SkColor4Shader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
     return SkShader::MakeColorShader(xformer->apply(fCachedByteColor));
 }
diff --git a/src/shaders/SkColorShader.h b/src/shaders/SkColorShader.h
index 235f8e330a..9fab094996 100644
--- a/src/shaders/SkColorShader.h
+++ b/src/shaders/SkColorShader.h
@@ -50,6 +50,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader)
 
 protected:
@@ -105,6 +106,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader)
 
 protected:
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
index 845d320490..e7125ad966 100644
--- a/src/shaders/SkComposeShader.cpp
+++ b/src/shaders/SkComposeShader.cpp
@@ -141,3 +141,18 @@ std::unique_ptr<GrFragmentProcessor> SkComposeShader::asFragmentProcessor(
                                                               std::move(fpA), fMode);
 }
 #endif
+
+void SkComposeShader::toString(SkString* str) const {
+    str->append("SkComposeShader: (");
+
+    str->append("dst: ");
+    as_SB(fDst)->toString(str);
+    str->append(" src: ");
+    as_SB(fSrc)->toString(str);
+    str->appendf(" mode: %s", SkBlendMode_Name(fMode));
+    str->appendf(" lerpT: %g", fLerpT);
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h
index c2dc2c3984..9208502613 100644
--- a/src/shaders/SkComposeShader.h
+++ b/src/shaders/SkComposeShader.h
@@ -35,6 +35,7 @@ public:
     bool asACompose(ComposeRec* rec) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeShader)
 
 protected:
diff --git a/src/shaders/SkEmptyShader.h b/src/shaders/SkEmptyShader.h
index 7956003625..12848b3738 100644
--- a/src/shaders/SkEmptyShader.h
+++ b/src/shaders/SkEmptyShader.h
@@ -20,6 +20,7 @@ class SkEmptyShader : public SkShaderBase {
 public:
     SkEmptyShader() {}
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkEmptyShader)
 
 protected:
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 7744f89be9..8ef647f5c9 100644
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -173,6 +173,17 @@ sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
     return sk_sp<SkShader>{ new SkImageShader(image, tx,ty, localMatrix, clampAsIfUnpremul) };
 }
 
+void SkImageShader::toString(SkString* str) const {
+    const char* gTileModeName[SkShader::kTileModeCount] = {
+        "clamp", "repeat", "mirror"
+    };
+
+    str->appendf("ImageShader: ((%s %s) ", gTileModeName[fTileModeX], gTileModeName[fTileModeY]);
+    fImage->toString(str);
+    this->INHERITED::toString(str);
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h
index 0d40c23ea4..3e980d38f4 100644
--- a/src/shaders/SkImageShader.h
+++ b/src/shaders/SkImageShader.h
@@ -23,6 +23,7 @@ public:
 
     bool isOpaque() const override;
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkImageShader)
 
 #if SK_SUPPORT_GPU
diff --git a/src/shaders/SkLocalMatrixShader.cpp b/src/shaders/SkLocalMatrixShader.cpp
index 28d7775b27..509c66d43e 100644
--- a/src/shaders/SkLocalMatrixShader.cpp
+++ b/src/shaders/SkLocalMatrixShader.cpp
@@ -71,6 +71,16 @@ bool SkLocalMatrixShader::onAppendStages(const StageRec& rec) const {
     return as_SB(fProxyShader)->appendStages(newRec);
 }
 
+void SkLocalMatrixShader::toString(SkString* str) const {
+    str->append("SkLocalMatrixShader: (");
+
+    as_SB(fProxyShader)->toString(str);
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const {
     if (localMatrix.isIdentity()) {
         return sk_ref_sp(const_cast<SkShader*>(this));
diff --git a/src/shaders/SkLocalMatrixShader.h b/src/shaders/SkLocalMatrixShader.h
index 9f89812139..48d1ef17a9 100644
--- a/src/shaders/SkLocalMatrixShader.h
+++ b/src/shaders/SkLocalMatrixShader.h
@@ -38,6 +38,7 @@ public:
         return fProxyShader;
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLocalMatrixShader)
 
 protected:
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index b3dc9d428f..d864e165e5 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -364,6 +364,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPerlinNoiseShaderImpl)
 
 protected:
@@ -1476,6 +1477,37 @@ std::unique_ptr<GrFragmentProcessor> SkPerlinNoiseShaderImpl::asFragmentProcesso
 
 #endif
 
+void SkPerlinNoiseShaderImpl::toString(SkString* str) const {
+    str->append("SkPerlinNoiseShaderImpl: (");
+
+    str->append("type: ");
+    switch (fType) {
+        case kFractalNoise_Type:
+            str->append("\"fractal noise\"");
+            break;
+        case kTurbulence_Type:
+            str->append("\"turbulence\"");
+            break;
+        default:
+            str->append("\"unknown\"");
+            break;
+    }
+    str->append(" base frequency: (");
+    str->appendScalar(fBaseFrequencyX);
+    str->append(", ");
+    str->appendScalar(fBaseFrequencyY);
+    str->append(") number of octaves: ");
+    str->appendS32(fNumOctaves);
+    str->append(" seed: ");
+    str->appendScalar(fSeed);
+    str->append(" stitch tiles: ");
+    str->append(fStitchTiles ? "true " : "false ");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static bool valid_input(SkScalar baseX, SkScalar baseY, int numOctaves, const SkISize* tileSize,
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
index 576598bfe6..f958814ca1 100644
--- a/src/shaders/SkPictureShader.cpp
+++ b/src/shaders/SkPictureShader.cpp
@@ -338,6 +338,22 @@ void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor ds
     fBitmapShaderContext->shadeSpan(x, y, dstC, count);
 }
 
+void SkPictureShader::toString(SkString* str) const {
+    static const char* gTileModeName[SkShader::kTileModeCount] = {
+        "clamp", "repeat", "mirror"
+    };
+
+    str->appendf("PictureShader: [%f:%f:%f:%f] ",
+                 fPicture->cullRect().fLeft,
+                 fPicture->cullRect().fTop,
+                 fPicture->cullRect().fRight,
+                 fPicture->cullRect().fBottom);
+
+    str->appendf("(%s, %s)", gTileModeName[fTmx], gTileModeName[fTmy]);
+
+    this->INHERITED::toString(str);
+}
+
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
         const GrFPArgs& args) const {
diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h
index 25fe53feb5..6e5201e8fb 100644
--- a/src/shaders/SkPictureShader.h
+++ b/src/shaders/SkPictureShader.h
@@ -28,6 +28,7 @@ public:
     static sk_sp<SkShader> Make(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*,
                                 const SkRect*);
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPictureShader)
 
 #if SK_SUPPORT_GPU
diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp
index a17cca191c..f3ffd26ef4 100644
--- a/src/shaders/SkShader.cpp
+++ b/src/shaders/SkShader.cpp
@@ -208,6 +208,13 @@ sk_sp<SkShader> SkShader::MakePictureShader(sk_sp<SkPicture> src, TileMode tmx,
     return SkPictureShader::Make(std::move(src), tmx, tmy, localMatrix, tile);
 }
 
+void SkShaderBase::toString(SkString* str) const {
+    if (!fLocalMatrix.isIdentity()) {
+        str->append(" ");
+        fLocalMatrix.toString(str);
+    }
+}
+
 bool SkShaderBase::appendStages(const StageRec& rec) const {
     return this->onAppendStages(rec);
 }
@@ -251,3 +258,13 @@ bool SkShaderBase::onAppendStages(const StageRec& rec) const {
 sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) {
     return SkShader::MakeEmptyShader();
 }
+
+#include "SkEmptyShader.h"
+
+void SkEmptyShader::toString(SkString* str) const {
+    str->append("SkEmptyShader: (");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h
index d0b830b6a2..cddc5540ee 100644
--- a/src/shaders/SkShaderBase.h
+++ b/src/shaders/SkShaderBase.h
@@ -207,6 +207,8 @@ public:
         return nullptr;
     }
 
+    virtual void toString(SkString* str) const;
+
     SK_DEFINE_FLATTENABLE_TYPE(SkShaderBase)
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
 
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index eea70ca847..78f514c1c6 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -626,6 +626,38 @@ void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
     }
 }
 
+void SkGradientShaderBase::toString(SkString* str) const {
+
+    str->appendf("%d colors: ", fColorCount);
+
+    for (int i = 0; i < fColorCount; ++i) {
+        str->appendHex(this->getLegacyColor(i), 8);
+        if (i < fColorCount-1) {
+            str->append(", ");
+        }
+    }
+
+    if (fColorCount > 2) {
+        str->append(" points: (");
+        for (int i = 0; i < fColorCount; ++i) {
+            str->appendScalar(this->getPos(i));
+            if (i < fColorCount-1) {
+                str->append(", ");
+            }
+        }
+        str->append(")");
+    }
+
+    static const char* gTileModeName[SkShader::kTileModeCount] = {
+        "clamp", "repeat", "mirror", "decal",
+    };
+
+    str->append(" ");
+    str->append(gTileModeName[fTileMode]);
+
+    this->INHERITED::toString(str);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h
index e50f1a69e4..dccb4e70cb 100644
--- a/src/shaders/gradients/SkGradientShaderPriv.h
+++ b/src/shaders/gradients/SkGradientShaderPriv.h
@@ -80,6 +80,7 @@ protected:
 
     SkGradientShaderBase(SkReadBuffer& );
     void flatten(SkWriteBuffer&) const override;
+    void toString(SkString* str) const override;
 
     void commonAsAGradient(GradientInfo*) const;
 
diff --git a/src/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp
index 34cac269f5..a377442dc4 100644
--- a/src/shaders/gradients/SkLinearGradient.cpp
+++ b/src/shaders/gradients/SkLinearGradient.cpp
@@ -207,4 +207,14 @@ std::unique_ptr<GrFragmentProcessor> SkLinearGradient::asFragmentProcessor(
 
 #endif
 
+void SkLinearGradient::toString(SkString* str) const {
+    str->append("SkLinearGradient (");
+
+    str->appendf("start: (%f, %f)", fStart.fX, fStart.fY);
+    str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY);
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
 
diff --git a/src/shaders/gradients/SkLinearGradient.h b/src/shaders/gradients/SkLinearGradient.h
index d100a1cf30..111ed99744 100644
--- a/src/shaders/gradients/SkLinearGradient.h
+++ b/src/shaders/gradients/SkLinearGradient.h
@@ -19,6 +19,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient)
 
 protected:
diff --git a/src/shaders/gradients/SkRadialGradient.cpp b/src/shaders/gradients/SkRadialGradient.cpp
index ec0f8a0f95..6d193615a4 100644
--- a/src/shaders/gradients/SkRadialGradient.cpp
+++ b/src/shaders/gradients/SkRadialGradient.cpp
@@ -185,3 +185,19 @@ void SkRadialGradient::appendGradientStages(SkArenaAlloc*, SkRasterPipeline* p,
                                             SkRasterPipeline*) const {
     p->append(SkRasterPipeline::xy_to_radius);
 }
+
+void SkRadialGradient::toString(SkString* str) const {
+    str->append("SkRadialGradient: (");
+
+    str->append("center: (");
+    str->appendScalar(fCenter.fX);
+    str->append(", ");
+    str->appendScalar(fCenter.fY);
+    str->append(") radius: ");
+    str->appendScalar(fRadius);
+    str->append(" ");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
diff --git a/src/shaders/gradients/SkRadialGradient.h b/src/shaders/gradients/SkRadialGradient.h
index d6963f6971..b0d6854221 100644
--- a/src/shaders/gradients/SkRadialGradient.h
+++ b/src/shaders/gradients/SkRadialGradient.h
@@ -19,6 +19,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient)
 
 protected:
diff --git a/src/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp
index cfce5fe025..c409d9a4e1 100644
--- a/src/shaders/gradients/SkSweepGradient.cpp
+++ b/src/shaders/gradients/SkSweepGradient.cpp
@@ -241,6 +241,20 @@ sk_sp<SkShader> SkSweepGradient::onMakeColorSpace(SkColorSpaceXformer* xformer)
                                        fGradFlags, &this->getLocalMatrix());
 }
 
+void SkSweepGradient::toString(SkString* str) const {
+    str->append("SkSweepGradient: (");
+
+    str->append("center: (");
+    str->appendScalar(fCenter.fX);
+    str->append(", ");
+    str->appendScalar(fCenter.fY);
+    str->append(") ");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 void SkSweepGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
                                            SkRasterPipeline*) const {
     p->append(SkRasterPipeline::xy_to_unit_angle);
diff --git a/src/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h
index a58c95752d..dce9a3561b 100644
--- a/src/shaders/gradients/SkSweepGradient.h
+++ b/src/shaders/gradients/SkSweepGradient.h
@@ -20,6 +20,7 @@ public:
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient)
 
 protected:
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
index 7d5026bc22..73ec3f213b 100644
--- a/src/shaders/gradients/SkTwoPointConicalGradient.cpp
+++ b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
@@ -199,6 +199,31 @@ sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer*
                                                  fTileMode, fGradFlags, &this->getLocalMatrix());
 }
 
+
+void SkTwoPointConicalGradient::toString(SkString* str) const {
+    str->append("SkTwoPointConicalGradient: (");
+
+    str->append("center1: (");
+    str->appendScalar(fCenter1.fX);
+    str->append(", ");
+    str->appendScalar(fCenter1.fY);
+    str->append(") radius1: ");
+    str->appendScalar(fRadius1);
+    str->append(" ");
+
+    str->append("center2: (");
+    str->appendScalar(fCenter2.fX);
+    str->append(", ");
+    str->appendScalar(fCenter2.fY);
+    str->append(") radius2: ");
+    str->appendScalar(fRadius2);
+    str->append(" ");
+
+    this->INHERITED::toString(str);
+
+    str->append(")");
+}
+
 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
                                                      SkRasterPipeline* postPipeline) const {
     const auto dRadius = fRadius2 - fRadius1;
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.h b/src/shaders/gradients/SkTwoPointConicalGradient.h
index b965e15038..f0d341f17c 100644
--- a/src/shaders/gradients/SkTwoPointConicalGradient.h
+++ b/src/shaders/gradients/SkTwoPointConicalGradient.h
@@ -65,6 +65,7 @@ public:
     const SkMatrix& getGradientMatrix() const { return fPtsToUnit; }
     const FocalData& getFocalData() const { return fFocalData; }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointConicalGradient)
 
 protected:
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 4032ad2577..5049dd6f59 100644
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -44,6 +44,7 @@ public:
             GrContext*, const GrColorSpaceInfo&) const override;
 #endif
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkGaussianColorFilter)
 
 protected:
@@ -62,6 +63,10 @@ sk_sp<SkFlattenable> SkGaussianColorFilter::CreateProc(SkReadBuffer&) {
     return Make();
 }
 
+void SkGaussianColorFilter::toString(SkString* str) const {
+    str->append("SkGaussianColorFilter ");
+}
+
 #if SK_SUPPORT_GPU
 
 std::unique_ptr<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index e809dfb114..aee1d405be 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -838,6 +838,7 @@ class ZeroBoundsImageFilter : public SkImageFilter {
 public:
     static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ZeroBoundsImageFilter)
 
 protected:
@@ -861,6 +862,10 @@ sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) {
     return nullptr;
 }
 
+void ZeroBoundsImageFilter::toString(SkString* str) const {
+    str->appendf("ZeroBoundsImageFilter: ()");
+}
+
 }  // anonymous namespace
 
 DEF_TEST(Canvas_SaveLayerWithNullBoundsAndZeroBoundsImageFilter, r) {
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp
index 1481139e1d..a53dad8517 100644
--- a/tests/GrShapeTest.cpp
+++ b/tests/GrShapeTest.cpp
@@ -1162,6 +1162,7 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo)
         }
         static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
         Factory getFactory() const override { return nullptr; }
+        void toString(SkString*) const override {}
     private:
         RRectPathEffect() {}
     };
@@ -1244,6 +1245,7 @@ void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
         }
         static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
         Factory getFactory() const override { return nullptr; }
+        void toString(SkString*) const override {}
     private:
         AddLineTosPathEffect() {}
     };
@@ -1280,6 +1282,7 @@ void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo
             return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
         }
         Factory getFactory() const override { return nullptr; }
+        void toString(SkString*) const override {}
     private:
         MakeHairlinePathEffect() {}
     };
@@ -1365,6 +1368,7 @@ void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo&
             return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
         }
         Factory getFactory() const override { return nullptr; }
+        void toString(SkString*) const override {}
     private:
         bool fInvert;
         EmptyPathEffect(bool invert) : fInvert(invert) {}
@@ -1445,6 +1449,7 @@ void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
         }
         static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
         Factory getFactory() const override { return nullptr; }
+        void toString(SkString*) const override {}
     private:
         FailurePathEffect() {}
     };
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index b57af7f30b..8e46715fbb 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -59,6 +59,7 @@ public:
         return sk_sp<SkImageFilter>(new MatrixTestImageFilter(reporter, expectedMatrix));
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(MatrixTestImageFilter)
 
 protected:
@@ -102,6 +103,7 @@ public:
         return nullptr;
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(FailImageFilter)
 
 private:
@@ -113,6 +115,11 @@ sk_sp<SkFlattenable> FailImageFilter::CreateProc(SkReadBuffer& buffer) {
     return sk_sp<SkFlattenable>(new FailImageFilter());
 }
 
+void FailImageFilter::toString(SkString* str) const {
+    str->appendf("FailImageFilter: (");
+    str->append(")");
+}
+
 void draw_gradient_circle(SkCanvas* canvas, int width, int height) {
     SkScalar x = SkIntToScalar(width / 2);
     SkScalar y = SkIntToScalar(height / 2);
@@ -290,6 +297,7 @@ public:
             : SkImageFilter(nullptr, 0, nullptr), fBounds(bounds) {}
 
 private:
+    void toString(SkString*) const override {}
     Factory getFactory() const override { return nullptr; }
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* src, const Context&,
@@ -312,6 +320,11 @@ sk_sp<SkFlattenable> MatrixTestImageFilter::CreateProc(SkReadBuffer& buffer) {
     return nullptr;
 }
 
+void MatrixTestImageFilter::toString(SkString* str) const {
+    str->appendf("MatrixTestImageFilter: (");
+    str->append(")");
+}
+
 static sk_sp<SkImage> make_small_image() {
     auto surface(SkSurface::MakeRasterN32Premul(kBitmapSize, kBitmapSize));
     SkCanvas* canvas = surface->getCanvas();
@@ -1863,6 +1876,8 @@ DEF_TEST(ImageFilterColorSpaceDAG, reporter) {
     public:
         TestFilter() : INHERITED(nullptr, 0, nullptr) {}
 
+        void toString(SkString*) const override {}
+
         Factory getFactory() const override { return nullptr; }
 
         size_t cloneCount() const { return fCloneCount; }
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index d25adea16f..b9f8a05a80 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -357,6 +357,7 @@ public:
         return sk_sp<DummyImageFilter>(new DummyImageFilter(visited));
     }
 
+    void toString(SkString* str) const override;
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(DummyImageFilter)
     bool visited() const { return fVisited; }
 
@@ -385,6 +386,11 @@ sk_sp<SkFlattenable> DummyImageFilter::CreateProc(SkReadBuffer& buffer) {
     return DummyImageFilter::Make(visited);
 }
 
+void DummyImageFilter::toString(SkString* str) const {
+    str->appendf("DummyImageFilter: (");
+    str->append(")");
+}
+
 };
 
 // Check that PDF rendering of image filters successfully falls back to
diff --git a/tests/QuickRejectTest.cpp b/tests/QuickRejectTest.cpp
index 42e145b3bc..103a31d936 100644
--- a/tests/QuickRejectTest.cpp
+++ b/tests/QuickRejectTest.cpp
@@ -29,6 +29,10 @@ public:
         return nullptr;
     }
 
+    void toString(SkString* str) const override {
+        str->append("TestLooper:");
+    }
+
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(TestLooper)
 
 private:
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 5b06275153..b183a958ec 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -1407,11 +1407,7 @@ bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefiniti
                                 child.fContentStart;
                         this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
                         if (fPendingMethod) {
-                            if (fIndent >= 4) {
-                                fIndent -= 4;
-                            } else {
-                                fIndent = 0;
-                            }
+                            fIndent -= 4;
                             fPendingMethod = false;
                         }
                         startDef = requireDense ? requireDense : &child;