[go: nahoru, domu]

Ensure that thin, aliased strokes are always rasterized

Without anti-aliasing, thin strokes (device width < 1) may or may not
raster, depending on their position relative to sample points.

This is generally undesirable, and can be avoided with Skia's hairline
stroke mode or by enabling anti-aliasing.

This CL adds logic to detect subpixel aliased strokes at raster time,
and adjust the paint on the fly to ensure that the stroke is visible.

The implementation is based on a ScopedImageFlags refactoring:

1) rename to ScopedRasterFlags (more accurate, considering it already
   performs some non-image flags filtering)

2) get rid of instantiation preconditions - they are duplicated in
   in the ctor, as filter triggers

3) store a reference to the original flags and construct the modified
   flags lazily, only when/if needed

4) if no filtering is triggered/performed, return the original flags

BUG=791111

Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel
Change-Id: I029efcf97be8a201d3224957a42c3394be6c272a
Reviewed-on: https://chromium-review.googlesource.com/811606
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: vmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#522652}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 29f127f..a1a5a18 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -622,7 +622,7 @@
     "paint/paint_op_buffer_unittest.cc",
     "paint/paint_op_helper_unittest.cc",
     "paint/paint_shader_unittest.cc",
-    "paint/scoped_image_flags_unittest.cc",
+    "paint/scoped_raster_flags_unittest.cc",
     "paint/solid_color_analyzer_unittest.cc",
     "paint/transfer_cache_unittest.cc",
     "raster/playback_image_provider_unittest.cc",
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index 3448f4d..93438a6 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -66,8 +66,8 @@
     "record_paint_canvas.h",
     "render_surface_filters.cc",
     "render_surface_filters.h",
-    "scoped_image_flags.cc",
-    "scoped_image_flags.h",
+    "scoped_raster_flags.cc",
+    "scoped_raster_flags.h",
     "skia_paint_canvas.cc",
     "skia_paint_canvas.h",
     "skia_paint_image_generator.cc",
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 5edbd0b..4c603ea 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -12,7 +12,7 @@
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
 #include "cc/paint/paint_record.h"
-#include "cc/paint/scoped_image_flags.h"
+#include "cc/paint/scoped_raster_flags.h"
 #include "third_party/skia/include/core/SkAnnotation.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkRegion.h"
@@ -2165,18 +2165,10 @@
 
     if (op->IsPaintOpWithFlags()) {
       const auto* flags_op = static_cast<const PaintOpWithFlags*>(op);
-      base::Optional<ScopedImageFlags> scoped_flags;
-      const bool needs_flag_override =
-          iter.alpha() != 255 ||
-          (flags_op->flags.HasDiscardableImages() && params.image_provider);
-      if (needs_flag_override) {
-        scoped_flags.emplace(params.image_provider, &flags_op->flags,
-                             canvas->getTotalMatrix(), iter.alpha());
-      }
-
-      const PaintFlags* raster_flags =
-          scoped_flags ? scoped_flags->flags() : &flags_op->flags;
-      if (raster_flags)
+      const ScopedRasterFlags scoped_flags(
+          &flags_op->flags, params.image_provider, canvas->getTotalMatrix(),
+          iter.alpha());
+      if (const auto* raster_flags = scoped_flags.flags())
         flags_op->RasterWithFlags(canvas, raster_flags, params);
       continue;
     }
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 2b1e250..c32b09e2 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -1026,7 +1026,6 @@
   friend class DisplayItemList;
   friend class PaintOpBufferOffsetsTest;
   friend class SolidColorAnalyzer;
-  friend class ScopedImageFlags;
 
   // Replays the paint op buffer into the canvas. If |indices| is specified, it
   // contains indices in an increasing order and only the indices specified in
diff --git a/cc/paint/paint_op_buffer_serializer.cc b/cc/paint/paint_op_buffer_serializer.cc
index 856c09b..e24784a 100644
--- a/cc/paint/paint_op_buffer_serializer.cc
+++ b/cc/paint/paint_op_buffer_serializer.cc
@@ -5,7 +5,7 @@
 #include "cc/paint/paint_op_buffer_serializer.h"
 
 #include "base/bind.h"
