title: Flutter: inverted ClipOval
body: <p>I am new to Flutter and I am trying to write a library to allow users to pan/zoom their profile picture.</p>
  
  <p>In order to make it visual, I would like to stack their picture with an "inverted" ClipOval, to show the boundaries.</p>
  
  <p>So far, this is the result I obtain:</p>
  
  <p><a href="https://i.stack.imgur.com/Jus9w.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/Jus9w.jpg" alt="enter image description here"></a></p>
  
  <p>This shows the boundaries but this is not user friendly and I would like to "invert" the ClipOval so that the center of the clip is "clear" and the outside is grayed out (something like a mask).</p>
  
  <p>Is there any way to achieve this?</p>
  
  <p>Here is the code I have so far (part of it comes from <a href="https://github.com/perlatus/flutter_zoomable_image" rel="noreferrer">flutter_zoomable_image</a>):</p>
  
  <pre><code>import 'dart:ui' as ui;
  import 'package:flutter/gestures.dart';
  import 'package:flutter/material.dart';
  import 'package:flutter/rendering.dart';
  
  class ImagePanner extends StatefulWidget {
    ImagePanner(this.image, {Key key}) : super(key: key);
  
    /// The image to be panned
    final ImageProvider image;
  
    @override
    _ImagePannerState createState() =&gt; new _ImagePannerState();
  }
  
  class _ImagePannerState extends State&lt;ImagePanner&gt; {
    ImageStream _imageStream;
    ui.Image _image;
    double _zoom = 1.0;
    Offset _offset = Offset.zero;
    double _scale = 16.0;
  
    @override
    void didChangeDependencies() {
      _resolveImage();
      super.didChangeDependencies();
    }
  
    @override
    void reassemble() {
      _resolveImage();
      super.reassemble();
    }
  
    @override
    Widget build(BuildContext context) {
      if (_image == null) {
        return new Container();
      }
      return new Container(
        width: double.INFINITY,
        color: Colors.amber,
        child: new Padding(
            padding: new EdgeInsets.all(50.0),
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: &lt;Widget&gt;[
                new AspectRatio(
                  aspectRatio: 1.0,
                  child: new Stack(
                    children: [
                      _child(),
                      new Opacity(
                        opacity: 0.5,
                        child: new ClipOval(
                          child: new Container(
                            color: Colors.black,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            )),
      );
    }
  
  
    Widget _child() {
      Widget bloated = new CustomPaint(
        child: new Container(),
        painter: new _ImagePainter(
          image: _image,
          offset: _offset,
          zoom: _zoom / _scale,
        ),
      );
  
      bloated = new Stack(
        children: [
          new Container(
          ),
          bloated
        ],
      );
  
      return new Transform(
          transform: new Matrix4.diagonal3Values(_scale, _scale, _scale),
          child: bloated);
    }
  
    void _resolveImage() {
      _imageStream = widget.image.resolve(createLocalImageConfiguration(context));
      _imageStream.addListener(_handleImageLoaded);
    }
  
    void _handleImageLoaded(ImageInfo info, bool synchronousCall) {
      print("image loaded: $info $synchronousCall");
      setState(() {
        _image = info.image;
      });
    }
  }
  
  class _ImagePainter extends CustomPainter {
    const _ImagePainter({this.image, this.offset, this.zoom});
  
    final ui.Image image;
    final Offset offset;
    final double zoom;
  
    @override
    void paint(Canvas canvas, Size size) {
      paintImage(canvas: canvas, rect: offset &amp; (size * zoom), image: image);
    }
  
    @override
    bool shouldRepaint(_ImagePainter old) {
      return old.image != image || old.offset != offset || old.zoom != zoom;
    }
  }
  </code></pre>
  
  <p>The outcome I would like to obtain is the following so that users will directly see the boundaries and will be able to center, pan, zoom their profile picture INSIDE the oval.</p>
  
  <p><a href="https://i.stack.imgur.com/opMks.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/opMks.jpg" alt="enter image description here"></a></p>
  
  <p>(I made this via Photoshop, since I don't know how to achieve this with Flutter)</p>
  
  <p>Many thanks for your help.</p>