将两个图像的差异绘制到画布

时间:2019-11-30 18:07:53

标签: canvas flutter dart

我试图在Flutter中画出与Image的两个Canvas之差,而我真正的意思是我只想画出不同之处(您可以在下面看到我希望看到的内容)。
它需要同步发生,即在paint通话中!

在此答案中,两个图像分别称为imageAimageB

代码

canvas.drawImage(
  imageA,
  offset,
  Paint(),
);
canvas.drawImage(
  imageB,
  offset,
  Paint()
    ..blendMode = BlendMode.difference,
);

可视化

imageAimageB分别绘制。

imageAimageB之间的差异。您可以看到我期望看到的 left ,以及使用 right 上面的代码得到的实际结果。
左侧图像中的灰色/白色背景颜色应该是透明,即在两个图像相同的情况下,alpha应该等于零。此外,expected图像中红色正方形周围的黑色阴影是伪影,而不是我在应用程序中想要的。

问题

我并不是说expected的图像就是我期望从BlendMode.difference获得的图像,但是我想知道如何获得expected的输出,即我仅能实现绘制两个图像之间的区别。
这意味着我只想渲染imageB中的imageA 不同的像素,否则不渲染任何东西,即alpha值0。

说明

我将再次尝试更清楚地解释它:

  • 如果颜色相同,则绘制透明像素(删除源像素)。

  • 如果颜色不同,请绘制目标像素。

1 个答案:

答案 0 :(得分:2)

当前抖动不支持您要执行的操作。根据我的研究,Flutter在本机级别上处理画布油漆。您可以从以下代码片段中看到这一点:

  /// Fills the canvas with the given [Paint].
  ///
  /// To fill the canvas with a solid color and blend mode, consider
  /// [drawColor] instead.
  void drawPaint(Paint paint) {
    assert(paint != null);
    _drawPaint(paint._objects, paint._data);
  }
  void _drawPaint(List<dynamic> paintObjects, ByteData paintData) native 'Canvas_drawPaint';

这来自最新版本的flutter(1.9.2)的canvas实现。如果没有异步代码,您可以执行的任何像素操作都必须使用抖动BlendModeImageFilter来完成,它们缺乏个性化的遮罩支持。

要做您打算做的事情的唯一方法是实际实现本机代码,我真的不认为您想做。我花了最后6个小时来弄清楚官方flutter API文档中没有什么可以做您想要做的事情,但是问题是,flutter中的大多数事情都是旨在实现异步的。

如果您打算冒险以本机方式实现此功能,为什么不要求将其实现为官方Flutter github中的功能?我的意思是说,要做很多工作,不要让它浪费。

仅仅因为我不想空手而归,所以我用image lib编写了您想要的东西,但是使用了async:

enter image description here

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:image/image.dart' as im;

import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MyApp();
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ByteBuffer imgBytes;
  double canvasHeight = 500;
  double canvasWidth = 300;

  ui.PictureRecorder recorder1;
  Canvas canvas1;

  ui.PictureRecorder recorder2;
  Canvas canvas2;

  ui.Picture picture1;
  ui.Picture picture2;

  @override
  void initState() {
    // Canvas1
    recorder1 = ui.PictureRecorder();
    canvas1 = Canvas(recorder1,
        Rect.fromPoints(Offset(0.0, 0.0), Offset(canvasWidth, canvasHeight)));

    // Draw red background
    var paint = Paint();
    paint.style = PaintingStyle.fill;
    paint.color = Colors.red;

    canvas1.drawRect(Offset(0, 0) & Size(canvasWidth, canvasHeight), paint);

    // Draw black square
    paint = Paint();
    paint.style = PaintingStyle.fill;
    paint.color = Colors.black;

    canvas1.drawRect(
        Offset(canvasWidth * 0.5 / 4, canvasHeight / 2) &
            Size(canvasHeight / 8, canvasHeight / 8),
        paint);

    picture1 = recorder1.endRecording();

    // Canvas2
    recorder2 = ui.PictureRecorder();
    canvas2 = Canvas(recorder2,
        Rect.fromPoints(Offset(0.0, 0.0), Offset(canvasWidth, canvasHeight)));

    // Draw red background
    paint = Paint();
    paint.style = PaintingStyle.fill;
    paint.color = Colors.red;

    canvas2.drawRect(Offset(0, 0) & Size(canvasWidth, canvasHeight), paint);

    // Draw blue square
    paint = Paint();
    paint.style = PaintingStyle.fill;
    paint.color = Colors.blue;

    canvas2.drawRect(
        Offset(canvasWidth * 2.5 / 4, canvasHeight / 2) &
            Size(canvasHeight / 8, canvasHeight / 8),
        paint);

    picture2 = recorder2.endRecording();

    (() async {
      ui.Image img1 =
          await picture1.toImage(canvasWidth.toInt(), canvasHeight.toInt());
      ByteData byteData1 =
          await img1.toByteData(format: ui.ImageByteFormat.png);
      im.Image decodedPng1 = im.decodePng(byteData1.buffer.asUint8List());

      ui.Image img2 =
          await picture2.toImage(canvasWidth.toInt(), canvasHeight.toInt());
      ByteData byteData2 =
          await img2.toByteData(format: ui.ImageByteFormat.png);
      im.Image decodedPng2 = im.decodePng(byteData2.buffer.asUint8List());

      for (int i = 0; i < canvasHeight; i += 1) {
        for (int j = 0; j < canvasWidth; j += 1) {
          int pixel1 = decodedPng1.getPixel(j, i);
          int r1 = pixel1 & 0xff;
          int g1 = (pixel1 >> 8) & 0xff;
          int b1 = (pixel1 >> 16) & 0xff;
          int a1 = (pixel1 >> 24) & 0xff;

          int pixel2 = decodedPng2.getPixel(j, i);
          int r2 = pixel2 & 0xff;
          int g2 = (pixel2 >> 8) & 0xff;
          int b2 = (pixel2 >> 16) & 0xff;
          int a2 = (pixel2 >> 24) & 0xff;

          if (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) {
            // Draw black transparent
            decodedPng2.setPixel(j, i, 0);
          } else {
            // Leave as is
          }
        }
      }

      setState(() {
        imgBytes = Uint8List.fromList(im.encodePng(decodedPng2)).buffer;
      });
    })();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Dummy'),
        ),
        body: imgBytes != null
            ? Center(
                child: Image.memory(
                  Uint8List.view(imgBytes),
                  width: canvasWidth,
                  height: canvasHeight,
                ),
              )
            : Container(),
      ),
    );
  }
}