# Clipping widgets with bezier curves in Flutter

Source: https://iiro.dev/clipping-widgets-with-bezier-curves-in-flutter/
Published: 2017-09-04

Learn how to implement custom clippers in Flutter and use Bezier curves to cut widgets into custom shapes.

---

Hello World!

This is the very first post on [flutter.rocks](https://flutter.rocks/), a blog by two developers with different backgrounds. We'll take you through our journey of using [Flutter](https://flutter.io/) to create cross-platform mobile applications. We'll also share our best tips, tricks and useful guides along the way. If this sounds something you would be interested in reading more about, be sure to subscribe at the bottom of the page.

Without further ado, we are kicking the blog off with a technical post, where we look into clipping images with Bezier curves.

![The end result: an image of a coffee cup with wavy bottom.](/images/clipping-images-with-bezier-curves/wave_clip_result.png)



I was browsing [UpLabs](https://www.uplabs.com/) when I stumbled upon this [Walkthrough Coffeeshop App UI by Dual Pixel](https://www.uplabs.com/posts/walkthrough-coffeeshop-app). The wavy image is the part that caught my interest. Having created something similar just a couple days ago for my hobby Flutter project, this was the perfect idea for our first post on the blog. So let's get into it.

## Source code

The source of the complete result is [here](https://github.com/FlutterRocks/wavy-image-mask).

## The implementation

First, create a new Flutter project. Let's call this one _wavy\_image\_mask_. Assuming you have Flutter installed, run `flutter create wavy_image_mask` on your terminal. Open the project on IntelliJ IDEA.

Let's create a new `StatelessWidget` called _WavyHeaderImage_. Create a new Dart file called _wavy\_header\_image.dart_. Place it in the _lib_ folder where our app code resides. For now, we'll just return a placeholder `Text` widget from the build method:


**File:** `wavy_header_image.dart`


```dart
class WavyHeaderImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('This is where our image will be.');
  }
}
```


Return to the main file. Remove all the sample code Flutter generates for you, and replace the `Scaffold` body with the _WavyHeaderImage_ we just created. You should end up with the following code:


**File:** `main.dart`


```dart
import 'wavy_header_image_unimplemented.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Wavy image mask',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: WavyHeaderImage(),
    );
  }
}
```


## Creating the header image

Now we'll need a nice looking header image. We'll use [this one](https://www.pexels.com/photo/close-up-of-coffee-cup-on-table-312418/) from Chevanon Photography. 

It's a little too big for our needs, [so here's a smaller one](/images/clipping-images-with-bezier-curves/coffee_header.jpeg).

### Adding assets in Flutter

To include images or files in general, we use Flutter's asset system. Create a folder called _images_ in the root of your app. Add the _coffee\_header.jpeg_ you just downloaded inside of it. Your folder structure should now look like this:

![The folder structure after adding the coffee_header.jpeg to our images folder.](/images/clipping-images-with-bezier-curves/wavy_image_mask_folder_structure.png)

In order to make the image available to our code, we'll also need to include an `assets` section inside of our `flutter` section in the _pubspec.yaml_ file. So our pubspec file should now look something like this:


**File:** `pubspec.yaml`


```yaml
name: wavy_image_mask
description: A new Flutter project.

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true
  assets:
    - images/coffee_header.jpeg
```

Now we can use the image in our app.

### Making some waves

One, naive way of creating the wave mask would be asking a designer to just edit the original image with Photoshop. We could also ask for a transparent image that has the waves drawn with white color. Then we'd just slap that on top of the image, position it on the bottom and call it a day.

A better way of doing that is clipping the wavy parts of the image ourselves. This solution will look better on different screen sizes. We also don't need to include an extra wave overlay image in our assets. Thanks to a nice set of graphics related APIs in Flutter, we can do this very easily.

#### Using ClipPaths

The class we're going to use for creating the wave shape on the bottom is the [ClipPath](https://docs.flutter.io/flutter/widgets/ClipPath-class.html) widget. ClipPath needs two arguments: a `clipper` and a `child`. The _clipper_ is a [CustomClipper&lt;Path&gt;](https://docs.flutter.io/flutter/rendering/CustomClipper-class.html) object that determines a [Path](https://docs.flutter.io/flutter/dart-ui/Path-class.html). This path is used by the `ClipPath` widget for preventing the `child` painting anything outside the path.

Reopen the _wavy\_header\_image.dart_ we created at the very beginning. Remove the placeholder _Text_ widget and replace that with a `ClipPath`. 

The _child_ parameter is `new Image.asset('images/coffee_header.jpeg')`. This simply loads our image from the images folder and displays it. For _clipper_, we're just going to say `new BottomWaveClipper()`. We'll also need to create a new class for the _BottomWaveClipper_, which we'll have in the same file.

This is what we should have at this point:


**File:** `wavy_header_image.dart`


```dart
class WavyHeaderImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ClipPath(
      child: Image.asset('images/coffee_header.jpeg'),
      clipper: BottomWaveClipper(),
    );
  }
}

class BottomWaveClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    // This is where we decide what part of our image is going to be
    // visible. If you try to run the app now, nothing will be shown.
    return Path();
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
```


Now we can get to the fun part.

#### Drawing the path

All _CustomClippers_ get a _size_ parameter in their `getClip` method. This size represents the width and height of the _child_ passed to the _ClipPath_ object. In our case, it's the coffee image we want to clip. Since our _CustomClipper_ uses paths for determining the clip, we use methods from the _Path_ class for drawing the clip. 

So, for example, **if we want to clip the bottom right part of our image diagonally out**, we'd end up something like this:


```dart
@override
Path getClip(Size size) {
  var path = Path();

  // Draw a straight line from current point to the bottom left corner.
  path.lineTo(0.0, size.height);

  // Draw a straight line from current point to the top right corner.
  path.lineTo(size.width, 0.0);

  // Draws a straight line from current point to the first point of the path.
  // In this case (0, 0), since that's where the paths start by default.
  path.close();
  return path;
}
```


Since we don't want to clip out anything other than the wave at the bottom, we'll add `path.lineTo(size.width, size.height);` right before drawing a line to the top right corner. This modification returns the image as is, without any clipping, but sets us up for actually clipping the wavy part out.

Now that we learned how to clip by drawing straight lines with our path, let's draw the waves. Since the bottom of the wave goes vertically below the first point on bottom left corner, we'll have to move that point a little higher. Similarly, the bottom right point is also a little higher than the bottom left one.


```dart
@override
Path getClip(Size size) {
  var path = Path();

  // Since the wave goes vertically lower than bottom left starting point,
  // we'll have to make this point a little higher.
  path.lineTo(0.0, size.height - 20);

  // TODO: The wavy clipping magic happens here, between the bottom left and bottom right points.

  // The bottom right point also isn't at the same level as its left counterpart,
  // so we'll adjust that one too.
  path.lineTo(size.width, size.height - 40);
  path.lineTo(size.width, 0.0);
  path.close();
}
```


This creates a slight clipped angle in the bottom part of the image. Here's how it should look now:

![A slightly angled clip on the bottom.](/images/clipping-images-with-bezier-curves/coffee_bottom_clip_part_1.png)


#### What's a quadratic bezier?

Now we can actually finally implement the waves. To do so, we use the [quadraticBezierTo](https://docs.flutter.io/flutter/dart-ui/Path/quadraticBezierTo.html) method. Quadratic beziers allow us to easily create curves on our paths.

The API is pretty similar to the `lineTo` method, but instead of giving one coordinate, we give two. One for our end destination, other for a so called _control point_.

Basically, we just draw a straight line to our destination, and the _control point_ acts like a magnet. It pulls the middle of our path to its direction and distorts our straight line into a curve.

### Determining the end & control points

In our case, we need to draw two quadratic beziers. The control point for the first line should drag the middle of the line down. Similarly, the control point for the second line should drag the middle of the line up. 

If we look at the mockup, the first line doesn't exactly end horizontally at the center. Likewise, the second line is a little bit longer than half of the width of the image.

Based on our knowledge so far, we want to align our points and control points something like this:

![End & control points for our bezier curves.](/images/clipping-images-with-bezier-curves/coffee_bottom_clip_points.png)

You might look at that and say "those curves are way too big!" and you're right. However, those **red control points are not where our curved line will touch**, but they'll merely _attract_ the line like a magnet. The end result will actually look pretty close to the mockup.

#### The first curve

These are the facts we know about the *first curve*:

* the endpoint is horizontally a little under the center
* the endpoint is vertically a little higher than the starting point
* the control point is horizontally at one fourth of the image width
* the control point is vertically at the bottom of the image

Based on these facts, the first bezier looks like this:


```dart
final firstControlPoint = Offset(size.width / 4, size.height);
final firstEndPoint = Offset(size.width / 2.25, size.height - 30.0);
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
    firstEndPoint.dx, firstEndPoint.dy);
```


We could just skip creating the Offset objects and just use the x & y coordinates directly, but this is just to make it more readable. This way, it's easier to see what the parameters to `quadraticBezierTo` method are on a first glance.

#### The second curve

Here are the facts of the second curve:

* the endpoint is horizontally at the right edge of the image
* the endpoint is vertically a little higher than the endpoint of the first curve
* the control point is horizontally little below 3/4 of the image width
* the control point is vertically about 20 pixels or so above the endpoint

With a little trial and error, this is what we get for the second curve:


```dart
var secondControlPoint =
    Offset(size.width - (size.width / 3.25), size.height - 65);
var secondEndPoint = Offset(size.width, size.height - 40);
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
    secondEndPoint.dx, secondEndPoint.dy);
```


And we're done! If you did everything right, the end result should look like this:

![The end result of our clipping journey.](/images/clipping-images-with-bezier-curves/wave_clip_result.png)

Here's the complete code for the _WavyHeaderImage_ widget:


**File:** `wavy_header_image.dart`


```dart
class WavyHeaderImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ClipPath(
      child: Image.asset('images/coffee_header.jpeg'),
      clipper: BottomWaveClipper(),
    );
  }
}

class BottomWaveClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0.0, size.height - 20);

    var firstControlPoint = Offset(size.width / 4, size.height);
    var firstEndPoint = Offset(size.width / 2.25, size.height - 30.0);
    path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
        firstEndPoint.dx, firstEndPoint.dy);

    var secondControlPoint =
        Offset(size.width - (size.width / 3.25), size.height - 65);
    var secondEndPoint = Offset(size.width, size.height - 40);
    path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
        secondEndPoint.dx, secondEndPoint.dy);

    path.lineTo(size.width, size.height - 40);
    path.lineTo(size.width, 0.0);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
```


The complete source can be found [here](https://github.com/FlutterRocks/wavy-image-mask).
