Как я могу замаскировать виджет изображением пользовательского интерфейса png с помощью пользовательской раскраски flutter

Кажется, я не могу замаскировать виджет с помощью png ui.Image и сохранить прозрачность. Прозрачные (невидимые) пиксели становятся черными.

Я пробовал использовать BlendMode srcIn при рисовании изображения маски png в моем собственном рисовальщике, ожидаемым поведением является цветной фон, замаскированный формой мармеладного боба. Тем не менее, прозрачные (должны быть удалены) пиксели отображаются черными (см. Прикрепленное изображение). Это ошибка? или я что-то упускаю? Мне также известен вариант ImageShader, но он не обладает необходимой гибкостью в моем приложении. При переходе между экранами и применении анимации героя есть короткий момент, когда холст отображается должным образом.

Любая помощь будет высоко ценится:)

Снимок экрана приложенияэкран печати приложения

Изображение маскимаска изображения

BG Изображениеизображение bg

Ожидаетсяожидается

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

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

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      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;

  initState() {
    super.initState();

    load('https://img.pngio.com/shape-png-free-download-free-shapes-png-493_315.png')
        .then((image) {
      setState(() {
        mask = image;
      });
    });
  }

  Future<Uint8List> _loadFromUrl(String url) async {
    final response = await http.get(url);

    if (response.statusCode >= 200 && response.statusCode < 300) {
      return response.bodyBytes;
    } else {
      return null;
    }
  }

  Future<ui.Image> load(String asset) async {
    var a = await _loadFromUrl(asset);
    ui.Codec codec = await ui.instantiateImageCodec(a);
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: <Widget>[
          Positioned(
            top: 10,
            left: 100,
            child: Hero(
                tag: 'imageHero',
                child: Container(
                  width: 200,
                  height: 200,
                  child: CustomPaint(
                    foregroundPainter: LayerPainter(mask: mask),
                    child: Image.network(
                        "https://www.thecommercialhotel.com/wp-content/uploads/2015/11/Live-Lounge-BG.jpg",
                        fit: BoxFit.fill),
                  ),
                )),
          )
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(context,
              MaterialPageRoute(builder: (BuildContext context) {
            return Page2(mask: mask,);
          }));
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class LayerPainter extends CustomPainter {
  ui.Image mask;

  LayerPainter({@required this.mask}) {}

  @override
  void paint(Canvas canvas, Size size) {
    if (mask != null) {
      canvas.drawImageRect(
          mask,
          Rect.fromLTWH(0, 0, mask.width.toDouble(), mask.height.toDouble()),
          Rect.fromLTWH(0, 0, size.width, size.height),
          new Paint()..blendMode = BlendMode.dstIn);
    }
  }

  @override
  bool shouldRepaint(LayerPainter oldDelegate) {
    return oldDelegate.mask != this.mask;
  }
}

class Page2 extends StatelessWidget {

  final ui.Image mask;

  Page2({ @required this.mask});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(
        children: <Widget>[
          Positioned(
            top: 400,
            left: 100,
            child: Hero(
                tag: 'imageHero',
                child: Container(
                  width: 200,
                  height: 200,
                  child: CustomPaint(
                    foregroundPainter: LayerPainter(mask: mask),
                    child: Image.network(
                        "https://www.thecommercialhotel.com/wp-content/uploads/2015/11/Live-Lounge-BG.jpg",
                        fit: BoxFit.fill),
                  ),
                )),
          )
        ],
      ));
  }
}

2 ответа

Не удалось решить проблему рендеринга Custom Painter, я решил использовать другой подход. Я просто визуализирую замаскированный png на отдельном холсте, экспортирую ui.Image и рисую его с помощью специального рисовальщика. Thins позволяет мне использовать BlendMode.srcIn и сохранять прозрачность. Вы можете узнать больше об этом в этом сообщении в блоге

Без специального рисователя вы можете получить ожидаемый результат, используя ShaderMask и ImageShader. Это видео и мой репозиторий помогут вам.

Другие вопросы по тегам