-#include "cc/paint/scoped_image_flags.h"
+#include "cc/paint/scoped_raster_flags.h"
 #include "ui/gfx/skia_util.h"
 
 namespace cc {
@@ -124,18 +124,10 @@
     PaintOp::SerializeOptions* options,
     const PlaybackParams& params,
     uint8_t alpha) {
-  const bool needs_flag_override =
-      alpha != 255 ||
-      (flags_op->flags.HasDiscardableImages() && options->image_provider);
-
-  base::Optional<ScopedImageFlags> scoped_flags;
-  if (needs_flag_override) {
-    scoped_flags.emplace(options->image_provider, &flags_op->flags,
-                         options->canvas->getTotalMatrix(), alpha);
-  }
-
-  const PaintFlags* flags_to_serialize =
-      scoped_flags ? scoped_flags->flags() : &flags_op->flags;
+  const ScopedRasterFlags scoped_flags(
+      &flags_op->flags, options->image_provider,
+      options->canvas->getTotalMatrix(), alpha);
+  const PaintFlags* flags_to_serialize = scoped_flags.flags();
   if (!flags_to_serialize)
     return true;
 
diff --git a/cc/paint/paint_shader.h b/cc/paint/paint_shader.h
index 6c72074..de9afa1 100644
--- a/cc/paint/paint_shader.h
+++ b/cc/paint/paint_shader.h
@@ -134,7 +134,7 @@
   friend class PaintOpReader;
   friend class PaintOpSerializationTestUtils;
   friend class PaintOpWriter;
-  friend class ScopedImageFlags;
+  friend class ScopedRasterFlags;
   FRIEND_TEST_ALL_PREFIXES(PaintShaderTest, DecodePaintRecord);
 
   explicit PaintShader(Type type);
diff --git a/cc/paint/scoped_image_flags.cc b/cc/paint/scoped_image_flags.cc
deleted file mode 100644
index ea81ad5..0000000
--- a/cc/paint/scoped_image_flags.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// 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/paint/scoped_image_flags.h"
-
-#include "cc/paint/image_provider.h"
-#include "cc/paint/paint_image_builder.h"
-
-namespace cc {
-ScopedImageFlags::DecodeStashingImageProvider::DecodeStashingImageProvider(
-    ImageProvider* source_provider)
-    : source_provider_(source_provider) {
-  DCHECK(source_provider_);
-}
-ScopedImageFlags::DecodeStashingImageProvider::~DecodeStashingImageProvider() =
-    default;
-
-ImageProvider::ScopedDecodedDrawImage
-ScopedImageFlags::DecodeStashingImageProvider::GetDecodedDrawImage(
-    const DrawImage& draw_image) {
-  auto decode = source_provider_->GetDecodedDrawImage(draw_image);
-  if (!decode)
-    return ScopedDecodedDrawImage();
-
-  // No need to add any destruction callback to the returned image. The images
-  // decoded here match the lifetime of this provider.
-  auto image_to_return = ScopedDecodedDrawImage(decode.decoded_image());
-  decoded_images_->push_back(std::move(decode));
-  return image_to_return;
-}
-
-ScopedImageFlags::ScopedImageFlags(ImageProvider* image_provider,
-                                   const PaintFlags* flags,
-                                   const SkMatrix& ctm,
-                                   uint8_t alpha) {
-  DCHECK(flags->HasDiscardableImages() || alpha != 255);
-  if (flags->HasDiscardableImages() && image_provider) {
-    DCHECK(flags->HasShader());
-
-    decode_stashing_image_provider_.emplace(image_provider);
-    modified_flags_.emplace(*flags);
-    if (flags->getShader()->shader_type() == PaintShader::Type::kImage) {
-      DecodeImageShader(flags, ctm);
-    } else if (flags->getShader()->shader_type() ==
-               PaintShader::Type::kPaintRecord) {
-      DecodeRecordShader(flags, ctm);
-    } else {
-      NOTREACHED();
-    }
-
-    // We skip the op if any images fail to decode.
-    if (decode_failed_)
-      return;
-  }
-
-  if (alpha != 255) {
-    DCHECK(flags->SupportsFoldingAlpha());
-    if (!modified_flags_)
-      modified_flags_.emplace(*flags);
-    modified_flags_->setAlpha(SkMulDiv255Round(flags->getAlpha(), alpha));
-  }
-
-  DCHECK(modified_flags_);
-}
-
-ScopedImageFlags::~ScopedImageFlags() = default;
-
-void ScopedImageFlags::DecodeImageShader(const PaintFlags* original,
-                                         const SkMatrix& ctm) {
-  const PaintImage& paint_image = original->getShader()->paint_image();
-  SkMatrix matrix = original->getShader()->GetLocalMatrix();
-
-  SkMatrix total_image_matrix = matrix;
-  total_image_matrix.preConcat(ctm);
-  SkRect src_rect = SkRect::MakeIWH(paint_image.width(), paint_image.height());
-  SkIRect int_src_rect;
-  src_rect.roundOut(&int_src_rect);
-  DrawImage draw_image(paint_image, int_src_rect, original->getFilterQuality(),
-                       total_image_matrix);
-  auto decoded_draw_image =
-      decode_stashing_image_provider_->GetDecodedDrawImage(draw_image);
-
-  if (!decoded_draw_image) {
-    decode_failed_ = true;
-    return;
-  }
-
-  const auto& decoded_image = decoded_draw_image.decoded_image();
-  DCHECK(decoded_image.image());
-
-  bool need_scale = !decoded_image.is_scale_adjustment_identity();
-  if (need_scale) {
-    matrix.preScale(1.f / decoded_image.scale_adjustment().width(),
-                    1.f / decoded_image.scale_adjustment().height());
-  }
-
-  sk_sp<SkImage> sk_image =
-      sk_ref_sp<SkImage>(const_cast<SkImage*>(decoded_image.image().get()));
-  PaintImage decoded_paint_image = PaintImageBuilder::WithDefault()
-                                       .set_id(paint_image.stable_id())
-                                       .set_image(std::move(sk_image))
-                                       .TakePaintImage();
-  modified_flags_->setFilterQuality(decoded_image.filter_quality());
-  modified_flags_->setShader(
-      PaintShader::MakeImage(decoded_paint_image, original->getShader()->tx(),
-                             original->getShader()->ty(), &matrix));
-}
-
-void ScopedImageFlags::DecodeRecordShader(const PaintFlags* original,
-                                          const SkMatrix& ctm) {
-  auto decoded_shader = original->getShader()->CreateDecodedPaintRecord(
-      ctm, &*decode_stashing_image_provider_);
-  if (!decoded_shader) {
-    decode_failed_ = true;
-    return;
-  }
-
-  modified_flags_->setShader(std::move(decoded_shader));
-}
-
-}  // namespace cc
diff --git a/cc/paint/scoped_raster_flags.cc b/cc/paint/scoped_raster_flags.cc
new file mode 100644
index 0000000..deb5ee8
--- /dev/null
+++ b/cc/paint/scoped_raster_flags.cc
@@ -0,0 +1,154 @@
+// 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/paint/scoped_raster_flags.h"
+
+#include "cc/paint/image_provider.h"
+#include "cc/paint/paint_image_builder.h"
+
+namespace cc {
+ScopedRasterFlags::DecodeStashingImageProvider::DecodeStashingImageProvider(
+    ImageProvider* source_provider)
+    : source_provider_(source_provider) {
+  DCHECK(source_provider_);
+}
+ScopedRasterFlags::DecodeStashingImageProvider::~DecodeStashingImageProvider() =
+    default;
+
+ImageProvider::ScopedDecodedDrawImage
+ScopedRasterFlags::DecodeStashingImageProvider::GetDecodedDrawImage(
+    const DrawImage& draw_image) {
+  auto decode = source_provider_->GetDecodedDrawImage(draw_image);
+  if (!decode)
+    return ScopedDecodedDrawImage();
+
+  // No need to add any destruction callback to the returned image. The images
+  // decoded here match the lifetime of this provider.
+  auto image_to_return = ScopedDecodedDrawImage(decode.decoded_image());
+  decoded_images_->push_back(std::move(decode));
+  return image_to_return;
+}
+
+ScopedRasterFlags::ScopedRasterFlags(const PaintFlags* flags,
+                                     ImageProvider* image_provider,
+                                     const SkMatrix& ctm,
+                                     uint8_t alpha)
+    : original_flags_(flags) {
+  if (flags->HasDiscardableImages() && image_provider) {
+    DCHECK(flags->HasShader());
+
+    decode_stashing_image_provider_.emplace(image_provider);
+    if (flags->getShader()->shader_type() == PaintShader::Type::kImage) {
+      DecodeImageShader(ctm);
+    } else if (flags->getShader()->shader_type() ==
+               PaintShader::Type::kPaintRecord) {
+      DecodeRecordShader(ctm);
+    } else {
+      NOTREACHED();
+    }
+
+    // We skip the op if any images fail to decode.
+    if (decode_failed_)
+      return;
+  }
+
+  if (alpha != 255) {
+    DCHECK(flags->SupportsFoldingAlpha());
+    MutableFlags()->setAlpha(SkMulDiv255Round(flags->getAlpha(), alpha));
+  }
+
+  AdjustStrokeIfNeeded(ctm);
+}
+
+ScopedRasterFlags::~ScopedRasterFlags() = default;
+
+void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) {
+  const PaintImage& paint_image = flags()->getShader()->paint_image();
+  SkMatrix matrix = flags()->getShader()->GetLocalMatrix();
+
+  SkMatrix total_image_matrix = matrix;
+  total_image_matrix.preConcat(ctm);
+  SkRect src_rect = SkRect::MakeIWH(paint_image.width(), paint_image.height());
+  SkIRect int_src_rect;
+  src_rect.roundOut(&int_src_rect);
+  DrawImage draw_image(paint_image, int_src_rect, flags()->getFilterQuality(),
+                       total_image_matrix);
+  auto decoded_draw_image =
+      decode_stashing_image_provider_->GetDecodedDrawImage(draw_image);
+
+  if (!decoded_draw_image) {
+    decode_failed_ = true;
+    return;
+  }
+
+  const auto& decoded_image = decoded_draw_image.decoded_image();
+  DCHECK(decoded_image.image());
+
+  bool need_scale = !decoded_image.is_scale_adjustment_identity();
+  if (need_scale) {
+    matrix.preScale(1.f / decoded_image.scale_adjustment().width(),
+                    1.f / decoded_image.scale_adjustment().height());
+  }
+
+  sk_sp<SkImage> sk_image =
+      sk_ref_sp<SkImage>(const_cast<SkImage*>(decoded_image.image().get()));
+  PaintImage decoded_paint_image = PaintImageBuilder::WithDefault()
+                                       .set_id(paint_image.stable_id())
+                                       .set_image(std::move(sk_image))
+                                       .TakePaintImage();
+  MutableFlags()->setFilterQuality(decoded_image.filter_quality());
+  MutableFlags()->setShader(
+      PaintShader::MakeImage(decoded_paint_image, flags()->getShader()->tx(),
+                             flags()->getShader()->ty(), &matrix));
+}
+
+void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm) {
+  auto decoded_shader = flags()->getShader()->CreateDecodedPaintRecord(
+      ctm, &*decode_stashing_image_provider_);
+  if (!decoded_shader) {
+    decode_failed_ = true;
+    return;
+  }
+
+  MutableFlags()->setShader(std::move(decoded_shader));
+}
+
+void ScopedRasterFlags::AdjustStrokeIfNeeded(const SkMatrix& ctm) {
+  // With anti-aliasing turned off, strokes with a device space width in (0, 1)
+  // may not raster at all.  To avoid this, we have two options:
+  //
+  // 1) force a hairline stroke (stroke-width == 0)
+  // 2) force anti-aliasing on
+
+  SkSize scale;
+  if (flags()->isAntiAlias() ||                          // safe to raster
+      flags()->getStyle() == PaintFlags::kFill_Style ||  // not a stroke
+      !flags()->getStrokeWidth() ||                      // explicit hairline
+      !ctm.decomposeScale(&scale)) {                     // cannot decompose
+    return;
+  }
+
+  const auto stroke_vec =
+      SkVector::Make(flags()->getStrokeWidth() * scale.width(),
+                     flags()->getStrokeWidth() * scale.height());
+  if (stroke_vec.x() >= 1.f && stroke_vec.y() >= 1.f)
+    return;  // safe to raster
+
+  const auto can_substitute_hairline =
+      flags()->getStrokeCap() == PaintFlags::kDefault_Cap &&
+      flags()->getStrokeJoin() == PaintFlags::kDefault_Join;
+  if (can_substitute_hairline && stroke_vec.x() < 1.f && stroke_vec.y() < 1.f) {
+    // Use modulated hairline when possible, as it is faster and produces
+    // results closer to the original intent.
+    MutableFlags()->setStrokeWidth(0);
+    MutableFlags()->setAlpha(std::round(
+        flags()->getAlpha() * std::sqrt(stroke_vec.x() * stroke_vec.y())));
+    return;
+  }
+
+  // Fall back to anti-aliasing.
+  MutableFlags()->setAntiAlias(true);
+}
+
+}  // namespace cc
diff --git a/cc/paint/scoped_image_flags.h b/cc/paint/scoped_raster_flags.h
similarity index 61%
rename from cc/paint/scoped_image_flags.h
rename to cc/paint/scoped_raster_flags.h
index d6df03d..159b4d4 100644
--- a/cc/paint/scoped_image_flags.h
+++ b/cc/paint/scoped_raster_flags.h
@@ -2,33 +2,35 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CC_PAINT_SCOPED_IMAGE_FLAGS_H_
-#define CC_PAINT_SCOPED_IMAGE_FLAGS_H_
+#ifndef CC_PAINT_SCOPED_RASTER_FLAGS_H_
+#define CC_PAINT_SCOPED_RASTER_FLAGS_H_
 
