Flutter截图保存并加水印

一、屏幕截图

利用RepaintBoundary组件截屏,RepaintBoundary 能够截图是因为它创建了一个独立的绘制层,Flutter 可以捕获该层的内容并转换为图像。使用时,将屏幕上需要截取的内容用RepaintBoundary组件包裹,并传入全局变量GlobalKey

dart 复制代码
GlobalKey pictureKey = GlobalKey();

RepaintBoundary(
    key: pictureKey,
    child: Container(child:...)
)

实现截图的步骤

  • 通过 GlobalKey 获取 RepaintBoundary 的引用。
  • 调用 RepaintBoundarytoImage 方法,Flutter 会将该边界内的内容渲染为 ui.Image 对象。
  • ui.Image 转换为常见的图像格式。
  • 可以借助path_provider插件获取app目录并存储图片。
dart 复制代码
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'package:path_provider/path_provider.dart';

Future<void> _captureImage() async {
    RenderObject? boundary = pictureKey.currentContext?.findRenderObject();
    ui.Image image = await boundary.toImage();
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    Directory directory = await getTemporaryDirectory();
    String imagePath =
        '${directory.path}/${DateTime.now().microsecondsSinceEpoch}.png';
    await File(imagePath).writeAsBytes(pngBytes);
}
  • 如果是webview,使用RepaintBoundary是无法截图的,可以使用webview自带的截图方法,如FLutter中经常使用的flutter_inappwebview插件,可以使用webControllertakeScreenshot方法获取截图。
dart 复制代码
Uint8List? uint8List = await webController?.takeScreenshot();

二、图片加水印

上面已经通过RepaintBoundary拿到了ui.Image,可以利用ui.Image来创建Canvas画布,利用画布添加水印,可以添加图片、文字,最后,通过ui.PicturetoImage方法获取带水印的图片。

dart 复制代码
Future<void> _captureImage() async {
    RenderObject? boundary = pictureKey.currentContext?.findRenderObject();
    ui.Image image = await boundary.toImage();
    // 创建一个 Canvas 来绘制水印
    ui.PictureRecorder recorder = ui.PictureRecorder();
    Canvas canvas = Canvas(recorder);
    // 将原始图像绘制在canvas上
    canvas.drawImage(image, Offset.zero, Paint());
    // 添加图片水印
    // 从assets中读取水印图片
    ByteData waterData =
        await rootBundle.load('assets/images/water_mark.png');
    ui.Image waterImage =
        await decodeImageFromList(waterData.buffer.asUint8List());
    int width = image.width;
    int height = image.height;
    canvas.drawImageNine(
        waterImage,
        // 要绘制的水印图片区域
        ui.Rect.fromLTWH(
            0, 0, waterImage.width.toDouble(), waterImage.height.toDouble()),
        // 绘制水印图片的目标区域,如:以原始图片中心点为中心,绘制宽100高100的水印图片
        ui.Rect.fromCenter(
            center: Offset(width / 2, height / 2),
            width: 100,
            height: 100),
        Paint());
    ui.Picture picture = recorder.endRecording();
    // 获取带水印的图片
    ui.Image newImg = await picture.toImage(width, height);
    ...
}

如果是整张水印铺满原始图片的情况,直接指定水印图片宽高容易造成水印图片的拉伸变形,所以最好是保持水印图片的宽高比。可以通过计算原始图片的宽高比和水印图片的宽高比来进行判断:

如下图,把整张水印图片铺在原始图片上,为了避免水印图片拉伸变形,需要保持水印图片的宽高比。

  • 如果原始图片更宽,以原始图片的高为水印图片高,按水印图片宽高比计算出对应的水印图片宽度
  • 如果水印图片更宽,以原始图片的宽度为水印图片宽,按水印图片宽高比计算出对应的水印图片高度
dart 复制代码
// 原始图片宽高比
double imgRatio = width / height;
// 水印图片宽高比
double waterImgRatio = waterImage.width / waterImage.height;
double dstWidth = width.toDouble();
double dstHeight = height.toDouble();
if (imgRatio > waterImgRatio) {
  // 如果原始图片更宽,以原始图片的高为水印图片高,按水印图片宽高比计算出对应的水印图片宽度
  dstWidth = waterImgRatio * dstHeight;
} else {
  // 反之,水印图片更宽,以原始图片的宽度为水印图片宽,按水印图片宽高比计算出对应的水印图片高度
  dstHeight = dstWidth / waterImgRatio;
}
canvas.drawImageNine(
    waterImage,
    ui.Rect.fromLTWH(
        0, 0, waterImage.width.toDouble(), waterImage.height.toDouble()),
    ui.Rect.fromCenter(
        center: Offset(width / 2, height / 2),
        width: dstWidth,
        height: dstHeight),
    Paint());

除了添加图片,还可以添加文字水印:

dart 复制代码
final textPainter = TextPainter(
    text: TextSpan(
      text: '水印',
      style: TextStyle(
        color: Colors.white.withOpacity(0.5),
        fontSize: 40,
        fontWeight: FontWeight.bold,
      ),
    ),
    textDirection: TextDirection.ltr,
  );
textPainter.layout();
// 在原始图片中心绘制水印文字
textPainter.paint(canvas, Offset(
    (width - textPainter.width) / 2,
    (height - textPainter.height) / 2)
);

三、保存图片到相册

上文已经获取到了带水印的图片,一般用于保存或分享,可以借助image_gallery_saver插件来保存图片到相册。

dart 复制代码
ByteData? byteData = await newImg.toByteData(format: ui.ImageByteFormat.png);
Uint8List? pngUint8List = byteData?.buffer.asUint8List();
saveToAlbum(pngUint8List);

Future<String> saveToAlbum(
  Uint8List imageBytes, {
  int quality = 100,
  String? name,
}) async {
  dynamic result = await ImageGallerySaver.saveImage(
    imageBytes,
    quality: quality,
    name: name,
    isReturnImagePathOfIOS: true,
  );
  if (result is! Map) {
    return Future.value('');
  }
  return Future.value(result.['filePath']??''));
}
相关推荐
钛态8 小时前
Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
服务器·驱动开发·安全·flutter·华为·单元测试·harmonyos
念格11 小时前
Flutter 弹窗 UI 不刷新?用 StatefulBuilder 解决
flutter
程序员老刘13 小时前
2026春招Flutter岗位为何变少?我看到的3个招聘逻辑变化
flutter·ai编程·客户端
念格14 小时前
Flutter 实现点击任意位置收起键盘的最佳实践
flutter
念格14 小时前
Flutter ListView Physics 滚动物理效果详解
flutter
国医中兴14 小时前
ClickHouse的数据模型设计:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
国医中兴16 小时前
ClickHouse数据导入导出最佳实践:从性能到可靠性
flutter·harmonyos·鸿蒙·openharmony
国医中兴17 小时前
大数据处理的性能优化技巧:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
●VON18 小时前
Flutter 入门指南:从基础组件到状态管理核心机制
前端·学习·flutter·von
西西学代码19 小时前
Flutter---SingleChildScrollView
前端·javascript·flutter