介绍
本篇文章使用了ffmpeg_kit_flutter,Image,image_picker,path_provider等依赖,主要实现了给视频,图片添加水印的功能,该添加水印不会随着视频,图片的格式,分辨率的变化而变化。
提前需要准备的方法
其中的ui和img是这样的。
import 'dart:ui' as ui;
import 'package:image/image.dart' as img;
制作水印图片的方法
该方法中的位置设置都是设置好的,位置是右下角(该是获取的手机屏幕的分辨率来调整的)
static Future<Uint8List?> _getWatermark({String? id, String? name}) async {
var imgWidth = ui.window.physicalSize.width;
var pictureRecorder = ui.PictureRecorder();
var canvas = Canvas(pictureRecorder);
var images = await getImage("" //assets的图片路径
);
var width = images.width;
Paint _linePaint = Paint()..color = const Color.fromRGBO(0, 0, 0, 1);
// 绘制图片
canvas.drawImage(images, Offset(imgWidth - width - 21, 0),
_linePaint); // 这个Offset是值可以自己算(0,0起点开始,中间的话就是画布宽度-2*图片的宽度):图片的宽就是分辨率的宽。
// 绘制文字
ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
textAlign: TextAlign.right,
fontWeight: FontWeight.w500,
fontStyle: FontStyle.normal,
fontSize: 24.0.px,
));
pb.pushStyle(ui.TextStyle(color: Colors.white));
pb.addText("$name:$id");
///计算数据的长度
var textPainter = TextPainter(
text: TextSpan(
text: "$name:$id",
style: TextStyle(fontSize: 24.px, fontWeight: FontWeight.w500),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.left,
textScaleFactor: 1.0,
);
textPainter.layout();
var textImgWidth = textPainter.width;
// 设置文本的宽度约束
var pc = ui.ParagraphConstraints(width: imgWidth - 23);
ui.Paragraph paragraph = pb.build()..layout(pc);
canvas.drawParagraph(paragraph, Offset(-5, images.height.toDouble() + 20));
///判断名称的长度
if (imgWidth - 23 <= textImgWidth) {
picture = await pictureRecorder
.endRecording()
.toImage(imgWidth.toInt() - 10, (images.height + 102)); //设置生成图片的宽和高
var pngImageBytes =
await picture.toByteData(format: ui.ImageByteFormat.png);
return pngImageBytes?.buffer.asUint8List();
} else {
picture = await pictureRecorder
.endRecording()
.toImage(imgWidth.toInt() - 10, (images.height + 79)); //设置生成图片的宽和高
var pngImageBytes =
await picture.toByteData(format: ui.ImageByteFormat.png);
return pngImageBytes?.buffer.asUint8List();
}
}
//图片转换
static Future<ui.Image> getImage(String asset) async {
ByteData data = await rootBundle.load(asset);
var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
var fi = await codec.getNextFrame();
return fi.image;
}
图片添加水印
给图片添加水印使用到了ffmpeg的命令,除此之外还需要获取图片的分辨率,之后保存到本地。
该代码是ffmpeg的主要命令,其中的url,_imagePath分别是网络图片的路径跟水印图片,
${appDocDir.path}/${time}.png呢是图片保存的地址。
String command = "-i " +
url +
" -i " +
" $_imagePath " +
" -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 -qscale:v 1 " +
" ${appDocDir.path}/${time}.png";
这个是完整的图片添加水印代码:(文章中的_getCurrentTime是写的一个获取当前时间的代码)
static var picture;//这个属性是接收水印图片的大小
static Future<String?> _saveNetworkImage(String url,
{String? userId, String? nickName}) async {
//userId跟nickName是我需要水印的id和名称
if (userId != null && nickName != null) {
//该是获取缓存临时路径
var appDocDir = await getTemporaryDirectory();
//该方法是使用了画笔生成的图
Uint8List? pngBytes = await _getWatermark(id: userId, name: nickName);
//自己创建的水印图片的路径
String _imagePath = appDocDir.path + '/syimage.png'; //水印图片
File file = File(_imagePath);
//将水印图片保存到_imagePath 中
await file.writeAsBytes(pngBytes!);
//该方法是获取图片的分辨率
var getNetworkImageSize = await _getNetworkImageSize(url);
//水印图片的宽高
var width = picture.width;
var height = picture.height;
//网络图片的宽高
var imageWidth = getNetworkImageSize.width;
var imageHeight = getNetworkImageSize.height;
//获取最小的宽高比的值,用来调整水印的大小
var imageWH = imageWidth / width > imageHeight / height
? imageHeight / height
: imageWidth / width;
var time = _getCurrentTime();
///ffmpeg命令生成图片水印
String command = "-i " +
url +
" -i " +
" $_imagePath " +
" -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 -qscale:v 1 " +
" ${appDocDir.path}/${time}.png";
//执行ffmpeg命令,并继续其他操作
await FFmpegKit.executeAsync(command, (session) async {
final returnCode = await session.getReturnCode();
//ffmpeg命令执行是否成功。returnCode = 0成功:1失败
if (ReturnCode.isSuccess(returnCode)) {
//保存到本地---自己在网上查
} else {
//ffmpeg命令执行失败
}
});
} else {
//如果没有传递userId和nickName直接保存
}
}
视频添加水印
给视频添加水印跟着图片呢是差不多的,步骤都是一样的,ffmpag命令也没有差太多,可以说是一样的,接下来上ffmpeg命令代码:
String command = "-i " +
savePath +
" -i " +
" $_imagePath " +
" -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 " +
//这一行好像是提高视频的质量的,不是特别清楚,自己可以看看
" -acodec copy -q:v 0 -q:a 0 " +
" ${appDocDir.path}/${time}.mp4";
接下来就是完整的给视频添加水印了:
视频添加水印是需要获取视频的分辨率的,使用到的方法是VideoPlayerController。
static Future<String?> _saveNetworkVideoFile(String videoUrl,
{String? userId, String? nickName}) async {
var appDocDir = await getTemporaryDirectory();
String savePath = "${appDocDir.path}/${_getCurrentTime()}.mp4";
debugPrint("---------resultsavePath=$savePath");
//下载视频videoUrl是视频路径,savePath是将视频保存到的地址
await Dio().download(videoUrl, savePath, onReceiveProgress: (count, total) {
debugPrint("---------${(count / total * 100).toStringAsFixed(0)}%");
});
if (userId != null && nickName != null) {
var videoWidth;
var videoHeight;
final File localVideoFile = File(savePath);
// 初始化VideoPlayerController
VideoPlayerController _controller =
VideoPlayerController.file(localVideoFile);
// 确保视频初始化完成
await _controller.initialize();
// 视频加载完元数据后,可以通过value.format.description获取宽高
if (_controller.value.isInitialized) {
videoWidth = _controller.value.size.width.toInt();
videoHeight = _controller.value.size.height.toInt();
print('Local Video Resolution: $videoWidth x $videoHeight');
}
var width = picture.width;
var height = picture.height;
var imageWH = videoWidth / width > videoHeight / height
? videoHeight / height
: videoWidth / width;
var time = _getCurrentTime();
Uint8List? pngBytes = await _getWatermark(id: userId, name: nickName);
String _imagePath = appDocDir.path + '/syimage.png'; //水印图片
File file = File(_imagePath);
file.writeAsBytes(pngBytes!);
///ffmpeg命令生成图片水印
String command = "-i " +
savePath +
" -i " +
" $_imagePath " +
" -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 " +
" -acodec copy -q:v 0 -q:a 0 " +
" ${appDocDir.path}/${time}.mp4";
await FFmpegKit.executeAsync(command, (session) async {
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
//ffmpeg命令执行成功,保存到本地----自己上网找
} else {
//ffmpeg命令执行失败
}
});
} else {
//没有userId和nickName,直接保存到本地(不添加水印)
}
}