[go: nahoru, domu]

cc/paint: Add a paint op helper with a ToString functionality.

This patch adds a helper class that can stringify PaintOps. This is
done in a best effort manner to help with debugging. Also adds
corresponding unittests.

R=enne@chromium.org

Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Change-Id: Ic5003365251fa40710e7a79ccf3934096d9f7d6a
Reviewed-on: https://chromium-review.googlesource.com/764390
Reviewed-by: enne <enne@chromium.org>
Commit-Queue: vmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516057}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 83dba38..3a96df7 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -461,6 +461,7 @@
     "test/mock_occlusion_tracker.h",
     "test/ordered_texture_map.cc",
     "test/ordered_texture_map.h",
+    "test/paint_op_helper.h",
     "test/pixel_comparator.cc",
     "test/pixel_comparator.h",
     "test/pixel_test.cc",
@@ -619,6 +620,7 @@
     "paint/oop_pixeltest.cc",
     "paint/paint_image_unittest.cc",
     "paint/paint_op_buffer_unittest.cc",
+    "paint/paint_op_helper_unittest.cc",
     "paint/paint_shader_unittest.cc",
     "paint/scoped_image_flags_unittest.cc",
     "paint/solid_color_analyzer_unittest.cc",
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 1a7311a..b3e7541 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -234,12 +234,6 @@
 
 class CC_PAINT_EXPORT AnnotateOp final : public PaintOp {
  public:
-  enum class AnnotationType {
-    URL,
-    LinkToDestination,
-    NamedDestination,
-  };
-
   static constexpr PaintOpType kType = PaintOpType::Annotate;
   AnnotateOp(PaintCanvas::AnnotationType annotation_type,
              const SkRect& rect,
diff --git a/cc/paint/paint_op_helper_unittest.cc b/cc/paint/paint_op_helper_unittest.cc
new file mode 100644
index 0000000..df93bf9e
--- /dev/null
+++ b/cc/paint/paint_op_helper_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/test/paint_op_helper.h"
+#include "cc/paint/paint_canvas.h"
+#include "cc/paint/paint_op_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(PaintOpHelper, AnnotateToString) {
+  AnnotateOp op(PaintCanvas::AnnotationType::URL, SkRect::MakeXYWH(1, 2, 3, 4),
+                nullptr);
+  op.type = static_cast<uint32_t>(AnnotateOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(
+      str,
+      "AnnotateOp(type=URL, rect=[1.000,2.000 3.000x4.000], data=<SkData>)");
+}
+
+TEST(PaintOpHelper, ClipPathToString) {
+  ClipPathOp op(SkPath(), SkClipOp::kDifference, true);
+  op.type = static_cast<uint32_t>(ClipPathOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "ClipPathOp(path=<SkPath>, op=kDifference, antialias=true)");
+}
+
+TEST(PaintOpHelper, ClipRectToString) {
+  ClipRectOp op(SkRect::MakeXYWH(10.1f, 20.2f, 30.3f, 40.4f),
+                SkClipOp::kIntersect, false);
+  op.type = static_cast<uint32_t>(ClipRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "ClipRectOp(rect=[10.100,20.200 30.300x40.400], op=kIntersect, "
+            "antialias=false)");
+}
+
+TEST(PaintOpHelper, ClipRRectToString) {
+  ClipRRectOp op(SkRRect::MakeRect(SkRect::MakeXYWH(1, 2, 3, 4)),
+                 SkClipOp::kDifference, false);
+  op.type = static_cast<uint32_t>(ClipRRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "ClipRRectOp(rrect=[bounded by 1.000,2.000 3.000x4.000], "
+            "op=kDifference, antialias=false)");
+}
+
+TEST(PaintOpHelper, ConcatToString) {
+  ConcatOp op(SkMatrix::MakeAll(1, 2, 3, 4, 5, 6, 7, 8, 9));
+  op.type = static_cast<uint32_t>(ConcatOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "ConcatOp(matrix=[  1.0000   2.0000   3.0000][  4.0000   5.0000   "
+            "6.0000][  7.0000   8.0000   9.0000])");
+}
+
+TEST(PaintOpHelper, DrawColorToString) {
+  DrawColorOp op(SkColorSetARGB(11, 22, 33, 44), SkBlendMode::kSrc);
+  op.type = static_cast<uint32_t>(DrawColorOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawColorOp(color=rgba(22, 33, 44, 11), mode=kSrc)");
+}
+
+TEST(PaintOpHelper, DrawDRRectToString) {
+  DrawDRRectOp op(SkRRect::MakeRect(SkRect::MakeXYWH(1, 2, 3, 4)),
+                  SkRRect::MakeRect(SkRect::MakeXYWH(5, 6, 7, 8)),
+                  PaintFlags());
+  op.type = static_cast<uint32_t>(DrawDRRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "DrawDRRectOp(outer=[bounded by 1.000,2.000 3.000x4.000], "
+            "inner=[bounded by 5.000,6.000 7.000x8.000])");
+}
+
+TEST(PaintOpHelper, DrawImageToString) {
+  DrawImageOp op(PaintImage(), 10.5f, 20.3f, nullptr);
+  op.type = static_cast<uint32_t>(DrawImageOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawImageOp(image=<paint image>, left=10.500, top=20.300)");
+}
+
+TEST(PaintOpHelper, DrawImageRectToString) {
+  DrawImageRectOp op(PaintImage(), SkRect::MakeXYWH(1, 2, 3, 4),
+                     SkRect::MakeXYWH(5, 6, 7, 8), nullptr,
+                     PaintCanvas::kStrict_SrcRectConstraint);
+  op.type = static_cast<uint32_t>(DrawImageRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "DrawImageRectOp(image=<paint image>, src=[1.000,2.000 "
+            "3.000x4.000], dst=[5.000,6.000 7.000x8.000], "
+            "constraint=kStrict_SrcRectConstraint)");
+}
+
+TEST(PaintOpHelper, DrawIRectToString) {
+  DrawIRectOp op(SkIRect::MakeXYWH(1, 2, 3, 4), PaintFlags());
+  op.type = static_cast<uint32_t>(DrawIRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawIRectOp(rect=[1,2 3x4])");
+}
+
+TEST(PaintOpHelper, DrawLineToString) {
+  DrawLineOp op(1.1f, 2.2f, 3.3f, 4.4f, PaintFlags());
+  op.type = static_cast<uint32_t>(DrawLineOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawLineOp(x0=1.100, y0=2.200, x1=3.300, y1=4.400)");
+}
+
+TEST(PaintOpHelper, DrawOvalToString) {
+  DrawOvalOp op(SkRect::MakeXYWH(100, 200, 300, 400), PaintFlags());
+  op.type = static_cast<uint32_t>(DrawOvalOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawOvalOp(oval=[100.000,200.000 300.000x400.000])");
+}
+
+TEST(PaintOpHelper, DrawPathToString) {
+  SkPath path;
+  DrawPathOp op(path, PaintFlags());
+  op.type = static_cast<uint32_t>(DrawPathOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawPathOp(path=<SkPath>)");
+}
+
+TEST(PaintOpHelper, DrawRecordToString) {
+  DrawRecordOp op(nullptr);
+  op.type = static_cast<uint32_t>(DrawRecordOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawRecordOp(record=<paint record>)");
+}
+
+TEST(PaintOpHelper, DrawRectToString) {
+  DrawRectOp op(SkRect::MakeXYWH(-1, -2, -3, -4), PaintFlags());
+  op.type = static_cast<uint32_t>(DrawRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawRectOp(rect=[-1.000,-2.000 -3.000x-4.000])");
+}
+
+TEST(PaintOpHelper, DrawRRectToString) {
+  DrawRRectOp op(SkRRect::MakeRect(SkRect::MakeXYWH(-1, -2, 3, 4)),
+                 PaintFlags());
+  op.type = static_cast<uint32_t>(DrawRRectOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "DrawRRectOp(rrect=[bounded by -1.000,-2.000 3.000x4.000])");
+}
+
+TEST(PaintOpHelper, DrawTextBlobToString) {
+  DrawTextBlobOp op(nullptr, 100, -222, PaintFlags());
+  op.type = static_cast<uint32_t>(DrawTextBlobOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "DrawTextBlobOp(blob=<paint text blob>, x=100.000, y=-222.000)");
+}
+
+TEST(PaintOpHelper, NoopToString) {
+  NoopOp op;
+  op.type = static_cast<uint32_t>(NoopOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "NoopOp()");
+}
+
+TEST(PaintOpHelper, RestoreToString) {
+  RestoreOp op;
+  op.type = static_cast<uint32_t>(RestoreOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "RestoreOp()");
+}
+
+TEST(PaintOpHelper, RotateToString) {
+  RotateOp op(360);
+  op.type = static_cast<uint32_t>(RotateOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "RotateOp(degrees=360.000)");
+}
+
+TEST(PaintOpHelper, SaveToString) {
+  SaveOp op;
+  op.type = static_cast<uint32_t>(SaveOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "SaveOp()");
+}
+
+TEST(PaintOpHelper, SaveLayerToString) {
+  SkRect bounds = SkRect::MakeXYWH(1, 2, 3, 4);
+  SaveLayerOp op(&bounds, nullptr);
+  op.type = static_cast<uint32_t>(SaveLayerOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "SaveLayerOp(bounds=[1.000,2.000 3.000x4.000])");
+}
+
+TEST(PaintOpHelper, SaveLayerAlphaToString) {
+  SkRect bounds = SkRect::MakeXYWH(1, 2, 3, 4);
+  SaveLayerAlphaOp op(&bounds, 255, false);
+  op.type = static_cast<uint32_t>(SaveLayerAlphaOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "SaveLayerAlphaOp(bounds=[1.000,2.000 3.000x4.000], alpha=255, "
+            "preserve_lcd_text_requests=false)");
+}
+
+TEST(PaintOpHelper, ScaleToString) {
+  ScaleOp op(12, 13.9f);
+  op.type = static_cast<uint32_t>(ScaleOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "ScaleOp(sx=12.000, sy=13.900)");
+}
+
+TEST(PaintOpHelper, SetMatrixToString) {
+  SetMatrixOp op(SkMatrix::MakeAll(-1, 2, -3, 4, -5, 6, -7, 8, -9));
+  op.type = static_cast<uint32_t>(SetMatrixOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str,
+            "SetMatrixOp(matrix=[ -1.0000   2.0000  -3.0000][  4.0000  -5.0000 "
+            "  6.0000][ -7.0000   8.0000  -9.0000])");
+}
+
+TEST(PaintOpHelper, TranslateToString) {
+  TranslateOp op(0, 0);
+  op.type = static_cast<uint32_t>(TranslateOp::kType);
+  std::string str = PaintOpHelper::ToString(&op);
+  EXPECT_EQ(str, "TranslateOp(dx=0.000, dy=0.000)");
+}
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/test/paint_op_helper.h b/cc/test/paint_op_helper.h
new file mode 100644
index 0000000..5a0810d4
--- /dev/null
+++ b/cc/test/paint_op_helper.h
@@ -0,0 +1,381 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_TEST_PAINT_OP_HELPER_H_
+#define CC_TEST_PAINT_OP_HELPER_H_
+
+#include <sstream>
+#include <string>
+
+#include "base/strings/stringprintf.h"
+#include "cc/paint/paint_op_buffer.h"
+
+namespace cc {
+
+// A helper class to help with debugging PaintOp/PaintOpBuffer.
+// Note that this file is primarily used for debugging. As such, it isn't
+// typically a part of BUILD.gn (except for self-testing), so all of the
+// implementation should be limited ot the header.
+class PaintOpHelper {
+ public:
+  static std::string ToString(PaintOp* base_op) {
+    std::ostringstream str;
+    str << std::boolalpha;
+    switch (base_op->GetType()) {
+      case PaintOpType::Annotate: {
+        const auto* op = static_cast<const AnnotateOp*>(base_op);
+        str << "AnnotateOp(type="
+            << PaintOpHelper::EnumToString(op->annotation_type)
+            << ", rect=" << PaintOpHelper::SkiaTypeToString(op->rect)
+            << ", data=" << PaintOpHelper::SkiaTypeToString(op->data) << ")";
+        break;
+      }
+      case PaintOpType::ClipPath: {
+        const auto* op = static_cast<const ClipPathOp*>(base_op);
+        str << "ClipPathOp(path=" << PaintOpHelper::SkiaTypeToString(op->path)
+            << ", op=" << PaintOpHelper::SkiaTypeToString(op->op)
+            << ", antialias=" << op->antialias << ")";
+        break;
+      }
+      case PaintOpType::ClipRect: {
+        const auto* op = static_cast<const ClipRectOp*>(base_op);
+        str << "ClipRectOp(rect=" << PaintOpHelper::SkiaTypeToString(op->rect)
+            << ", op=" << PaintOpHelper::SkiaTypeToString(op->op)
+            << ", antialias=" << op->antialias << ")";
+        break;
+      }
+      case PaintOpType::ClipRRect: {
+        const auto* op = static_cast<const ClipRRectOp*>(base_op);
+        str << "ClipRRectOp(rrect="
+            << PaintOpHelper::SkiaTypeToString(op->rrect)
+            << ", op=" << PaintOpHelper::SkiaTypeToString(op->op)
+            << ", antialias=" << op->antialias << ")";
+        break;
+      }
+      case PaintOpType::Concat: {
+        const auto* op = static_cast<const ConcatOp*>(base_op);
+        str << "ConcatOp(matrix=" << PaintOpHelper::SkiaTypeToString(op->matrix)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawColor: {
+        const auto* op = static_cast<const DrawColorOp*>(base_op);
+        str << "DrawColorOp(color="
+            << PaintOpHelper::SkiaTypeToString(op->color)
+            << ", mode=" << PaintOpHelper::SkiaTypeToString(op->mode) << ")";
+        break;
+      }
+      case PaintOpType::DrawDRRect: {
+        const auto* op = static_cast<const DrawDRRectOp*>(base_op);
+        str << "DrawDRRectOp(outer="
+            << PaintOpHelper::SkiaTypeToString(op->outer)
+            << ", inner=" << PaintOpHelper::SkiaTypeToString(op->inner) << ")";
+        break;
+      }
+      case PaintOpType::DrawImage: {
+        const auto* op = static_cast<const DrawImageOp*>(base_op);
+        str << "DrawImageOp(image=" << PaintOpHelper::ImageToString(op->image)
+            << ", left=" << PaintOpHelper::SkiaTypeToString(op->left)
+            << ", top=" << PaintOpHelper::SkiaTypeToString(op->top) << ")";
+        break;
+      }
+      case PaintOpType::DrawImageRect: {
+        const auto* op = static_cast<const DrawImageRectOp*>(base_op);
+        str << "DrawImageRectOp(image="
+            << PaintOpHelper::ImageToString(op->image)
+            << ", src=" << PaintOpHelper::SkiaTypeToString(op->src)
+            << ", dst=" << PaintOpHelper::SkiaTypeToString(op->dst)
+            << ", constraint=" << PaintOpHelper::EnumToString(op->constraint)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawIRect: {
+        const auto* op = static_cast<const DrawIRectOp*>(base_op);
+        str << "DrawIRectOp(rect=" << PaintOpHelper::SkiaTypeToString(op->rect)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawLine: {
+        const auto* op = static_cast<const DrawLineOp*>(base_op);
+        str << "DrawLineOp(x0=" << PaintOpHelper::SkiaTypeToString(op->x0)
+            << ", y0=" << PaintOpHelper::SkiaTypeToString(op->y0)
+            << ", x1=" << PaintOpHelper::SkiaTypeToString(op->x1)
+            << ", y1=" << PaintOpHelper::SkiaTypeToString(op->y1) << ")";
+        break;
+      }
+      case PaintOpType::DrawOval: {
+        const auto* op = static_cast<const DrawOvalOp*>(base_op);
+        str << "DrawOvalOp(oval=" << PaintOpHelper::SkiaTypeToString(op->oval)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawPath: {
+        const auto* op = static_cast<const DrawPathOp*>(base_op);
+        str << "DrawPathOp(path=" << PaintOpHelper::SkiaTypeToString(op->path)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawRecord: {
+        const auto* op = static_cast<const DrawRecordOp*>(base_op);
+        str << "DrawRecordOp(record="
+            << PaintOpHelper::RecordToString(op->record) << ")";
+        break;
+      }
+      case PaintOpType::DrawRect: {
+        const auto* op = static_cast<const DrawRectOp*>(base_op);
+        str << "DrawRectOp(rect=" << PaintOpHelper::SkiaTypeToString(op->rect)
+            << ")";
+        break;
+      }
+      case PaintOpType::DrawRRect: {
+        const auto* op = static_cast<const DrawRRectOp*>(base_op);
+        str << "DrawRRectOp(rrect="
+            << PaintOpHelper::SkiaTypeToString(op->rrect) << ")";
+        break;
+      }
+      case PaintOpType::DrawTextBlob: {
+        const auto* op = static_cast<const DrawTextBlobOp*>(base_op);
+        str << "DrawTextBlobOp(blob="
+            << PaintOpHelper::TextBlobToString(op->blob)
+            << ", x=" << PaintOpHelper::SkiaTypeToString(op->x)
+            << ", y=" << PaintOpHelper::SkiaTypeToString(op->y) << ")";
+        break;
+      }
+      case PaintOpType::Noop: {
+        str << "NoopOp()";
+        break;
+      }
+      case PaintOpType::Restore: {
+        str << "RestoreOp()";
+        break;
+      }
+      case PaintOpType::Rotate: {
+        const auto* op = static_cast<const RotateOp*>(base_op);
+        str << "RotateOp(degrees="
+            << PaintOpHelper::SkiaTypeToString(op->degrees) << ")";
+        break;
+      }
+      case PaintOpType::Save: {
+        str << "SaveOp()";
+        break;
+      }
+      case PaintOpType::SaveLayer: {
+        const auto* op = static_cast<const SaveLayerOp*>(base_op);
+        str << "SaveLayerOp(bounds="
+            << PaintOpHelper::SkiaTypeToString(op->bounds) << ")";
+        break;
+      }
+      case PaintOpType::SaveLayerAlpha: {
+        const auto* op = static_cast<const SaveLayerAlphaOp*>(base_op);
+        str << "SaveLayerAlphaOp(bounds="
+            << PaintOpHelper::SkiaTypeToString(op->bounds)
+            << ", alpha=" << static_cast<uint32_t>(op->alpha)
+            << ", preserve_lcd_text_requests=" << op->preserve_lcd_text_requests
+            << ")";
+        break;
+      }
+      case PaintOpType::Scale: {
+        const auto* op = static_cast<const ScaleOp*>(base_op);
+        str << "ScaleOp(sx=" << PaintOpHelper::SkiaTypeToString(op->sx)
+            << ", sy=" << PaintOpHelper::SkiaTypeToString(op->sy) << ")";
+        break;
+      }
+      case PaintOpType::SetMatrix: {
+        const auto* op = static_cast<const SetMatrixOp*>(base_op);
+        str << "SetMatrixOp(matrix="
+            << PaintOpHelper::SkiaTypeToString(op->matrix) << ")";
+        break;
+      }
+      case PaintOpType::Translate: {
+        const auto* op = static_cast<const TranslateOp*>(base_op);
+        str << "TranslateOp(dx=" << PaintOpHelper::SkiaTypeToString(op->dx)
+            << ", dy=" << PaintOpHelper::SkiaTypeToString(op->dy) << ")";
+        break;
+      }
+    }
+    return str.str();
+  }
+
+ private:
+  template <typename T>
+  static std::string SkiaTypeToString(const T&) {
+    return "<unknown skia type>";
+  }
+
+  static std::string SkiaTypeToString(const SkScalar& scalar) {
+    return base::StringPrintf("%.3f", scalar);
+  }
+
+  static std::string SkiaTypeToString(const SkRect& rect) {
+    return base::StringPrintf("[%.3f,%.3f %.3fx%.3f]", rect.x(), rect.y(),
+                              rect.width(), rect.height());
+  }
+
+  static std::string SkiaTypeToString(const SkIRect& rect) {
+    return base::StringPrintf("[%d,%d %dx%d]", rect.x(), rect.y(), rect.width(),
+                              rect.height());
+  }
+
+  static std::string SkiaTypeToString(const SkRRect& rect) {
+    return base::StringPrintf("[bounded by %.3f,%.3f %.3fx%.3f]",
+                              rect.rect().x(), rect.rect().y(),
+                              rect.rect().width(), rect.rect().height());
+  }
+
+  static std::string SkiaTypeToString(const ThreadsafeMatrix& matrix) {
+    return SkiaTypeToString(static_cast<const SkMatrix&>(matrix));
+  }
+
+  static std::string SkiaTypeToString(const SkMatrix& matrix) {
+    SkString str;
+    matrix.toString(&str);
+    return str.c_str();
+  }
+
+  static std::string SkiaTypeToString(const SkColor& color) {
+    return base::StringPrintf("rgba(%d, %d, %d, %d)", SkColorGetR(color),
+                              SkColorGetG(color), SkColorGetB(color),
+                              SkColorGetA(color));
+  }
+
+  static std::string SkiaTypeToString(const SkBlendMode& mode) {
+    switch (mode) {
+      default:
+        break;
+      case SkBlendMode::kClear:
+        return "kClear";
+      case SkBlendMode::kSrc:
+        return "kSrc";
+      case SkBlendMode::kDst:
+        return "kDst";
+      case SkBlendMode::kSrcOver:
+        return "kSrcOver";
+      case SkBlendMode::kDstOver:
+        return "kDstOver";
+      case SkBlendMode::kSrcIn:
+        return "kSrcIn";
+      case SkBlendMode::kDstIn:
+        return "kDstIn";
+      case SkBlendMode::kSrcOut:
+        return "kSrcOut";
+      case SkBlendMode::kDstOut:
+        return "kDstOut";
+      case SkBlendMode::kSrcATop:
+        return "kSrcATop";
+      case SkBlendMode::kDstATop:
+        return "kDstATop";
+      case SkBlendMode::kXor:
+        return "kXor";
+      case SkBlendMode::kPlus:
+        return "kPlus";
+      case SkBlendMode::kModulate:
+        return "kModulate";
+      case SkBlendMode::kScreen:
+        return "kScreen";
+      case SkBlendMode::kOverlay:
+        return "kOverlay";
+      case SkBlendMode::kDarken:
+        return "kDarken";
+      case SkBlendMode::kLighten:
+        return "kLighten";
+      case SkBlendMode::kColorDodge:
+        return "kColorDodge";
+      case SkBlendMode::kColorBurn:
+        return "kColorBurn";
+      case SkBlendMode::kHardLight:
+        return "kHardLight";
+      case SkBlendMode::kSoftLight:
+        return "kSoftLight";
+      case SkBlendMode::kDifference:
+        return "kDifference";
+      case SkBlendMode::kExclusion:
+        return "kExclusion";
+      case SkBlendMode::kMultiply:
+        return "kMultiply";
+      case SkBlendMode::kHue:
+        return "kHue";
+      case SkBlendMode::kSaturation:
+        return "kSaturation";
+      case SkBlendMode::kColor:
+        return "kColor";
+      case SkBlendMode::kLuminosity:
+        return "kLuminosity";
+    }
+    return "<unknown SkBlendMode>";
+  }
+
+  static std::string SkiaTypeToString(const SkClipOp& op) {
+    switch (op) {
+      default:
+        break;
+      case SkClipOp::kDifference:
+        return "kDifference";
+      case SkClipOp::kIntersect:
+        return "kIntersect";
+    }
+    return "<unknown SkClipOp>";
+  }
+
+  static std::string SkiaTypeToString(const sk_sp<SkData> data) {
+    return "<SkData>";
+  }
+
+  static std::string SkiaTypeToString(const ThreadsafePath& path) {
+    return SkiaTypeToString(static_cast<const SkPath&>(path));
+  }
+
+  static std::string SkiaTypeToString(const SkPath& path) {
+    // TODO(vmpstr): SkPath has a dump function which we can use here?
+    return "<SkPath>";
+  }
+
+  template <typename T>
+  static std::string EnumToString(T) {
+    return "<unknown enum type>";
+  }
+
+  static std::string EnumToString(PaintCanvas::AnnotationType type) {
+    switch (type) {
+      default:
+        break;
+      case PaintCanvas::AnnotationType::URL:
+        return "URL";
+      case PaintCanvas::AnnotationType::NAMED_DESTINATION:
+        return "NAMED_DESTINATION";
+      case PaintCanvas::AnnotationType::LINK_TO_DESTINATION:
+        return "LINK_TO_DESTINATION";
+    }
+    return "<unknown AnnotationType>";
+  }
+
+  static std::string EnumToString(
+      const PaintCanvas::SrcRectConstraint& constraint) {
+    switch (constraint) {
+      default:
+        break;
+      case PaintCanvas::kStrict_SrcRectConstraint:
+        return "kStrict_SrcRectConstraint";
+      case PaintCanvas::kFast_SrcRectConstraint:
+        return "kFast_SrcRectConstraint";
+    }
+    return "<unknown SrcRectConstraint>";
+  }
+
+  static std::string ImageToString(const PaintImage& image) {
+    return "<paint image>";
+  }
+
+  static std::string RecordToString(const sk_sp<const PaintRecord>& record) {
+    return "<paint record>";
+  }
+
+  static std::string TextBlobToString(
+      const scoped_refptr<PaintTextBlob>& blob) {
+    return "<paint text blob>";
+  }
+};
+
+}  // namespace cc
+
+#endif  // CC_TEST_PAINT_OP_HELPER_H_