Render any bounded Flutter widget into an image watermark, then merge it into a source image.
This package is useful when your watermark is more than plain text, such as:
- text with custom styles
- icons and logos
- badges, chips, and cards
- any widget tree with a fixed size
- Single watermark placement
- Repeated tiled watermark placement
- Diagonal repeated watermark placement
- Multiple watermark items in a single export
- PNG and JPEG output
- Source image from bytes, file, path, or URL
- Preview widget for fast UI iteration
Install from pub.dev:
flutter pub add flutter_widget_watermarkOr add it manually to pubspec.yaml:
dependencies:
flutter_widget_watermark: ^0.1.0Then run:
flutter pub getImport it in your Dart code:
import 'package:flutter_widget_watermark/flutter_widget_watermark.dart';Designed for Flutter mobile first.
- Android: supported
- iOS: supported
- Web: limited, because
dart:iohelpers are not available and network/image rendering constraints can differ - Desktop: should work for many cases, but mobile is the primary target
WidgetWatermark.applyToBytes() and related methods require a BuildContext.
That is because the watermark widget is rendered offscreen using an OverlayEntry and RepaintBoundary before it is merged into the image.
Your watermark widget must have a clear size through:
watermarkSizewhen using the single-watermark APIsWatermarkItem.sizewhen using the multi-watermark API
final result = await WidgetWatermark.applyToBytes(
context: context,
imageBytes: bytes,
watermarkSize: const Size(220, 60),
watermark: Container(
alignment: Alignment.center,
child: const Text('CONFIDENTIAL'),
),
config: WatermarkConfig.single(
alignment: Alignment.bottomRight,
opacity: 0.6,
padding: const EdgeInsets.all(16),
),
);result is a Uint8List containing the encoded image bytes. The default output
format is PNG.
WidgetWatermark.applyToBytes()WidgetWatermark.applyToFile()WidgetWatermark.applyToPath()WidgetWatermark.applyToUrl()WidgetWatermark.applyMultipleToBytes()
The old class and method names are still available:
FlutterWidgetWatermark.apply()FlutterWidgetWatermark.applyFromFile()FlutterWidgetWatermark.applyFromPath()FlutterWidgetWatermark.applyFromUrl()FlutterWidgetWatermark.applyMulti()
Those legacy methods now forward to the new API and are marked deprecated.
final result = await WidgetWatermark.applyToBytes(
context: context,
imageBytes: bytes,
watermarkSize: const Size(220, 60),
watermark: const Center(child: Text('CONFIDENTIAL')),
config: WatermarkConfig.single(
alignment: Alignment.bottomRight,
opacity: 0.5,
padding: const EdgeInsets.all(16),
),
);final result = await WidgetWatermark.applyToUrl(
context: context,
imageUrl: 'https://picsum.photos/1200/800',
watermarkSize: const Size(220, 60),
watermark: const Center(child: Text('CONFIDENTIAL')),
config: WatermarkConfig.diagonal(
opacity: 0.12,
spacing: const Size(48, 56),
),
);For private URLs:
final result = await WidgetWatermark.applyToUrl(
context: context,
imageUrl: 'https://example.com/private-image.jpg',
headers: {
'Authorization': 'Bearer your-token',
},
watermarkSize: const Size(220, 60),
watermark: const Center(child: Text('PRIVATE')),
);final result = await WidgetWatermark.applyToPath(
context: context,
imagePath: file.path,
watermarkSize: const Size(180, 50),
watermark: const Center(child: Text('LOCAL FILE')),
);final result = await WidgetWatermark.applyToBytes(
context: context,
imageBytes: bytes,
watermarkSize: const Size(180, 46),
watermark: const Center(
child: Text(
'PROOF',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w700,
),
),
),
config: WatermarkConfig.diagonal(
opacity: 0.14,
rotation: -0.8,
spacing: const Size(24, 30),
),
);final result = await WidgetWatermark.applyMultipleToBytes(
context: context,
imageBytes: bytes,
watermarks: [
WatermarkItem(
widget: const Center(child: Text('TOP LEFT')),
size: const Size(120, 40),
layout: WatermarkLayout.aligned(
Alignment.topLeft,
padding: const EdgeInsets.all(16),
),
opacity: 0.8,
),
WatermarkItem(
widget: const Center(child: Text('CONFIDENTIAL')),
size: const Size(200, 50),
layout: WatermarkLayout.diagonal(
spacing: const Size(90, 90),
),
opacity: 0.1,
rotation: -0.55,
),
],
);Use WatermarkPreview while designing your UI:
WatermarkPreview(
imageProvider: MemoryImage(bytes),
watermark: const Center(child: Text('CONFIDENTIAL')),
watermarkSize: const Size(220, 60),
config: WatermarkConfig.diagonal(
opacity: 0.12,
spacing: const Size(48, 56),
),
)Places exactly one watermark using alignment and padding.
Repeats the watermark across the image using spacing and optional stagger.
Repeats the watermark in a more regular grid pattern.
Repeats the watermark diagonally. Useful for proof, confidential, or anti-crop overlays.
Places the watermark at manually provided coordinates.
- The watermark widget should be bounded to a known size.
- Large source images can use significant memory during decoding and encoding.
- URL helpers use
http, while file helpers rely ondart:io. - Web support is not the main target for this package version.
- The public API needs a live Flutter context because widget rendering happens through Flutter's rendering pipeline.
This package includes tests for:
- config factory defaults
- layout factory defaults
- alignment offset calculation
Image rendering tests are intentionally light here because widget-to-image rendering can be sensitive to environment and frame timing.
See the included example/ app for:
- bytes input
- URL input
- path input
- preview mode
- multi-watermark mode