addWatermark function

Future<Uint8List> addWatermark({
  1. required Uint8List imageBytes,
  2. required List<String> watermarkLines,
  3. Color watermarkColor = Colors.white,
  4. int watermarkOpacity = 204,
  5. double watermarkFontSize = 18,
  6. Color backgroundColor = Colors.black,
  7. int backgroundOpacity = 178,
  8. EdgeInsets padding = const EdgeInsets.all(18),
})

给图片添加多行水印(左下角位置)

Implementation

Future<Uint8List> addWatermark({
  required Uint8List imageBytes,
  required List<String> watermarkLines,
  Color watermarkColor = Colors.white,
  int watermarkOpacity = 204,
  double watermarkFontSize = 18,
  Color backgroundColor = Colors.black,
  int backgroundOpacity = 178,
  EdgeInsets padding = const EdgeInsets.all(18),
}) async {
  final normalizedWatermarkOpacity = watermarkOpacity.clamp(0, 255);
  final normalizedBackgroundOpacity = backgroundOpacity.clamp(0, 255);

  ui.Codec? codec;
  ui.Image? image;
  Picture? picture;
  ui.Image? watermarkedImage;

  try {
    codec = await instantiateImageCodec(imageBytes);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    image = frameInfo.image;

    final double imageWidth = image.width.toDouble();
    final double imageHeight = image.height.toDouble();

    final PictureRecorder recorder = PictureRecorder();
    final Canvas canvas = Canvas(recorder);
    canvas.drawImage(image, Offset.zero, Paint());

    final textStyle = TextStyle(
      color: watermarkColor.withAlpha(normalizedWatermarkOpacity),
      fontSize: watermarkFontSize,
      fontFamily: 'System',
    );

    final String watermarkText = watermarkLines.join('\n');
    final textSpan = TextSpan(text: watermarkText, style: textStyle);

    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout(maxWidth: imageWidth * 0.6);

    final double x = padding.left;
    final double y = (imageHeight - textPainter.height - padding.bottom).clamp(
      0.0,
      imageHeight,
    );
    final Offset watermarkPosition = Offset(x, y);

    final double backgroundWidth = textPainter.width + padding.horizontal;
    final double backgroundHeight = textPainter.height + padding.vertical;
    final Rect backgroundRect = Rect.fromLTWH(
      watermarkPosition.dx - padding.left,
      watermarkPosition.dy - padding.top,
      backgroundWidth,
      backgroundHeight,
    );

    final Paint backgroundPaint = Paint()
      ..color = backgroundColor.withAlpha(normalizedBackgroundOpacity)
      ..maskFilter = MaskFilter.blur(BlurStyle.normal, 2);
    canvas.drawRRect(
      RRect.fromRectAndRadius(backgroundRect, Radius.circular(8)),
      backgroundPaint,
    );

    textPainter.paint(canvas, watermarkPosition);

    picture = recorder.endRecording();
    watermarkedImage = await picture.toImage(image.width, image.height);

    final ByteData? byteData = await watermarkedImage.toByteData(
      format: ImageByteFormat.png,
    );
    if (byteData == null) {
      throw StateError('Failed to encode watermarked image to PNG bytes.');
    }

    return byteData.buffer.asUint8List();
  } finally {
    codec?.dispose();
    image?.dispose();
    picture?.dispose();
    watermarkedImage?.dispose();
  }
}