+#include "base/containers/stack_container.h"
 #include "base/macros.h"
+#include "cc/paint/image_provider.h"
 #include "cc/paint/paint_export.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_flags.h"
 
 namespace cc {
-class ImageProvider;
 
 // A helper class to modify the flags for raster. This includes alpha folding
 // from SaveLayers and decoding images.
-class CC_PAINT_EXPORT ScopedImageFlags {
+class CC_PAINT_EXPORT ScopedRasterFlags {
  public:
-  // |image_provider| and |flags| must outlive this class.
-  ScopedImageFlags(ImageProvider* image_provider,
-                   const PaintFlags* flags,
-                   const SkMatrix& ctm,
-                   uint8_t alpha);
-  ~ScopedImageFlags();
+  // |flags| and |image_provider| must outlive this class.
+  ScopedRasterFlags(const PaintFlags* flags,
+                    ImageProvider* image_provider,
+                    const SkMatrix& ctm,
+                    uint8_t alpha);
+  ~ScopedRasterFlags();
 
   // The usage of these flags should not extend beyond the lifetime of this
   // object.
   const PaintFlags* flags() const {
     if (decode_failed_)
       return nullptr;
-    return &*modified_flags_;
+
+    return modified_flags_ ? &*modified_flags_ : original_flags_;
   }
 
  private:
@@ -52,17 +54,24 @@
     DISALLOW_COPY_AND_ASSIGN(DecodeStashingImageProvider);
   };
 
-  void DecodeImageShader(const PaintFlags* original, const SkMatrix& ctm);
-  void DecodeRecordShader(const PaintFlags* original, const SkMatrix& ctm);
-  void DecodeFailed();
+  void DecodeImageShader(const SkMatrix& ctm);
+  void DecodeRecordShader(const SkMatrix& ctm);
+  void AdjustStrokeIfNeeded(const SkMatrix& ctm);
 
-  bool decode_failed_ = false;
+  PaintFlags* MutableFlags() {
+    if (!modified_flags_)
+      modified_flags_.emplace(*original_flags_);
+    return &*modified_flags_;
+  }
+
+  const PaintFlags* original_flags_;
   base::Optional<PaintFlags> modified_flags_;
   base::Optional<DecodeStashingImageProvider> decode_stashing_image_provider_;
+  bool decode_failed_ = false;
 
-  DISALLOW_COPY_AND_ASSIGN(ScopedImageFlags);
+  DISALLOW_COPY_AND_ASSIGN(ScopedRasterFlags);
 };
 
 }  // namespace cc
 
