Added the superellipse (a.k.a. squircle) shape to flutter. (#26295)

* Added the superellipse (a.k.a. squircle) shape to flutter, which is needed to recreate some cupertino components, e.g. buttons in pixel-perfect detail (issue #13914).
diff --git a/AUTHORS b/AUTHORS
index 3e7c5fd..f033b44 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -33,4 +33,5 @@
 Jasper van Riet <jaspervanriet@gmail.com>
 Mattijs Fuijkschot <mattijs.fuijkschot@gmail.com>
 TruongSinh Tran-Nguyen <i@truongsinh.pro>
-Marco Scannadinari <m@scannadinari.co.uk>
+Sander Dalby Larsen <srdlarsen@gmail.com>
+Marco Scannadinari <m@scannadinari.co.uk>
\ No newline at end of file
diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version
index 3c0cd70..a48622d 100644
--- a/bin/internal/goldens.version
+++ b/bin/internal/goldens.version
@@ -1 +1 @@
-c47f1308188dca65b3899228cac37f252ea8b411
+034b2a540bc46375cf0c175a0fd512dcd46971e0
diff --git a/packages/flutter/lib/painting.dart b/packages/flutter/lib/painting.dart
index 55894da..1bd1945 100644
--- a/packages/flutter/lib/painting.dart
+++ b/packages/flutter/lib/painting.dart
@@ -51,6 +51,7 @@
 export 'src/painting/rounded_rectangle_border.dart';
 export 'src/painting/shape_decoration.dart';
 export 'src/painting/stadium_border.dart';
+export 'src/painting/superellipse_shape.dart';
 export 'src/painting/text_painter.dart';
 export 'src/painting/text_span.dart';
 export 'src/painting/text_style.dart';
diff --git a/packages/flutter/lib/src/painting/superellipse_shape.dart b/packages/flutter/lib/src/painting/superellipse_shape.dart
new file mode 100644
index 0000000..58ed507
--- /dev/null
+++ b/packages/flutter/lib/src/painting/superellipse_shape.dart
@@ -0,0 +1,166 @@
+// Copyright 2018 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.
+
+import 'dart:math' as math;
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+/// Creates a superellipse - a shape similar to a rounded rectangle, but with
+/// a smoother transition from the sides to the rounded corners and greater
+/// curve continuity.
+///
+/// {@tool sample}
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Material(
+///     shape: SuperellipseShape(
+///       borderRadius: BorderRadius.circular(28.0),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+/// * [RoundedRectangleBorder] Which creates a square with rounded corners,
+///   however it doesn't allow the corners to bend the sides of the square
+///   like a superellipse, resulting in a more square shape.
+class SuperellipseShape extends ShapeBorder {
+  /// The arguments must not be null.
+  const SuperellipseShape({
+    this.side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+  }) : assert(side != null),
+       assert(borderRadius != null);
+
+  /// The radius for each corner.
+  ///
+  /// Negative radius values are clamped to 0.0 by [getInnerPath] and
+  /// [getOuterPath].
+  final BorderRadiusGeometry borderRadius;
+
+  /// The style of this border.
+  final BorderSide side;
+
+  @override
+  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);
+
+  @override
+  ShapeBorder scale(double t) {
+    return SuperellipseShape(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+    );
+  }
+
+  @override
+  ShapeBorder lerpFrom(ShapeBorder a, double t) {
+    assert(t != null);
+    if (a is SuperellipseShape) {
+      return SuperellipseShape(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder lerpTo(ShapeBorder b, double t) {
+    assert(t != null);
+    if (b is SuperellipseShape) {
+      return SuperellipseShape(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  double _clampToShortest(RRect rrect, double value) {
+    return value > rrect.shortestSide ? rrect.shortestSide : value;
+  }
+
+  Path _getPath(RRect rrect) {
+    final double left = rrect.left;
+    final double right = rrect.right;
+    final double top = rrect.top;
+    final double bottom = rrect.bottom;
+    //  Radii will be clamped to the value of the shortest side
+    /// of [rrect] to avoid strange tie-fighter shapes.
+    final double tlRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusX));
+    final double tlRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusY));
+    final double trRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusX));
+    final double trRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusY));
+    final double blRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusX));
+    final double blRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusY));
+    final double brRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusX));
+    final double brRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusY));
+
+    return Path()
+      ..moveTo(left, top + tlRadiusX)
+      ..cubicTo(left, top, left, top, left + tlRadiusY, top)
+      ..lineTo(right - trRadiusX, top)
+      ..cubicTo(right, top, right, top, right, top + trRadiusY)
+      ..lineTo(right, bottom - blRadiusX)
+      ..cubicTo(right, bottom, right, bottom, right - blRadiusY, bottom)
+      ..lineTo(left + brRadiusX, bottom)
+      ..cubicTo(left, bottom, left, bottom, left, bottom - brRadiusY)
+      ..close();
+  }
+
+  @override
+  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
+    if (rect.isEmpty)
+      return;
+    switch (side.style) {
+      case BorderStyle.none:
+      break;
+      case BorderStyle.solid:
+        final Path path = getOuterPath(rect, textDirection: textDirection);
+        final Paint paint = side.toPaint();
+        canvas.drawPath(path, paint);
+        break;
+    }
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    if (runtimeType != other.runtimeType)
+      return false;
+    final SuperellipseShape typedOther = other;
+    return side == typedOther.side
+        && borderRadius == typedOther.borderRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius);
+
+  @override
+  String toString() {
+    return '$runtimeType($side, $borderRadius)';
+  }
+}
\ No newline at end of file
diff --git a/packages/flutter/test/painting/superellipse_shape_test.dart b/packages/flutter/test/painting/superellipse_shape_test.dart
new file mode 100644
index 0000000..867f8fd
--- /dev/null
+++ b/packages/flutter/test/painting/superellipse_shape_test.dart
@@ -0,0 +1,121 @@
+// Copyright 2018 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.
+
+import 'dart:io' show Platform;
+import 'package:flutter/material.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/mock_canvas.dart';
+
+void main() {
+  test('SuperellipseShape scale and lerp', () {
+    final SuperellipseShape c10 = SuperellipseShape(side: const BorderSide(width: 10.0), borderRadius: BorderRadius.circular(100.0));
+    final SuperellipseShape c15 = SuperellipseShape(side: const BorderSide(width: 15.0), borderRadius: BorderRadius.circular(150.0));
+    final SuperellipseShape c20 = SuperellipseShape(side: const BorderSide(width: 20.0), borderRadius: BorderRadius.circular(200.0));
+    expect(c10.dimensions, const EdgeInsets.all(10.0));
+    expect(c10.scale(2.0), c20);
+    expect(c20.scale(0.5), c10);
+    expect(ShapeBorder.lerp(c10, c20, 0.0), c10);
+    expect(ShapeBorder.lerp(c10, c20, 0.5), c15);
+    expect(ShapeBorder.lerp(c10, c20, 1.0), c20);
+  });
+
+  test('SuperellipseShape BorderRadius.zero', () {
+    final Rect rect1 = Rect.fromLTRB(10.0, 20.0, 30.0, 40.0);
+    final Matcher looksLikeRect1 = isPathThat(
+      includes: const <Offset>[ Offset(10.0, 20.0), Offset(20.0, 30.0) ],
+      excludes: const <Offset>[ Offset(9.0, 19.0), Offset(31.0, 41.0) ],
+    );
+
+    // Default border radius and border side are zero, i.e. just a rectangle.
+    expect(const SuperellipseShape().getOuterPath(rect1), looksLikeRect1);
+    expect(const SuperellipseShape().getInnerPath(rect1), looksLikeRect1);
+
+    // Represents the inner path when borderSide.width = 4, which is just rect1
+    // inset by 4 on all sides.
+    final Matcher looksLikeInnerPath = isPathThat(
+      includes: const <Offset>[ Offset(14.0, 24.0), Offset(16.0, 26.0) ],
+      excludes: const <Offset>[ Offset(9.0, 23.0), Offset(27.0, 37.0) ],
+    );
+
+    const BorderSide side = BorderSide(width: 4.0);
+    expect(const SuperellipseShape(side: side).getOuterPath(rect1), looksLikeRect1);
+    expect(const SuperellipseShape(side: side).getInnerPath(rect1), looksLikeInnerPath);
+  });
+
+  test('SuperellipseShape non-zero BorderRadius', () {
+    final Rect rect = Rect.fromLTRB(10.0, 20.0, 30.0, 40.0);
+    final Matcher looksLikeRect = isPathThat(
+      includes: const <Offset>[ Offset(15.0, 25.0), Offset(20.0, 30.0) ],
+      excludes: const <Offset>[ Offset(10.0, 20.0), Offset(30.0, 40.0) ],
+    );
+    const SuperellipseShape border = SuperellipseShape(
+      borderRadius: BorderRadius.all(Radius.circular(5.0))
+    );
+    expect(border.getOuterPath(rect), looksLikeRect);
+    expect(border.getInnerPath(rect), looksLikeRect);
+  });
+
+  testWidgets('Golden test even radii', (WidgetTester tester) async {
+    await tester.pumpWidget(RepaintBoundary(
+      child: Material(
+        color: Colors.blueAccent[400],
+        shape: SuperellipseShape(
+          borderRadius: BorderRadius.circular(28.0),
+        ),
+      ),
+    ));
+
+    await tester.pumpAndSettle();
+
+    await expectLater(
+      find.byType(RepaintBoundary),
+      matchesGoldenFile('superellipse_shape.golden_test_even_radii.png'),
+      skip: !Platform.isLinux,
+    );
+  });
+
+  testWidgets('Golden test varying radii', (WidgetTester tester) async {
+    await tester.pumpWidget(RepaintBoundary(
+      child: Material(
+        color: Colors.greenAccent[400],
+        shape: const SuperellipseShape(
+          borderRadius: BorderRadius.only(
+            topLeft: Radius.circular(28.0),
+            bottomRight: Radius.circular(14.0),
+          ),
+        ),
+      ),
+    ));
+
+    await tester.pumpAndSettle();
+
+    await expectLater(
+      find.byType(RepaintBoundary),
+      matchesGoldenFile('superellipse_shape.golden_test_varying_radii.png'),
+      skip: !Platform.isLinux,
+    );
+  });
+
+  testWidgets('Golden test large radii', (WidgetTester tester) async {
+    await tester.pumpWidget(RepaintBoundary(
+      child: Material(
+        color: Colors.redAccent[400],
+        shape: SuperellipseShape(
+          borderRadius: BorderRadius.circular(50.0),
+        ),
+      ),
+    ));
+
+    await tester.pumpAndSettle();
+
+    await expectLater(
+      find.byType(RepaintBoundary),
+      matchesGoldenFile('superellipse_shape.golden_test_large_radii.png'),
+      skip: !Platform.isLinux,
+    );
+  });
+
+}