本文转载自北斗星_And大神的文章,未经原博主同意,禁止转载

前言

今天继续讲Flutter的实现篇,画中画效果的实现。先看一下PIP的实现效果.

更多效果请查看PIP DEMO
代码地址:FlutterPIP

为什么会有此文?
一天在浏览朋友圈时,发现了一个朋友发了一张图(当然不是女朋友,但是个女的),类似上面效果部分. 一看效果挺牛啊,这是怎么实现的呢?心想要不自己实现一下吧?于是开始准备用Android实现一下.

但最近正好学了一下Flutter,并在学习Flutter 自定义View CustomPainter时,发现了和Android上有相同的API,Canvas,Paint,Path等. 查看Canvas的绘图部分drawImage代码如下

 /// Draws the given [Image] into the canvas with its top-left corner at the/// given [Offset]. The image is composited into the canvas using the given [Paint].void drawImage(Image image, Offset p, Paint paint) {assert(image != null); // image is checked on the engine sideassert(_offsetIsValid(p));assert(paint != null);_drawImage(image, p.dx, p.dy, paint._objects, paint._data);}void _drawImage(Image image,double x,double y,List<dynamic> paintObjects,ByteData paintData) native 'Canvas_drawImage';

可以看出drawImage 调用了内部的_drawImage,而内部的_drawImage使用的是native Flutter Engine的代码 ‘Canvas_drawImage’,交给了Flutter Native去绘制.那Canvas的绘图就可以和移动端的Native一样高效 (Flutter的绘制原理,决定了Flutter的高效性).关于Flutter的高效可以查看 Flutter 高性能原理

实现步骤
看效果从底层往上层,图片被分为3个部分,第一部分是底层的高斯模糊效果,第二层是原图被裁剪的部分,第三层是一个效果遮罩。

Flutter 高斯模糊效果的实现

Flutter提供了BackdropFilter,关于BackdropFilter的官方文档是这么说的

A widget that applies a filter to the existing painted content and then paints child.The filter will be applied to all the area within its parent or ancestor widget’s clip.
If there’s no clip, the filter will be applied to the full screen.

简单来说,他就是一个筛选器,筛选所有绘制到子内容的小控件,官方demo例子如下