-#endif  // CC_PAINT_SCOPED_IMAGE_FLAGS_H_
+#endif  // CC_PAINT_SCOPED_RASTER_FLAGS_H_
diff --git a/cc/paint/scoped_image_flags_unittest.cc b/cc/paint/scoped_raster_flags_unittest.cc
similarity index 60%
rename from cc/paint/scoped_image_flags_unittest.cc
rename to cc/paint/scoped_raster_flags_unittest.cc
index 7bdb8b8a..e3127f5 100644
--- a/cc/paint/scoped_image_flags_unittest.cc
+++ b/cc/paint/scoped_raster_flags_unittest.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "cc/paint/scoped_image_flags.h"
+#include "cc/paint/scoped_raster_flags.h"
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "cc/paint/paint_op_buffer.h"
 #include "cc/test/skia_common.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -42,7 +43,7 @@
 };
 }  // namespace
 
-TEST(ScopedImageFlagsTest, KeepsDecodesAlive) {
+TEST(ScopedRasterFlagsTest, KeepsDecodesAlive) {
   auto record = sk_make_sp<PaintOpBuffer>();
   record->push<DrawImageOp>(CreateDiscardablePaintImage(gfx::Size(10, 10)), 0.f,
                             0.f, nullptr);
@@ -58,7 +59,7 @@
   PaintFlags flags;
   flags.setShader(record_shader);
   {
-    ScopedImageFlags scoped_flags(&provider, &flags, SkMatrix::I(), 255);
+    ScopedRasterFlags scoped_flags(&flags, &provider, SkMatrix::I(), 255);
     ASSERT_TRUE(scoped_flags.flags());
     EXPECT_NE(scoped_flags.flags(), &flags);
     SkPaint paint = scoped_flags.flags()->ToSkPaint();
@@ -68,16 +69,52 @@
   EXPECT_EQ(provider.ref_count(), 0);
 }
 
-TEST(ScopedImageFlagsTest, NoImageProvider) {
+TEST(ScopedRasterFlagsTest, NoImageProvider) {
   PaintFlags flags;
   flags.setAlpha(255);
   flags.setShader(PaintShader::MakeImage(
       CreateDiscardablePaintImage(gfx::Size(10, 10)),
       SkShader::TileMode::kClamp_TileMode, SkShader::TileMode::kClamp_TileMode,
       &SkMatrix::I()));
-  ScopedImageFlags scoped_flags(nullptr, &flags, SkMatrix::I(), 10);
+  ScopedRasterFlags scoped_flags(&flags, nullptr, SkMatrix::I(), 10);
   EXPECT_NE(scoped_flags.flags(), &flags);
   EXPECT_EQ(scoped_flags.flags()->getAlpha(), SkMulDiv255Round(255, 10));
 }
 
+TEST(ScopedRasterFlagsTest, ThinAliasedStroke) {
+  PaintFlags flags;
+  flags.setStyle(PaintFlags::kStroke_Style);
+  flags.setStrokeWidth(1);
+  flags.setAntiAlias(false);
+
+  struct {
+    SkMatrix ctm;
+    uint8_t alpha;
+
+    bool expect_same_flags;
+    bool expect_aa;
+    float expect_stroke_width;
+    uint8_t expect_alpha;
+  } tests[] = {
+      // No downscaling                    => no stroke change.
+      {SkMatrix::MakeScale(1.0f, 1.0f), 255, true, false, 1.0f, 0xFF},
+      // Symmetric downscaling             => modulated hairline stroke.
+      {SkMatrix::MakeScale(0.5f, 0.5f), 255, false, false, 0.0f, 0x80},
+      // Symmetric downscaling w/ alpha    => modulated hairline stroke.
+      {SkMatrix::MakeScale(0.5f, 0.5f), 127, false, false, 0.0f, 0x40},
+      // Anisotropic scaling              => AA stroke.
+      {SkMatrix::MakeScale(0.5f, 1.5f), 255, false, true, 1.0f, 0xFF},
+  };
+
+  for (const auto& test : tests) {
+    ScopedRasterFlags scoped_flags(&flags, nullptr, test.ctm, test.alpha);
+    ASSERT_TRUE(scoped_flags.flags());
+
+    EXPECT_EQ(scoped_flags.flags() == &flags, test.expect_same_flags);
+    EXPECT_EQ(scoped_flags.flags()->isAntiAlias(), test.expect_aa);
+    EXPECT_EQ(scoped_flags.flags()->getStrokeWidth(), test.expect_stroke_width);
+    EXPECT_EQ(scoped_flags.flags()->getAlpha(), test.expect_alpha);
+  }
+}
+
 }  // namespace cc
diff --git a/third_party/WebKit/LayoutTests/platform/linux/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/linux/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
index 7b3d3c5..88f7e9d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
index f1b87c9d..75b3112 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/win/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
index 877f319..d106386a 100644
--- a/third_party/WebKit/LayoutTests/platform/win/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/win7/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
index 380301d..6a4680ee 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/svg/W3C-SVG-1.1/filters-composite-02-b-expected.png
Binary files differ