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']??''));
}
相关推荐
火柴就是我18 分钟前
android:enableJetifier=true 再学习
android·flutter
星释2 小时前
鸿蒙Flutter三方库适配指南-04.使用MacOS搭建开发环境
flutter·macos·harmonyos
消失的旧时光-194313 小时前
Flutter 异步编程:Future 与 Stream 深度解析
android·前端·flutter
星释13 小时前
鸿蒙Flutter三方库适配指南-02.Flutter相关知识基础
flutter·华为·harmonyos
傅里叶21 小时前
Flutter项目使用 buf.build
flutter
恋猫de小郭1 天前
iOS 26 开始强制 UIScene ,你的 Flutter 插件准备好迁移支持了吗?
android·前端·flutter
yuanlaile1 天前
Flutter开发HarmonyOS鸿蒙App商业项目实战已出炉
flutter·华为·harmonyos
CodeCaptain1 天前
可直接落地的「Flutter 桥接鸿蒙 WebSocket」端到端实施方案
websocket·flutter·harmonyos
stringwu1 天前
Flutter 中的 MVVM 架构实现指南
前端·flutter
消失的旧时光-19432 天前
Flutter 异步体系终章:FutureBuilder 与 StreamBuilder 架构优化指南
flutter·架构