Stack(fit: StackFit.expand,children: <Widget>[Text('0' * 10000),Center(child: ClipRect(  // <-- clips to the 200x200 [Container] belowchild: BackdropFilter(filter: ui.ImageFilter.blur(sigmaX: 5.0,sigmaY: 5.0,),child: Container(alignment: Alignment.center,width: 200.0,height: 200.0,child: Text('Hello World'),),),),),],
)

效果就是对中间200*200大小的地方实现了模糊效果.
本文对底部图片高斯模糊效果的实现如下

Stack(fit: StackFit.expand,children: <Widget>[Container(alignment: Alignment.topLeft,child: CustomPaint(painter: DrawPainter(widget._originImage),size: Size(_width, _width))),Center(child: ClipRect(child: BackdropFilter(filter: flutterUi.ImageFilter.blur(sigmaX: 5.0,sigmaY: 5.0,),child: Container(alignment: Alignment.topLeft,color: Colors.white.withOpacity(0.1),width: _width,height: _width,
//                child: Text('  '),),),),),],);

其中Container的大小和图片大小一致,并且Container需要有子控件,或者背景色. 其中子控件和背景色可以任意.
实现效果如图

Flutter 图片裁剪
图片裁剪原理
在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果.

Flutter 中也有相同的API,通过设置画笔Paint的blendMode属性,可以达到相同的效果.混合模式具体可以Flutter查看官方文档,有示例.

此处用到的混合模式是BlendMode.dstIn,文档注释如下

/// Show the destination image, but only where the two images overlap. The
/// source image is not rendered, it is treated merely as a mask. The color
/// channels of the source are ignored, only the opacity has an effect.
/// To show the source image instead, consider [srcIn].
// To reverse the semantic of the mask (only showing the source where the
/// destination is present, rather than where it is absent), consider [dstOut].
/// This corresponds to the “Destination in Source” Porter-Duff operator.

大概说的意思就是,只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响. 用Android里面的一个公式表示为

\(\alpha_{out} = \alpha_{src}\)\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)

实际裁剪

我们要用到一个Frame图片(frame.png),用来和原图进行混合,Frame图片如下

frame.png

实现代码

/// 通过 frameImage 和 原图,绘制出 被裁剪的图形static Future<flutterUi.Image> drawFrameImage(String originImageUrl, String frameImageUrl) {Completer<flutterUi.Image> completer = new Completer<flutterUi.Image>();//加载图片Future.wait([OriginImage.getInstance().loadImage(originImageUrl),ImageLoader.load(frameImageUrl)]).then((result) {Paint paint = new Paint();PictureRecorder recorder = PictureRecorder();Canvas canvas = Canvas(recorder);int width = result[1].width;int height = result[1].height;//图片缩放至frame大小,并移动到中央double originWidth = 0.0;double originHeight = 0.0;if (width > height) {double scale = height / width.toDouble();originWidth = result[0].width.toDouble();originHeight = result[0].height.toDouble() * scale;} else {double scale = width / height.toDouble();originWidth = result[0].width.toDouble() * scale;originHeight = result[0].height.toDouble();}canvas.drawImageRect(result[0],Rect.fromLTWH((result[0].width - originWidth) / 2.0,(result[0].height - originHeight) / 2.0,originWidth,originHeight),Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),paint);//裁剪图片paint.blendMode = BlendMode.dstIn;canvas.drawImage(result[1], Offset(0, 0), paint);recorder.endRecording().toImage(width, height).then((image) {completer.complete(image);});}).catchError((e) {print("加载error:" + e);});return completer.future;}

分为三个主要步骤

  • 第一个步骤,加载原图和Frame图片,使用Future.wait 等待两张图片都加载完成
  • 原图进行缩放,平移处理,缩放至frame合适大小,在将图片平移至图片中央
  • 设置paint的混合模式,绘制Frame图片,完成裁剪

裁剪后的效果图如下

Flutter 图片合成及保存

裁剪完的图片和效果图片(mask.png)的合成

先看一下mask图片长啥样

裁剪完的图片和mask图片的合成,不需要设置混合模式,裁剪图片在底层,合成完的图片在上层.既可实现,但需要注意的是,裁剪的图片需要画到效果区域,所以x,y需要有偏移量,实现代码如下:

/// mask 图形 和被裁剪的图形 合并static Future<flutterUi.Image> drawMaskImage(String originImageUrl,String frameImageUrl, String maskImage, Offset offset) {Completer<flutterUi.Image> completer = new Completer<flutterUi.Image>();Future.wait([ImageLoader.load(maskImage),//获取裁剪图片drawFrameImage(originImageUrl, frameImageUrl)]).then((result) {Paint paint = new Paint();PictureRecorder recorder = PictureRecorder();Canvas canvas = Canvas(recorder);int width = result[0].width;int height = result[0].height;//合成canvas.drawImage(result[1], offset, paint);canvas.drawImageRect(result[0],Rect.fromLTWH(0, 0, result[0].width.toDouble(), result[0].height.toDouble()),Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),paint);//生成图片recorder.endRecording().toImage(width, height).then((image) {completer.complete(image);});}).catchError((e) {print("加载error:" + e);});return completer.future;}

效果实现

本文开始介绍了,图片分为三层,所以此处使用了Stack组件来包装PIP图片

 new Container(width: _width,height: _width,child: new Stack(children: <Widget>[getBackgroundImage(),//底部高斯模糊图片//合成后的效果图片,使用CustomPaint 绘制出来CustomPaint(painter: DrawPainter(widget._image),size: Size(_width, _width)),],)
)
class DrawPainter extends CustomPainter {DrawPainter(this._image);flutterUi.Image _image;Paint _paint = new Paint();@overridevoid paint(Canvas canvas, Size size) {if (_image != null) {print("draw this Image");print("width =" + size.width.toString());print("height =" + size.height.toString());canvas.drawImageRect(_image,Rect.fromLTWH(0, 0, _image.width.toDouble(), _image.height.toDouble()),Rect.fromLTWH(0, 0, size.width, size.height),_paint);}}@overridebool shouldRepaint(CustomPainter oldDelegate) {return true;}
}

图片保存

Flutter 是一个跨平台的高性能UI框架,使用到Native Service的部分,需要各自实现,此处需要把图片保存到本地,使用了一个库,用于获取各自平台的可以保存文件的文件路径.

path_provider: ^0.4.1

实现步骤,先将上面的PIP用一个RepaintBoundary 组件包裹,然后通过给RepaintBoundary设置key,再去截图保存,实现代码如下

 Widget getPIPImageWidget() {return RepaintBoundary(key: pipCaptureKey,child: new Center(child: new DrawPIPWidget(_originImage, _image)),);}

截屏保存

Future<void> _captureImage() async {RenderRepaintBoundary boundary =pipCaptureKey.currentContext.findRenderObject();var image = await boundary.toImage();ByteData byteData = await image.toByteData(format: ImageByteFormat.png);Uint8List pngBytes = byteData.buffer.asUint8List();getApplicationDocumentsDirectory().then((dir) {String path = dir.path + "/pip.png";new File(path).writeAsBytesSync(pngBytes);_showPathDialog(path);});}

显示图片的保存路径

Future<void> _showPathDialog(String path) async {return showDialog<void>(context: context,barrierDismissible: false,builder: (BuildContext context) {return AlertDialog(title: Text('PIP Path'),content: SingleChildScrollView(child: ListBody(children: <Widget>[Text('Image is save in $path'),],),),actions: <Widget>[FlatButton(child: Text('退出'),onPressed: () {Navigator.of(context).pop();},),],);},);}

手势交互实现思路
目前的实现方式是:把原图移动到中央进行裁剪,默认认为图片的重要显示区域在中央,这样就会存在一个问题,如果图片的重要显示区域没有在中央,或者画中画效果的显示区域不在中央,会存在一定的偏差.

所以需要添加手势交互,当图片重要区域不在中央,或者画中画效果不在中央,可以手动调整显示区域。

实现思路:添加手势操作,获取当前手势的offset,重新拿原图和frame区域进行裁剪,就可以正常显示.(目前暂未去实现)

Flutter 实现画中画效果相关推荐

  1. 《Unity开发实战》——2.2节创建画中画效果

    本节书摘来自华章社区<Unity开发实战>一书中的第2章,第2.2节创建画中画效果,作者 (爱尔兰)Matt Smith (巴西)Chico Queiroz,更多章节内容可以访问云栖社区& ...

  2. php画中画,画中画功能 怎么将两个视频叠加播放,制作成画中画效果

    对于很多视频编辑工具,小编都会想要操作看看效果咋样,这不,小编刚找到一款视频编辑工具,使用了一下觉得制作效果很是不错,可以进行视频加画面特效.照片制作成电子相册.视频合并加转场特效.添加字幕等等操作. ...

  3. ffmpeg 命令画中画效果

    画中画效果也是和图片水印一样使用movie配合overlay实现 使用ffplay预览一下: ffplay -i cctvhttp.flv -vf "movie=cctvhttp.flv[s ...

  4. 视频画中画效果该怎么实现?这款软件让你一秒成大神

    Camtasia是一款好用的软件,他可以进行屏幕录像,视频剪辑,特效添加等.画中画是一个比较常见的视频效果,做起来也是非常简单,跟着一起来看看吧. 打开Camtasia软件,导入两个素材到媒体箱中. ...

  5. 在视频中添加图片,图片中添加视频,制作画中画效果

    在剪辑中,画中画是一种非常常见的视频表现手法,它在视频或图片上添加一个比尺寸小的剪辑画面,那么这些剪辑画面可以进行缩放,加强视频内容的生动性.下面一起来试试吧. 方法1:视频中添加图片 在媒体梦工厂的 ...

  6. ffmpeg画中画效果

    1. 画中画效果overlay滤镜(覆盖.叠加) overlay的使用语法: ffmpeg  -i  input1  -i  input2  -filter_complex  overlay=x:y ...

  7. 多个视频文件合成画中画效果(Python版)

    Step 1 从视频中分离出音频(MP4->mp3) def separateMp4ToMp3(tmp):mp4 = tmp.replace('.tmp', '.mp4')print('---& ...

  8. 视频画中画效果制作,原来这么简单就可以做出

    制作视频画中画的效果,以前在看到画中画的视频时总感觉这个视频制作出来会很难,在网上学习用视频媒体梦工厂后,原来是如此的简单.所以就赶紧来给大家分享一下操作方法,下面一起来看看. 先将全部视频素材保存在 ...

  9. 剪辑画中画视频,如何用视频实现画中画效果

    当有多个视频时,想要在视频中添加画中画效果,如何可以同时实现多个视频呢?今天就为大家分享制作画中画视频的技巧,下面一起来试试吧. 准备工具: 安装一个媒体梦工厂 多个视频 开始操作: 打开媒体梦工厂, ...

最新文章

  1. GitHub怎样fork别人代码到自己仓库并进行贡献
  2. python接口自动化参数化_Python读取txt文件数据的方法(用于接口自动化参数化数据)...
  3. BZOJ2816:[ZJOI2012]网络(LCT)
  4. 颠覆传统4S店,特斯拉发布智能售后服务体系
  5. oracle 5632,17、oracle 性能管理
  6. IOS开发基础之微博项目
  7. wapper打成linux服务,Wrapper配置详解及高级应用(转)
  8. Hadoop2.x环境搭建
  9. Linux下几种文件传输命令 sz rz sftp scp
  10. 【MyBatis框架】Mybatis开发dao方法第二部分
  11. JavaScript二进制、八进制和十六进制数值
  12. magento后台使用POST表单时,要使用必要参数form_key才能正常通讯
  13. 几句话介绍MagicAjax
  14. 国家统计局统计用区划和城乡划分代码
  15. NPDP是什么考试?产品经理必知
  16. 美格智能5G R16模组SRM825N顺利通过国内CCC、SRRC、CTA认证
  17. $monitor用法
  18. 关于docker报错:No connection could be made because the target machine actively refused it.
  19. ps照片处理器怎么打文字_什么是文字处理器?
  20. 离职中层解密乐视危机起爆点:手机业务巨亏

热门文章

  1. Linux 查找文件位置命令
  2. android 自定义旋转转盘(类似抽奖转盘)
  3. 全局描述符表GDT-第一部分
  4. 2020年广东工业大学第十届文远知行杯新生程序设计竞赛(同步赛)(详细题解)
  5. 多张图片合成一张图片(alpha混合)
  6. 回顾各种编码的创新和异同-MEPG2, MPEG4, H.264/AVC以及H.265/HEVC比较(转)
  7. C/C++零基础游戏开发:教你用C++开发24点游戏,在场的人都惊了!
  8. NLP | 注意力机制Attention Mechannism图文详解及代码
  9. html5论文关键词是啥,论文关键词一般几个
  10. C语言 结构体基础知识