使用自定义画家在 Flutter 中屏蔽两个图像

Masking two images in Flutter using a Custom Painter

你好谁能告诉我为什么这个掩蔽尝试的背景是黑色的。这一定很接近,但我无法消除背景。我已经看到其他人提到 saveLayer(rect, paint) 是这里的关键,因为它将整个 canvas rect 推入了屏蔽操作。 (no masking operation) and (没有实际答案)相似但对我没有用。

main.dart

import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ui.Image mask;
  ui.Image image;

  @override
  void initState() {
    super.initState();
    load('images/squircle.png').then((i) {
      setState(() {
        mask = i;
      });
    });
    load('images/noodlejpg.jpg').then((i) {
      setState(() {
        image = i;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.blue, title: Text('I am a title')),
      body: SafeArea(
        child: SizedBox(
          width: 200.0,
          height: 200.0,
          child: CustomPaint(painter: OverlayPainter(mask, image)),
        ),
      ),
    );
  }

  Future<ui.Image> load(String asset) async {
    ByteData data = await rootBundle.load(asset);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }
}

class OverlayPainter extends CustomPainter {
  ui.Image mask;
  ui.Image image;

  OverlayPainter(this.mask, this.image);

  @override
  void paint(Canvas canvas, Size size) {
    var paint = new Paint();
    paint.isAntiAlias = true;

    if (image != null) {
      var rect = Rect.fromLTRB(0, 0, 200, 200);
      Size outputSize = rect.size;
      Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
      final FittedSizes fittedSizes =
          applyBoxFit(BoxFit.cover, inputSize, outputSize);
      final Size sourceSize = fittedSizes.source;

      canvas.save();
      final Rect sourceRect = Alignment.center.inscribe(
        sourceSize,
        Offset.zero & inputSize,
      );
      canvas.drawImageRect(image, sourceRect, rect, paint);
      canvas.restore();
    }

    if (mask != null) {
      var rect = Rect.fromLTRB(0, 0, 200, 200);
      Size outputSize = rect.size;
      Size inputSize = Size(mask.width.toDouble(), mask.height.toDouble());
      final FittedSizes fittedSizes =
          applyBoxFit(BoxFit.cover, inputSize, outputSize);
      final Size sourceSize = fittedSizes.source;

      canvas.saveLayer(rect, Paint()..blendMode = BlendMode.dstIn);

      final Rect sourceRect = Alignment.center.inscribe(
        sourceSize,
        Offset.zero & inputSize,
      );
      canvas.drawImageRect(mask, sourceRect, rect, paint);
      canvas.restore();
    }
  }

  @override
  bool shouldRepaint(OverlayPainter oldDelegate) {
    return mask != oldDelegate.mask || image != oldDelegate.image;
  }
}

noodlejpg.jpg

squircle.jpg

结果

关键在于什么时候调用saveLayer和什么时候调用restore

来自here

When using Canvas.saveLayer and Canvas.restore, the blend mode of the Paint given to the Canvas.saveLayer will be applied when Canvas.restore is called. Each call to Canvas.saveLayer introduces a new layer onto which shapes and images are painted; when Canvas.restore is called, that layer is then composited onto the parent layer, with the source being the most-recently-drawn shapes and images, and the destination being the parent layer. (For the first Canvas.saveLayer call, the parent layer is the canvas itself.)

工作代码

  @override
  void paint(Canvas canvas, Size size) {
    if (image != null && mask != null) {
      var rect = Rect.fromLTRB(0, 0, 200, 200);
      Size outputSize = rect.size;
      Paint paint = new Paint();

      //Mask
      Size maskInputSize = Size(mask.width.toDouble(), mask.height.toDouble());
      final FittedSizes maskFittedSizes =
          applyBoxFit(BoxFit.cover, maskInputSize, outputSize);
      final Size maskSourceSize = maskFittedSizes.source;

      final Rect maskSourceRect = Alignment.center
          .inscribe(maskSourceSize, Offset.zero & maskInputSize);

      canvas.saveLayer(rect, paint);
      canvas.drawImageRect(mask, maskSourceRect, rect, paint);

      //Image
      Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
      final FittedSizes fittedSizes =
          applyBoxFit(BoxFit.cover, inputSize, outputSize);
      final Size sourceSize = fittedSizes.source;
      final Rect sourceRect =
          Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);

      canvas.drawImageRect(
          image, sourceRect, rect, paint..blendMode = BlendMode.srcIn);
      canvas.restore();
    }
  }

结果:

作为没有任何 CustomPainters 的选项

import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: SafeArea(
          child: Center(
            child: SizedBox(
              width: 300,
              height: 300,
              child: MaskedImage(asset: 'images/noodlejpg.jpeg', mask: 'images/circle.png'),
            ),
          ),
        ),
      ),
    );
  }
}

class MaskedImage extends StatelessWidget {
  final String asset;
  final String mask;

  MaskedImage({@required this.asset, @required this.mask});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return FutureBuilder<List>(
        future: _createShaderAndImage(asset, mask, constraints.maxWidth, constraints.maxHeight),
        builder: (context, snapshot) {
          if (!snapshot.hasData) return const SizedBox.shrink();
          return ShaderMask(
            blendMode: BlendMode.dstATop,
            shaderCallback: (rect) => snapshot.data[0],
            child: snapshot.data[1],
          );
        },
      );
    });
  }

  Future<List> _createShaderAndImage(String asset, String mask, double w, double h) async {
    ByteData data = await rootBundle.load(asset);
    ByteData maskData = await rootBundle.load(mask);

    Codec codec = await instantiateImageCodec(maskData.buffer.asUint8List(), targetWidth: w.toInt(), targetHeight: h.toInt());
    FrameInfo fi = await codec.getNextFrame();

    ImageShader shader = ImageShader(fi.image, TileMode.clamp, TileMode.clamp, Matrix4.identity().storage);
    Image image = Image.memory(data.buffer.asUint8List(), fit: BoxFit.cover, width: w, height: h);
    return [shader, image];
  }
}