简体   繁体   English

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

[英]Masking two images in Flutter using a Custom Painter

Hello can anyone tell me why the background to this masking attempt is black.你好,谁能告诉我为什么这次屏蔽尝试的背景是黑色的。 This must be close but I just can't kill the background.这必须很接近,但我无法杀死背景。 I've seen others reference that saveLayer(rect, paint) is the key here as that shoves the whole canvas rect in to the masking operation.我已经看到其他人提到saveLayer(rect, paint)是这里的关键,因为它将整个画布矩形推入屏蔽操作。 This question (no masking operation) and this one (no actual answer) are similar but were no use to me. 这个问题(没有屏蔽操作)和这个问题(没有实际答案)很相似,但对我没有用。

main.dart 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面条图片.jpg

在此处输入图片说明

squircle.jpg松鼠.jpg

在此处输入图片说明

result结果

在此处输入图片说明

as an option without any CustomPainters作为没有任何 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];
  }
}

在此处输入图片说明

The key is in when to call saveLayer and when to call restore .关键在于何时调用saveLayer以及何时调用restore

From 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 .使用 Canvas.saveLayer 和 Canvas.restore 时,将在调用 Canvas.restore 时应用赋予 Canvas.saveLayer 的 Paint 混合模式 Each call to Canvas.saveLayer introduces a new layer onto which shapes and images are painted;每次调用 Canvas.saveLayer 都会引入一个新层,在该层上绘制形状和图像; 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.当 Canvas.restore 被调用时,该层然后被合成到父层上,源是最近绘制的形状和图像,目标是父层。 (For the first Canvas.saveLayer call, the parent layer is the canvas itself.) (对于第一次 Canvas.saveLayer 调用,父层是画布本身。)

Working code工作代码

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

Result:结果:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM