Flutter - drawAtlas 圆形抠图

Flutter - drawAtlas circular cutout

我正在尝试实现图像的圆形剪切,我正在使用 drawAtlas。到目前为止,这是我的实现:

canvas.drawAtlas(
  image!,
  [
    /* Identity transform */
    RSTransform.fromComponents(
      rotation: 0.0,
      scale: 1,
      anchorX: 0,
      anchorY: 0,
      translateX: 0,
      translateY: 0,
    )
  ],
  [
    Rect.fromCircle(
      center: Offset(size.width / 2, size.height / 2),
      radius: 200,
    ),
  ],
  [],
  null,
  null,
  Paint(),
);

虽然它确实有效,但它绘制了一个矩形图像。我想用一些 strokeWidth 绘制同一图像的圆形切口。是否可以使用 drawAtlas 这样做?

问得好!尽管在您的代码中,您正在调用 Rect.fromCircle 来指定裁剪区域,但它仍然是一个矩形(您正在从一个圆构建一个矩形)。 drawAtlas 方法仅支持从较大的图像中裁剪矩形区域,您可能已经发现了这一点。

这里的关键是利用canvas.clipRRect缩小“允许绘画区域”。这类似于在 Photoshop 中添加选择框。这样做之后,任何在选择框外绘制的东西都将被忽略。换句话说,通过调用 canvas.clipRRect,我们将无法再在裁剪区域之外绘制任何内容。

想必在画完圆形图集后,你可能还想画其他东西到canvas。对于其他事情,您可能不希望局限于裁剪的圆圈。为了解决这个问题,我们可以使用canvas.saveLayer保存裁剪发生前canvas的状态,然后进行圆形裁剪,然后绘制altas,最后调用canvas.restore恢复先前保存的状态。本质上,这将清除剪裁,允许我们在需要时在 canvas 上的任何位置绘制更多内容。

解法:

简而言之,paint 方法可能如下所示:

  void paint(Canvas canvas, Size size) {
    // Save the canvas state so we can clip it and then restore it later
    canvas.saveLayer(Rect.largest, Paint());
    // Clip the canvas, so we are only allowed to draw inside the circle
    canvas.clipRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, 100, 100),
        Radius.circular(50),
      ),
    );
    // Draw the atlas (call your method)
    _drawAtlas(canvas, size);
    // Restore the canvas to its original state, so further drawings are not clipped
    canvas.restore();
  }

演示:

在演示中,当您按下按钮时,应用程序将截取灰色渐变容器内的 Flutter Logo 的屏幕截图,并将其用作 ui.image 数据。然后它将执行 drawAtlas 将图像的圆形部分绘制到 canvas。

完整演示代码(将所有内容粘贴到 main.dart 到 运行):

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _globalKey = GlobalKey();

  ui.Image? _image;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          children: [
            RepaintBoundary(
              key: _globalKey,
              child: Container(
                width: 300,
                height: 300,
                decoration: BoxDecoration(
                  gradient: RadialGradient(
                    colors: [Colors.white, Colors.grey],
                  ),
                ),
                child: FlutterLogo(),
              ),
            ),
            ElevatedButton(
              child: Text('Press Me'),
              onPressed: () async {
                final render = (_globalKey.currentContext!.findRenderObject()
                    as RenderRepaintBoundary);
                final image = await render.toImage();
                setState(() {
                  _image = image;
                });
              },
            ),
            if (_image != null)
              CustomPaint(
                size: Size(300, 300),
                painter: MyPainter(_image!),
              ),
          ],
        ),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  final ui.Image image;

  MyPainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    // Save the canvas state so we can clip it and then restore it later
    canvas.saveLayer(Rect.largest, Paint());
    // Clip the canvas, so we are only allowed to draw inside the circle
    canvas.clipRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, 100, 100),
        Radius.circular(50),
      ),
    );
    // Draw the atlas (call your method)
    _drawAtlas(canvas, size);
    // Restore the canvas to its original state, so further drawings are not clipped
    canvas.restore();
  }

  _drawAtlas(Canvas canvas, Size size) {
    canvas.drawAtlas(
      image,
      [
        /* Identity transform */
        RSTransform.fromComponents(
          rotation: 0.0,
          scale: 1,
          anchorX: 0,
          anchorY: 0,
          translateX: 0,
          translateY: 0,
        )
      ],
      [
        Rect.fromCircle(
          center: Offset(size.width / 2, size.height / 2),
          radius: 50,
        ),
      ],
      [],
      null,
      null,
      Paint(),
    );
  }

  @override
  bool shouldRepaint(oldDelegate) => true;
}