Add colorFilterLayer/Widget (#35468)

diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version
index d264de3..3299376 100644
--- a/bin/internal/goldens.version
+++ b/bin/internal/goldens.version
@@ -1 +1,2 @@
-b2cef0060add1b33bf65c97aaf80146b54cf7b86
+09a19be7d0cd25eb7602b6ead198e8cf234a9ed2
+
diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart
index e41b6c0..22ef5d0 100644
--- a/packages/flutter/lib/src/rendering/layer.dart
+++ b/packages/flutter/lib/src/rendering/layer.dart
@@ -1196,6 +1196,45 @@
   }
 }
 
+/// A composite layer that applies a [ColorFilter] to its children.
+class ColorFilterLayer extends ContainerLayer {
+  /// Creates a layer that applies a [ColorFilter] to its children.
+  ///
+  /// The [ColorFilter] property must be non-null before the compositing phase
+  /// of the pipeline.
+  ColorFilterLayer({
+    @required ColorFilter colorFilter,
+  }) : _colorFilter = colorFilter,
+       assert(colorFilter != null);
+
+  /// The color filter to apply to children.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ColorFilter get colorFilter => _colorFilter;
+  ColorFilter _colorFilter;
+  set colorFilter(ColorFilter value) {
+    if (value != _colorFilter) {
+      _colorFilter = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    builder.pushColorFilter(colorFilter);
+    addChildrenToScene(builder, layerOffset);
+    builder.pop();
+    return null; // this does not return an engine layer yet.
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
+  }
+}
+
 /// A composited layer that applies a given transformation matrix to its
 /// children.
 ///
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index 3aeb6d8..8d9424f 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -444,6 +444,24 @@
     }
   }
 
+  /// Blend further painting with a color filter.
+  ///
+  /// * `offset` is the offset from the origin of the canvas' coordinate system
+  ///   to the origin of the caller's coordinate system.
+  /// * `colorFilter` is the [ColorFilter] value to use when blending the
+  ///   painting done by `painter`.
+  /// * `painter` is a callback that will paint with the `colorFilter` applied.
+  ///   This function calls the `painter` synchronously.
+  ///
+  /// A [RenderObject] that uses this function is very likely to require its
+  /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
+  /// ancestor render objects that this render object will include a composited
+  /// layer, which, for example, causes them to use composited clips.
+  void pushColorFilter(Offset offset, ColorFilter colorFilter, PaintingContextCallback painter) {
+    assert(colorFilter != null);
+    pushLayer(ColorFilterLayer(colorFilter: colorFilter), painter, offset);
+  }
+
   /// Transform further painting using a matrix.
   ///
   /// * `needsCompositing` is whether the child needs compositing. Typically
diff --git a/packages/flutter/lib/src/widgets/color_filter.dart b/packages/flutter/lib/src/widgets/color_filter.dart
new file mode 100644
index 0000000..923784d
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/color_filter.dart
@@ -0,0 +1,60 @@
+// Copyright 2019 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+
+import 'framework.dart';
+
+/// Applies a [ColorFilter] to its child.
+@immutable
+class ColorFiltered extends SingleChildRenderObjectWidget {
+  /// Creates a widget that applies a [ColorFilter] to its child.
+  ///
+  /// The [colorFilter] must not be null.
+  const ColorFiltered({@required this.colorFilter, Widget child, Key key})
+      : assert(colorFilter != null),
+        super(key: key, child: child);
+
+  /// The color filter to apply to the child of this widvget.
+  final ColorFilter colorFilter;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) => _ColorFilterRenderObject(colorFilter);
+
+  @override
+  void updateRenderObject(BuildContext context, _ColorFilterRenderObject renderObject) {
+    renderObject..colorFilter = colorFilter;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
+  }
+}
+
+class _ColorFilterRenderObject extends RenderProxyBox {
+  _ColorFilterRenderObject(this._colorFilter);
+
+  ColorFilter get colorFilter => _colorFilter;
+  ColorFilter _colorFilter;
+  set colorFilter(ColorFilter value) {
+    assert(value != null);
+    if (value != _colorFilter) {
+      _colorFilter = value;
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    context.pushColorFilter(offset, colorFilter, super.paint);
+  }
+}
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index d0871cf..5c8e006 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -27,6 +27,7 @@
 export 'src/widgets/basic.dart';
 export 'src/widgets/binding.dart';
 export 'src/widgets/bottom_navigation_bar_item.dart';
+export 'src/widgets/color_filter.dart';
 export 'src/widgets/container.dart';
 export 'src/widgets/debug.dart';
 export 'src/widgets/dismissible.dart';
diff --git a/packages/flutter/test/rendering/layers_test.dart b/packages/flutter/test/rendering/layers_test.dart
index 8abcf84..0137209 100644
--- a/packages/flutter/test/rendering/layers_test.dart
+++ b/packages/flutter/test/rendering/layers_test.dart
@@ -250,6 +250,15 @@
     });
   });
 
