使用自定义画笔在Flutter中遮罩两个图像

时间:2020-02-29 19:20:33

标签: flutter dart mask flutter-canvas

你好,谁能告诉我为什么这种掩盖尝试的背景是黑色的。这必须很近,但是我无法杀死背景。我看到其他人提到saveLayer(rect, paint)是关键,因为这将整个画布推向了掩盖操作。 This question(无掩码操作)和this one(无实际答案)相似,但对我没有用。

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

enter image description here

squircle.jpg

enter image description here

结果

enter image description here

2 个答案:

答案 0 :(得分:1)

关键在于何时呼叫saveLayer和何时呼叫restore

来自here

在使用Canvas.saveLayer和Canvas.restore时,在调用Canvas.restore时将应用赋予Canvas.saveLayer的Paint的混合模式。每次对Canvas.saveLayer的调用都会引入一个新图层,在该图层上绘制形状和图像。调用Canvas.restore时,该层将被合成到父层上,其中源是最近绘制的形状和图像,目标是父层。 (对于第一个Canvas.saveLayer调用,父层是画布本身。)

工作代码

  @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();
    }
  }

结果:

enter image description here

答案 1 :(得分:1)

作为没有任何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];
  }
}

enter image description here

相关问题