+  test('mutating ColorFilterLayer fields triggers needsAddToScene', () {
+    final ColorFilterLayer layer = ColorFilterLayer(
+      colorFilter: const ColorFilter.mode(Color(0xFFFF0000), BlendMode.color),
+    );
+    checkNeedsAddToScene(layer, () {
+      layer.colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color);
+    });
+  });
+
   test('mutating ShaderMaskLayer fields triggers needsAddToScene', () {
     const Gradient gradient = RadialGradient(colors: <Color>[Color(0x00000000), Color(0x00000001)]);
     final Shader shader = gradient.createShader(Rect.zero);
diff --git a/packages/flutter/test/widgets/color_filter_test.dart b/packages/flutter/test/widgets/color_filter_test.dart
new file mode 100644
index 0000000..2f09cd1
--- /dev/null
+++ b/packages/flutter/test/widgets/color_filter_test.dart
@@ -0,0 +1,69 @@
+// Copyright 2019 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+  testWidgets('Color filter - red', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      const RepaintBoundary(
+        child: ColorFiltered(
+          colorFilter: ColorFilter.mode(Colors.red, BlendMode.color),
+          child: Placeholder(),
+        ),
+      ),
+    );
+    await expectLater(
+      find.byType(ColorFiltered),
+      matchesGoldenFile(
+        'color_filter_red.png',
+        version: 1,
+      ),
+      skip: !isLinux
+    );
+  });
+
+  testWidgets('Color filter - sepia', (WidgetTester tester) async {
+    // TODO(dnfield): This should be const. https://github.com/dart-lang/sdk/issues/37503
+    final ColorFilter sepia = ColorFilter.matrix(<double>[
+      0.39,  0.769, 0.189, 0, 0, //
+      0.349, 0.686, 0.168, 0, 0, //
+      0.272, 0.534, 0.131, 0, 0, //
+      0,     0,     0,     1, 0, //
+    ]);
+    await tester.pumpWidget(
+      RepaintBoundary(
+        child: ColorFiltered(
+          colorFilter: sepia,
+          child: MaterialApp(
+            title: 'Flutter Demo',
+            theme: ThemeData(primarySwatch: Colors.blue),
+            home: Scaffold(
+              appBar: AppBar(
+                title: const Text('Sepia ColorFilter Test'),
+              ),
+              body: const Center(
+                child:Text('Hooray!'),
+              ),
+              floatingActionButton: FloatingActionButton(
+                onPressed: () { },
+                tooltip: 'Increment',
+                child: const Icon(Icons.add),
+              ),
+            ),
+          ),
+        ),
+      )
+    );
+    await expectLater(
+      find.byType(ColorFiltered),
+      matchesGoldenFile(
+        'color_filter_sepia.png',
+        version: 1,
+      ),
+      skip: !isLinux
+    );
+  });
+}
\ No newline at end of file